15 KiB
15 KiB
backup / restore 예시
모든 예시는 실제 매니페스트로 kubectl apply -f 가능하다.
좋은 예시 1: Velero 설치 후 BackupStorageLocation / VolumeSnapshotLocation
---
apiVersion: velero.io/v1
kind: BackupStorageLocation
metadata:
name: default
namespace: velero
labels:
app.kubernetes.io/part-of: platform-backup
spec:
provider: aws
objectStorage:
bucket: acme-prod-velero-backups
prefix: k3s-prod
config:
region: us-east-1
s3ForcePathStyle: "false"
s3Url: https://s3.us-east-1.amazonaws.com
default: true
accessMode: ReadWrite
credential:
name: velero-s3-credentials
key: cloud
---
apiVersion: velero.io/v1
kind: VolumeSnapshotLocation
metadata:
name: csi-default
namespace: velero
spec:
provider: csi
왜 좋은가:
- 백업 저장소가 클러스터 외부 S3 (같은 cluster MinIO에 넣지 않음)
- credential은 별도 Secret
- CSI snapshot location이 명시됨
❌ 나쁜 예시 1: 같은 cluster 안 MinIO를 백업 저장소로 사용
spec:
provider: aws
objectStorage:
bucket: backups
config:
s3Url: http://minio.object-prod.svc.cluster.local:9000 # 같은 cluster!
문제:
- cluster 장애 = 백업 동시 소실
- MinIO 자체를 복구하려면 외부 백업이 또 필요 — 순환 의존
좋은 예시 2: Velero Schedule (tier별 분리, 30일 retention)
---
apiVersion: velero.io/v1
kind: Schedule
metadata:
name: gold-daily
namespace: velero
labels:
backup.platform.io/tier: gold
spec:
schedule: "0 2 * * *" # 매일 02:00 UTC
useOwnerReferencesInBackup: true
template:
ttl: 720h0m0s # 30일 retention
includedNamespaces:
- auth-prod
- data-prod
- object-prod
includedResources:
- persistentvolumeclaims
- persistentvolumes
- secrets
- configmaps
- deployments
- statefulsets
- services
- ingresses
- networkpolicies
labelSelector:
matchLabels:
backup.platform.io/tier: gold
snapshotVolumes: true
defaultVolumesToFsBackup: false
csiSnapshotTimeout: 30m
storageLocation: default
volumeSnapshotLocations:
- csi-default
hooks:
resources:
- name: postgres-consistent
includedNamespaces: [data-prod]
labelSelector:
matchLabels:
app.kubernetes.io/name: postgres
pre:
- exec:
container: postgres
command: ["/bin/sh", "-c", "psql -U postgres -c CHECKPOINT"]
onError: Fail
timeout: 2m
---
apiVersion: velero.io/v1
kind: Schedule
metadata:
name: bronze-weekly-fsb
namespace: velero
labels:
backup.platform.io/tier: bronze
spec:
schedule: "0 3 * * 0" # 매주 일요일 03:00 UTC
template:
ttl: 2160h0m0s # 90일
includedNamespaces: ["archive-prod"]
labelSelector:
matchLabels:
backup.platform.io/tier: bronze
snapshotVolumes: false
defaultVolumesToFsBackup: true # kopia/restic FSB
storageLocation: default
왜 좋은가:
- Schedule이 tier별로 분리되어 RPO/retention/도구를 구분
- CSI snapshot(gold)과 FSB(bronze)를 목적에 맞게 선택
- Postgres는 pre-hook으로
CHECKPOINT를 수행해 crash-consistent에 가까운 스냅샷 확보 labelSelector가 PVC의backup.platform.io/tier와 매칭
좋은 예시 3: Velero Restore
---
apiVersion: velero.io/v1
kind: Restore
metadata:
name: auth-prod-restore-2026-04-16
namespace: velero
spec:
backupName: gold-daily-20260415020000
includedNamespaces: ["auth-prod"]
restorePVs: true
existingResourcePolicy: none # 기존 리소스 보존, 누락된 것만 복원
namespaceMapping:
auth-prod: auth-prod-restore # 검증용 별도 네임스페이스로 복원
labelSelector:
matchLabels:
backup.platform.io/tier: gold
왜 좋은가:
- 복원 대상이
auth-prod-restore로 분리되어 운영 영향 없이 검증 가능 existingResourcePolicy: none으로 실수 덮어쓰기 방지restorePVs: true로 PVC/PV까지 함께 복원
좋은 예시 4: CloudNativePG Cluster + ScheduledBackup + Backup
---
apiVersion: v1
kind: Namespace
metadata:
name: data-prod
labels:
pod-security.kubernetes.io/enforce: restricted
---
apiVersion: v1
kind: Secret
metadata:
name: cnpg-s3-credentials
namespace: data-prod
type: Opaque
stringData:
ACCESS_KEY_ID: REPLACE_VIA_VSO
ACCESS_SECRET_KEY: REPLACE_VIA_VSO
---
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: auth-pg
namespace: data-prod
labels:
app.kubernetes.io/name: auth-pg
app.kubernetes.io/component: database
app.kubernetes.io/part-of: auth-platform
backup.platform.io/tier: gold
spec:
instances: 3
imageName: ghcr.io/cloudnative-pg/postgresql:16.4-8
primaryUpdateStrategy: unsupervised
postgresql:
parameters:
shared_buffers: "512MB"
max_connections: "200"
wal_compression: "on"
archive_timeout: "60s"
bootstrap:
initdb:
database: auth
owner: auth_app
secret:
name: auth-pg-app
storage:
size: 50Gi
storageClass: fast-ssd-retain
walStorage:
size: 20Gi
storageClass: fast-ssd-retain
monitoring:
enablePodMonitor: true
resources:
requests: {cpu: "500m", memory: "2Gi"}
limits: {cpu: "2", memory: "4Gi"}
backup:
retentionPolicy: "30d"
barmanObjectStore:
destinationPath: s3://acme-prod-pg-backups/auth-pg
endpointURL: https://s3.us-east-1.amazonaws.com
s3Credentials:
accessKeyId:
name: cnpg-s3-credentials
key: ACCESS_KEY_ID
secretAccessKey:
name: cnpg-s3-credentials
key: ACCESS_SECRET_KEY
wal:
compression: gzip
maxParallel: 8
data:
compression: gzip
immediateCheckpoint: true
jobs: 4
affinity:
podAntiAffinityType: required
topologyKey: kubernetes.io/hostname
---
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
name: auth-pg-daily
namespace: data-prod
spec:
schedule: "0 0 2 * * *" # 매일 02:00 (CNPG는 6-field cron)
backupOwnerReference: self
cluster:
name: auth-pg
method: barmanObjectStore
---
apiVersion: postgresql.cnpg.io/v1
kind: Backup
metadata:
name: auth-pg-premigration-2026-04-16
namespace: data-prod
spec:
cluster:
name: auth-pg
method: barmanObjectStore
왜 좋은가:
- Postgres 16, 3 instances, 자동 failover
- WAL continuous archiving + daily base backup으로 RPO 5분 / PITR 가능
ScheduledBackup이 cron 기반 정기 백업,Backup이 on-demand (마이그레이션 직전 등)enablePodMonitor로 Prometheus 연동podAntiAffinity로 노드 분산backup.retentionPolicy: 30d
❌ 나쁜 예시 2: StatefulSet + cron으로 pg_dump 하나만
apiVersion: batch/v1
kind: CronJob
metadata:
name: pg-dump-nightly
spec:
schedule: "0 3 * * *"
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
securityContext:
runAsNonRoot: true
runAsUser: 999
runAsGroup: 999
fsGroup: 999
seccompProfile:
type: RuntimeDefault
containers:
- name: dump
image: postgres:16
command: ["sh", "-c", "pg_dumpall -U postgres > /backup/dump.sql"]
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { memory: 512Mi }
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- { name: tmp, mountPath: /tmp }
- { name: backup, mountPath: /backup }
volumes:
- name: tmp
emptyDir: {}
- name: backup
emptyDir: {}
문제:
- PITR 불가 (base backup + WAL 아님)
- single file → 대규모에서 restore 시간 폭증
- logical dump는 replication slot / extension / large object 처리에 구멍
- 같은 cluster의 PVC에 저장 시 장애 시 동시 소실
좋은 예시 5: CNPG PITR restore (bootstrap.recovery)
---
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: auth-pg-restore
namespace: data-prod
spec:
instances: 3
imageName: ghcr.io/cloudnative-pg/postgresql:16.4-8
storage:
size: 50Gi
storageClass: fast-ssd-retain
walStorage:
size: 20Gi
storageClass: fast-ssd-retain
bootstrap:
recovery:
source: auth-pg-source
recoveryTarget:
targetTime: "2026-04-16 09:45:00.00+00"
externalClusters:
- name: auth-pg-source
barmanObjectStore:
destinationPath: s3://acme-prod-pg-backups/auth-pg
endpointURL: https://s3.us-east-1.amazonaws.com
s3Credentials:
accessKeyId: {name: cnpg-s3-credentials, key: ACCESS_KEY_ID}
secretAccessKey: {name: cnpg-s3-credentials, key: ACCESS_SECRET_KEY}
wal:
maxParallel: 8
왜 좋은가:
- PITR을 declarative CRD로 표현
- 원본 cluster를 건드리지 않고 별도
auth-pg-restore클러스터로 복원 - 특정 시점(
targetTime)까지 WAL replay
좋은 예시 6: K3s etcd snapshot + S3 업로드
# /etc/rancher/k3s/config.yaml (control-plane nodes)
etcd-snapshot-schedule-cron: "0 */6 * * *"
etcd-snapshot-retention: 28
etcd-s3: true
etcd-s3-endpoint: "s3.us-east-1.amazonaws.com"
etcd-s3-bucket: "acme-prod-k3s-etcd"
etcd-s3-folder: "prod-cluster-1"
etcd-s3-region: "us-east-1"
etcd-s3-access-key-file: /var/lib/rancher/k3s/server/etcd-s3-access
etcd-s3-secret-key-file: /var/lib/rancher/k3s/server/etcd-s3-secret
secrets-encryption: true
token 별도 보관 (예: 운영자 금고 / 외부 Vault):
/var/lib/rancher/k3s/server/token → offline backup, 접근 로그 남김
왜 좋은가:
- 6시간마다 etcd snapshot + S3 자동 업로드 + 28개 보관
- secrets encryption 활성화로 snapshot 유출 시 노출 감소
- server token을 snapshot과 같은 위치에 두지 않음
주의: 이 snapshot은 PVC 데이터를 포함하지 않는다. 반드시 Velero + CNPG backup과 병행.
좋은 예시 7: Vault raft snapshot (CronJob)
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: vault-raft-snapshot
namespace: vault
spec:
schedule: "0 */6 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 0
template:
spec:
restartPolicy: Never
serviceAccountName: vault-snapshot
securityContext:
runAsNonRoot: true
runAsUser: 100
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: snapshot
image: hashicorp/vault@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
env:
- name: VAULT_ADDR
value: https://vault.vault.svc:8200
- name: VAULT_TOKEN
valueFrom:
secretKeyRef:
name: vault-snapshot-token
key: token
command:
- sh
- -c
- |
set -eu
TS=$(date -u +%Y%m%dT%H%M%SZ)
vault operator raft snapshot save /snap/vault-${TS}.snap
aws s3 cp /snap/vault-${TS}.snap s3://acme-prod-vault-snap/ --sse aws:kms
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { memory: 512Mi }
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: {drop: ["ALL"]}
volumeMounts:
- {name: snap, mountPath: /snap}
volumes:
- name: snap
emptyDir: {}
왜 좋은가:
- 6시간마다 raft snapshot + S3 (SSE-KMS) 업로드
- snapshot용 scoped token 사용 (최소권한)
concurrencyPolicy: Forbid로 snapshot 중복 실행 방지
좋은 예시 8: MinIO bucket replication (DR)
# 소스 클러스터 MinIO에서
mc alias set src https://minio.prod-a.acme.io $SRC_KEY $SRC_SECRET
mc alias set dst https://minio.prod-b.acme.io $DST_KEY $DST_SECRET
mc admin replicate add src dst
mc version enable src/assets
mc version enable dst/assets
mc replicate add src/assets --remote-bucket dst/assets --replicate "delete,delete-marker,existing-objects,metadata-sync"
# DR 발생 시 (소스 완전 장애 후 복구)
mc replicate resync start src/assets --remote-bucket dst/assets
왜 좋은가:
- bucket versioning이 replication 전제
resync로 DR 복구 경로 확보mc mirror를 단독 DR 수단으로 사용하지 않음
❌ 나쁜 예시 3: mc mirror만 단독 사용
mc mirror --overwrite src/assets dst/assets # 현재 객체만 동기화, 버전 이력 없음
문제:
- 버전 이력 / 삭제 marker / metadata 누락
- 랜섬웨어 / 실수 삭제 시 복구 불가
좋은 예시 9: Restore drill 기록 양식
# /runbooks/restore-drills/2026-Q1-auth-pg.yaml
drill:
id: drill-2026-q1-auth-pg
component: cloudnativepg:auth-pg
tier: gold
target_rpo: 5m
target_rto: 30m
executed_at: 2026-03-18T14:00:00Z
executor: sre@acme.io
source_backup: barman:auth-pg/base/20260318T020000
restore_target_time: "2026-03-17 23:59:00+00"
restore_cluster: auth-pg-drill
result:
status: success
observed_rpo: 3m
observed_rto: 22m
verification_query: "select count(*) from users where created_at < '2026-03-17 23:59:00'"
verification_result: 1842317
issues:
- description: "WAL fetch parallelism bumped from 4 to 8 for better RTO"
action: "updated Cluster.spec.externalClusters[0].barmanObjectStore.wal.maxParallel to 8"
next_drill_due: 2026-06-18
왜 좋은가:
- RPO/RTO 목표 vs 실측을 같이 기록
- 검증 query 결과까지 남김
- 다음 drill 예정일이 명시 → 90일 초과 시 경보
나쁜 예시 4: "Git에 manifest 있으니 복구 완료"
✗ manifests are in Git
✗ so restore is solved
문제:
- DB state, Vault state, MinIO objects, K3s cluster state 모두 복구 안 됨
- Argo CD sync만으로는 runtime data가 돌아오지 않음
나쁜 예시 5: K3s etcd snapshot만 있으면 PVC도 복구된다고 오해
✗ k3s etcd-snapshot restore → all data back
문제:
- etcd snapshot은 API object 선언만 복구. PVC 안의 파일은 복구 안 됨
- 반드시 Velero + DB-level backup과 병행