Kubernetes를 공부하다 보면 Pod와 Deployment까지는 비교적 쉽게 이해할 수 있지만, 데이터 저장 단계에서 새로운 개념을 만나게 됩니다. 바로 Storage, PersistentVolume(PV), PersistentVolumeClaim(PVC)입니다.
Kubernetes의 Pod는 기본적으로 언제든지 삭제되고 다시 생성될 수 있습니다. 문제는 Pod 내부에 데이터를 저장하면 Pod가 삭제되는 순간 데이터까지 사라질 수 있다는 것입니다.
특히 PostgreSQL, MariaDB, MySQL과 같은 데이터베이스 서버를 Kubernetes에서 운영하려면 애플리케이션과 데이터 저장 영역을 분리해야 합니다.
이번 글에서는 Kubernetes의 Storage 구조와 함께 PersistentVolume, PersistentVolumeClaim, StorageClass가 각각 어떤 역할을 하는지 알아보고, 실제 PostgreSQL을 Kubernetes에서 운영하는 예제를 통해 실무적인 저장장치 관리 방법까지 살펴보겠습니다.
일반적인 컨테이너 환경에서는 컨테이너 내부에 파일을 저장할 수 있습니다.
예를 들어 PostgreSQL 컨테이너가 다음과 같은 경로를 사용한다고 가정하겠습니다.
/var/lib/postgresql/data
컨테이너가 정상적으로 실행되는 동안에는 데이터가 유지됩니다.
하지만 다음과 같은 상황이 발생하면 문제가 됩니다.
PostgreSQL Pod
│
▼
Pod 장애
│
▼
Pod 삭제
│
▼
새로운 Pod 생성
│
▼
기존 데이터 접근 불가
Kubernetes에서 Pod는 고정된 서버가 아닙니다.
Deployment가 Pod를 관리하는 경우 장애가 발생하면 새로운 Pod가 생성될 수 있고, 새로운 Pod는 기존 Pod와 다른 환경을 사용할 수 있습니다.
따라서 중요한 데이터는 Pod의 생명주기와 분리해서 관리해야 합니다.
이를 위해 Kubernetes에서는 Volume이라는 개념을 사용합니다.
Volume은 Pod에서 사용할 수 있는 저장 공간입니다.
가장 기본적인 형태는 다음과 같습니다.
Pod
│
├── Container
│
└── Volume
│
└── Storage
Pod 안의 컨테이너는 Volume을 특정 디렉터리에 Mount하여 사용할 수 있습니다.
예를 들어 다음과 같이 구성할 수 있습니다.
volumes:
- name: app-data
emptyDir: {}
그리고 컨테이너에 연결합니다.
volumeMounts:
- name: app-data
mountPath: /data
그러면 컨테이너에서는 다음과 같은 경로를 사용할 수 있습니다.
/data
하지만 emptyDir은 Pod의 생명주기와 밀접하게 연결되어 있기 때문에 데이터베이스의 영구 저장소로 사용하는 것은 적절하지 않습니다.
emptyDir은 Pod가 Node에 생성될 때 만들어지는 임시 저장공간입니다.
Node
│
└── Pod
│
└── emptyDir
Pod가 실행되는 동안 컨테이너 간 데이터를 공유하는 용도로 사용할 수 있습니다.
예를 들어 다음과 같은 용도로 활용할 수 있습니다.
임시 파일
캐시
컨테이너 간 데이터 공유
임시 작업 공간
하지만 Pod 자체가 삭제되면 emptyDir의 데이터도 함께 삭제될 수 있습니다.
따라서 다음과 같은 서비스에는 적합하지 않습니다.
PostgreSQL
MariaDB
MySQL
중요한 파일 저장
사용자 업로드 파일
이러한 데이터는 별도의 영구 저장소를 사용해야 합니다.
PersistentVolume, 줄여서 PV는 Kubernetes 클러스터에서 사용할 수 있는 영구적인 저장 공간을 추상화한 리소스입니다.
쉽게 설명하면 다음과 같습니다.
실제 Storage
│
▼
PersistentVolume
│
▼
Pod
실제 저장소는 여러 형태가 될 수 있습니다.
로컬 디스크
NFS
iSCSI
클라우드 블록 스토리지
클라우드 파일 스토리지
Ceph
기타 Storage 시스템
Kubernetes는 이러한 저장장치를 PV라는 형태로 추상화할 수 있습니다.
PV가 실제 저장 공간이라면 PVC는 애플리케이션이 저장 공간을 요청하는 방법입니다.
즉 다음과 같이 이해하면 쉽습니다.
PV
→ 제공되는 저장 공간
PVC
→ 필요한 저장 공간을 요청하는 것
예를 들어 개발자가 다음과 같이 요청할 수 있습니다.
"10GB 저장 공간이 필요합니다."
이를 PVC로 표현할 수 있습니다.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
Kubernetes는 조건에 맞는 PV를 찾아 PVC와 연결합니다.
전체 구조를 그림으로 표현하면 다음과 같습니다.
Kubernetes
│
┌──────────┴──────────┐
│ │
Pod PVC
│ │
│ ▼
│ PV
│ │
│ ▼
│ 실제 Storage
│
▼
Volume Mount
중요한 점은 Pod가 일반적으로 PV를 직접 선택하는 것이 아니라 PVC를 통해 저장 공간을 요청한다는 것입니다.
Pod
↓
PVC
↓
PV
↓
Storage
이 구조를 이해하는 것이 Kubernetes Storage의 핵심입니다.
PV에는 여러 가지 중요한 설정이 있습니다.
대표적으로 다음 항목을 확인해야 합니다.
capacity
accessModes
persistentVolumeReclaimPolicy
storageClassName
volumeMode
예를 들어 20Gi 크기의 PV를 다음과 같이 만들 수 있습니다.
apiVersion: v1
kind: PersistentVolume
metadata:
name: postgres-pv
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath:
path: /data/postgres
여기서 중요한 것은 hostPath입니다.
실습 환경에서는 간단하게 사용할 수 있지만, 여러 Node로 구성된 운영 클러스터에서는 주의해야 합니다.
PVC를 공부할 때 반드시 알아야 하는 것이 Access Mode입니다.
대표적으로 다음 세 가지가 있습니다.
ReadWriteOnce
ReadOnlyMany
ReadWriteMany
RWO라고 표현합니다.
하나의 Node에서 읽기와 쓰기가 가능한 방식입니다.
Node 1
│
└── Pod
│
└── PVC
데이터베이스와 같은 워크로드에서 자주 사용됩니다.
ROX라고 표현합니다.
여러 Node에서 읽을 수 있지만 쓰기는 허용하지 않는 방식입니다.
RWX라고 표현합니다.
여러 Node에서 읽기와 쓰기가 가능합니다.
공유 파일 저장소가 필요한 환경에서 유용합니다.
중요한 점은 사용하는 Storage 시스템이 어떤 Access Mode를 지원하는지 확인해야 한다는 것입니다.
모든 스토리지가 RWX를 지원하는 것은 아닙니다.
PV를 직접 하나씩 만들어 관리하는 방식은 서버가 많아질수록 관리하기 어려워집니다.
이를 해결하기 위해 사용하는 것이 StorageClass입니다.
StorageClass는 저장장치를 동적으로 생성할 수 있도록 해주는 Kubernetes의 저장소 관리 방법입니다.
구조는 다음과 같습니다.
PVC 생성
│
▼
StorageClass
│
▼
Storage Provisioner
│
▼
실제 Storage 생성
│
▼
PV 생성
이를 Dynamic Provisioning이라고 합니다.
예를 들어 개발자가 다음과 같은 PVC를 생성합니다.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
spec:
storageClassName: standard
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
StorageClass가 설정되어 있다면 Kubernetes 환경의 Storage Provisioner가 요청을 처리하여 필요한 저장공간을 동적으로 생성할 수 있습니다.
클라우드 환경에서는 이러한 방식이 특히 중요합니다.
Kubernetes Storage에는 크게 두 가지 접근 방법이 있습니다.
관리자가 먼저 PV를 생성합니다.
관리자
↓
PV 생성
↓
PVC 생성
↓
PV와 PVC 연결
작은 테스트 환경이나 특정 저장장치를 직접 관리해야 하는 경우 사용할 수 있습니다.
PVC를 생성하면 StorageClass가 필요한 저장공간을 자동으로 생성합니다.
사용자
↓
PVC 생성
↓
StorageClass
↓
Storage 자동 생성
↓
PV 생성
↓
PVC 연결
대규모 Kubernetes 환경에서는 Dynamic Provisioning이 훨씬 편리합니다.
다음은 PostgreSQL에서 사용할 PVC 예제입니다.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
적용합니다.
kubectl apply -f postgres-pvc.yaml
확인합니다.
kubectl get pvc
예를 들어 다음과 같이 표시될 수 있습니다.
NAME STATUS VOLUME CAPACITY
postgres-pvc Bound postgres-pv 10Gi
여기서 중요한 상태가 Bound입니다.
PVC가 PV와 정상적으로 연결되었다는 의미입니다.
PVC를 생성했다고 해서 자동으로 Pod에서 사용하는 것은 아닙니다.
Deployment 또는 StatefulSet에서 Volume으로 연결해야 합니다.
예를 들어 다음과 같이 구성할 수 있습니다.
volumes:
- name: postgres-data
persistentVolumeClaim:
claimName: postgres-pvc
컨테이너에서는 다음과 같이 Mount합니다.
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
전체적인 구조는 다음과 같습니다.
PostgreSQL Container
│
▼
/var/lib/postgresql/data
│
▼
Volume
│
▼
PVC
│
▼
PV
│
▼
Storage
이렇게 구성하면 PostgreSQL 컨테이너와 데이터 저장 공간을 분리할 수 있습니다.
데이터베이스 서버에서는 Storage가 특히 중요합니다.
예를 들어 PostgreSQL이 다음과 같은 데이터를 저장한다고 가정하겠습니다.
Database
├── Table
├── Index
├── WAL
└── Transaction Data
이 데이터가 컨테이너 내부에만 존재한다면 Pod 장애 시 데이터 관리가 어려워집니다.
반면 PVC를 사용하면 다음과 같이 분리할 수 있습니다.
PostgreSQL Pod
│
▼
PostgreSQL Data Directory
│
▼
PVC
│
▼
PV
│
▼
Persistent Storage
Pod가 삭제되어도 저장장치 자체는 별도로 유지될 수 있습니다.
여기에서 한 가지 중요한 개념이 등장합니다.
데이터베이스처럼 상태를 가지고 있는 애플리케이션은 일반적인 Deployment보다 StatefulSet을 사용하는 경우가 많습니다.
Deployment는 주로 stateless 애플리케이션에 적합합니다.
Nginx
FastAPI
Web Application
API Server
반면 StatefulSet은 다음과 같은 상태 저장 애플리케이션을 관리하는 데 적합한 기능을 제공합니다.
PostgreSQL
MariaDB
Kafka
기타 Stateful Workload
예를 들어 다음과 같은 구조를 만들 수 있습니다.
StatefulSet
│
├── PostgreSQL-0
│ │
│ └── PVC
│
└── PostgreSQL-1
│
└── PVC
StatefulSet에서는 Pod와 저장소를 연결하여 상태를 유지하는 구조를 만들 수 있습니다.
실무에서는 Storage가 정상적으로 연결되지 않는 장애가 자주 발생합니다.
가장 먼저 PVC 상태를 확인합니다.
kubectl get pvc
Pending 상태라면 PV 또는 StorageClass 문제를 확인해야 합니다.
kubectl describe pvc postgres-pvc
PV 상태도 확인합니다.
kubectl get pv
StorageClass를 확인합니다.
kubectl get storageclass
Pod 상태도 확인합니다.
kubectl get pods
Pod 이벤트를 확인합니다.
kubectl describe pod <pod-name>
문제가 발생한 경우 다음과 같은 순서로 확인하는 것이 좋습니다.
PVC Pending
│
▼
StorageClass 확인
│
▼
PV 확인
│
▼
Provisioner 확인
│
▼
Node 상태 확인
│
▼
실제 Storage 확인
PVC가 다음과 같이 표시될 수 있습니다.
NAME STATUS
postgres-pvc Pending
이 경우 무조건 Pod 문제라고 생각하면 안 됩니다.
먼저 PVC 이벤트를 확인합니다.
kubectl describe pvc postgres-pvc
다음과 같은 항목을 확인해야 합니다.
StorageClass
Access Mode
Requested Storage
Provisioner
Available PV
예를 들어 요청한 용량보다 작은 PV만 존재한다면 PVC가 Bound되지 않을 수 있습니다.
PVC 요구사항
10Gi
PV
5Gi
이 경우 조건이 맞지 않습니다.
PV에는 데이터 관리와 관련된 중요한 설정이 있습니다.
대표적으로 다음 정책이 있습니다.
Retain
Delete
Recycle
현재 실무에서 특히 중요한 것은 Retain과 Delete입니다.
PVC가 삭제되어도 PV와 데이터가 유지될 수 있습니다.
중요한 데이터를 보존해야 하는 경우 유용합니다.
PVC 삭제
│
▼
PV 유지
│
▼
데이터 유지
PVC가 삭제되면 연결된 저장공간까지 삭제되도록 구성할 수 있습니다.
클라우드 환경에서는 실제 Storage 리소스까지 삭제될 수 있으므로 주의해야 합니다.
운영 데이터에서는 Reclaim Policy를 반드시 확인해야 합니다.
Storage는 단순히 용량만 중요한 것이 아닙니다.
다음 요소도 중요합니다.
IOPS
Throughput
Latency
Capacity
Availability
Durability
예를 들어 데이터베이스에서는 작은 I/O가 매우 많이 발생할 수 있기 때문에 IOPS와 Latency가 중요할 수 있습니다.
반대로 대용량 백업이나 영상 파일 처리에서는 Sequential Throughput이 중요할 수 있습니다.
따라서 다음과 같이 목적에 따라 Storage를 선택해야 합니다.
Database
→ 낮은 Latency + 높은 IOPS
Backup
→ 높은 Throughput + 큰 용량
공유 파일
→ RWX + 안정적인 파일 스토리지
로그
→ 충분한 용량 + 관리 정책
시스템 엔지니어라면 Kubernetes Storage를 기존 Linux Storage와 연결해서 이해하는 것이 좋습니다.
Linux에서는 다음과 같은 구조를 배웠습니다.
Disk
↓
Partition
↓
Filesystem
↓
Mount
↓
Directory
Kubernetes에서는 추상화 계층이 추가됩니다.
Physical Storage
↓
Storage System
↓
PV
↓
PVC
↓
Pod Volume
↓
Mount
↓
Application
따라서 지금까지 배운 Linux 디스크 관리 지식은 Kubernetes Storage를 이해하는 데 그대로 활용할 수 있습니다.
Kubernetes Storage를 운영할 때는 다음 사항을 반드시 확인해야 합니다.
첫째, 중요한 데이터를 컨테이너 내부에 직접 저장하지 않습니다.
둘째, 데이터베이스와 같은 Stateful Application에는 적절한 Persistent Storage를 사용합니다.
셋째, PVC의 Access Mode를 Storage가 지원하는지 확인합니다.
넷째, StorageClass와 Dynamic Provisioning의 동작 방식을 이해합니다.
다섯째, PV의 Reclaim Policy를 확인합니다.
여섯째, 백업과 복구 정책을 별도로 운영합니다.
특히 중요한 점은 PV와 PVC를 사용한다고 해서 백업이 자동으로 되는 것은 아니라는 사실입니다.
Persistent Storage
≠
Backup
스토리지 장애나 운영자의 실수, 데이터 손상 등에 대비하려면 별도의 백업 정책이 필요합니다.
Kubernetes Storage는 단순히 디스크를 하나 추가하는 기능이 아닙니다. Kubernetes에서 컨테이너와 데이터를 분리하고, 상태 저장 애플리케이션을 안정적으로 운영하기 위한 핵심 구조입니다.
전체적인 관계를 정리하면 다음과 같습니다.
Kubernetes
│
Pod
│
Volume Mount
│
PVC
│
PV
│
StorageClass
│
Storage Provisioner
│
실제 저장장치
각 구성 요소의 역할을 다시 정리하면 다음과 같습니다.
Volume
→ Pod에서 사용하는 저장 공간
PV
→ Kubernetes가 관리하는 영구 저장 공간
PVC
→ 애플리케이션이 저장 공간을 요청하는 객체
StorageClass
→ 저장공간을 동적으로 생성하기 위한 정책
Provisioner
→ 실제 저장장치를 생성하고 연결하는 구성 요소
특히 시스템 엔지니어라면 다음 흐름을 반드시 기억해야 합니다.
Pod
↓
PVC
↓
PV
↓
Storage
그리고 데이터베이스 환경에서는 여기에 다음 개념까지 연결해야 합니다.
PostgreSQL
↓
StatefulSet
↓
PVC
↓
PV
↓
Persistent Storage
↓
Backup
이 구조를 이해하면 Kubernetes에서 단순히 컨테이너를 실행하는 수준을 넘어 데이터가 어디에 저장되고, Pod가 장애가 났을 때 데이터가 어떻게 유지되며, 저장소 장애가 발생했을 때 어디부터 확인해야 하는지까지 판단할 수 있게 됩니다.