Post

Part 14/26: Volume, PV, PVC, StorageClass

Part 14/26: Volume, PV, PVC, StorageClass

컨테이너의 파일시스템은 일시적(ephemeral)이다. 컨테이너가 재시작되면 모든 데이터가 사라진다. Kubernetes는 Volume을 통해 데이터 지속성을 제공하고, PersistentVolume(PV)PersistentVolumeClaim(PVC)을 통해 스토리지를 추상화한다.

Volume의 필요성

컨테이너 스토리지의 한계

1
2
3
4
5
6
컨테이너 재시작 시:
┌────────────────┐      ┌────────────────┐
│  Container A   │  →   │  Container A'  │
│  /data/file.txt│      │  /data/ (비어있음)│
└────────────────┘      └────────────────┘
         기존 데이터 손실

Volume의 역할:

  1. 데이터 지속성: 컨테이너 재시작에도 데이터 유지
  2. 데이터 공유: 동일 Pod 내 컨테이너 간 파일 공유
  3. 외부 스토리지 연결: 클라우드 스토리지, NFS 등 연결

Volume 타입

emptyDir

Pod가 노드에 스케줄될 때 생성되고, Pod가 삭제되면 함께 삭제된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: Pod
metadata:
  name: emptydir-pod
spec:
  containers:
  - name: writer
    image: busybox
    command: ["sh", "-c", "echo 'data' > /cache/data.txt; sleep 3600"]
    volumeMounts:
    - name: cache-volume
      mountPath: /cache
  - name: reader
    image: busybox
    command: ["sh", "-c", "cat /cache/data.txt; sleep 3600"]
    volumeMounts:
    - name: cache-volume
      mountPath: /cache
  volumes:
  - name: cache-volume
    emptyDir: {}

메모리 기반 emptyDir (tmpfs):

1
2
3
4
5
volumes:
- name: cache-volume
  emptyDir:
    medium: Memory  # RAM에 저장 (더 빠르지만 용량 제한)
    sizeLimit: 100Mi

용도:

  • 컨테이너 간 임시 데이터 공유
  • 캐시 데이터
  • 작업 디렉토리

hostPath

노드의 파일시스템을 직접 마운트한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Pod
metadata:
  name: hostpath-pod
spec:
  containers:
  - name: app
    image: nginx
    volumeMounts:
    - name: host-data
      mountPath: /data
  volumes:
  - name: host-data
    hostPath:
      path: /mnt/data
      type: DirectoryOrCreate  # 없으면 생성

hostPath type 옵션:

type설명
(빈 문자열)검사 없음
DirectoryOrCreate없으면 디렉토리 생성 (755 권한)
Directory디렉토리가 존재해야 함
FileOrCreate없으면 파일 생성 (644 권한)
File파일이 존재해야 함
SocketUNIX 소켓이 존재해야 함
CharDevice문자 디바이스가 존재해야 함
BlockDevice블록 디바이스가 존재해야 함

주의사항:

  • 특정 노드에 의존하므로 Pod 이동 시 데이터 접근 불가
  • 보안 위험: 노드 파일시스템 접근 가능
  • 운영 환경에서 사용 지양 (DaemonSet 로그 수집 등 예외)

NFS

네트워크 파일 시스템을 마운트한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Pod
metadata:
  name: nfs-pod
spec:
  containers:
  - name: app
    image: nginx
    volumeMounts:
    - name: nfs-volume
      mountPath: /data
  volumes:
  - name: nfs-volume
    nfs:
      server: nfs-server.example.com
      path: /exports/data
      readOnly: false

ConfigMap과 Secret 볼륨

1
2
3
4
5
6
7
8
volumes:
- name: config-volume
  configMap:
    name: app-config
- name: secret-volume
  secret:
    secretName: app-secret
    defaultMode: 0400  # 파일 권한

Projected Volume

여러 소스를 하나의 볼륨으로 합친다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: v1
kind: Pod
metadata:
  name: projected-pod
spec:
  containers:
  - name: app
    image: nginx
    volumeMounts:
    - name: all-in-one
      mountPath: /projected
  volumes:
  - name: all-in-one
    projected:
      sources:
      - secret:
          name: db-secret
      - configMap:
          name: app-config
      - serviceAccountToken:
          path: token
          expirationSeconds: 3600

PersistentVolume (PV)

PV는 클러스터 레벨의 스토리지 리소스이다. 관리자가 프로비저닝하거나 StorageClass를 통해 동적으로 생성된다.

PV 정의

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-example
spec:
  capacity:
    storage: 10Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  hostPath:
    path: /mnt/data

Access Modes

모드약어설명
ReadWriteOnceRWO단일 노드에서 읽기/쓰기
ReadOnlyManyROX여러 노드에서 읽기 전용
ReadWriteManyRWX여러 노드에서 읽기/쓰기
ReadWriteOncePodRWOP단일 Pod에서만 읽기/쓰기 (1.22+)

스토리지 타입별 지원:

  • AWS EBS: RWO만 지원
  • NFS: RWO, ROX, RWX 모두 지원
  • GCE PD: RWO, ROX 지원

Reclaim Policy

PVC가 삭제될 때 PV의 처리 방식:

정책설명용도
RetainPV와 데이터 유지중요 데이터, 수동 복구 필요
DeletePV와 외부 스토리지 삭제동적 프로비저닝 기본값
Recycle데이터 삭제 후 재사용 (deprecated)사용하지 않음
1
2
spec:
  persistentVolumeReclaimPolicy: Retain  # 또는 Delete

PV 상태

상태설명
AvailablePVC에 바인딩되지 않은 상태
BoundPVC에 바인딩됨
ReleasedPVC 삭제됨, 아직 재사용 불가
Failed자동 복구 실패

PersistentVolumeClaim (PVC)

PVC는 사용자의 스토리지 요청이다. Pod는 PVC를 통해 PV를 사용한다.

PVC 정의

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-example
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: manual  # PV의 storageClassName과 일치해야 함

PVC와 PV 바인딩

PVC는 다음 조건을 만족하는 PV에 바인딩된다:

  1. accessModes 일치
  2. storageClassName 일치
  3. capacity >= requests.storage
  4. selector 조건 만족 (지정된 경우)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Label Selector로 특정 PV 선택
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-selector
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  selector:
    matchLabels:
      type: ssd

Pod에서 PVC 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-pvc
spec:
  containers:
  - name: app
    image: nginx
    volumeMounts:
    - name: data-volume
      mountPath: /data
  volumes:
  - name: data-volume
    persistentVolumeClaim:
      claimName: pvc-example

StorageClass

StorageClass는 동적 프로비저닝을 위한 스토리지 유형 정의이다.

StorageClass 정의

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-storage
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  iopsPerGB: "10"
  fsType: ext4
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer

주요 Provisioner

환경Provisioner설명
AWSkubernetes.io/aws-ebsEBS 볼륨
GCPkubernetes.io/gce-pdGCE Persistent Disk
Azurekubernetes.io/azure-diskAzure Disk
NFS별도 provisioner 필요NFS 동적 프로비저닝
Localkubernetes.io/no-provisioner로컬 볼륨 (수동)

volumeBindingMode

모드설명
ImmediatePVC 생성 시 즉시 PV 바인딩
WaitForFirstConsumerPod가 스케줄될 때 바인딩 (권장)

WaitForFirstConsumer 장점:

  • Pod가 스케줄될 노드의 토폴로지 고려
  • 적절한 가용 영역에 볼륨 생성

동적 프로비저닝 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp2
---
# PVC (StorageClass 참조)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: dynamic-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: standard  # StorageClass 참조
# PV가 자동으로 생성되고 바인딩됨

Default StorageClass

1
2
3
4
5
6
7
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"  # 기본 SC
provisioner: kubernetes.io/aws-ebs

PVC에서 storageClassName을 지정하지 않으면 기본 StorageClass가 사용된다.

1
2
3
4
5
6
7
8
9
10
11
12
# storageClassName 생략 시 기본 SC 사용
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: default-sc-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  # storageClassName 생략

동적 프로비저닝을 원하지 않을 때:

1
2
spec:
  storageClassName: ""  # 빈 문자열로 명시

Volume Expansion

StorageClass에서 allowVolumeExpansion이 true면 PVC 크기를 확장할 수 있다.

1
2
3
4
5
6
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: expandable
provisioner: kubernetes.io/aws-ebs
allowVolumeExpansion: true
1
2
3
4
5
6
# PVC 크기 확장
kubectl patch pvc my-pvc -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'

# 또는 edit
kubectl edit pvc my-pvc
# spec.resources.requests.storage 값 수정

주의:

  • 축소(shrink)는 지원되지 않음
  • 일부 스토리지는 Pod 재시작 필요
  • 파일시스템 확장은 자동 또는 수동

StatefulSet과 volumeClaimTemplates

StatefulSet은 각 Pod마다 별도의 PVC를 생성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: standard
      resources:
        requests:
          storage: 10Gi

생성되는 PVC:

  • data-mysql-0
  • data-mysql-1
  • data-mysql-2

StatefulSet 삭제 시 PVC는 유지된다. 수동으로 삭제해야 한다.

트러블슈팅

PVC가 Pending 상태

1
2
3
4
5
6
7
# PVC 상태 확인
kubectl get pvc
kubectl describe pvc <pvc-name>

# 이벤트 확인
# "waiting for first consumer" → volumeBindingMode: WaitForFirstConsumer
# "no persistent volumes available" → 조건 맞는 PV 없음

원인과 해결:

  1. PV 부족: PV 생성 또는 StorageClass 확인
  2. accessModes 불일치: PV와 PVC의 accessModes 확인
  3. storageClassName 불일치: 두 리소스의 storageClassName 확인
  4. 용량 부족: PV 용량 >= PVC 요청 용량

Volume 마운트 실패

1
2
3
4
5
# Pod 이벤트 확인
kubectl describe pod <pod-name>

# "Unable to mount volumes" 확인
# "MountVolume.SetUp failed" 확인

원인과 해결:

  1. PVC 바인딩 안 됨: PVC 상태 확인
  2. 노드에 볼륨 연결 실패: 클라우드 권한, 네트워크 확인
  3. 파일시스템 오류: fsType 확인

디버깅 명령어

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 스토리지 리소스 전체 확인
kubectl get pv,pvc,sc

# PV 상세 정보
kubectl describe pv <pv-name>

# PVC 상세 정보
kubectl describe pvc <pvc-name>

# StorageClass 확인
kubectl get storageclass
kubectl describe storageclass <sc-name>

# Pod 볼륨 마운트 확인
kubectl exec <pod> -- df -h
kubectl exec <pod> -- ls -la /data

기술 면접 대비

자주 묻는 질문

Q: PV와 PVC의 차이점과 관계는?

A: PV(PersistentVolume)는 클러스터 관리자가 프로비저닝하는 스토리지 리소스이다. PVC(PersistentVolumeClaim)는 사용자의 스토리지 요청이다. PVC는 조건에 맞는 PV에 바인딩되고, Pod는 PVC를 통해 스토리지를 사용한다. 이 추상화를 통해 사용자는 실제 스토리지 구현을 알 필요 없이 스토리지를 요청할 수 있다.

Q: Dynamic Provisioning이란?

A: StorageClass를 정의해두면 PVC 생성 시 자동으로 PV가 생성되는 것이다. 관리자가 미리 PV를 만들 필요 없이 PVC의 요청에 따라 클라우드 스토리지(EBS, GCE PD 등)가 동적으로 프로비저닝된다. volumeBindingMode를 WaitForFirstConsumer로 설정하면 Pod 스케줄링 시점에 적절한 토폴로지에 볼륨이 생성된다.

Q: Reclaim Policy의 Retain과 Delete 차이는?

A: Delete는 PVC 삭제 시 PV와 연결된 외부 스토리지(EBS 등)도 함께 삭제된다. Retain은 PVC가 삭제되어도 PV와 데이터가 유지되며, 수동으로 복구하거나 재사용할 수 있다. 동적 프로비저닝의 기본값은 Delete이므로, 중요 데이터는 Retain으로 설정하거나 백업 전략이 필요하다.

Q: emptyDir과 hostPath의 차이는?

A: emptyDir은 Pod 생성 시 빈 디렉토리가 만들어지고 Pod 삭제 시 함께 삭제된다. 같은 Pod 내 컨테이너 간 데이터 공유에 사용한다. hostPath는 노드의 파일시스템을 직접 마운트하므로 Pod 삭제 후에도 데이터가 유지된다. 하지만 특정 노드에 의존하게 되고 보안 위험이 있어 운영 환경에서는 지양한다.

Q: volumeBindingMode WaitForFirstConsumer의 장점은?

A: PVC 생성 시 즉시 바인딩(Immediate)하면 Pod가 스케줄될 노드와 다른 가용 영역에 볼륨이 생성될 수 있다. WaitForFirstConsumer는 Pod가 실제로 스케줄될 때 해당 노드의 토폴로지를 고려해 볼륨을 생성하므로, 가용 영역 불일치 문제를 방지한다.

CKA 시험 대비 필수 명령어

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# PV 생성 (YAML 필수)
kubectl apply -f pv.yaml

# PVC 생성 (YAML 필수)
kubectl apply -f pvc.yaml

# 조회
kubectl get pv
kubectl get pvc
kubectl get storageclass
kubectl get sc  # 축약형

# 상세 정보
kubectl describe pv <pv-name>
kubectl describe pvc <pvc-name>

# PVC 삭제
kubectl delete pvc <pvc-name>

# PV 수동 재활용 (Released → Available)
kubectl patch pv <pv-name> -p '{"spec":{"claimRef": null}}'

CKA 빈출 시나리오

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 시나리오 1: PV 생성
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-storage
spec:
  capacity:
    storage: 100Mi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  hostPath:
    path: /mnt/data

# 시나리오 2: PVC 생성
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-storage
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 50Mi

# 시나리오 3: Pod에서 PVC 사용
apiVersion: v1
kind: Pod
metadata:
  name: pod-storage
spec:
  containers:
  - name: app
    image: nginx
    volumeMounts:
    - name: data
      mountPath: /usr/share/nginx/html
  volumes:
  - name: data
    persistentVolumeClaim:
      claimName: pvc-storage

다음 단계

This post is licensed under CC BY 4.0 by the author.