네트워크 이해하기 (2)에서 CNI(Container Network Interface)를, 컴퓨팅 이해하기에서 CRI(Container Runtime Interface)를 다뤘습니다. 스토리지에도 동일한 표준 인터페이스가 있습니다.
Kubernetes의 3대 표준 인터페이스
인터페이스
풀네임
역할
CNI
Container Network Interface
네트워크 플러그인 표준
CRI
Container Runtime Interface
컨테이너 런타임 표준
CSI
Container Storage Interface
스토리지 플러그인 표준
왜 “드라이버”라고 부를까?
하드웨어 드라이버와 같은 개념입니다:
프린터 드라이버: OS와 프린터 사이의 표준 인터페이스
CSI 드라이버: Kubernetes와 스토리지 시스템 사이의 표준 인터페이스
OS가 “인쇄해줘”라고 하면 프린터 드라이버가 해당 프린터에 맞게 번역하듯이, Kubernetes가 “볼륨 만들어줘”라고 하면 CSI 드라이버가 해당 스토리지 시스템(EBS, GCP PD 등)에 맞게 API를 호출합니다.
CSI 드라이버 구조
flowchart TB
subgraph WORKERS["워커 노드들"]
subgraph N1["Node 1"]
NP1["Node Plugin<br/>(DaemonSet)"]
POD1["Pod"]
NP1 <-->|"Pod 경로에<br/>마운트/언마운트"| POD1
end
subgraph N2["Node 2"]
NP2["Node Plugin<br/>(DaemonSet)"]
POD2["Pod"]
NP2 <-->|"Pod 경로에<br/>마운트/언마운트"| POD2
end
end
subgraph CP["Control Plane"]
API["API Server"]
end
subgraph CSI["CSI Driver"]
CTRL["Controller Plugin (Deployment)<br/>CreateVolume / DeleteVolume<br/>ControllerPublishVolume / ControllerUnpublishVolume"]
end
subgraph BACKEND["스토리지 백엔드"]
SB["AWS EBS / GCP PD /<br/>Azure Disk / NFS"]
end
API <-->|"PVC/PV 이벤트"| CTRL
CTRL <-->|"볼륨 생성/삭제<br/>노드에 attach/detach"| SB
CSI 드라이버는 두 가지 컴포넌트로 구성됩니다:
컴포넌트
배포 방식
역할
Controller Plugin
Deployment
볼륨 생성/삭제, 워커 노드에 attach/detach
Node Plugin
DaemonSet (모든 노드)
Pod 경로에 마운트/언마운트, 포맷
Controller Plugin의 역할
Controller Plugin은 클러스터 레벨의 스토리지 작업을 담당합니다:
작업
언제 호출
설명
CreateVolume
PVC 생성 시 (동적 프로비저닝)
스토리지 API로 볼륨 생성 (예: AWS EC2 CreateVolume)
DeleteVolume
PVC 삭제 시
스토리지 API로 볼륨 삭제
ControllerPublishVolume
Pod 스케줄링 후
볼륨을 특정 노드에 attach
ControllerUnpublishVolume
Pod 삭제 후
볼륨을 노드에서 detach
수동 프로비저닝에서도 Controller Plugin이 필요한가?
네! CreateVolume만 안 쓰이고, attach/detach는 여전히 필요합니다. 이미 존재하는 볼륨이라도 Pod가 스케줄링된 노드에 붙여야 하니까요.
Node Plugin의 역할
Node Plugin은 각 워커 노드에서 실행됩니다:
작업
설명
NodeStageVolume
attach된 디바이스를 포맷하고 노드의 글로벌 경로에 마운트
NodePublishVolume
글로벌 경로를 Pod의 경로로 bind mount
NodeUnpublishVolume
Pod 경로에서 언마운트
왜 Node Plugin이 필요한가?
Controller가 “EBS를 EC2에 붙여!”라고 해서 /dev/xvdf가 생겼다고 해도, 그걸 Pod 컨테이너 안에서 /data로 보이게 하려면 마운트 작업이 필요합니다. 이걸 Node Plugin이 담당합니다.
Pod 스케줄링 → Controller: attach → Node: mount → Pod 사용 가능!
StorageClass와 동적 프로비저닝
매번 PV를 수동으로 만드는 건 번거롭습니다. StorageClass를 사용하면 PVC 생성 시 PV가 자동으로 생성됩니다. 실무에서는 대부분 동적 프로비저닝을 사용합니다.
동적 프로비저닝 흐름
sequenceDiagram
participant DEV as 개발자
participant API as API Server
participant CTRL as CSI Controller Plugin
participant CLOUD as 클라우드 API
participant NODE as CSI Node Plugin
participant POD as Pod
DEV->>API: ① PVC 생성 (storageClassName: fast-ssd)
API->>CTRL: ② PVC 감지
CTRL->>CLOUD: ③ CreateVolume (예: EBS 생성)
CLOUD-->>CTRL: 볼륨 ID 반환
CTRL->>API: ④ PV 자동 생성 & PVC 바인딩
DEV->>API: ⑤ Pod 생성 (PVC 참조)
API->>CTRL: ⑥ Pod 스케줄링됨
CTRL->>CLOUD: ⑦ ControllerPublishVolume (노드에 attach)
CLOUD-->>NODE: 디바이스 연결됨
NODE->>POD: ⑧ NodePublishVolume (Pod 경로에 마운트)
POD-->>DEV: ⑨ Pod 실행, 볼륨 사용 가능
개발자가 PVC 생성 (StorageClass 지정)
CSI Controller가 PVC 감지
스토리지 백엔드에 볼륨 생성 요청 (예: AWS EBS 생성)
PV 자동 생성 및 PVC와 바인딩
Pod가 PVC 사용
StorageClass 정의
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: ebs.csi.aws.com# CSI 드라이버 이름
parameters:
type: gp3# AWS EBS 타입
iops: "3000"
throughput: "125"
reclaimPolicy: Delete# PVC 삭제 시 PV도 삭제
allowVolumeExpansion: true# 볼륨 확장 허용
volumeBindingMode: WaitForFirstConsumer# Pod 스케줄링 후 바인딩
필드
설명
provisioner
사용할 CSI 드라이버
parameters
스토리지 타입 등 드라이버별 설정
reclaimPolicy
PVC 삭제 시 PV/데이터 처리 방식
allowVolumeExpansion
볼륨 크기 확장 허용 여부
volumeBindingMode
PV 바인딩 시점
동적 프로비저닝 PVC 예시
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-app-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
storageClassName: fast-ssd# 이 StorageClass가 PV를 자동 생성
이 PVC를 생성하면:
Kubernetes가 fast-ssd StorageClass를 찾음
CSI 드라이버(ebs.csi.aws.com)가 20GB gp3 EBS 볼륨 생성
해당 EBS에 대응하는 PV 자동 생성
PVC와 PV 바인딩 완료
volumeBindingMode: WaitForFirstConsumer
이 옵션은 PV가 최초 생성될 때의 바인딩 시점을 제어합니다.
기본값인 Immediate는 PVC 생성 즉시 볼륨을 생성합니다. AZ 종속적인 Block Storage (EBS, GCP PD Zonal 등) 를 사용할 때, 볼륨이 특정 AZ에 먼저 생성되고 Pod가 다른 AZ의 노드에 스케줄링되면 문제가 됩니다.
Immediate 모드 + AZ 종속 스토리지:
PVC 생성 → 볼륨이 AZ-a에 생성 → Pod가 AZ-b에 스케줄링 → 💥 실패!
WaitForFirstConsumer 모드:
PVC 생성 → (대기) → Pod가 AZ-b에 스케줄링 → 볼륨이 AZ-b에 생성 → ✅ 성공!
NFS, EFS 같은 AZ 무관한 스토리지는 Immediate여도 문제없습니다. 하지만 Block Storage를 사용하는 클라우드 환경에서는 WaitForFirstConsumer가 권장됩니다.
참고: Pod 재시작 시에는 이미 PV가 존재하므로 이 옵션은 무관합니다. 재시작 시에는 PV의 nodeAffinity를 보고 Scheduler가 적절한 노드를 선택합니다.
수동 프로비저닝
동적 프로비저닝이 일반적이지만, 기존 스토리지를 재사용하거나 특수한 설정이 필요할 때는 수동으로 PV를 만들 수 있습니다.
# 1. 관리자가 PV 생성
apiVersion: v1
kind: PersistentVolume
metadata:
name: existing-volume
spec:
capacity:
storage: 100Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: ""# 빈 문자열: 특정 StorageClass 없음
csi:
driver: ebs.csi.aws.com
volumeHandle: vol-existing123456# 이미 존재하는 EBS ID
fsType: ext4
---
# 2. 개발자가 PVC 생성 (위 PV와 바인딩됨)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
storageClassName: ""# PV와 동일하게 빈 문자열
방식
흐름
사용 사례
동적
PVC 생성 → StorageClass → PV 자동 생성
일반적인 사용 (90%+)
수동
관리자가 PV 생성 → 개발자가 PVC 생성 → 바인딩
기존 스토리지 재사용, 특수 설정
마무리
이 글에서는 Kubernetes 스토리지의 핵심 개념과 동작 원리를 살펴봤습니다.
핵심 개념:
Kubernetes Volume: 스토리지 자체가 아닌 “스토리지를 Pod에 연결하는 설정”
spec.volumes[]: 상위 개념이고, emptyDir/hostPath/persistentVolumeClaim 등은 타입
PV: 클러스터 레벨의 실제 스토리지 자원
PVC: 개발자의 스토리지 요청서
StorageClass: 동적 프로비저닝을 위한 템플릿
CSI: CNI, CRI와 함께하는 스토리지 플러그인 표준 인터페이스
기억해야 할 것들:
볼륨은 클라우드 네트워크 스토리지에 실재하고, 노드에 attach → Pod에 mount
PV-PVC는 이름이 아닌 조건 기반으로 바인딩됨
WaitForFirstConsumer는 PV 최초 생성 시에만 해당, 재시작은 nodeAffinity로 처리
CSI Controller Plugin은 워커 노드에 attach/detach, CSI Node Plugin은 Pod 경로에 mount
다음 글에서는 AccessModes, Reclaim Policy, StatefulSet 스토리지 관리, 트러블슈팅 등 실전 활용과 운영에 대해 다룹니다.