Published on

K8s PVC Pending 해결 - EBS CSI StorageClass

Authors

Kubernetes에서 PVCPending 상태로 오래 멈춰 있으면, 애플리케이션은 Pod 스케줄링 단계에서 함께 막히는 경우가 많습니다. 특히 EKS에서 EBS CSI 드라이버를 쓰는 환경이라면, 원인은 대개 StorageClass 설정(프로비저너, volumeBindingMode, 파라미터) 또는 권한/IAM, 토폴로지(가용영역) 불일치로 귀결됩니다.

이 글에서는 PVC Pending증상별로 빠르게 분류하고, EBS CSI 기반 StorageClass를 올바르게 구성해 안정적으로 동적 프로비저닝이 되도록 만드는 실전 체크리스트를 제공합니다.

문제의 성격상 “원인 하나”가 아니라 여러 설정이 동시에 얽혀 발생하는 경우가 많습니다. 따라서 아래 순서대로 확인하면 재현 가능하게 좁혀갈 수 있습니다.

PVC Pending을 먼저 분류하기: 이벤트가 답이다

가장 먼저 할 일은 PVC와 관련 이벤트를 확인하는 것입니다.

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

describe 출력에서 보통 다음과 같은 힌트가 나옵니다.

  • waiting for a volume to be created, either by external provisioner ...
  • no persistent volumes available for this claim and no storage class is set
  • failed to provision volume with StorageClass ... AccessDenied ...
  • could not create volume in ... because no subnets matched ...
  • node(s) had volume node affinity conflict

이벤트 메시지에 따라 접근이 달라집니다.

  • 동적 프로비저닝 자체가 안 됨: 프로비저너/CSI 설치/권한 문제
  • 프로비저닝은 되는데 바인딩/스케줄링이 안 됨: volumeBindingMode, AZ/토폴로지, 노드/파드 제약 문제
  • 프로비저닝은 요청되나 AWS API에서 실패: IAM, KMS, 태그/쿼터, 서브넷/보안 설정 문제

참고로, 클러스터 전반 트러블슈팅 루틴은 다른 장애에서도 유사합니다. 이미지 풀 문제를 함께 겪는다면 Kubernetes ImagePullBackOff·ErrImagePull 해결 체크리스트처럼 “이벤트 기반으로 원인 분류”가 가장 빠릅니다.

EBS CSI 드라이버가 맞는지 확인: in-tree에서 CSI로

EKS 최신 환경에서는 EBS 볼륨은 EBS CSI 드라이버(ebs.csi.aws.com)를 사용하는 것이 표준입니다. 반대로 kubernetes.io/aws-ebs 같은 in-tree 프로비저너를 StorageClass에서 계속 쓰면, 환경/버전에 따라 의도치 않게 프로비저닝이 실패하거나(또는 더 이상 권장되지 않음) Pending이 길어질 수 있습니다.

드라이버 설치 여부는 다음으로 확인합니다.

kubectl get pods -n kube-system | grep -i ebs
kubectl get csidriver
kubectl describe csidriver ebs.csi.aws.com

EKS에서는 보통 eks add-on으로 설치합니다.

aws eks describe-addon --cluster-name <cluster> --addon-name aws-ebs-csi-driver

만약 드라이버가 없거나 비정상이라면, PVC는 external provisioner를 기다리며 Pending에 머무를 수 있습니다.

StorageClass 핵심: provisioner와 volumeBindingMode

PVC Pending의 가장 흔한 원인은 StorageClass가 CSI 프로비저너를 가리키지 않거나, 혹은 바인딩 모드가 토폴로지와 충돌하는 것입니다.

올바른 EBS CSI StorageClass 예시

아래는 범용적으로 많이 쓰는 gp3 기반 StorageClass 예시입니다.

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

포인트는 다음입니다.

  • provisioner는 반드시 ebs.csi.aws.com
  • volumeBindingMode는 웬만하면 WaitForFirstConsumer
    • 파드가 어느 노드(AZ)에 스케줄될지 결정된 뒤, 그 AZ에 맞춰 EBS를 생성/바인딩
    • 멀티 AZ 노드그룹에서 Immediate를 쓰면, 볼륨이 먼저 특정 AZ에 만들어져 node affinity conflict로 Pending이 이어질 수 있음

현재 StorageClass 목록과 기본 StorageClass는 이렇게 확인합니다.

kubectl get storageclass
kubectl get storageclass -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.annotations.storageclass\.kubernetes\.io/is-default-class}{"\n"}{end}'

기본 StorageClass가 의도와 다르면, 원하는 StorageClass에 기본 어노테이션을 설정할 수 있습니다.

kubectl patch storageclass ebs-gp3 -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

PVC에 StorageClass가 지정되지 않은 경우

이벤트에 no storage class is set가 보이면, PVC가 StorageClass를 지정하지 않았고, 클러스터에도 기본 StorageClass가 없을 가능성이 큽니다.

PVC 예시는 다음처럼 storageClassName을 명시합니다.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-data
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: ebs-gp3
  resources:
    requests:
      storage: 20Gi

EBS는 기본적으로 ReadWriteOnce(단일 노드 마운트) 모델이므로, ReadWriteMany가 필요하다면 EFS 같은 다른 스토리지를 고려해야 합니다.

WaitForFirstConsumer가 중요한 이유: AZ 불일치로 인한 Pending

EBS는 AZ 종속입니다. 즉, 볼륨이 ap-northeast-2a에 만들어졌다면, 해당 볼륨은 같은 AZ 노드에만 붙습니다.

멀티 AZ 노드그룹에서 다음 조합이 흔한 실패 패턴입니다.

  • StorageClass가 Immediate
  • PVC가 먼저 생성되며 임의의 AZ에 EBS가 생성
  • 파드는 다른 AZ 노드로 스케줄 시도
  • 결과: volume node affinity conflict 또는 스케줄 불가로 Pending 지속

이를 해결하는 정석이 volumeBindingMode: WaitForFirstConsumer입니다.

추가로, 파드에 nodeSelectornodeAffinity로 특정 AZ를 강제했다면, StorageClass/서브넷/노드그룹이 그 AZ를 실제로 제공하는지까지 같이 봐야 합니다.

IAM 권한 문제: AccessDenied로 프로비저닝 실패

PVC 이벤트에 AccessDenied 또는 UnauthorizedOperation가 보이면, 대개 EBS CSI 컨트롤러(프로비저너)가 AWS API를 호출할 권한이 없습니다.

EKS에서는 IRSA(IAM Roles for Service Accounts)를 쓰는 구성이 일반적입니다.

확인 포인트:

  • kube-systemebs-csi-controller-sa 서비스어카운트에 IAM Role 어노테이션이 붙어 있는지
  • 해당 Role에 AmazonEBSCSIDriverPolicy(또는 동등 커스텀 정책)가 연결되어 있는지
kubectl get sa -n kube-system ebs-csi-controller-sa -o yaml

출력에서 다음 형태가 보여야 합니다.

  • eks.amazonaws.com/role-arn: arn:aws:iam::...:role/...

권한 문제는 PVC Pending뿐 아니라, 워크로드 전반 장애로 이어질 수 있습니다. 특히 노드/파드 시간이 틀어져 STS 호출이 실패하는 경우도 있으니, 토큰/STS 관련 오류가 함께 보이면 EKS Pod 시간 드리프트로 STS·TLS 실패 해결하기도 같이 점검하는 편이 안전합니다.

KMS 암호화 사용 시: encrypted와 kmsKeyId

조직 정책상 EBS를 KMS로 암호화해야 하는 경우, StorageClass에 encrypted: "true"만 넣고 끝내면 안 되는 케이스가 있습니다.

  • 기본 EBS 암호화가 계정 레벨에서 활성화되어 있지 않음
  • 특정 CMK를 강제해야 함
  • CSI 역할에 KMS 권한이 없음

StorageClass에 키를 명시할 수도 있습니다.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: ebs-gp3-kms
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
parameters:
  type: gp3
  encrypted: "true"
  kmsKeyId: arn:aws:kms:ap-northeast-2:123456789012:key/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee

이때 CSI 컨트롤러 Role에 최소한 다음 권한이 필요합니다.

  • kms:Encrypt, kms:Decrypt, kms:GenerateDataKey, kms:CreateGrant, kms:DescribeKey

KMS 권한이 없으면 이벤트에 KMS 관련 AccessDeniedException이 찍히면서 PVC가 Pending에 머뭅니다.

서브넷/토폴로지/태그 문제: EKS에서 흔한 함정

EBS CSI는 볼륨 생성 시 클러스터 VPC 환경과 서브넷 토폴로지의 영향을 받습니다. 특히 EKS에서 로드밸런서/서브넷 태그 이슈를 겪어본 팀이라면, 스토리지도 비슷한 결로 막힐 수 있습니다.

확인 포인트:

  • 노드가 실제로 어느 AZ 서브넷에 붙어 있는지
  • (환경에 따라) 서브넷 태그/라우팅/엔드포인트 정책으로 EC2 API가 막히지 않는지
  • 계정의 EBS 볼륨 쿼터 초과 여부

PVC 이벤트에서 could not create volume 류 메시지가 나오면, AWS 쪽 실패이므로 CloudTrail/컨트롤러 로그를 같이 확인하는 것이 빠릅니다.

kubectl logs -n kube-system -l app=ebs-csi-controller -c ebs-plugin --tail=200

실제 재현 가능한 해결 절차(권장 순서)

여기부터는 “무엇을 바꾸면 바로 풀리는지”에 초점을 맞춘 절차입니다.

1) PVC/PV/파드 상태와 이벤트를 한 번에 본다

kubectl get pvc -n <namespace>
kubectl describe pvc -n <namespace> <pvc-name>
kubectl get pod -n <namespace> -o wide
kubectl describe pod -n <namespace> <pod-name>

파드 이벤트에 0/.. nodes are available과 함께 볼륨 관련 메시지가 있으면, volumeBindingMode/AZ 문제일 가능성이 큽니다.

2) StorageClass가 CSI인지 확인하고, 아니라면 새로 만든다

kubectl get storageclass -o yaml

provisionerebs.csi.aws.com가 아니라면, 위에서 제시한 ebs-gp3를 생성하고 PVC에 지정합니다.

3) 멀티 AZ라면 volumeBindingMode를 WaitForFirstConsumer로

기존 StorageClass를 수정하기는 어렵거나(immutable 필드) 위험할 수 있으니, 보통은 새 StorageClass를 만들고 PVC를 새로 생성하는 방식이 안전합니다.

4) IRSA/IAM 정책을 점검한다

AccessDenied가 보이면 거의 100% 여기입니다.

  • 서비스어카운트 Role 어노테이션
  • Role 정책(관리형 정책 또는 커스텀)
  • OIDC provider 연결

5) KMS를 쓴다면 키 정책과 CSI Role 권한까지 본다

키 정책에서 해당 Role이 허용되어 있는지까지 확인해야 합니다.

예시: “PVC Pending”을 끝내는 최소 구성

아래는 가장 단순한 동작 예시입니다.

StorageClass

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

PVC

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: demo-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: ebs-gp3
  resources:
    requests:
      storage: 5Gi

Pod

apiVersion: v1
kind: Pod
metadata:
  name: demo-pod
spec:
  containers:
    - name: app
      image: public.ecr.aws/docker/library/busybox:1.36
      command: ["sh", "-c", "echo ok > /data/ok.txt; sleep 3600"]
      volumeMounts:
        - name: data
          mountPath: /data
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: demo-pvc

적용 후에는 아래 순서로 상태가 변하는지 확인합니다.

  • PVCPending에서 Bound
  • PodPending에서 Running
kubectl apply -f sc.yaml
kubectl apply -f pvc.yaml
kubectl apply -f pod.yaml
kubectl get pvc,pv,pod

운영 팁: 장애를 줄이는 StorageClass 설계 체크

  • 멀티 AZ 노드그룹이라면 WaitForFirstConsumer를 기본값으로 고려
  • gp2 대신 gp3로 표준화(성능/비용 튜닝 여지)
  • allowVolumeExpansion: true로 증설 대응(파일시스템 확장까지 워크로드별 검증)
  • KMS 강제 환경은 kmsKeyId와 CSI Role의 KMS 권한을 세트로 관리
  • 클러스터 업그레이드 시 in-tree에서 CSI로의 전환 여부 점검

마무리

PVC Pending은 쿠버네티스 자체 문제라기보다, 스토리지 프로비저닝의 조건(드라이버, 권한, 토폴로지, 바인딩 모드) 중 하나가 충족되지 않아 생기는 “결과”인 경우가 대부분입니다.

정리하면, EKS에서 EBS를 쓴다면 다음 2가지만 먼저 맞추는 것이 성공 확률이 가장 높습니다.

  • StorageClass의 provisionerebs.csi.aws.com
  • 멀티 AZ 환경에서는 volumeBindingModeWaitForFirstConsumer

그 다음으로 이벤트에 AccessDenied나 KMS 오류가 보이면 IRSA/IAM/KMS 정책을 집중 점검하면, Pending을 재현 가능하게 해소할 수 있습니다.