Cloudnet AWES 3주차 스터디를 진행하며 정리한 글입니다.
이번 포스팅에서는 쿠버네티스에서 사용하는 스토리지에 대해 알아보도록 하겠습니다.
스토리지 (Storage)
쿠버네티스에서 Pod이 재시작되거나 이동할 경우 기존 데이터가 사라질 수 있습니다. 어플리케이션을 실행할 때 데이터의 영속성이 필요한 경우가 발생하는데, 이를 해결하기 위해 Storage를 사용합니다.
# 모니터링
kubectl get pod -w
# redis 파드 생성
❯ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: redis
spec:
terminationGracePeriodSeconds: 0
containers:
- name: redis
image: redis
EOF
# redis 파드 내에 파일 작성
❯ kubectl exec -it redis -- pwd
❯ kubectl exec -it redis -- sh -c "echo hello > /data/hello.txt"
❯ kubectl exec -it redis -- cat /data/hello.txt
hello
# ps 설치
❯ kubectl exec -it redis -- sh -c "apt update && apt install procps -y"
❯ kubectl exec -it redis -- ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
redis 1 0.3 0.2 143876 10296 ? Ssl 08:03 0:00 redis-server
root 240 0.0 0.1 8088 4036 pts/0 Rs+ 08:04 0:00 ps aux
# redis 프로세스 강제 종료 : 컨테이너 내에서 PID 1 프로세스(즉, 메인 프로세스)를 종료하였을때 컨테이너 재시작
❯ kubectl exec -it redis -- kill 1
❯ kubectl get pod
NAME READY STATUS RESTARTS AGE
redis 1/1 Running 1 (3s ago) 6
# redis 파드 내에 파일 확인
❯ kubectl exec -it redis -- cat /data/hello.txt
cat: /data/hello.txt: No such file or directory
command terminated with exit code 1
❯ kubectl exec -it redis -- ls -l /data
total 0
# 파드 삭제
❯ kubectl delete pod redis
파드는 재시작되지 않지만, 컨테이너가 종료 되었을 때 쿠버네티스 재시작 정책에 따라 컨테이너만 재시작 되었습니다.
이때 Pod에 연결된 별도의 볼륨 설정이 없기 때문에, 데이터가 삭제된 것을 알 수 있습니다.
- restartPolicy : Always -> 컨테이너 자동으로 재시작
- restartPolicy : OnFailure -> 종료 코드가 0이 아닌 경우 재시작
- restartPolicy : Never -> 절대 재시작하지 않음
볼륨(Volume)
Pod 내부에서 사용할 수 있는 저장 공간
쿠버네티스는 다양한 유형의 볼륨이 사용 가능합니다. 임시 볼륨 유형은 파드의 수명을 갖지만, 퍼시스턴트 볼륨은 파드의 수명을 넘어 존재합니다.
아래는 쿠버네티스에서 사용하는 다양한 볼륨에 대해 설명하였습니다. 다만 일부만 소개하였으므로, 다른 볼륨도 궁금하시다면 아래 공식문서를 참고하시길 권장드립니다.
https://kubernetes.io/ko/docs/concepts/storage/volumes/
볼륨
컨테이너 내의 디스크에 있는 파일은 임시적이며, 컨테이너에서 실행될 때 애플리케이션에 적지 않은 몇 가지 문제가 발생한다. 한 가지 문제는 컨테이너가 크래시될 때 파일이 손실된다는 것
kubernetes.io

emptyDir
Pod가 노드에 할당될 때 처음 생성되는 빈 디렉터리로 초기화되는 볼륨
컨테이너가 재시작해도 데이터는 유지되지만, Pod가 실행되는 동안에만 유지되는 임시 볼륨으로 Pod자체가 삭제되면 데이터도 사라집니다. (이때 컨테이너 크래시-OOM KILL, Exit code 1 등-가 일어나도 Pod가 유지되는 한 emptyDir의 데이터는 남아있지만, Pod이 삭제되거나 재배치되는 경우 데이터는 사라집니다.)
같은 Pod내의 여러 컨테이너가 공유 가능하지만, 해당 볼륨은 각각 컨테이너에서 동일하거나 다른 경로에 마운트될 수 있습니다.
# redis 파드 생성
❯ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: redis
spec:
terminationGracePeriodSeconds: 0
containers:
- name: redis
image: redis
volumeMounts:
- name: redis-storage
mountPath: /data/redis
volumes:
- name: redis-storage
emptyDir: {}
EOF
# redis 파드 내에 파일 작성
❯ kubectl exec -it redis -- pwd
❯ kubectl exec -it redis -- sh -c "echo hello > /data/hello.txt"
❯ kubectl exec -it redis -- cat /data/hello.txt
hello
# ps 설치
❯ kubectl exec -it redis -- sh -c "apt update && apt install procps -y"
❯ kubectl exec -it redis -- ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
redis 1 0.3 0.2 143876 10516 ? Ssl 08:12 0:00 redis-server
root 229 0.0 0.1 8088 4024 pts/0 Rs+ 08:13 0:00 ps aux
# redis 프로세스 강제 종료 : 컨테이너 내에서 PID 1 프로세스(즉, 메인 프로세스)를 종료하였을때 컨테이너 재시작
❯ kubectl exec -it redis -- kill 1
❯ kubectl get pod
NAME READY STATUS RESTARTS AGE
redis 1/1 Running 1 (5s ago) 2m
# redis 파드 내에 파일 확인
❯ kubectl exec -it redis -- cat /data/redis/hello.txt
hello
❯ kubectl exec -it redis -- ls -l /data
total 4
-rw-r--r--. 1 redis root 6 Feb 22 08:12 hello.txt
# 파드 삭제
❯ kubectl delete pod redis
위 경우도 위에 별도 볼륨을 지정하지 않은 경우에는 데이터가 삭제되었으나, emptyDir 설정 시 Pod가 유지되었기 때문에 데이터가 남아있는 것을 알 수 있습니다.
hostPath
노드의 파일시스템에 있는 파일이나 디렉터리를 Pod에 마운트하여 사용하는 볼륨
노드에 의존적이기 때문에 Pod가 이동하면 데이터는 노드에 남아있지만, Pod가 새로운 노드에서 실행되면 접근할 수 없습니다.
다만, 많은 보안 위험이 있기 때문에, hostPath를 사용하지 않는 것을 권장하고 있다고 합니다.
PersistentVolume (PV)
쿠버네티스 클러스터에서 관리하는 독립적인 스토리지 리소스로, 관리자가 미리 생성한 스토리지 리소스
PV는 파드들과 별개의 라이프 사이클을 가지는데, Local Volume을 사용할 수 도 있지만, 외부 스토리지 볼륨을 사용할 수도 있습니다.
PVC가 PV를 요청하면, 쿠버네티스가 자동으로 Bound 상태로 연결합니다.
PersistentVolumeClaim (PVC)
Pod가 PV를 사용하기 위해 요청하는 스토리지 할당 요청, Pod와 PV를 연결
PVC가 PV와 매칭되면 Bound 상태가 되어 사용 가능합니다.

이때 PV 상태란?
PVC와 PV가 연결된 상태입니다.
- Bound : PV가 PVC에 할당되어 연결된 상태
- Available : PVC에서 사용할 수 있도록 준비된 상태
- Released : 이전에 할당된 PVC와 연결이 끊어진 상태 PVC는 삭제 되었지만, 아직 리소스 반환은 되지 않아 반환 정책에 따라 데이터는 유지 또는 삭제 예정
- Failed : PV 생성 또는 반환 중 오류가 발생한 상태
PV, PVC의 접근 모드(accessMode)란?
PV와 PVC의 스토리지 접근 방식을 정의한 것입니다.
- ReadWriteOnce : 하나의 노드에서만 읽기/쓰기 가능 (ex, AWS EBS)
- ReadWriteMany : 여러 노드에서 읽기만 가능
- ReadWriteOncePod : 여러 노드에서 읽기/쓰기 가능
PV 반환정책이란?
PV가 더 이상 사용되지 않을 때 데이터를 어떻게 처리할 지 설정한 정책입니다.
- Retain : PVC가 삭제되어도 PV 데이터는 유지 (관리자가 직접 정리해야함)
- Delete : PVC가 삭제되면 PV도 자동 삭제 (동적 프로비저닝 볼륨에 주로 사용)
- Recycle : 기존 데이터를 삭제하고 새로운 PVC를 사용할 수 있도록 초기화 (거의 사용 안 함)
PV와 PVC가 연결되는 과정은 다음과 같습니다.

1. 관리자는 네트워크 스토리지 유형을 설정합니다.
2. 관리자는 PV를 생성합니다. (쿠버네티스 API에 PV 디스크립터 게시)
3. 사용자는 PVC를 생성합니다.
4. 쿠버네티스는 적정한 크기의 접근 모드의 PV를 찾고 PVC를 PV에 바인딩합니다.
5. 사용자는 PVC를 참조하는 볼륨을 가진 Pod를 생성합니다.
StorageClass
PVC에서 스토리지 생성 요청이 들어오면, 어떤 CSI 드라이버를 사용할지 정의하는 설정
PVC의 요청이 있을 때 자동으로 PV를 생성하는 기능을 제공하며, PVC에서 storageclass를 지정하면 해당 storageclass에 맞는 스토리지가 자동으로 프로비저닝 됩니다.
CSI (Container Storage Interface)
쿠버네티스를 비롯한 컨테이너 오케이스트레이션 도구와 다양한 스토리지 백엔드를 연결하는 표준 인터페이스
기존에는 쿠버네티스 소스 코드 내부에 지원하는 스토리지 프로비저너는 직접 특정 스토리지를 지원해야 했지만, 쿠버네티스 버전 라이프사이클에 따라 신규 기능이 배포되기 때문에 신규 기능이 필요하면 쿠버네티스 버전을 업그레이드 해야했습니다.
하지만, CSI가 도입되면서 스토리지 공급업체가 자체적으로 CSI 플러그인을 개발하여 다양한 프로바이더를 선택하여 사용할 수 있었고, 사용자는 쿠버네티스 버전에 종속되지 않고 별도의 스토리지를 동적으로 프로비저닝을 할 수 있게 되었습니다.

아래는 local-path-provisioner storageClass 배포하는 예제 코드입니다.
# 배포
❯ kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.31/deploy/local-path-storage.yaml
namespace/local-path-storage created
serviceaccount/local-path-provisioner-service-account created
role.rbac.authorization.k8s.io/local-path-provisioner-role created
clusterrole.rbac.authorization.k8s.io/local-path-provisioner-role created
rolebinding.rbac.authorization.k8s.io/local-path-provisioner-bind created
clusterrolebinding.rbac.authorization.k8s.io/local-path-provisioner-bind created
deployment.apps/local-path-provisioner created
storageclass.storage.k8s.io/local-path created
configmap/local-path-config created
# 확인
❯ kubectl get all -n local-path-storage
NAME READY STATUS RESTARTS AGE
pod/local-path-provisioner-84967477f-f55cz 1/1 Running 0 50s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/local-path-provisioner 1/1 1 1 50s
NAME DESIRED CURRENT READY AGE
replicaset.apps/local-path-provisioner-84967477f 1 1 1 50s
❯ kubectl get pod -n local-path-storage -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
local-path-provisioner-84967477f-f55cz 1/1 Running 0 78s 192.168.1.157 ip-192-168-1-115.ap-northeast-2.compute.internal
❯ kubectl describe cm -n local-path-storage local-path-config
Name: local-path-config
Namespace: local-path-storage
Labels: <none>
Annotations: <none>
Data
====
config.json:
----
{
"nodePathMap":[
{
"node":"DEFAULT_PATH_FOR_NON_LISTED_NODES",
"paths":["/opt/local-path-provisioner"]
}
]
}
helperPod.yaml:
----
apiVersion: v1
kind: Pod
metadata:
name: helper-pod
spec:
priorityClassName: system-node-critical
tolerations:
- key: node.kubernetes.io/disk-pressure
operator: Exists
effect: NoSchedule
containers:
- name: helper-pod
image: busybox
imagePullPolicy: IfNotPresent
setup:
----
#!/bin/sh
set -eu
mkdir -m 0777 -p "$VOL_DIR"
teardown:
----
#!/bin/sh
set -eu
rm -rf "$VOL_DIR"
BinaryData
====
Events: <none>
❯ kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
gp2 kubernetes.io/aws-ebs Delete WaitForFirstConsumer false 3h48m
local-path rancher.io/local-path Delete WaitForFirstConsumer false 2m17s
PV,PVC 를 사용하는 파드 생성, 파드 삭제 후 파드 재생성해서 데이터 유지 되는지 확인합니다.
# PVC 생성
❯ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: localpath-claim
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi
EOF
# PVC 확인
❯ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
localpath-claim Pending local-path <unset> 6s
❯ kubectl describe pvc
Name: localpath-claim
Namespace: default
StorageClass: local-path
Status: Pending
Volume:
Labels: <none>
Annotations: <none>
Finalizers: [kubernetes.io/pvc-protection]
Capacity:
Access Modes:
VolumeMode: Filesystem
Used By: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal WaitForFirstConsumer 11s (x2 over 12s) persistentvolume-controller waiting for first consumer to be created before binding
❯ kubectl describe pv
No resources found
# 파드 생성
❯ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
terminationGracePeriodSeconds: 3
containers:
- name: app
image: centos
command: ["/bin/sh"]
args: ["-c", "while true; do echo \$(date -u) >> /data/out.txt; sleep 5; done"]
volumeMounts:
- name: persistent-storage
mountPath: /data
volumes:
- name: persistent-storage
persistentVolumeClaim:
claimName: localpath-claim
EOF
pod/app created
# 파드 확인
❯ kubectl get pod,pv,pvc
NAME READY STATUS RESTARTS AGE
pod/app 0/1 ContainerCreating 0 10s
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
persistentvolume/pvc-79ca32ad-e28e-4862-aba7-f64ed93d5ab4 1Gi RWO Delete Bound default/localpath-claim local-path <unset> 3s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
persistentvolumeclaim/localpath-claim Bound pvc-79ca32ad-e28e-4862-aba7-f64ed93d5ab4 1Gi RWO local-path <unset> 97s
❯ kubectl describe pv
Name: pvc-79ca32ad-e28e-4862-aba7-f64ed93d5ab4
Labels: <none>
Annotations: local.path.provisioner/selected-node: ip-192-168-3-152.ap-northeast-2.compute.internal
pv.kubernetes.io/provisioned-by: rancher.io/local-path
Finalizers: [kubernetes.io/pv-protection]
StorageClass: local-path
Status: Bound
Claim: default/localpath-claim
Reclaim Policy: Delete
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 1Gi
Node Affinity: ## 본인 노드에만 뜰 수 있도록 지정
Required Terms:
Term 0: kubernetes.io/hostname in [ip-192-168-3-152.ap-northeast-2.compute.internal]
Message:
Source:
Type: HostPath (bare host directory volume)
Path: /opt/local-path-provisioner/pvc-79ca32ad-e28e-4862-aba7-f64ed93d5ab4_default_localpath-claim
HostPathType: DirectoryOrCreate
Events: <none>
❯ kubectl exec -it app -- tail -f /data/out.txt
Sat Feb 22 08:38:52 UTC 2025
Sat Feb 22 08:38:57 UTC 2025
Sat Feb 22 08:39:02 UTC 2025
Sat Feb 22 08:39:07 UTC 2025
Sat Feb 22 08:39:12 UTC 2025
Sat Feb 22 08:39:17 UTC 2025
...
# 워커노드 중 현재 파드가 배포되어 있다만, 아래 경로에 out.txt 파일 존재 확인
❯ for node in $N1 $N2 $N3; do ssh ec2-user@$node tree /opt/local-path-provisioner; done
/opt/local-path-provisioner [error opening dir]
0 directories, 0 files
/opt/local-path-provisioner [error opening dir]
0 directories, 0 files
/opt/local-path-provisioner
└── pvc-79ca32ad-e28e-4862-aba7-f64ed93d5ab4_default_localpath-claim
└── out.txt
# 해당 워커노드 자체에서 out.txt 파일 확인
❯ ssh ec2-user@$N3 tail -f /opt/local-path-provisioner/pvc-79ca32ad-e28e-4862-aba7-f64ed93d5ab4_default_localpath-claim/ou
t.txt
Sat Feb 22 09:16:29 UTC 2025
Sat Feb 22 09:16:34 UTC 2025
Sat Feb 22 09:16:39 UTC 2025
Sat Feb 22 09:16:44 UTC 2025
Sat Feb 22 09:16:49 UTC 2025
Sat Feb 22 09:16:54 UTC 2025
# 파드 삭제 후 PV/PVC 확인
❯ kubectl delete pod app
pod "app" deleted
❯ kubectl get pod,pv,pvc
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
persistentvolume/pvc-79ca32ad-e28e-4862-aba7-f64ed93d5ab4 1Gi RWO Delete Bound default/localpath-claim local-path <unset> 40m
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
persistentvolumeclaim/localpath-claim Bound pvc-79ca32ad-e28e-4862-aba7-f64ed93d5ab4 1Gi RWO local-path <unset> 41m
❯ for node in $N1 $N2 $N3; do ssh ec2-user@$node tree /opt/local-path-provisioner; done
/opt/local-path-provisioner [error opening dir]
0 directories, 0 files
/opt/local-path-provisioner [error opening dir]
0 directories, 0 files
/opt/local-path-provisioner
└── pvc-79ca32ad-e28e-4862-aba7-f64ed93d5ab4_default_localpath-claim
└── out.txt
1 directory, 1 file
# 파드 다시 실행
❯ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
terminationGracePeriodSeconds: 3
containers:
- name: app
image: centos
command: ["/bin/sh"]
args: ["-c", "while true; do echo \$(date -u) >> /data/out.txt; sleep 5; done"]
volumeMounts:
- name: persistent-storage
mountPath: /data
volumes:
- name: persistent-storage
persistentVolumeClaim:
claimName: localpath-claim
EOF
pod/app created
# 확인
❯ kubectl exec -it app -- head /data/out.txt
Sat Feb 22 08:38:52 UTC 2025
Sat Feb 22 08:38:57 UTC 2025
Sat Feb 22 08:39:02 UTC 2025
Sat Feb 22 08:39:07 UTC 2025
Sat Feb 22 08:39:12 UTC 2025
Sat Feb 22 08:39:17 UTC 2025
Sat Feb 22 08:39:22 UTC 2025
Sat Feb 22 08:39:27 UTC 2025
Sat Feb 22 08:39:32 UTC 2025
Sat Feb 22 08:39:37 UTC 2025
❯ kubectl exec -it app -- tail -f /data/out.txt
Sat Feb 22 09:18:29 UTC 2025
Sat Feb 22 09:18:34 UTC 2025
Sat Feb 22 09:18:39 UTC 2025
Sat Feb 22 09:18:44 UTC 2025
Sat Feb 22 09:20:15 UTC 2025
Sat Feb 22 09:20:20 UTC 2025
Sat Feb 22 09:20:25 UTC 2025
Sat Feb 22 09:20:30 UTC 2025
Sat Feb 22 09:20:35 UTC 2025
Sat Feb 22 09:20:40 UTC 2025
PV, PVC로 볼륨을 설정한 Pod를 삭제 후 다시 붙여도 데이터는 존재하는 것을 알 수 있습니다.
중간에 09:18 ~09:20 사이는 Pod이 삭제되었을 동안 해당 경로에 데이터를 쓰지 않았기 때문에 시간 상 공백이 생긴 채로 out.txt 파일 존재하는 것을 알 수 있습니다.
++) 이 때! hostPath와 Local Path Provisioner는 무슨 차이가 있을까요? w/ GPT
둘 다 노드의 로컬 디스크를 사용하는 볼륨이지만, 차이점이 있습니다.
| 구분 | hostPath | Local Path Provisioner (StorageClass) |
| 스토리지 타입 | 노드의 특정 디렉터리 직접 마운트 | StorageClass 통해 로컬 볼륨 동적으로 생성 |
| 프로비저닝 방식 | 정적 프로비저닝 (수동 설정) | 동적 프로비저닝 (PVC 요청 시 자동 생성) |
| Pod 이동 시 데이터 유지 | 불가능 | 불가능 |
| 멀티 노드 지원 | 불가능 | 불가능 |
| 스토리지 관리 | 관리자가 직접 노드 경로 설정 필요 | StorageClass 통해 자동화 |
| 주요 사용 사례 | 디버깅, 로깅, 특정 노드의 데이터 활용 | 노듸 로컬 볼륨 자동으로 생성 및 관리 |
즉 쿠버네티스에서 PV, PVC를 이용하여 데이터를 동적으로 프로비저닝하는 방식은 이렇게 동작합니다.
PVC → StorageClass → CSI Driver → PV → Pod 마운트
PVC가 생성되면 StorageClass를 참조하고, StorageClass에서 어떤 CSI Driver 즉, 어떤 스토리지 백엔드를 사용할지 결정합니다. 이후 PVC가 필요한 Pod이 배포되면 CSI Driver가 PV를 생성하고 실제 스토리지(ex, AWS EBS 등)을 할당합니다. PV가 생성되면서 PVC와 바인딩되고, volumeMounts 설정에 따라 Pod에 마운트되어 볼륨을 사용할 수 있습니다.
(단, StorageClass의 volumeBindingMode가 WaitForFirstConsumer인 경우일때만입니다.! 이 때는 Pod이 생성될 때까지 대기하는데, volumeBindingMode가 Immediate라면 PVC 만들면 PV와 EBS 볼륨이 즉시 생성됩니다.)
퍼시스턴트 볼륨
이 페이지에서는 쿠버네티스의 퍼시스턴트 볼륨 에 대해 설명한다. 볼륨에 대해 익숙해지는 것을 추천한다. 소개 스토리지 관리는 컴퓨트 인스턴스 관리와는 별개의 문제다. 퍼시스턴트볼륨 서
kubernetes.io
'스터디 > AEWS' 카테고리의 다른 글
| [AEWS] 4주차 EKS 로깅 (컨트롤 플레인, 데이터 플레인) (0) | 2025.03.02 |
|---|---|
| [AEWS] 3주차 EKS에서 CSI Driver를 통한 AWS 스토리지 사용하기 (EBS, EFS) (0) | 2025.02.23 |
| [AEWS] 2주차 EKS VPC CNI를 통한 파드 간 통신 (작성중) (0) | 2025.02.16 |
| [AEWS] 2주차 EKS 노드 최대 파드 생성 갯수 (0) | 2025.02.16 |
| [AEWS] 2주차 EKS VPC CNI 알아보기 (0) | 2025.02.15 |