6.5 스냅샷과 볼륨 확장
볼륨을 만들어 Pod에 붙이는 것까지가 앞 장들의 흐름이었다면, 이 장은 이미 데이터를 담고 운영 중인 볼륨을 다룬다. 특정 시점의 복제 원본을 만들어 두는 스냅샷, 그리고 차오르는 디스크를 중단 없이 키우는 볼륨 확장이다. 둘 다 CSI 드라이버의 능력 위에서 동작하고, Kubernetes API 쪽은 external-snapshotter와 external-resizer 사이드카가 담당한다. 마지막에는 DBA에게 가장 중요한 질문을 다룬다. 스냅샷을 백업이라고 불러도 되는가.
스냅샷 삼형제
스냅샷 API는 PV 계층과 평행한 세 리소스로 구성된다.
| 스냅샷 계층 | 대응 볼륨 계층 | 범위 | 담는 것 |
|---|---|---|---|
| VolumeSnapshot | PVC | namespace | 사용자의 스냅샷 요청 |
| VolumeSnapshotContent | PV | 클러스터 | 실제 스냅샷의 표현 |
| VolumeSnapshotClass | StorageClass | 클러스터 | 드라이버·삭제 정책 |
세 리소스는 코어 API가 아니라 snapshot.storage.k8s.io 그룹의 CRD다. v1 API는 Kubernetes 1.20에서 GA가 됐다. CRD와 이를 처리하는 snapshot controller가 클러스터에 설치되어 있어야 하는데, 배포판이나 관리형 서비스에 따라 기본 설치 여부가 갈리므로 kubectl api-resources | grep volumesnapshot으로 존재부터 확인한다. 드라이버 쪽 요건은 스냅샷을 지원하는 CSI 드라이버와 external-snapshotter 사이드카다.
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: ebs-snapclass
driver: ebs.csi.aws.com
deletionPolicy: Delete
---
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: data-snap-1
spec:
volumeSnapshotClassName: ebs-snapclass
source:
persistentVolumeClaimName: data-claimVolumeSnapshot을 만들면 external-snapshotter가 드라이버의 CreateSnapshot을 호출하고, 만들어진 스냅샷을 표현하는 VolumeSnapshotContent가 생겨 요청과 바인딩된다. PVC와 PV의 관계 그대로다. 완료 여부는 readyToUse로 확인한다.
kubectl get volumesnapshot data-snap-1
NAME READYTOUSE SOURCEPVC RESTORESIZE SNAPSHOTCLASS AGE
data-snap-1 true data-claim 10Gi ebs-snapclass 2mVolumeSnapshotClass의 deletionPolicy는 PV의 reclaim policy와 같은 구도다. Delete면 VolumeSnapshot을 지울 때 content와 스토리지의 실제 스냅샷까지 함께 삭제되고, Retain이면 content가 남는다.
스냅샷에서 새 PVC 복원
스냅샷의 사용처는 복원이다. 새 PVC의 dataSource에 스냅샷을 지정하면 provisioner가 빈 볼륨 대신 스냅샷 내용으로 채워진 볼륨을 만든다.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-restored
spec:
storageClassName: fast-ssd
dataSource:
name: data-snap-1
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi복원은 언제나 새 볼륨의 생성이다. 원본 PVC를 스냅샷 시점으로 제자리에서 되돌리는 기능은 없으므로, 되돌리려면 스냅샷에서 새 PVC를 만들고 워크로드가 새 PVC를 참조하도록 바꾼다. 요청 용량은 스냅샷의 RESTORESIZE 이상이어야 한다. 같은 dataSource 문법에 kind로 PersistentVolumeClaim을 지정하면 기존 PVC를 직접 복제하는 볼륨 클로닝이 되는데, 이 역시 드라이버가 지원할 때의 이야기다.
온라인 볼륨 확장
6.3에서 미뤄 둔 절차다. allowVolumeExpansion이 true인 StorageClass에서 만들어진 PVC는 요청 용량을 키우는 것만으로 확장이 시작된다. 확장 기능은 Kubernetes 1.24에서 GA가 됐다.
클래스 확인
kubectl get storageclass fast-ssd -o jsonpath='{.allowVolumeExpansion}'false거나 비어 있으면 PVC의 용량 수정 자체가 거부된다.
PVC 요청 용량 수정
kubectl patch pvc data-claim -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'controller 단계: 볼륨 확장
external-resizer가 변경을 감지해 드라이버의 ControllerExpandVolume을 호출하고, 스토리지 시스템의 볼륨이 커진다. 이 시점에는 블록 장치만 커진 상태고 파일시스템은 이전 크기 그대로다.
node 단계: 파일시스템 확장
kubelet이 볼륨이 마운트된 노드에서 NodeExpandVolume을 호출해 파일시스템을 늘린다. ext4의 resize2fs, XFS의 xfs_growfs에 해당하는 작업을 node 플러그인이 수행한다. 온라인 확장을 지원하는 드라이버면 Pod가 볼륨을 사용하는 중에도 진행된다.
반영 확인
kubectl get pvc data-claimstatus.capacity가 새 크기로 바뀌어야 완료다. spec의 요청만 커져 있고 status가 그대로면 아직 진행 중이거나 어느 단계에서 멈춘 것이므로, kubectl describe pvc의 조건(condition)과 이벤트를 확인한다. FileSystemResizePending 조건이 남아 있으면 파일시스템 확장이 Pod 기동 시점으로 미뤄진 상태라, 그 볼륨을 쓰는 Pod를 재기동해야 마무리된다.
축소는 지원되지 않는다는 6.3의 이야기가 여기서도 유효하다. 확장에 실패했을 때 요청 값을 원래대로 되돌리는 복구는 버전과 드라이버에 따라 제약이 있다. 덧붙여 일부 클라우드 블록 스토리지는 같은 볼륨의 연속 확장 사이에 시간 간격 제한을 두므로, 조금씩 자주 키우는 운용이 통하지 않는 환경이 있다. 데이터베이스 볼륨이라면 사용량 추세를 근거로 여유를 두고 한 번에 키우는 편이 낫다.
스냅샷은 백업인가
스토리지 스냅샷은 그 순간 디스크에 기록되어 있던 블록의 모습이다. 실행 중인 데이터베이스의 볼륨을 예고 없이 찍으면, 결과물은 그 순간 전원이 차단된 서버의 디스크와 같다. 이 상태를 crash-consistent라고 부른다. 메모리 버퍼에만 있고 디스크에 내려가지 않은 변경은 담기지 않고, WAL은 마지막으로 디스크에 동기화된 지점까지만 있다.
PostgreSQL 같은 데이터베이스는 이 상태에서 기동하도록 설계되어 있다. crash recovery가 WAL을 재생해 정합성을 복구하기 때문이다. 그래서 스냅샷만 주기적으로 만들어 두면 되는 것처럼 보이지만, 조건이 붙는다.
첫째, 복원할 때마다 crash recovery를 통과해야 한다. 마지막 checkpoint 이후의 WAL을 전부 재생해야 기동이 끝나므로, checkpoint 간격이 긴 인스턴스는 복원 후 기동도 오래 걸린다. 스냅샷 직전에 CHECKPOINT를 실행해 두면 재생할 양이 줄어든다.
둘째, 볼륨이 하나일 때의 이야기다. 데이터 디렉터리와 WAL을 서로 다른 볼륨에 둔 구성이라면 두 볼륨의 스냅샷은 같은 순간이 아니다. VolumeSnapshot은 볼륨 하나 단위이고, 여러 볼륨을 한 시점으로 묶어 찍는 원자성을 제공하지 않는다. 볼륨 간 시점이 어긋난 스냅샷 세트는 crash-consistent조차 아니어서, 복원한 인스턴스가 기동에 실패하거나 부정합인 채로 기동할 수 있다.
application-consistent는 애플리케이션이 스냅샷을 준비한 상태에서 만든 스냅샷이다. 데이터베이스라면 스냅샷 전후로 애플리케이션 수준의 절차를 밟는다. PostgreSQL은 pg_backup_start()로 백업 모드에 들어간 뒤 볼륨들의 스냅샷을 만들고 pg_backup_stop()으로 빠져나온다. 백업 모드로 감싸면 볼륨 간 스냅샷 시점이 어긋나도, 그 구간의 WAL을 재생해 일관된 시점으로 수렴시키는 복원이 성립한다. 파일시스템 수준에서 쓰기를 잠시 멈추는 fsfreeze를 병행하는 방법도 있다.
application-consistent 스냅샷을 얻었다 해도 백업이라고 부르기에는 남는 것이 있다. 스냅샷은 대부분 원본과 같은 스토리지 시스템 안에 존재한다. 스토리지 시스템 자체의 장애, 계정 단위의 삭제 사고, 리전 장애 앞에서는 원본과 운명을 같이한다. 스냅샷은 시점 복제일 뿐이라 잘못된 DELETE나 페이지 손상도 충실히 담고, 특정 트랜잭션 직전으로 돌아가는 PITR은 WAL 아카이빙 없이는 성립하지 않는다. 스냅샷의 자리는 빠른 복원 지점이고, 다른 위치에 보관하는 백업과 WAL 아카이브를 갖춘 백업 체계의 한 구성 요소다.
정리
스냅샷 삼형제는 PVC, PV, StorageClass와 평행하게 동작하고, 복원은 dataSource를 통한 새 볼륨 생성이다. 볼륨 확장은 controller 단계와 node 단계를 거쳐 파일시스템까지 늘어나야 완료다. 스냅샷은 기본적으로 crash-consistent이므로, 데이터베이스에서는 checkpoint와 백업 모드를 더해 application-consistent로 만들고, 그렇게 만든 스냅샷도 백업 체계의 구성 요소 하나로만 취급한다.