Certified Kubernetes Administrator (CKA) #17 Storage 2: StorageClass, 동적 프로비저닝, reclaim policy, expansion

#16 Storage 1에서 PV와 PVC, 그리고 관리자가 PV를 미리 만들어 두는 정적 프로비저닝을 다뤘습니다. 정적 방식은 동작은 확실하지만, 사용자가 스토리지를 요청할 때마다 관리자가 손으로 PV를 만들어 줘야 합니다. 노드가 수십 대이고 PVC 요청이 수시로 들어오는 클러스터에서는 이 방식이 곧 병목이 됩니다.

이번 글은 그 병목을 없애는 StorageClass와 동적 프로비저닝입니다. PVC 하나만 만들면 PV가 자동으로 생기게 만들고, 그 PV를 지울 때 데이터를 남길지 지울지 정하는 reclaim policy, 그리고 이미 쓰고 있는 볼륨을 무중단으로 키우는 volume expansion까지 운영 관점에서 정리하겠습니다.

정적 프로비저닝의 한계 #

#16에서 본 정적 방식의 흐름을 떠올려 보겠습니다. 관리자가 먼저 PV를 만들어 두면, 사용자가 PVC로 용량과 접근 모드를 요청하고, 컨트롤러가 조건이 맞는 PV를 찾아 PVC에 묶습니다(binding). 문제는 PV를 미리 만들어 둬야 한다는 점입니다.

  • 사용자가 요청할 용량과 개수를 관리자가 미리 알 수 없습니다.
  • 요청이 올 때마다 관리자가 PV를 손으로 만들면 운영 부담이 커집니다.
  • 미리 만들어 둔 PV의 용량이 요청과 정확히 맞지 않으면 자원이 낭비됩니다.

동적 프로비저닝은 이 흐름을 뒤집습니다. 사용자가 PVC를 만드는 순간, 그 PVC가 가리키는 StorageClass가 PV를 즉시 자동으로 만들어 줍니다. 관리자는 PV를 일일이 만들 필요가 없고, 어떤 종류의 스토리지를 어떻게 만들지를 정의하는 StorageClass 한 개만 준비하면 됩니다.

StorageClass란 #

StorageClass는 스토리지의 종류(class)를 정의하는 클러스터 단위 오브젝트입니다. “이 등급의 스토리지를 요청하면 이런 방식으로 볼륨을 만들어라"라는 템플릿입니다. 네임스페이스에 속하지 않습니다.

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

각 필드의 의미는 다음과 같습니다.

  • provisioner: 실제로 볼륨을 만드는 주체입니다. 어떤 CSI 드라이버 또는 in-tree 플러그인이 프로비저닝을 담당할지 지정합니다. 클라우드라면 EBS,PD,Disk 등의 CSI 드라이버, 온프레미스라면 NFS,Ceph,로컬 프로비저너가 들어갑니다.
  • parameters: provisioner에 넘기는 옵션입니다. 디스크 타입(gp3,premium), 파일시스템, 복제 수 같은 값이 provisioner마다 다르게 정의됩니다.
  • reclaimPolicy: 이 StorageClass가 만든 PV를 PVC가 사라진 뒤 어떻게 처리할지입니다. 뒤에서 자세히 다루겠습니다.
  • volumeBindingMode: PV를 언제 바인딩하고 프로비저닝할지를 정합니다. 이 값이 동작에 큰 영향을 줍니다.
  • allowVolumeExpansion: 이 값이 true여야 나중에 PVC 용량을 키울 수 있습니다.

동적 프로비저닝의 흐름 #

동적 프로비저닝에서 사용자가 만드는 것은 PVC 하나뿐입니다. PVC가 storageClassName으로 StorageClass를 지정하면, 그 StorageClass의 provisioner가 조건에 맞는 PV를 자동으로 만들어 PVC에 묶습니다.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 20Gi

이 PVC를 적용하면 다음 순서로 일이 진행됩니다.

  1. 사용자가 data-pvc를 만듭니다. storageClassName: fast-ssd로 등급을 지정합니다.
  2. fast-ssd StorageClass의 provisioner가 20Gi 볼륨을 실제 스토리지(예: EBS)에 만듭니다.
  3. 그 볼륨을 표현하는 PV가 자동으로 생성되어 data-pvc에 바인딩됩니다.
  4. Pod가 이 PVC를 마운트하면 볼륨을 쓰기 시작합니다.

관리자가 PV를 손으로 만든 적이 없다는 점이 핵심입니다. PVC와 StorageClass만으로 PV가 자동으로 등장했습니다.

# StorageClass 목록 (default 표시 확인)
k get storageclass

# PVC가 PV에 바인딩됐는지 확인
k get pvc data-pvc
k get pv

default StorageClass #

PVC에서 storageClassName을 생략하면 클러스터의 default StorageClass가 쓰입니다. default는 StorageClass에 붙은 annotation으로 지정됩니다.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer

annotation 값이 "true"인 StorageClass가 default가 됩니다. 기존 default를 다른 StorageClass로 바꾸려면, 현재 default의 annotation을 "false"로 내리고 새 StorageClass의 annotation을 "true"로 올립니다. default는 하나만 두는 것이 안전합니다. 둘 이상이면 어느 것이 선택될지 예측하기 어렵습니다.

# 명령으로 default 전환
k patch storageclass standard \
  -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

storageClassName: ""(빈 문자열)을 명시하면 default를 쓰지 않고 동적 프로비저닝을 끄겠다는 뜻입니다. 이 경우 정적으로 만들어 둔 PV에만 바인딩됩니다. 이 구분이 시험에서 자주 헷갈리는 지점입니다.

volumeBindingMode #

volumeBindingModePV를 언제 만들고 묶을지를 정합니다. 두 값의 차이가 스케줄링과 직결되므로 정확히 이해해야 합니다.

동작쓰임
ImmediatePVC를 만드는 즉시 PV를 프로비저닝하고 바인딩어디서나 접근 가능한 네트워크 스토리지
WaitForFirstConsumer이 PVC를 쓰는 Pod가 스케줄될 때까지 바인딩을 미룸토폴로지 제약이 있는 스토리지(특정 zone,노드)

Immediate는 PVC가 생기자마자 볼륨을 만듭니다. 그런데 클라우드 디스크나 로컬 볼륨처럼 특정 zone이나 노드에 묶이는 스토리지라면 문제가 생깁니다. Pod가 아직 어느 노드에 갈지 정해지지 않았는데 볼륨이 먼저 한 zone에 만들어지면, 스케줄러가 다른 zone의 노드를 골랐을 때 볼륨을 마운트할 수 없습니다.

WaitForFirstConsumer는 이 문제를 피합니다. Pod가 실제로 스케줄될 때까지 바인딩과 프로비저닝을 미루고, 스케줄러가 노드를 정한 뒤 그 노드의 토폴로지에 맞는 볼륨을 만듭니다. 토폴로지 제약이 있는 스토리지에서는 이 값이 사실상 기본 선택입니다.

reclaim policy: 데이터를 지킬 것인가 #

reclaimPolicyPVC가 삭제되어 PV가 풀려난 뒤, 그 PV와 실제 데이터를 어떻게 처리할지를 정합니다. StorageClass에 지정하면 그 클래스가 만든 PV가 해당 정책을 물려받습니다.

PVC 삭제 후 동작데이터
DeletePV와 실제 볼륨(EBS 디스크 등)을 함께 삭제사라짐
RetainPV를 Released 상태로 남겨 두고 볼륨도 보존보존됨

Delete는 동적 프로비저닝의 기본값입니다. PVC를 지우면 PV도, 그 뒤의 실제 디스크도 함께 삭제됩니다. 임시 데이터나 재생성 가능한 데이터에 적합하고 자원 낭비가 없습니다. 다만 실수로 PVC를 지우면 데이터가 곧바로 사라진다는 위험이 있습니다.

Retain은 PVC를 지워도 PV와 실제 데이터를 남깁니다. PVC가 사라지면 PV는 Released 상태가 되는데, 이 상태의 PV는 자동으로 다른 PVC에 바인딩되지 않습니다. 데이터를 회수하거나 다시 쓰려면 관리자가 손으로 개입해야 합니다.

# Released 상태 PV의 claimRef를 비워 다시 Available로 만든다
k patch pv <pv-name> -p '{"spec":{"claimRef": null}}'

데이터베이스처럼 잃으면 안 되는 데이터Retain을 써서 PVC 삭제가 곧 데이터 삭제로 이어지지 않게 막는 것이 안전합니다. 이미 만들어진 PV의 정책은 다음처럼 사후에도 바꿀 수 있습니다.

# 특정 PV만 Retain으로 변경 (실수 삭제 방지)
k patch pv <pv-name> \
  -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'

StorageClass의 reclaimPolicy는 그 클래스가 앞으로 만들 PV에 적용됩니다. 이미 존재하는 PV에는 영향을 주지 않으므로, 기존 PV를 보호하려면 위처럼 PV를 직접 patch해야 합니다.

volume expansion: 볼륨 키우기 #

운영 중 디스크가 차오르면 볼륨을 키워야 합니다. 쿠버네티스는 PVC의 요청 용량을 키우는 방식으로 무중단 확장을 지원합니다. 조건은 두 가지입니다.

  • 해당 StorageClass에 allowVolumeExpansion: true가 설정되어 있어야 합니다.
  • provisioner(CSI 드라이버)가 확장을 지원해야 합니다.

확장은 PVC의 spec.resources.requests.storage 값을 더 큰 값으로 고치면 됩니다.

# 20Gi PVC를 50Gi로 확장
k patch pvc data-pvc \
  -p '{"spec":{"resources":{"requests":{"storage":"50Gi"}}}}'

주의할 점은 줄이는 것은 불가능하다는 점입니다. 용량은 키우기만 할 수 있고 줄일 수는 없습니다. 또한 확장은 실제 스토리지를 키우는 작업과 파일시스템을 키우는 작업으로 나뉘는데, 파일시스템 확장이 Pod가 떠 있는 상태에서 끝나는 경우도 있고 Pod 재시작을 요구하는 경우도 있습니다. 어느 쪽인지는 CSI 드라이버에 달려 있으므로, 확장 뒤 PVC와 Pod 상태를 함께 확인하는 습관이 필요합니다.

# 확장 진행 상황 확인
k get pvc data-pvc
k describe pvc data-pvc   # Conditions에서 FileSystemResizePending 등 확인

CSI 한 줄 정리 #

provisioner에 들어가는 ebs.csi.aws.com 같은 값이 바로 CSI(Container Storage Interface) 드라이버입니다. CSI는 쿠버네티스가 다양한 스토리지 벤더의 볼륨을 표준 방식으로 다루도록 정의한 인터페이스이며, 동적 프로비저닝,스냅샷,확장 같은 기능은 모두 CSI 드라이버가 이를 지원할 때 동작합니다.

시험 포인트 #

CKA의 Storage도메인은 비중이 10%이지만, 동적 프로비저닝과 reclaim policy는 출제 빈도가 높습니다. 다음을 손에 익혀 두겠습니다.

  • default StorageClass 전환: 한 클래스의 default annotation을 "false"로 내리고 다른 클래스를 "true"로 올리는 작업이 자주 나옵니다. default가 둘이 되지 않게 합니다.
  • storageClassName 생략과 빈 문자열의 차이: 생략하면 default 사용, ""이면 동적 프로비저닝을 끄고 정적 PV에만 바인딩됩니다.
  • reclaimPolicy 변경으로 데이터 보호: 특정 PV를 Retain으로 바꿔 PVC 삭제 시 데이터가 사라지지 않게 하는 작업이 전형적입니다. k patch pv를 외워 두겠습니다.
  • Released PV 재사용: Retain PV는 claimRef를 비워야 다시 Available이 됩니다.
  • volume expansion: allowVolumeExpansion: true 확인 후 PVC의 requests.storage를 키웁니다. 줄이기는 불가능합니다.
  • WaitForFirstConsumer: PVC가 Pending인데 원인이 “Pod가 아직 스케줄되지 않아서"인 경우를 이 모드로 설명할 수 있어야 합니다.

특히 PVC가 Pending에서 멈추는 상황은 트러블슈팅과 직결됩니다. WaitForFirstConsumer라면 Pod를 붙이기 전까지는 의도된 Pending이고, default StorageClass가 없거나 storageClassName이 잘못된 경우라면 진짜 오류입니다. 이 둘을 k describe pvc의 이벤트로 구분하는 감각이 점수를 가릅니다.

정리 #

이번 글에서 잡은 것:

  • StorageClass는 스토리지 등급을 정의하는 클러스터 단위 템플릿입니다. provisioner,parameters,reclaimPolicy,volumeBindingMode,allowVolumeExpansion으로 구성됩니다.
  • 동적 프로비저닝은 PVC가 StorageClass를 지정하면 PV가 자동으로 만들어지는 방식입니다. 관리자가 PV를 손으로 만들 필요가 없습니다.
  • default StorageClass는 annotation으로 지정하며, storageClassName 생략 시 적용됩니다. 빈 문자열은 동적 프로비저닝을 끄는 의미입니다.
  • volumeBindingModeImmediate(즉시)와 WaitForFirstConsumer(Pod 스케줄까지 대기)로 나뉘며, 토폴로지 제약 스토리지에서는 후자가 안전합니다.
  • reclaimPolicyDelete(PV,데이터 함께 삭제)와 Retain(보존)으로 나뉩니다. 중요한 데이터는 Retain으로 보호합니다.
  • volume expansionallowVolumeExpansion: true일 때 PVC 용량을 키워 무중단 확장합니다. 줄이기는 불가능합니다.

다음: Networking 1 #

스토리지를 마쳤으니 이제 네트워킹으로 넘어갑니다. Pod는 언제든 죽고 다시 뜨며 IP가 바뀌므로, 안정적인 접근점이 필요합니다. 그 접근점이 Service입니다.

#18 Networking 1: Service에서는 ClusterIP(클러스터 내부 접근), NodePort(노드 포트 노출), LoadBalancer(외부 부하 분산), ExternalName(DNS 별칭)이 각각 어떻게 동작하는지, selector와 endpoint가 어떻게 묶이는지, 그리고 kube-proxy가 트래픽을 어떻게 흘려보내는지까지 YAML로 정리하겠습니다.

X