데이터베이스 Pod를 재시작했는데 데이터까지 사라졌다면, 보통 컨테이너의 파일시스템에 데이터를 썼기 때문이다. 컨테이너 파일시스템은 Pod 수명에 묶여 있다. Pod가 다른 Node에서 다시 만들어져도 데이터를 이어 쓰려면, 애플리케이션과 실제 스토리지를 분리해 다뤄야 한다.
Kubernetes의 PV(PersistentVolume)와 PVC(PersistentVolumeClaim)는 이 분리를 위한 두 개의 API다. 핵심은 다음 한 문장이다.
운영자는 클러스터가 제공할 스토리지를 PV와 StorageClass로 준비하고, 개발자는 자기 namespace에서 PVC로 필요한 스토리지를 요청한다.
둘을 나눈 이유는 맡은 일이 다르기 때문이다. 스토리지 시스템의 접속 정보, 장애가 영향을 미치는 범위, 비용, 데이터 삭제 정책은 클러스터 운영자가 관리한다. 필요한 용량, 접근 방식, 마운트 경로는 애플리케이션을 만드는 개발자가 정한다. Kubernetes 공식 Persistent Volumes 문서도 PV를 클러스터 자원, PVC를 사용자의 스토리지 요청으로 정의한다.
PV와 PVC는 어떻게 연결될까?
PV와 StorageClass는 클러스터 범위 리소스다. PVC와 Pod는 namespace 범위 리소스다. Pod는 같은 namespace의 PVC를 참조하고, PVC가 바인딩한 PV를 통해 실제 스토리지 백엔드에 연결된다.
flowchart LR
subgraph OP["클러스터 운영자 영역"]
CSI["CSI 드라이버\n스토리지 백엔드 연결"]
SC["StorageClass\nfast-ssd"]
PV["PV\n클러스터 범위"]
CSI --> SC
SC -. 동적 생성 규칙 .-> PV
end
subgraph TEAM["team-a namespace · 개발자 영역"]
PVC["PVC\ndata 20Gi · RWO"]
POD["Pod\nvolumeMount: /var/lib/app"]
POD -->|claimName: data| PVC
end
PVC -->|1:1 바인딩| PV
PV --> BACKEND[("실제 볼륨\n디스크 · 파일시스템 · SAN")]
| 리소스 | 범위 | 주된 책임 | 비유 |
|---|---|---|---|
| PV | 클러스터 | 공급된 실제 저장 공간을 Kubernetes에 표현 | 임대 가능한 창고 |
| PVC | namespace | 애플리케이션이 원하는 저장 공간을 선언 | 창고 임대 신청서 |
| StorageClass | 클러스터 | 어떤 방식으로 새 저장 공간을 만들지 정의 | 창고 유형과 자동 발급 규칙 |
| Pod | namespace | PVC를 파일 경로에 마운트해 사용 | 임대한 창고를 쓰는 작업자 |
PV와 PVC의 바인딩은 기본적으로 하나의 PV와 하나의 PVC의 관계다. PVC에는 필요한 용량, accessModes, volumeMode, storageClassName 같은 조건을 쓴다. 컨트롤 플레인이 조건에 맞는 PV를 찾아 바인딩하거나, StorageClass에 따라 새 PV를 만든다. Pod는 PV 이름이나 클라우드 디스크 ID를 알 필요가 없다.
운영자는 스토리지를 어떻게 준비할까?
운영자는 먼저 개발자가 사용할 스토리지의 종류와 사용 규칙을 정한다. 대표적으로 다음 항목을 준비한다.
- 클러스터와 스토리지 백엔드를 연결하는 CSI 드라이버
- 성능·복제·가용 영역·암호화·회수 방식을 담은 StorageClass
- 필요한 경우 미리 만든 PV(정적 프로비저닝)
- namespace별 사용량을 제한하는 ResourceQuota와, 리소스 접근을 나누는 RBAC
StorageClass로 만드는 방법 정하기
아래 fast-ssd라는 이름은 개발자에게 공개하는 스토리지 종류다. 실제 provisioner, 파라미터, 암호화 설정은 사용하는 클라우드나 스토리지 제품의 CSI 드라이버에 따라 달라진다.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: csi.example.com
parameters:
tier: ssd
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
reclaimPolicy: Delete는 PVC가 사라진 뒤 해당 PV와 연결된 실제 볼륨도 삭제하는 정책이다. 반대로 Retain은 데이터를 보존해 운영자가 확인·정리할 때까지 남긴다. 실제 스토리지에서 어떤 삭제 작업이 일어나는지는 CSI 드라이버 문서도 함께 확인해야 한다.
WaitForFirstConsumer도 운영자가 정하는 중요한 선택이다. 이 모드에서는 PVC를 만든 즉시 볼륨을 만들지 않고, 이를 쓰는 Pod가 배치될 때까지 기다린다. 가용 영역에 묶이는 디스크라면 Pod가 실행될 위치와 볼륨의 위치를 맞출 수 있다. 자세한 동작은 공식 StorageClass 문서에서 확인할 수 있다.
볼륨을 미리 만들까, 필요할 때 만들까?
flowchart TB
subgraph STATIC["정적 프로비저닝"]
A1["운영자: 백엔드 볼륨 생성"] --> A2["운영자: PV 생성"]
A2 --> A3["개발자: 조건이 맞는 PVC 생성"]
A3 --> A4["PV ↔ PVC 바인딩"]
end
subgraph DYNAMIC["동적 프로비저닝"]
B1["운영자: CSI + StorageClass 준비"] --> B2["개발자: PVC 생성"]
B2 --> B3["프로비저너: 백엔드 볼륨과 PV 생성"]
B3 --> B4["PV ↔ PVC 바인딩"]
end
정적 프로비저닝은 옮겨 온 기존 디스크나 이미 존재하는 NFS 공유처럼 운영자가 저장 공간을 먼저 준비해야 할 때 적합하다. 이때 운영자는 실제 볼륨을 만들고 이를 가리키는 PV를 만든다. 개발자의 PVC가 PV의 용량, 접근 모드, StorageClass 등의 조건과 맞으면 둘이 연결된다.
대부분의 일반적인 워크로드에서는 동적 프로비저닝이 더 자연스럽다. 개발자가 PVC에 StorageClass를 지정하면 해당 provisioner가 실제 볼륨을 만들고 PV를 생성한다. PVC가 Pending이라고 해서 항상 장애는 아니다. WaitForFirstConsumer일 수 있고, 기본 StorageClass가 없거나, 용량·토폴로지·CSI 드라이버 조건이 맞지 않을 수도 있다. kubectl describe pvc의 이벤트부터 확인하는 이유다.
개발자 권한은 어디까지 열어야 할까?
PV와 StorageClass는 클러스터 전체에 영향을 주므로 보통 플랫폼 운영자만 관리한다. 반면 개발 팀에는 자신의 namespace에서 PVC와 Pod를 다룰 권한을 준다. 다음 Role은 핵심 차이를 보여 주는 축약 예시다.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: workload-storage-editor
namespace: team-a
rules:
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
이 Role에는 persistentvolumes나 storageclasses가 없다. 두 리소스는 namespace에 속하지 않으며, 접근하려면 별도의 ClusterRole 권한이 필요하다. 실제 권한은 배포 도구와 운영 절차에 맞게 더 좁혀야 한다. RBAC의 Role·ClusterRole 범위 차이는 공식 RBAC 문서를 참고할 수 있다.
운영자는 권한만 나누는 데서 멈추지 않는다. requests.storage의 합계를 제한하는 ResourceQuota, 사용할 수 있는 StorageClass, 기본 StorageClass의 유무도 함께 정해야 한다. 그래야 한 팀의 실수나 대량 배포 때문에 다른 팀이 사용할 스토리지까지 부족해지는 일을 막을 수 있다.
개발자는 스토리지를 어떻게 사용할까?
개발자는 일반적으로 PV를 만들지 않는다. 팀에 제공된 StorageClass 중 하나를 선택하고, PVC를 만든 뒤 Pod에서 그 이름을 참조한다. 애플리케이션 코드가 알아야 하는 것은 마운트 경로뿐이다.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
namespace: team-a
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd
resources:
requests:
storage: 20Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: team-a
spec:
replicas: 1
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: example.registry/api:1.0
volumeMounts:
- name: data
mountPath: /var/lib/app
volumes:
- name: data
persistentVolumeClaim:
claimName: app-data
sequenceDiagram participant D as 개발자 participant API as API server participant P as Provisioner / PV controller participant S as CSI 스토리지 participant K as kubelet D->>API: PVC(app-data, 20Gi, fast-ssd) 생성 P->>S: 볼륨 생성 요청 S-->>P: 볼륨 준비 P->>API: PV 생성 · PVC와 바인딩 D->>API: PVC를 참조하는 Pod 생성 K->>S: 볼륨 연결 / 마운트 K-->>D: /var/lib/app에서 사용 가능
여기서 requests.storage: 20Gi는 필요한 용량을 요청하는 값이다. 실제 사용량을 정확히 20Gi에서 막는지, 용량을 어떻게 늘리는지는 파일시스템과 CSI 드라이버, 스토리지 제품의 기능에 따라 달라진다. PVC에 적은 용량이 언제나 강제 사용량 제한으로 동작한다고 가정하지 말고 플랫폼 팀의 StorageClass 문서를 확인해야 한다.
accessModes는 무엇을 뜻할까?
ReadWriteOnce(RWO)는 일반적으로 하나의 Node에서 읽기·쓰기가 가능하도록 마운트할 수 있다는 뜻이다. Pod 하나만 쓸 수 있다는 뜻은 아니다. 같은 Node의 여러 Pod가 사용할 수 있는지는 볼륨 드라이버와 구성에 따라 달라진다. 클러스터 전체에서 단 하나의 Pod만 사용하게 하려면 CSI 볼륨에서 지원하는 ReadWriteOncePod를 검토해야 한다.
여러 Pod 복제본이 동시에 같은 데이터를 써야 한다면 ReadWriteMany(RWX)를 제공하는 StorageClass가 필요한지 운영자에게 확인한다. RWO PVC 하나를 여러 Node에 흩어진 복제본에 연결하면, 볼륨 연결이나 마운트 단계에서 문제가 생길 수 있다.
PVC를 지우면 데이터도 사라질까?
개발자가 PVC를 삭제할 수 있는 권한을 받았다는 것은 데이터 삭제 요청의 시작점에 설 수 있다는 뜻이기도 하다. 최종 데이터의 운명은 PV의 회수 정책과 CSI 드라이버 동작에 달려 있다.
flowchart TD
A["개발자: PVC 삭제"] --> B["PV: Released 상태"]
B --> C{"reclaimPolicy"}
C -->|Delete| D["PV와 백엔드 볼륨 삭제 시도"]
C -->|Retain| E["백엔드 데이터 보존"]
E --> F["운영자: 백업·검증·정리 후 재사용 또는 삭제"]
D --> G["데이터 복구가 어려울 수 있음"]
Delete는 짧게 쓰는 개발 환경처럼 PVC와 함께 데이터를 없애도 되는 경우에 맞는다. Retain은 데이터베이스 같은 중요 데이터를 실수로 지웠을 때 복구할 여지를 남긴다. 다만 Retain은 자동 재사용 기능이 아니다. PV가 Released 상태가 된 뒤 이전 PVC 정보와 실제 데이터를 안전하게 정리하고, 새 PVC에 연결할 수 있도록 준비하는 운영 절차가 필요하다.
또한 사용 중인 PVC를 바로 삭제하려 해도 Kubernetes의 Storage Object in Use Protection이 보호 절차를 적용할 수 있다. Pod와 PVC, PVC와 PV의 참조가 정리될 때까지 삭제가 지연될 수 있으므로, Terminating 상태를 억지로 finalizer에서 제거하기보다 어떤 Pod가 아직 claim을 참조하는지부터 확인해야 한다.
문제가 생기면 무엇부터 확인할까?
| 상황 | 개발자가 먼저 볼 것 | 운영자가 함께 볼 것 |
|---|---|---|
PVC가 Pending | kubectl describe pvc app-data -n team-a의 이벤트, StorageClass 이름, Pod 생성 여부 | 기본 StorageClass, provisioner 로그·상태, 용량과 토폴로지 |
Pod가 ContainerCreating에 멈춤 | Pod 이벤트, PVC가 Bound인지, 마운트 경로 | CSI 연결·마운트 오류, Node·가용 영역 제약, 접근 모드 |
| 용량을 늘리고 싶음 | StorageClass의 확장 지원 여부, PVC 수정 절차 | allowVolumeExpansion, CSI·파일시스템의 확장 지원, 백업·변경 절차 |
| 데이터를 지우고 싶음 | PVC 삭제가 데이터 삭제 요청일 수 있음을 인지 | PV reclaimPolicy, 백업 보존, 정리·승인 절차 |
개발자는 다음 명령으로 자신이 볼 수 있는 상태를 빠르게 확인할 수 있다.
kubectl get pvc -n team-a
kubectl describe pvc app-data -n team-a
kubectl get pod -n team-a
kubectl describe pod <pod-name> -n team-a
개발자에게 클러스터 범위의 PV 조회 권한이 없어도 기본적인 문제는 확인할 수 있다. PVC의 status.phase, volumeName, 이벤트와 Pod 이벤트를 보면 요청한 스토리지가 준비되고 있는지 알 수 있다. 여러 팀이 한 클러스터를 함께 쓰는 환경에서는 백엔드 식별자나 PV 상세 정보를 넓게 공개하기보다, 운영자가 필요한 이벤트와 대시보드, 지원 절차를 제공하는 편이 안전하다.
핵심 정리
PV와 PVC는 “디스크를 두 번 표현한 것”이 아니라 공급과 사용의 책임을 분리하는 장치다.
- 운영자는 CSI 드라이버, StorageClass, PV, 회수 정책, 사용량 제한, RBAC로 안전한 스토리지 사용 규칙을 만든다.
- 개발자는 namespace 안에서 PVC로 용량과 접근 방식을 요청하고, Pod에서는 PVC 이름과 마운트 경로만 사용한다.
- 동적 프로비저닝에서는 PVC가 새 볼륨 생성을 촉발하고, 정적 프로비저닝에서는 기존 PV와 조건이 맞는 PVC가 바인딩된다.
- PVC 삭제는 데이터 수명주기와 연결된다.
Delete와Retain을 워크로드의 중요도에 맞게 운영자가 선택하고 개발자가 이해해야 한다.
이 역할을 분명히 나누면 개발자는 스토리지 제품의 세부 동작을 몰라도 필요한 공간을 사용할 수 있다. 운영자는 여러 팀이 공유하는 스토리지의 성능, 보안, 비용, 복구 정책을 한곳에서 관리할 수 있다.