Published on

Kubernetes PVC Pending - EBS CSI 동적 프로비저닝 실패 해결

Authors

서론

EKS에서 StatefulSet이나 Deployment에 PVC를 붙였는데 PVC Pending이 계속 유지되면, 대부분 “동적 프로비저닝(External Provisioner)이 EBS 볼륨을 만들지 못하는 상황”입니다. 겉으로는 단순히 스토리지가 안 붙는 문제처럼 보이지만, 실제 원인은 IAM/IRSA 권한, StorageClass 파라미터, AZ 토폴로지, 서브넷/태그, CSI 컨트롤러 상태, 심지어는 잘못된 volumeBindingMode까지 다양합니다.

이 글에서는 EBS CSI Driver 기반 동적 프로비저닝이 실패해 PVC가 Pending으로 멈출 때를 전제로, “어디부터 확인해야 가장 빨리 원인을 좁힐 수 있는지”를 체크리스트 형태로 정리하고, 자주 나오는 에러 메시지별 처방을 함께 제공합니다.


PVC Pending의 구조: 어디에서 멈추는가

PVC가 Pending일 때는 보통 아래 중 하나입니다.

  1. PVC 이벤트에 Provisioning 시도가 아예 없음: StorageClass/프로비저너가 맞지 않거나 CSI 컨트롤러가 동작하지 않음
  2. Provisioning 시도는 있는데 EBS 생성이 실패: IAM/IRSA, API 제한, KMS, 태그/서브넷, 토폴로지 문제
  3. EBS는 생성됐는데 바인딩/스케줄링이 막힘: WaitForFirstConsumer + Pod 스케줄 불가, AZ 불일치

따라서 진단은 “PVC 이벤트 → CSI 컨트롤러 로그 → StorageClass/토폴로지 → IAM/IRSA” 순서로 내려가면 빠릅니다.


1) 가장 먼저: PVC/StorageClass 이벤트로 단서 확보

PVC 상태/이벤트 확인

kubectl get pvc -A
kubectl describe pvc -n <ns> <pvc-name>

Events 섹션에서 자주 보이는 패턴:

  • waiting for a volume to be created, either by external provisioner "ebs.csi.aws.com" ...
  • failed to provision volume with StorageClass ...
  • no persistent volumes available for this claim and no storage class is set

StorageClass가 실제로 무엇인지 확인

kubectl get storageclass
kubectl describe storageclass <sc-name>

EBS CSI 동적 프로비저닝이라면 대개 다음 중 하나여야 합니다.

  • provisioner: ebs.csi.aws.com
  • volumeBindingMode: WaitForFirstConsumer (권장)

만약 kubernetes.io/aws-ebs(in-tree)로 되어 있거나, 클러스터가 CSI로 전환된 상태에서 in-tree를 사용하면 예상치 못한 Pending이 나올 수 있습니다.


2) EBS CSI 컨트롤러가 정말 동작 중인가

EKS Add-on 또는 Helm으로 설치된 EBS CSI Driver는 보통 kube-system 네임스페이스에 있습니다.

kubectl -n kube-system get pods -l app.kubernetes.io/name=aws-ebs-csi-driver
kubectl -n kube-system get deploy ebs-csi-controller
kubectl -n kube-system get ds ebs-csi-node

정상이라면:

  • ebs-csi-controller Deployment의 Pod가 Running
  • ebs-csi-node DaemonSet이 노드 수만큼 Running

컨트롤러 로그를 보면 실패 원인이 직관적으로 드러나는 경우가 많습니다.

kubectl -n kube-system logs deploy/ebs-csi-controller -c ebs-plugin --tail=200
kubectl -n kube-system logs deploy/ebs-csi-controller -c csi-provisioner --tail=200

특히 csi-provisioner 컨테이너 로그에 “왜 PVC를 프로비저닝 못했는지”가 찍히는 경우가 많습니다.


3) 가장 흔한 원인 1: IRSA/IAM 권한 문제

EBS CSI Driver는 EC2 API를 호출해 EBS를 생성/태그/연결합니다. 권한이 없으면 PVC는 Pending으로 남고 이벤트에는 AccessDenied류가 뜹니다.

증상 예시

  • UnauthorizedOperation
  • AccessDenied: Not authorized to perform: ec2:CreateVolume
  • sts:AssumeRoleWithWebIdentity AccessDenied

이 경우는 대개 아래 둘 중 하나입니다.

  1. IRSA가 제대로 연결되지 않음 (ServiceAccount에 role-arn 주석 누락/오타)
  2. IAM Policy가 부족 (CreateVolume, CreateTags, AttachVolume 등)

IRSA 자체가 꼬였을 때의 점검 흐름은 아래 글이 그대로 도움이 됩니다.

ServiceAccount에 role-arn이 붙어있는지 확인

kubectl -n kube-system get sa ebs-csi-controller-sa -o yaml | sed -n '1,120p'

대개 아래가 있어야 합니다.

metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::<account-id>:role/<EbsCsiRole>

(참고) EBS CSI Driver에 필요한 대표 권한

AWS 관리형 정책 AmazonEBSCSIDriverPolicy를 쓰는 것이 가장 안전합니다. 커스텀 정책을 쓴다면 최소한 아래 액션들이 필요합니다.

  • ec2:CreateVolume, ec2:DeleteVolume, ec2:DescribeVolumes
  • ec2:CreateTags, ec2:DeleteTags
  • ec2:AttachVolume, ec2:DetachVolume
  • ec2:DescribeAvailabilityZones, ec2:DescribeInstances, ec2:DescribeTags

또한 KMS 암호화를 쓰면 kms:Encrypt/Decrypt/GenerateDataKey 등이 추가로 필요합니다(뒤에서 다룹니다).


4) 가장 흔한 원인 2: StorageClass 파라미터/모드 실수

volumeBindingMode가 핵심인 이유

EBS는 AZ에 종속됩니다. volumeBindingMode: Immediate이면 PVC 생성 시점에 AZ를 결정해야 하는데, 아직 Pod가 어느 노드(AZ)에 스케줄될지 모르므로 잘못된 AZ로 볼륨이 생성되거나(혹은 토폴로지 제약으로 생성 자체가 실패) 이후 스케줄링이 막힐 수 있습니다.

EKS에서는 보통 아래처럼 WaitForFirstConsumer를 권장합니다.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3-wffc
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
  type: gp3
  fsType: ext4

allowVolumeExpansion 누락

PVC 확장이 필요한 워크로드라면 allowVolumeExpansion: true가 없으면 확장이 실패합니다(이건 Pending과 직접 관련은 적지만 운영에서 자주 같이 터집니다).

fsType/파일시스템 이슈

대부분 ext4가 무난합니다. 특정 이미지/노드 설정에 따라 xfs를 쓸 때 마운트 실패가 날 수 있는데, 이 경우는 PVC Pending이 아니라 Pod 이벤트에서 MountVolume 실패로 나타나는 편입니다.


5) 가장 흔한 원인 3: AZ/토폴로지 불일치(특히 멀티 AZ)

증상

  • PVC는 Pending 또는 Bound가 늦게 됨
  • Pod는 Pending이며 스케줄러 이벤트에 볼륨 토폴로지 관련 메시지가 보임

Pod 이벤트 확인:

kubectl describe pod -n <ns> <pod>

WaitForFirstConsumer를 쓰는 경우, PVC는 “Pod가 스케줄될 노드의 AZ”가 정해진 뒤에야 볼륨이 만들어집니다. 따라서 Pod가 스케줄 불가능하면 PVC도 계속 Pending이 됩니다.

이때는 스토리지 문제가 아니라 노드 리소스/노드셀렉터/테인트/서브넷 IP 부족 같은 스케줄링 문제일 수 있습니다. Pod 자체가 Pending인 경우는 아래 글의 “IP 부족으로 Pending” 케이스도 함께 점검해볼 만합니다.

nodeSelector/affinity로 특정 AZ를 강제했는지 확인

예를 들어 Pod가 topology.kubernetes.io/zone=ap-northeast-2a만 허용하도록 묶여 있는데, 해당 AZ에 노드가 없거나 스케줄 불가하면 PVC는 끝까지 Pending입니다.


6) 서브넷/태그/EC2 환경 이슈: EBS 생성 자체가 실패하는 케이스

EBS CSI는 EBS를 만들 때 AZ를 기준으로 생성합니다. 다음과 같은 환경 이슈가 있으면 CreateVolume이 실패합니다.

  • 해당 AZ에서 EBS 쿼터/용량 제한
  • 계정/리전에 대한 API 제한(Throttling)
  • KMS 키 권한 문제(암호화 볼륨)

CSI 로그에서 Throttling/Limit 확인

kubectl -n kube-system logs deploy/ebs-csi-controller -c ebs-plugin --tail=300 | rg -i "thrott|limit|quota|kms|accessdenied|unauthorized"

Throttling이면 재시도/지수백오프가 걸리며 PVC가 오래 Pending으로 보일 수 있습니다. 이 경우 CloudTrail/CloudWatch에서 EC2 API 호출 실패를 같이 확인하는 게 빠릅니다.


7) KMS 암호화 사용 시: 키 정책과 IAM을 같이 봐야 함

StorageClass에서 암호화를 켜는 형태는 보통 아래 중 하나입니다.

  • 기본 EBS 암호화가 계정에 켜져 있음
  • StorageClass에 encrypted: "true"kmsKeyId를 지정

예시:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3-kms
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
parameters:
  type: gp3
  encrypted: "true"
  kmsKeyId: arn:aws:kms:ap-northeast-2:<account-id>:key/<key-id>

이때는 EBS CSI가 Assume한 Role에 KMS 권한이 있어야 하고, 동시에 KMS Key policy가 그 Role 사용을 허용해야 합니다. 한쪽만 맞아도 실패합니다.

로그에는 보통 kms:GenerateDataKey 또는 AccessDeniedException 형태가 남습니다.


8) 실전 트러블슈팅 플로우(10분 컷)

아래 순서대로 보면, 대부분 10~15분 안에 원인을 특정할 수 있습니다.

1) PVC 이벤트 확인

kubectl describe pvc -n <ns> <pvc>
  • 프로비저너가 ebs.csi.aws.com인지
  • 에러가 AccessDenied인지, topology인지, StorageClass 미지정인지

2) StorageClass 확인

kubectl get sc <sc> -o yaml
  • provisioner: ebs.csi.aws.com
  • volumeBindingMode: WaitForFirstConsumer 권장

3) CSI 컨트롤러 상태/로그

kubectl -n kube-system get pods -l app.kubernetes.io/name=aws-ebs-csi-driver
kubectl -n kube-system logs deploy/ebs-csi-controller -c csi-provisioner --tail=200
kubectl -n kube-system logs deploy/ebs-csi-controller -c ebs-plugin --tail=200

4) IRSA/IAM

kubectl -n kube-system get sa ebs-csi-controller-sa -o yaml | rg -n "role-arn|eks.amazonaws.com"
  • role-arn 존재 여부
  • STS AccessDenied가 있으면 IRSA부터 바로 수정

5) Pod 스케줄링 상태(WaitForFirstConsumer일 때 필수)

kubectl get pod -n <ns>
kubectl describe pod -n <ns> <pod>
  • Pod가 Pending이면, 스케줄러 이벤트를 먼저 해결(리소스 부족, taint, nodeSelector, IP 부족 등)

9) 재현 가능한 최소 예제: PVC Pending 상황 만들고 고치기

(1) 잘못된 StorageClass(Immediate)로 멀티 AZ에서 꼬이는 예

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3-immediate
provisioner: ebs.csi.aws.com
volumeBindingMode: Immediate
parameters:
  type: gp3

이 상태에서 특정 AZ로만 스케줄되는 워크로드가 있거나 노드 풀이 제한적이면, 볼륨이 다른 AZ에 만들어져 Pod 스케줄이 막힐 수 있습니다.

(2) 수정: WaitForFirstConsumer로 변경

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3-wffc
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
  type: gp3
  fsType: ext4

(3) PVC/Pod 예시

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-data
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: gp3-wffc
  resources:
    requests:
      storage: 20Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
    - name: app
      image: nginx:1.25
      volumeMounts:
        - name: data
          mountPath: /data
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: app-data

적용 후 확인:

kubectl apply -f ./pvc-pod.yaml
kubectl get pvc app-data -w
kubectl get pv -w

10) 운영 팁: “PVC Pending”을 장애로 키우지 않기

  • 클러스터 표준 StorageClass를 하나 정해두기: gp3 + WaitForFirstConsumer + allowVolumeExpansion 조합을 기본으로
  • EBS CSI Driver는 EKS Add-on으로 관리: 버전 호환성과 롤백이 쉬움
  • IRSA 변경은 GitOps로 추적: ServiceAccount annotation이 수동 변경되면 재발이 잦음
  • 스케줄링 문제와 분리해서 보기: Pod Pending이면 PVC도 같이 Pending처럼 보일 수 있음

리소스/스케일링 문제로 Pod가 Pending인 경우에는 HPA/metrics 쪽도 함께 점검해야 하는데, metrics-server가 0값을 내는 케이스는 스케줄링/오토스케일 판단을 왜곡해 장애를 키울 수 있습니다.


결론

PVC Pending은 “스토리지 생성이 안 됨” 하나로 보이지만, 실제로는 CSI 컨트롤러 동작 여부 → StorageClass 설정 → IRSA/IAM 권한 → 토폴로지(AZ)와 Pod 스케줄링이 맞물린 결과입니다.

가장 효율적인 접근은 kubectl describe pvc 이벤트에서 시작해, ebs-csi-controllercsi-provisioner/ebs-plugin 로그로 실패 원인을 확정하고, 그 다음에 StorageClass(WaitForFirstConsumer)와 IRSA/IAM, KMS/토폴로지를 순서대로 교정하는 것입니다.

이 흐름대로만 점검하면, “계속 Pending이라 막막한 상태”에서 벗어나 원인을 빠르게 좁히고 재발 방지까지 연결할 수 있습니다.