PVC 가 어떤 PV 에 붙는지, 이미 붙은 PVC 를 다른 볼륨으로 바꿀 수 있는지, Deployment 에서 파드마다 별도 볼륨을 줄 수 있는지 — 이 셋이 자주 걸리는 지점이다.
컨트롤러는 PVC 를 보면 먼저 조건에 맞는 기존 PV 를 찾는다. 모두 맞아야 붙는다.
| 항목 | 조건 |
|---|---|
storageClassName |
PVC 와 PV 가 같아야 한다. 둘 다 비어 있어도 맞는 것으로 본다 |
accessModes |
PV 가 PVC 가 요구한 모드를 포함해야 한다 |
| 용량 | PV 용량이 PVC 요청 이상이어야 한다 |
volumeMode |
둘 다 Filesystem 이거나 둘 다 Block |
selector |
PVC 에 있으면 PV 라벨이 맞아야 한다 |
nodeAffinity |
PV 에 있으면 스케줄 가능한 노드와 맞아야 한다 |
맞는 PV 가 하나라도 있으면 그것을 쓰고 동적 프로비저닝은 일어나지 않는다. 없을 때만 StorageClass 의 프로비저너가 새 PV 를 만든다.
미리 만든 PV 가 있는데 안 잡히는 경우는 대개 클래스 이름이 어긋난 것이다. PV 에 storageClassName 이 없고 PVC 에는 nfs-dynamic 같은 값이 있으면 서로 후보가 되지 않아, 컨트롤러가 새 볼륨을 동적으로 만들어 버린다. 특정 PV 를 꼭 쓰게 하려면 PVC 에 volumeName 으로 직접 지정하거나 양쪽 클래스 이름을 맞춘다.
NFS 정적 PV 를 만들어도 NFS 서버에 디렉터리가 생기지는 않는다. PV 오브젝트는 이미 있는 경로를 가리킬 뿐이다. 경로는 미리 만들고 권한도 맞춰 둔다.
# NFS 서버에서
mkdir -p /exported/path/myapp
chown 1000:1000 /exported/path/myapp
chmod 0770 /exported/path/myapp
exportfs -ra
showmount -e
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-myapp
spec:
capacity:
storage: 10Gi
accessModes: ["ReadWriteMany"]
persistentVolumeReclaimPolicy: Retain
mountOptions: ["vers=4.1", "hard", "timeo=600", "retrans=2"]
nfs:
server: nfs.example.com
path: /exported/path/myapp
디렉터리를 자동으로 만들고 싶으면 nfs-subdir-external-provisioner 같은 동적 프로비저너를 쓴다. 이때 하위 디렉터리는 PV 생성 시점이 아니라 PVC 생성 시점에 만들어진다.
워커 노드에서 손으로 마운트해 보는 것이 가장 빠른 사전 확인이다.
mount -t nfs -o vers=4.1 nfs.example.com:/exported/path/myapp /mnt
touch /mnt/healthcheck && umount /mnt
PVC 가 Bound 되면 volumeName · storageClassName · selector 는 사실상 불변이다. kubectl edit pvc 로 고쳐도 컨트롤러가 무시하거나 되돌린다. 볼륨을 바꾸려면 새 PVC 를 만들고 데이터를 옮긴 뒤 워크로드가 참조하는 이름을 바꾼다.
apiVersion: v1
kind: Pod
metadata:
name: pvc-migrator
spec:
restartPolicy: Never
containers:
- name: rsync
image: alpine:3.20
command: ["/bin/sh", "-c"]
args: ["apk add --no-cache rsync && rsync -aHAX /src/ /dst/"]
volumeMounts:
- { name: old, mountPath: /src }
- { name: new, mountPath: /dst }
volumes:
- name: old
persistentVolumeClaim: { claimName: old-pvc }
- name: new
persistentVolumeClaim: { claimName: new-pvc }
ReadWriteOnce 볼륨은 두 파드가 동시에 붙지 못하므로 애플리케이션을 먼저 내리고 복사한다. ReadWriteMany 라도 복사 중 쓰기가 들어가면 정합성이 깨지므로 멈추고 하는 편이 안전하다.
CSI 드라이버가 지원한다면 dataSource 로 기존 PVC 를 복제하거나 스냅샷에서 복원하는 방법이 더 빠르다. nfs-subdir-external-provisioner 는 복제·스냅샷을 지원하지 않으므로 위의 복사 방식을 쓴다.
용량만 늘리는 것은 다르다. StorageClass 에 allowVolumeExpansion: true 가 있으면 PVC 의 요청 용량을 늘려 확장할 수 있다.
volumeClaimTemplates 는 StatefulSet 전용이다. Deployment 는 레플리카에 고유 식별자가 없어 파드마다 다른 PVC 를 자동으로 만들 수 없다. 같은 PVC 를 여러 레플리카가 공유하면 ReadWriteOnce 볼륨에서는 두 번째 파드가 뜨지 못한다.
파드마다 고유한 영속 볼륨이 필요하면 StatefulSet 을 쓴다.
apiVersion: apps/v1
kind: StatefulSet
spec:
serviceName: app
replicas: 3
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-sc
resources:
requests:
storage: 10Gi
data-app-0 · data-app-1 처럼 파드마다 PVC 가 생기고 파드가 재생성돼도 같은 볼륨을 다시 붙는다. StatefulSet 을 지우면 이 PVC 들은 남으므로 정리는 따로 한다.
파드 수명과 함께 사라져도 되는 볼륨이면 제네릭 임시 볼륨을 쓴다.
volumes:
- name: scratch
ephemeral:
volumeClaimTemplate:
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-sc
resources:
requests:
storage: 5Gi