188. Storage - Section Introduction

  • Docker 스토리지 기본 구조
  • Container Storage Interface (CSI)
  • Kubernetes Volume / PersistentVolume / PersistentVolumeClaim
  • Storage Class (Dynamic Provisioning)

189-190. Docker Storage

Docker의 스토리지 레이어 구조

Image Layers (읽기 전용):

  • Layer 1: Base OS (Ubuntu)
  • Layer 2: apt packages
  • Layer 3: pip packages
  • Layer 4: Source code
  • Layer 5: Entrypoint

Container Layer (읽기/쓰기):

  • Writable Layer (컨테이너 삭제 시 소멸)

  • Image Layer: docker build 시 생성, 읽기 전용, 여러 컨테이너가 공유

  • Container Layer: docker run 시 생성, 읽기/쓰기 가능, 컨테이너 삭제 시 데이터 소멸

  • Copy-on-Write: 컨테이너에서 이미지 파일 수정 시 Container Layer에 복사본 생성

Docker 스토리지 드라이버

드라이버설명
AUFSUbuntu 기본 (레거시)
Overlay2현재 표준, 대부분 배포판 기본값
Device MapperRHEL/CentOS 계열
BTRFS / ZFS고급 파일시스템 기능 제공

Volume Mount vs Bind Mount

# Volume Mount: Docker가 관리하는 볼륨 (/var/lib/docker/volumes/)
docker volume create data_volume
docker run -v data_volume:/var/lib/mysql mysql
 
# Bind Mount: 호스트의 특정 경로를 직접 마운트
docker run -v /data/mysql:/var/lib/mysql mysql
 
# --mount 방식 (권장)
docker run \
  --mount type=bind,source=/data/mysql,target=/var/lib/mysql \
  mysql
  • Volume Mount: Host /var/lib/docker/volumes/data_volume → Container /var/lib/mysql
  • Bind Mount: Host /data/mysql (임의 경로) → Container /var/lib/mysql

191. Volume Driver Plugins in Docker

  • 스토리지 드라이버: 이미지/컨테이너 레이어 관리 (AUFS, Overlay2 등)
  • 볼륨 드라이버: Volume 데이터 관리 → 기본값은 local

주요 Volume Driver 플러그인

플러그인백엔드
local호스트 로컬 파일시스템 (기본)
rexray/ebsAWS Elastic Block Store
rexray/s3fsAWS S3
rexray/gce-pdGCP Persistent Disk
portworxPortworx 분산 스토리지
convoyNFS / EBS
# 특정 볼륨 드라이버 지정
docker run -it \
  --volume-driver rexray/ebs \
  --mount src=ebs-vol,target=/var/lib/mysql \
  mysql

192. Container Storage Interface (CSI)

CSI가 왜 필요한가?

“Kubernetes 본체와 스토리지 벤더를 분리하기 위한 표준 인터페이스”

과거에는 어땠나?

  • EBS, GCE PD, Azure Disk 등 각 스토리지별 코드가 Kubernetes 본체에 들어있었음 (in-tree volume plugin)
  • 스토리지 벤더가 기능 추가하려면 Kubernetes 릴리즈 주기를 기다려야
  • Kubernetes 코드가 점점 비대해지고 벤더 로직 때문에 버그 발생

CSI 도입 후:

  • Kubernetes는 표준 RPC만 정의 (CreateVolume, DeleteVolume 등)
  • 벤더가 외부 플러그인(CSI Driver) 을 독립적으로 개발/배포
  • Kubernetes 업그레이드와 무관하게 스토리지 기능 추가 가능
  • CRI(런타임), CNI(네트워크)와 같은 철학

컨테이너 인터페이스 표준 3종

인터페이스역할
CRI (Container Runtime Interface)컨테이너 런타임 연동 (containerd, CRI-O)
CNI (Container Network Interface)네트워크 플러그인 연동 (Calico, Flannel)
CSI (Container Storage Interface)스토리지 드라이버 연동

CSI 역할

Kubernetes → CSI 표준 RPC → CSI Driver → 각 벤더 구현 (AWS EBS / GCE PD / Portworx / …)

  • Kubernetes가 스토리지 벤더에 의존하지 않도록 표준 인터페이스 정의
  • CSI는 Kubernetes 전용이 아닌 범용 표준 (Cloud Foundry, Mesos 등도 지원)
  • 벤더는 CSI Driver만 구현하면 어느 오케스트레이터에서도 동작

CSI 주요 RPC (Remote Procedure Calls)

RPC설명
CreateVolume볼륨 생성
DeleteVolume볼륨 삭제
ControllerPublishVolume노드에 볼륨 연결
NodeStageVolume노드에서 볼륨 마운트 준비
NodePublishVolume컨테이너 경로에 최종 마운트

193. Volumes

공식문서: Volumes

Volume이란?

“컨테이너가 삭제돼도 데이터를 남기기 위해, Pod에 외부 저장소를 연결하는 장치”

왜 필요한가?

  • 컨테이너 안의 파일은 컨테이너가 죽으면 같이 사라짐 (Copy-on-Write 레이어)
  • 로그/DB 데이터/업로드 파일 등은 유지돼야 함
  • 여러 컨테이너가 같은 파일 공유해야 할 때도 Volume 필요

Kubernetes의 Volume 철학:

  • Docker와 달리 Pod 단위로 Volume을 정의

  • Pod의 생명주기 = Volume의 생명주기 (Pod가 죽으면 일부 Volume은 같이 사라짐, 예: emptyDir)

  • 진짜 영속화하려면 PV/PVC 사용

  • Pod 내 컨테이너는 기본적으로 임시 스토리지 사용 → Pod 삭제 시 데이터 소멸

  • Volume을 사용하면 Pod 외부에 데이터 영속화 가능

기본 Volume 예시 (hostPath)

apiVersion: v1
kind: Pod
metadata:
  name: random-number-generator
spec:
  containers:
  - name: alpine
    image: alpine
    command: ["/bin/sh", "-c"]
    args: ["shuf -i 0-100 -n 1 >> /opt/number.out;"]
    volumeMounts:
    - mountPath: /opt           # 컨테이너 내부 경로
      name: data-volume
  volumes:
  - name: data-volume
    hostPath:                   # 호스트 노드의 경로 사용
      path: /data
      type: Directory

hostPath는 단일 노드 환경에서만 적합. 멀티 노드 환경에서는 노드마다 데이터가 달라짐 → PersistentVolume 사용 권장

AWS EBS Volume 예시

volumes:
- name: data-volume
  awsElasticBlockStore:
    volumeID: <volume-id>
    fsType: ext4

194. Persistent Volumes (PV)

공식문서: Persistent Volumes

PV / PVC가 왜 분리되어 있나?

“스토리지 제공자(관리자)와 소비자(개발자)를 분리하기 위함”

한 몸처럼 만들면 안 되나?

  • 개발자가 Pod YAML에 “AWS EBS ID aaa-bbb-ccc” 같은 벤더 정보를 하드코딩하게 됨
  • 클라우드 바뀌면 모든 Pod YAML 재작성 필요
  • 스토리지 용량/성능 결정을 개발자가 다 알아야

PV/PVC 분리로 해결:

  • PV (관리자 영역): “클러스터에 10GB SSD 볼륨 5개 준비해뒀어” — 스토리지 세부사항 포함
  • PVC (개발자 영역): “나 5GB 볼륨 하나만 있으면 돼” — 요청만 작성
  • Kubernetes가 조건 맞는 PV를 찾아 자동 연결(바인딩)

비유: PV = 호텔 방(이미 준비된), PVC = 방 예약 요청서. 프론트 데스크(K8s)가 요청 조건에 맞는 빈 방을 배정.

  • 클러스터 관리자가 클러스터 전체 범위로 스토리지를 미리 프로비저닝

  • 개발자(사용자)는 PVC를 통해 필요한 만큼 요청

  • 관리자PersistentVolume (클러스터 레벨) 생성

  • 개발자PersistentVolumeClaim (요청서) 생성

  • Kubernetes가 조건 맞는 PV 찾아 PVC와 자동 바인딩

  • Pod는 PVC를 참조해서 사용

PV 생성

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-vol1
spec:
  accessModes:
  - ReadWriteOnce          # 단일 노드에서 읽기/쓰기
  capacity:
    storage: 1Gi
  hostPath:                # 실제 환경에서는 EBS, NFS 등 사용
    path: /tmp/data
  persistentVolumeReclaimPolicy: Retain   # PVC 삭제 후 처리 방식

Access Modes

Access Mode약어설명
ReadWriteOnceRWO단일 노드에서 읽기/쓰기
ReadOnlyManyROX여러 노드에서 읽기만
ReadWriteManyRWX여러 노드에서 읽기/쓰기
ReadWriteOncePodRWOP단일 Pod에서만 읽기/쓰기 (v1.22+)

언제 어떤 Access Mode를 써야 하나?

상황권장이유
일반 DB (MySQL, PostgreSQL)RWO블록 스토리지(EBS 등)만 지원, 한 Pod만 마운트
정적 콘텐츠 배포ROX여러 웹 서버가 동일 데이터 읽기만
파일 공유 (업로드/로그)RWX여러 Pod가 동시에 읽고 씀 (NFS, CephFS)
엄격한 단일 마운트 보장RWOP실수로 중복 마운트 방지

스토리지 종류별 지원 Access Mode:

  • 블록 스토리지 (EBS, GCE PD, Azure Disk): RWO만
  • 파일 스토리지 (NFS, EFS, Azure Files): RWO/ROX/RWX 모두
  • 분산 스토리지 (CephFS, GlusterFS): RWX 지원

Reclaim Policy

정책설명
RetainPVC 삭제 후 PV 유지 (데이터 보존, 수동 정리 필요)
DeletePVC 삭제 시 PV도 함께 삭제
Recycle데이터 초기화 후 재사용 (Deprecated)

언제 어떤 Reclaim Policy를 써야 하나?

데이터 중요도권장시나리오
고객 DB / 결제 기록RetainPVC 실수로 삭제해도 데이터 보존, 관리자가 복구 가능
캐시 / 임시 작업 공간Delete사라져도 무관, 자원 자동 정리
개발 환경Delete빠른 생성/삭제 반복

Retain 모드에서 PVC 삭제 후:

  • PV는 Released 상태로 전환 (다른 PVC와 바인딩 불가)
  • 관리자가 데이터 백업 후 PV의 claimRef 제거하고 재사용 가능
  • 혹은 PV도 수동 삭제

PV Lifecycle (Phase)

PV는 여러 단계를 거침. kubectl get pv의 STATUS 컬럼에서 확인 가능.

Available → (PVC 바인딩) → Bound → (PVC 삭제 + Retain) → Released → (수동 정리) → Available 재활용 또는 Bound → (PVC 삭제 + Delete) → PV 자체 삭제 / 문제 발생 시 Failed

Phase의미해결 방법
Available누구와도 바인딩 안됨PVC 요청 대기
BoundPVC와 연결됨정상 사용 중
ReleasedPVC는 삭제됐지만 데이터는 남음claimRef 제거 후 재활용
Failed자동 재활용 실패로그 확인, 수동 조치
kubectl get pv
kubectl describe pv pv-vol1

195. Persistent Volume Claims (PVC)

  • 개발자가 스토리지를 요청하는 객체
  • Kubernetes가 요청 조건에 맞는 PV와 자동 바인딩

PVC 생성

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

PV-PVC 바인딩 규칙

  1. PVC 생성 요청 (예: accessMode RWO, storage 500Mi)
  2. 조건 매칭 PV 탐색
  3. 매칭 성공 → PV와 1:1 바인딩 (Bound 상태)
  4. 매칭 실패 → Pending 상태 (PV 생성 대기)

PV 용량이 더 커도 전체 바인딩됨 → 남는 공간은 다른 PVC가 사용 불가

PV와 PVC는 1:1 관계 — 하나의 PV에 여러 PVC 바인딩 불가

PVC 확인 및 삭제

kubectl get pvc
kubectl describe pvc myclaim
 
# PVC 삭제 (PV Reclaim Policy에 따라 처리)
kubectl delete pvc myclaim

196. Using PVCs in Pods

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      # PVC 이름 참조

ReplicaSet / Deployment에서도 동일 방식

# Deployment spec.template.spec 아래 동일하게 volumes/volumeMounts 정의
spec:
  template:
    spec:
      containers:
      - name: app
        volumeMounts:
        - mountPath: /data
          name: app-storage
      volumes:
      - name: app-storage
        persistentVolumeClaim:
          claimName: myclaim

197-198. Lab - PV & PVC

# PV/PVC 상태 확인
kubectl get pv
kubectl get pvc
 
# PV 상세 확인 (용량, Access Mode, Reclaim Policy, Status)
kubectl describe pv <pv-name>
 
# PVC와 PV 바인딩 확인
kubectl get pvc <pvc-name> -o wide
 
# Pod에서 PVC 사용 확인
kubectl describe pod <pod-name> | grep -A5 "Volumes"
 
# PVC 생성 예시
kubectl apply -f - <<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: claim-log-1
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 50Mi
EOF

199-200. Application Configuration & Additional Topics

  • ConfigMap: 환경 변수, 설정 파일 주입 (평문)
  • Secret: 민감한 설정 주입 (Base64 인코딩)
  • 환경 변수 / volumeMount 두 가지 방식으로 Pod에 전달 가능
  • 상세 내용은 CKA-5 (Application Lifecycle Management) 참고

201. Storage Class

StorageClass란?

“PVC 요청이 들어오면 PV를 자동으로 만들어주는 템플릿”

왜 필요한가?

  • PV/PVC만 쓰면 → 관리자가 매번 PV를 수동 생성해야 함
  • 요청이 100개면 PV도 100개 만들어야 → 확장성 없음
  • “대신 만들어주는 자동화”가 필요

StorageClass 동작:

  1. 관리자가 StorageClass 하나 만들어둠 (예: “SSD-gold”)
  2. 개발자가 PVC에서 storageClassName: SSD-gold 지정
  3. PVC 생성 → K8s가 StorageClass의 provisioner를 호출
  4. 클라우드 API로 실제 디스크 생성 → PV 자동 생성 → PVC와 바인딩

StorageClass = PV 제조 공장의 설계도. PVC가 오면 이 설계도로 PV를 찍어냄.

Static vs Dynamic Provisioning

Static Provisioning (기존):

  1. 관리자가 GCP PD 수동 생성 (gcloud compute disks create ...)
  2. PV YAML 수동 작성/적용
  3. PVC와 바인딩

Dynamic Provisioning (StorageClass):

  1. StorageClass 정의 (provisioner + parameters)
  2. PVC에 storageClassName 지정
  3. PV 자동 생성 + 바인딩

StorageClass 생성

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: google-storage
provisioner: kubernetes.io/gce-pd    # GCP Persistent Disk
parameters:
  type: pd-standard                  # pd-standard / pd-ssd
  replication-type: none
reclaimPolicy: Delete                # PVC 삭제 시 PV도 삭제
volumeBindingMode: Immediate         # 즉시 바인딩

StorageClass를 사용하는 PVC

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
spec:
  accessModes:
  - ReadWriteOnce
  storageClassName: google-storage   # StorageClass 참조
  resources:
    requests:
      storage: 500Mi

StorageClass를 사용하면 PV를 수동으로 만들 필요 없음 — PVC 생성 시 자동으로 PV가 프로비저닝됨

주요 Provisioner 목록

Provisioner백엔드
kubernetes.io/gce-pdGCP Persistent Disk
kubernetes.io/aws-ebsAWS EBS
kubernetes.io/azure-diskAzure Managed Disk
kubernetes.io/no-provisionerLocal 볼륨 (수동)
ebs.csi.aws.comAWS EBS CSI Driver
pd.csi.storage.gke.ioGCP CSI Driver
driver.longhorn.ioLonghorn

Volume Binding Mode

모드설명
ImmediatePVC 생성 즉시 PV 프로비저닝 및 바인딩
WaitForFirstConsumerPod가 실제로 PVC를 사용할 때까지 대기 (노드 스케줄링 고려)

왜 WaitForFirstConsumer가 필요한가?

문제 시나리오 (Immediate 모드):

  1. PVC에 의해 zone-A에 PV 생성됨
  2. Pod가 스케줄링되는데, 리소스 문제로 zone-B 노드에 배치
  3. zone-A의 PV는 zone-B 노드에서 마운트 불가 → Pod Pending 영원히

WaitForFirstConsumer로 해결:

  1. PVC 생성 → PV 아직 안 만듦, 대기
  2. Pod가 어느 노드에 스케줄링될지 결정됨 (예: zone-B)
  3. 그제서야 zone-B에 PV 생성 → 바인딩 성공

Local Volume이나 Zone 제약이 있는 클라우드 스토리지는 반드시 WaitForFirstConsumer 사용.

Local StorageClass (노드 로컬 볼륨)

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
# Local PV (수동 생성 필요)
apiVersion: v1
kind: PersistentVolume
metadata:
  name: local-pv
spec:
  capacity:
    storage: 500Mi
  accessModes:
  - ReadWriteOnce
  storageClassName: local-storage
  local:
    path: /mnt/data
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - node01

202-203. Lab - Storage Class

# StorageClass 조회
kubectl get storageclass
kubectl get sc    # 약어
 
# StorageClass 상세 확인
kubectl describe sc <name>
 
# 특정 StorageClass를 사용하는 PVC 생성
kubectl apply -f pvc.yaml
 
# PVC 상태 확인 (Pending → Bound)
kubectl get pvc -w
 
# PV 자동 생성 확인
kubectl get pv
 
# Pod에서 StorageClass 기반 PVC 사용
kubectl describe pod <pod-name> | grep -A10 "Volumes"

StorageClass 전체 흐름 요약

  1. StorageClass 정의 (provisioner)
  2. PVC 생성 시 storageClassName 참조
  3. Kubernetes가 자동으로 PV 생성 + PVC에 바인딩
  4. Pod가 PVC 참조해서 마운트

여러 Pod가 각자의 PVC를 필요로 할 때 - StatefulSet

공식문서: StatefulSets

왜 Deployment + PVC로는 부족한가?

Deployment에 PVC를 붙이면:

  • 모든 Pod가 같은 PVC를 공유 (RWX 아니면 하나만 뜸)
  • DB 복제본처럼 각 Pod가 독립된 볼륨 필요한 경우 안 맞음

StatefulSet의 volumeClaimTemplates

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql-headless
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8
        volumeMounts:
        - name: data                  # volumeClaimTemplates 이름과 매치
          mountPath: /var/lib/mysql
  volumeClaimTemplates:              # ★ Pod마다 PVC 자동 생성
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: fast-ssd
      resources:
        requests:
          storage: 10Gi

동작:

  • mysql-0, mysql-1, mysql-2 각각 생성될 때
  • 각자 개별 PVC (data-mysql-0, data-mysql-1, data-mysql-2) 생성
  • StatefulSet 삭제해도 PVC는 보존 (데이터 보호)

DB, Kafka, Elasticsearch 등 각 인스턴스가 고유 데이터를 가져야 하는 워크로드는 StatefulSet + volumeClaimTemplates 조합.


스토리지 리소스는 대부분 Declarative 전용

리소스imperative 명령
PV❌ 없음 (YAML 필수)
PVC❌ 없음 (YAML 필수)
StorageClass❌ 없음 (YAML 필수)
VolumeSnapshot❌ 없음 (YAML 필수)

→ 스토리지 리소스를 만들어야 할 땐 기존 YAML 복사 + 수정이 가장 빠름:

# 기존 PVC YAML 뽑아서 수정
kubectl get pvc my-pvc -o yaml > pvc.yaml
# 불필요한 필드 (uid, resourceVersion 등) 제거 후 수정
 
# 또는 공식 문서에서 템플릿 복사