Storage - PV & PVC

2026. 1. 3. 19:49·CKA

Kubernetes PersistentVolume(PV) / PersistentVolumeClaim(PVC): “파드 YAML에서 스토리지 설정을 분리”하는 이유와 동작 원리

앞에서 Kubernetes의 Volume(예: hostPath)을 파드 정의 파일에 직접 붙여서 데이터를 유지하는 방법을 봤습니다.
하지만 그 방식은 규모가 커질수록 문제가 생깁니다.

  • 파드(또는 Deployment)마다 스토리지 상세 설정을 반복해야 함
  • 스토리지 백엔드가 바뀌면(예: hostPath → NFS → EBS) 모든 파드 YAML을 수정
  • 운영 환경에서는 스토리지 정책(암호화/스냅샷/삭제 정책/권한/성능 등)을 앱 개발자에게 맡기기 어렵고, 중앙 통제가 필요

이 문제를 해결하려고 Kubernetes는 스토리지를 “중앙에서 관리하는 리소스”로 분리했습니다.

  • PV(PersistentVolume): 클러스터에 존재하는 “스토리지 자원(풀)” (관리자/플랫폼 팀이 주로 관리)
  • PVC(PersistentVolumeClaim): 사용자가 “필요한 스토리지를 요청(Claim)”하는 객체
  • Kubernetes는 PVC의 요구사항(용량/접근모드/스토리지클래스 등)에 맞는 PV를 찾아 1:1로 바인딩합니다.

1) PV/PVC의 핵심 그림

[관리자/플랫폼 팀]                       [개발자/사용자]
    PV 생성/준비  ----------------->        PVC 생성(요청)
                                          |
                                          | (K8s가 매칭/바인딩)
                                          v
                                     PVC <-> PV (1:1 Binding)
                                          |
                                          v
                                   Pod/Deployment에서 PVC 마운트

중요한 스코프 차이:

  • PV는 클러스터 스코프(네임스페이스 없음)
  • PVC는 네임스페이스 스코프(특정 네임스페이스에 속함)

그래서 “PV/PVC는 서로 다른 오브젝트이며 역할이 분리된다”는 설명이 나옵니다.


2) PV 만들기 (정적 프로비저닝 예시)

강의 흐름대로 먼저 “정적(static) PV”를 하나 만들어보면 개념이 확 잡힙니다.
여기서는 학습용으로 hostPath를 쓰지만, 멀티 노드/운영에서는 hostPath는 권장되지 않습니다.

PV 예시 (hostPath, 학습용)

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-vol1
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  hostPath:
    path: /data
    type: DirectoryOrCreate

핵심 필드 정리:

  • capacity.storage: PV가 제공하는 용량(예: 1Gi)
  • accessModes: 접근 모드(아래에서 자세히)
  • persistentVolumeReclaimPolicy: PVC가 삭제됐을 때 PV를 어떻게 처리할지
  • hostPath: 실제 저장소(학습용). 운영에서는 NFS/CSI(EBS/GCE PD/Azure Disk 등)로 대체

적용/확인:

kubectl apply -f pv.yaml
kubectl get pv
kubectl describe pv pv-vol1

3) AccessModes, VolumeMode 정확히 이해하기

강의 스크립트에 “읽기 전용 한 번 / 읽기 쓰기 한 번 / 읽기 쓰기 많음” 같은 표현이 나오는데, Kubernetes의 표준 값은 다음입니다.

AccessModes

  • ReadWriteOnce (RWO): 한 노드에만 Read/Write로 마운트 가능 (파드 여러 개여도 “같은 노드”면 가능)
  • ReadOnlyMany (ROX): 여러 노드에서 ReadOnly로 마운트 가능
  • ReadWriteMany (RWX): 여러 노드에서 Read/Write로 마운트 가능 (NFS/CEPHFS 같은 공유 스토리지에서 흔함)

PV <-> PVC 는 1:1 맵핑이지만, PVC는 여러 파드에 할당이 될 수 있고, 그 경우 파드는 여러 노드에 스케줄링 될 수 있음

포인트: AccessModes는 “파드 개수”가 아니라 노드 기준으로 이해하는 게 안전합니다.

VolumeMode (PVC/PV에 존재)

  • Filesystem(기본): 일반적인 디렉터리 마운트
  • Block: raw block device로 제공(특수 케이스)

4) PVC 만들기: “나는 500Mi가 필요해요”

사용자는 PV를 직접 참조하지 않고, PVC로 요구사항을 선언합니다.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 500Mi

적용/확인:

kubectl apply -f pvc.yaml
kubectl get pvc
kubectl describe pvc myclaim

5) 바인딩(Binding)은 어떻게 일어나나?

PVC가 생성되면 Kubernetes는 다음을 기준으로 PV를 찾고 1:1 바인딩합니다.

  • 요청 용량 requests.storage ≤ PV의 capacity.storage
  • accessModes가 호환되는지
  • volumeMode(Filesystem/Block) 호환
  • storageClassName 매칭 여부(있다면 중요)
  • (선택) 라벨 셀렉터로 특정 PV 지정 가능

“작은 PVC가 큰 PV에 묶일 수 있다”의 의미

PVC가 500Mi를 요청해도, PV가 1Gi이면 조건을 만족하므로 바인딩될 수 있습니다.
하지만 남는 500Mi를 다른 PVC가 가져갈 수는 없습니다.
PV-PVC는 1:1이기 때문에, 결과적으로 “남는 용량이 낭비”될 수 있습니다.

매칭되는 PV가 없다면?

PVC는 Pending 상태로 남아있고, 이후 조건을 만족하는 PV가 생기면 자동으로 바인딩됩니다.

확인 포인트:

kubectl get pv,pvc
# STATUS: Available / Bound / Released 등을 확인

6) PVC를 Pod에서 사용하기

사용자가 준 예시 그대로, Pod의 volumes에 persistentVolumeClaim.claimName을 지정하면 됩니다.

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: myfrontend
      image: nginx
      volumeMounts:
        - mountPath: "/var/www/html"
          name: mypd
  volumes:
    - name: mypd
      persistentVolumeClaim:
        claimName: myclaim

확인:

kubectl apply -f pod.yaml
kubectl exec -it mypod -- sh -c 'echo hello > /var/www/html/index.html; ls -al /var/www/html'

7) Deployment/ReplicaSet에서도 동일: “Pod Template”에 넣는다

Deployment는 결국 “Pod 템플릿(spec.template)”을 반복 생성하므로, 같은 방식으로 PVC를 붙입니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx
          image: nginx
          volumeMounts:
            - name: html
              mountPath: /usr/share/nginx/html
      volumes:
        - name: html
          persistentVolumeClaim:
            claimName: myclaim

주의: ReadWriteOnce PVC를 여러 replica가 동시에 다른 노드에서 쓰려고 하면 문제가 생길 수 있습니다.
이 경우 설계상 RWX 스토리지(NFS/CEPHFS 등) 또는 앱 구조 변경이 필요합니다.


8) PVC 삭제하면 PV는 어떻게 되나? (Reclaim Policy)

PVC를 삭제:

kubectl delete pvc myclaim

그 다음 PV의 처리 방식은 persistentVolumeReclaimPolicy에 따라 달라집니다.

  • Retain(기본으로 많이 쓰임): PV는 남겨두고, 데이터도 그대로. 다만 “다른 PVC가 즉시 재사용”하진 못하는 경우가 많아(Released 상태) 관리자가 정리/재설정 필요
  • Delete: PVC 삭제 시 PV도 삭제(동적 프로비저닝에서 흔함). 실제 스토리지도 함께 삭제되도록 연동
  • Recycle: 과거 방식. “간단히 파일 삭제(rm -rf)” 수준으로 지우는 컨셉이었는데, 현대 환경(스냅샷/암호화/권한/클라우드 API/완전 삭제 보장)에 부족해 사실상 사용 중단/폐기 흐름입니다.

운영에서는 보통:

  • 테스트/임시 환경: Delete
  • 중요한 데이터/수동 검증 필요: Retain
    이런 식으로 정책화합니다.

9) 왜 요즘은 “PV를 직접 만들기”보다 “StorageClass + 동적 프로비저닝”이 기본인가?

정적 PV 방식은 “관리자가 PV를 미리 만들어 풀을 준비”해야 합니다. 규모가 커지면 운영 부담이 큽니다.

그래서 현대 Kubernetes에서는 대부분:

  • StorageClass를 만들고(“이 CSI 드라이버로 이런 타입의 디스크를 만들자”)
  • 사용자는 PVC만 만들면
  • CSI가 자동으로 볼륨을 생성/Attach/Mount 해주는 동적 프로비저닝(Dynamic Provisioning) 을 씁니다.

즉, “PV를 수동 생성”하는 모델에서
“PVC 요청을 트리거로 PV를 자동 생성”하는 모델로 넘어간 겁니다.


10) 트러블슈팅 체크리스트 (실습에서 자주 막히는 지점)

PVC가 Pending에서 안 넘어갈 때

kubectl describe pvc myclaim
kubectl get events --sort-by=.metadata.creationTimestamp

주요 원인:

  • 매칭되는 PV가 없음(정적 환경)
  • storageClassName 불일치
  • accessModes/volumeMode 불일치
  • 동적 프로비저닝인데 CSI/StorageClass가 준비 안 됨

실제로 어떤 PV에 붙었는지

kubectl get pvc myclaim -o wide
kubectl describe pvc myclaim
kubectl describe pv <bound-pv-name>

정리

  • 파드 YAML에 스토리지 설정을 직접 박으면, 규모가 커질수록 운영/변경이 어려움
  • PV는 “클러스터 레벨의 스토리지 자원”, PVC는 “네임스페이스 레벨의 스토리지 요청”
  • PVC는 조건(용량/접근모드/클래스 등)에 맞는 PV에 1:1 바인딩
  • PVC 삭제 후 PV 처리 방식은 Reclaim Policy(Retain/Delete/Recycle) 로 결정
  • 현대 운영에서는 대부분 StorageClass + CSI + 동적 프로비저닝이 기본

 

 

Practice Test - https://uklabs.kodekloud.com/topic/practice-test-persistent-volume-claims-2/

'CKA' 카테고리의 다른 글

Storage - 정리  (0) 2026.01.05
Storage - Storage Class  (0) 2026.01.03
Storage - Volume  (0) 2026.01.03
Storage - Kubernetes CSI(Container Storage Interface)  (0) 2026.01.03
Storage - Docker의 Storage Driver vs Volume Driver  (0) 2026.01.03
'CKA' 카테고리의 다른 글
  • Storage - 정리
  • Storage - Storage Class
  • Storage - Volume
  • Storage - Kubernetes CSI(Container Storage Interface)
5jyan5
5jyan5
  • 5jyan5
    jyan
    5jyan5
  • 전체
    오늘
    어제
    • 분류 전체보기 (243)
      • 김영한의 스프링 핵심 원리(기본편) (8)
      • 김영한의 스프링 핵심 원리 - 고급편 (11)
      • 김영한의 스프링 MVC 1편 (1)
      • 김영한의 스프링 DB 1편 (3)
      • 김영한의 스프링 MVC 2편 (3)
      • 김영한의 ORM 표준 JPA 프로그래밍(기본편) (9)
      • 김영한의 스프링 부트와 JPA 활용2 (2)
      • 김영한의 실전 자바 - 중급 1편 (1)
      • 김영한의 실전 자바 - 고급 1편 (9)
      • 김영한의 실전 자바 - 고급 2편 (9)
      • Readable Code: 읽기 좋은 코드를 작성.. (2)
      • 김영한의 실전 자바 - 고급 3편 (9)
      • CKA (119)
      • 개발 (37)
      • 경제 (4)
      • 리뷰 (1)
      • 정보 (2)
  • 블로그 메뉴

    • 링크

    • 공지사항

    • 인기 글

    • 태그

      스레드
      자바
      고급
      @discriminatorcolumn
      typequery
      버퍼
      Thread
      hibernate5module
      reentarantlock
      @args
      cglib
      양방향 맵핑
      jdk 동적 프록시
      gesingleresult
      jpq
      JPQL
      락
      프록시
      프록시 팩토리
      @within
      김영한
      단방향 맵핑
      조회 성능 최적화
      빈 후처리기
      requset scope
      Target
      WAS
      페치 조인
      @discriminatorvalue
      log trace
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.2
    5jyan5
    Storage - PV & PVC
    상단으로

    티스토리툴바