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 스토리지 드라이버
| 드라이버 | 설명 |
|---|---|
| AUFS | Ubuntu 기본 (레거시) |
| Overlay2 | 현재 표준, 대부분 배포판 기본값 |
| Device Mapper | RHEL/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/ebs | AWS Elastic Block Store |
rexray/s3fs | AWS S3 |
rexray/gce-pd | GCP Persistent Disk |
portworx | Portworx 분산 스토리지 |
convoy | NFS / EBS |
# 특정 볼륨 드라이버 지정
docker run -it \
--volume-driver rexray/ebs \
--mount src=ebs-vol,target=/var/lib/mysql \
mysql192. 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: ext4194. 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 | 약어 | 설명 |
|---|---|---|
ReadWriteOnce | RWO | 단일 노드에서 읽기/쓰기 |
ReadOnlyMany | ROX | 여러 노드에서 읽기만 |
ReadWriteMany | RWX | 여러 노드에서 읽기/쓰기 |
ReadWriteOncePod | RWOP | 단일 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
| 정책 | 설명 |
|---|---|
Retain | PVC 삭제 후 PV 유지 (데이터 보존, 수동 정리 필요) |
Delete | PVC 삭제 시 PV도 함께 삭제 |
Recycle | 데이터 초기화 후 재사용 (Deprecated) |
언제 어떤 Reclaim Policy를 써야 하나?
| 데이터 중요도 | 권장 | 시나리오 |
|---|---|---|
| 고객 DB / 결제 기록 | Retain | PVC 실수로 삭제해도 데이터 보존, 관리자가 복구 가능 |
| 캐시 / 임시 작업 공간 | 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 요청 대기 |
| Bound | PVC와 연결됨 | 정상 사용 중 |
| Released | PVC는 삭제됐지만 데이터는 남음 | claimRef 제거 후 재활용 |
| Failed | 자동 재활용 실패 | 로그 확인, 수동 조치 |
kubectl get pv
kubectl describe pv pv-vol1195. Persistent Volume Claims (PVC)
- 개발자가 스토리지를 요청하는 객체
- Kubernetes가 요청 조건에 맞는 PV와 자동 바인딩
PVC 생성
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500MiPV-PVC 바인딩 규칙
- PVC 생성 요청 (예: accessMode
RWO, storage500Mi) - 조건 매칭 PV 탐색
- 매칭 성공 → PV와 1:1 바인딩 (
Bound상태) - 매칭 실패 →
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 myclaim196. 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: myclaim197-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
EOF199-200. Application Configuration & Additional Topics
- ConfigMap: 환경 변수, 설정 파일 주입 (평문)
- Secret: 민감한 설정 주입 (Base64 인코딩)
- 환경 변수 / volumeMount 두 가지 방식으로 Pod에 전달 가능
- 상세 내용은 CKA-5 (Application Lifecycle Management) 참고
201. Storage Class
공식문서: Storage Classes / Dynamic Provisioning
StorageClass란?
“PVC 요청이 들어오면 PV를 자동으로 만들어주는 템플릿”
왜 필요한가?
- PV/PVC만 쓰면 → 관리자가 매번 PV를 수동 생성해야 함
- 요청이 100개면 PV도 100개 만들어야 → 확장성 없음
- “대신 만들어주는 자동화”가 필요
StorageClass 동작:
- 관리자가 StorageClass 하나 만들어둠 (예: “SSD-gold”)
- 개발자가 PVC에서
storageClassName: SSD-gold지정 - PVC 생성 → K8s가 StorageClass의 provisioner를 호출
- 클라우드 API로 실제 디스크 생성 → PV 자동 생성 → PVC와 바인딩
StorageClass = PV 제조 공장의 설계도. PVC가 오면 이 설계도로 PV를 찍어냄.
Static vs Dynamic Provisioning
Static Provisioning (기존):
- 관리자가 GCP PD 수동 생성 (
gcloud compute disks create ...) - PV YAML 수동 작성/적용
- PVC와 바인딩
Dynamic Provisioning (StorageClass):
- StorageClass 정의 (provisioner + parameters)
- PVC에
storageClassName지정 - 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: 500MiStorageClass를 사용하면 PV를 수동으로 만들 필요 없음 — PVC 생성 시 자동으로 PV가 프로비저닝됨
주요 Provisioner 목록
| Provisioner | 백엔드 |
|---|---|
kubernetes.io/gce-pd | GCP Persistent Disk |
kubernetes.io/aws-ebs | AWS EBS |
kubernetes.io/azure-disk | Azure Managed Disk |
kubernetes.io/no-provisioner | Local 볼륨 (수동) |
ebs.csi.aws.com | AWS EBS CSI Driver |
pd.csi.storage.gke.io | GCP CSI Driver |
driver.longhorn.io | Longhorn |
Volume Binding Mode
| 모드 | 설명 |
|---|---|
Immediate | PVC 생성 즉시 PV 프로비저닝 및 바인딩 |
WaitForFirstConsumer | Pod가 실제로 PVC를 사용할 때까지 대기 (노드 스케줄링 고려) |
왜 WaitForFirstConsumer가 필요한가?
문제 시나리오 (Immediate 모드):
- PVC에 의해 zone-A에 PV 생성됨
- Pod가 스케줄링되는데, 리소스 문제로 zone-B 노드에 배치
- zone-A의 PV는 zone-B 노드에서 마운트 불가 → Pod Pending 영원히
WaitForFirstConsumer로 해결:
- PVC 생성 → PV 아직 안 만듦, 대기
- Pod가 어느 노드에 스케줄링될지 결정됨 (예: zone-B)
- 그제서야 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:
- node01202-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 전체 흐름 요약
- StorageClass 정의 (provisioner)
- PVC 생성 시
storageClassName참조 - Kubernetes가 자동으로 PV 생성 + PVC에 바인딩
- 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 등) 제거 후 수정
# 또는 공식 문서에서 템플릿 복사