운영하다 보면 스테이트풀셋의 PVC가 어느 날 갑자기 꽉 차 있다. PostgreSQL 데이터 디렉터리든 Prometheus TSDB든, 예전 같으면 새 볼륨 붙이고 데이터 옮기고 파드 재배포하는 순서를 밟았다. 지금은 그럴 필요가 거의 없다. EBS는 Elastic Volumes로 붙어 있는 상태 그대로 커지고, 쿠버네티스가 파일시스템까지 자동으로 늘려준다. 이 글에서는 그 과정을 명령어 중심으로 짚는다.
결론부터 말하자면, StorageClass에 allowVolumeExpansion: true만 켜져 있으면 PVC의 spec.resources.requests.storage 값을 키우는 것으로 끝난다. EBS CSI 드라이버가 EC2 ModifyVolume을 호출해 디스크를 키우고, 파드가 떠 있는 노드에서 ext4/xfs 파일시스템을 자동으로 리사이즈한다. 파드 재시작은 필요 없다. 단, 용량은 늘릴 수만 있고 줄일 수 없으며, 같은 볼륨은 24시간 안에 네 번까지만 수정된다.
1. 개요 - 온라인 확장이 무엇이고 왜 2단계인가
PVC(PersistentVolumeClaim)는 파드가 요청하는 스토리지 명세다. 이 요청이 실제 EBS 볼륨(PV)에 바인딩된다. 볼륨 확장(volume expansion)은 이 PVC의 요청 용량을 키우면 아래 실물 EBS까지 따라 커지는 기능이다.
여기서 알아둘 점은 확장이 두 단계로 나뉜다는 것이다.
- 컨트롤러 확장: external-resizer 사이드카가 PVC 변경을 감지하고 AWS
ModifyVolumeAPI를 호출한다. 블록 디바이스 자체가 커지는 단계다. - 노드 확장: 커진 블록 디바이스 위의 파일시스템을 실제로 넓히는 단계다. 파드가 떠 있는 노드에서
resize2fs(ext4)나xfs_growfs(xfs)에 해당하는 작업을 CSI 드라이버가 대신 수행한다.
이 2단계 구조 때문에 확인 시점에 따라 PVC가 잠깐 FileSystemResizePending 상태로 보일 수 있다. 이건 에러가 아니라 "디스크는 커졌고 파일시스템 리사이즈를 기다리는 중"이라는 뜻이다. 아래에서 실제 출력으로 확인한다.
2. 사전 확인 - StorageClass와 CSI 드라이버
확장이 되려면 두 가지가 전제된다. StorageClass의 allowVolumeExpansion이 true여야 하고, EBS CSI 드라이버가 리사이즈를 지원하는 버전이어야 한다.
먼저 StorageClass를 본다.
$ kubectl get sc NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE gp3 (default) ebs.csi.aws.com Delete WaitForFirstConsumer true 30d
ALLOWVOLUMEEXPANSION 열이 true면 통과다. 만약 여기가 false이거나 비어 있으면 PVC를 키워도 API 서버가 요청을 거절한다. 기존 StorageClass는 이 필드를 나중에 켤 수 있다.
$ kubectl patch sc gp3 -p '{"allowVolumeExpansion": true}'
storageclass.storage.k8s.io/gp3 patched
드라이버 파드도 떠 있는지 본다. 확장 기능은 컨트롤러의 external-resizer 사이드카가 담당한다.
$ kubectl get pods -n kube-system -l app=ebs-csi-controller
NAME READY STATUS RESTARTS AGE
ebs-csi-controller-7d9c8b5f4c-abcde 6/6 Running 0 12d
ebs-csi-controller-7d9c8b5f4c-fghij 6/6 Running 0 12d
$ kubectl -n kube-system get deploy ebs-csi-controller -o jsonpath='{.spec.template.spec.containers[*].name}'
ebs-plugin csi-provisioner csi-attacher csi-snapshotter csi-resizer liveness-probe
컨테이너 목록에 csi-resizer가 보이면 확장 준비가 된 것이다. EBS CSI 드라이버는 v1.19.0 이상이면 최신 리사이즈 기능을 무리 없이 쓴다.
3. 확장 실행 - PVC 요청 용량 키우기
대상 PVC의 현재 용량을 확인한다. 여기서는 data-pvc가 10Gi라고 하자.
$ kubectl get pvc data-pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE data-pvc Bound pvc-3f2a1c9e-7b44-4c0a-9a1e-2b6d1f0e8c7a 10Gi RWO gp3 18d
파드가 이 PVC를 마운트해서 쓰고 있는 상태 그대로 요청 용량만 20Gi로 올린다. 파드를 내릴 필요 없다.
$ kubectl patch pvc data-pvc \
-p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
persistentvolumeclaim/data-pvc patched
patch가 먹히면 external-resizer가 바로 EBS ModifyVolume을 호출한다. EBS 쪽에서는 크기 증가가 optimizing 상태에 도달하면 곧 반영되고, 이 전환 자체는 대개 몇 초 안에 일어난다. 다만 볼륨이 완전히 최적화되기까지 백그라운드 작업은 볼륨 크기에 따라 몇 분에서 몇 시간까지 걸릴 수 있다. 파드 입장에서 쓸 수 있는 용량이 늘어나는 시점은 파일시스템 리사이즈가 끝난 뒤다.
4. 확인 - PVC 상태와 파드 안 용량
patch 직후 describe를 보면 진행 조건이 찍힌다. 시점에 따라 Resizing 또는 FileSystemResizePending이 보인다.
$ kubectl describe pvc data-pvc
...
Conditions:
Type Status ... Reason Message
---- ------ ------ -------
FileSystemResizePending True ... Waiting for user to (re-)start a pod to
finish file system resize of volume on node.
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Resizing 30s external-resizer ... External resizer is resizing volume ...
Normal FileSystemResizeRequired 20s external-resizer ... Require file system resize of volume on node
Normal FileSystemResizeSuccessful 5s kubelet MountVolume.NodeExpandVolume succeeded for volume ...
온라인 확장이 지원되는 최신 쿠버네티스에서는 마지막 FileSystemResizeSuccessful 이벤트가 파드를 재시작하지 않아도 찍힌다. 이 이벤트가 나오면 get pvc의 CAPACITY가 20Gi로 바뀐다.
$ kubectl get pvc data-pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE data-pvc Bound pvc-3f2a1c9e-7b44-4c0a-9a1e-2b6d1f0e8c7a 20Gi RWO gp3 18d
확실히 하려면 파드 안에서 마운트 지점을 직접 본다.
$ kubectl exec -it my-pod -- df -h /data Filesystem Size Used Avail Use% Mounted on /dev/nvme1n1 20G 8.1G 12G 41% /data
Size가 20G로 늘고 파드는 그대로 떠 있다. 여기까지가 정상 방식이다. 처음 해보면 FileSystemResizePending이 안 사라져서 당황하는데, 대개는 노드 확장이 아직 안 끝난 것뿐이다. CSI 드라이버 노드 파드 로그(kubectl logs -n kube-system ds/ebs-csi-node -c ebs-plugin)를 보면 리사이즈 진행이 찍힌다.
5. 함정 - 축소 불가, 24시간 제한, 멈춘 확장 복구
줄일 수는 없다
EBS는 크기를 늘릴 수만 있다. PVC 요청을 현재보다 작은 값으로 patch하면 거절된다.
$ kubectl patch pvc data-pvc \
-p '{"spec":{"resources":{"requests":{"storage":"5Gi"}}}}'
The PersistentVolumeClaim "data-pvc" is invalid: spec.resources.requests.storage:
Forbidden: field can not be less than previous value
정말 용량을 줄여야 하면 더 작은 새 볼륨을 만들고 애플리케이션 레벨 도구(rsync 등)로 데이터를 옮기는 수밖에 없다. 그러니 확장은 넉넉하되 과하지 않게, 한 번에 필요한 만큼만 올리는 편이 좋다.
24시간에 네 번, 이게 pending의 진짜 원인일 때가 있다
AWS 문서 기준으로 하나의 EBS 볼륨은 롤링 24시간 동안 최대 네 번까지 수정할 수 있다. 그리고 이전 수정이 completed 상태에 도달하기 전에는 다음 수정을 시작할 수 없다. 이 한도를 넘기면 다음 수정 가능 시각을 알려주는 에러가 난다.
테스트한다고 10Gi에서 12, 14, 16으로 찔끔찔끔 키우면 슬롯 네 개를 금방 다 쓴다. 그 뒤에 patch하면 쿠버네티스 PVC는 요청을 받아들여 Resizing으로 넘어가지만 EBS 쪽에서 진행이 안 되고 멈춰 있다. external-resizer 로그를 보면 이렇게 나온다.
$ kubectl logs -n kube-system deploy/ebs-csi-controller -c csi-resizer | tail -n 3 ... Error expanding volume ... : rpc error: code = Internal desc = Could not modify volume "vol-0abc123...": VolumeModificationRateExceeded: You've reached the maximum modification rate per volume limit. Wait at least the required interval ...
이럴 때는 방법이 없다. 24시간 창이 지나 슬롯이 빌 때까지 기다려야 한다. 그래서 확장은 필요한 목표치로 한 번에 올리는 게 맞다. 용량과 IOPS를 같이 바꿔야 한다면 따로따로 두 번 하지 말고 한 번에 반영해 슬롯을 아끼는 게 좋다.
너무 크게 요청해서 멈췄을 때 되돌리기
실수로 500Gi 같은 값을 넣었는데 EBS 쿼터나 파일시스템 한계에 걸려 확장이 멈추는 경우가 있다. 예전에는 한 번 키운 요청을 되돌릴 수 없어 곤란했는데, RecoverVolumeExpansionFailure 기능 게이트가 켜진 클러스터라면 spec.resources.requests.storage를 실제 할당된 용량 이상, 원래 목표 미만의 값으로 다시 낮춰 멈춘 확장을 정리할 수 있다. 이 기능의 활성화 여부는 쿠버네티스 버전에 따라 다르니 클러스터 상태를 먼저 확인해야 한다. 확실한 값이 아니면 처음부터 무리한 크기를 넣지 않는 게 안전하다.
6. 마무리
정리하면 온라인 PVC 확장의 핵심은 세 가지다. StorageClass에 allowVolumeExpansion: true를 켜두고, PVC의 요청 용량만 키우면 컨트롤러 확장과 노드 파일시스템 리사이즈가 파드 중단 없이 이어진다. 대신 축소는 불가능하고, 같은 볼륨은 24시간에 네 번이라는 EBS 한도를 넘기면 확장이 멈춘 것처럼 보인다. 이 두 제약만 기억하면 급할 때 새벽에 파드 내리는 일은 피할 수 있다.