쿠버네티스에는 데이터를 제공하는 부분과 마련한 자장소를 사용하는 부분으로 구분지었습니다.
PersistentVolume은 클러스터 관리자가 데이터를 어떻게 제공할 것인지에 관한 리소스
PersistentVolumeClaim은 일반 사용자가 데이터 저장소를 어떻게 활용할 것인지 정의하는 리소스
StorageClass는 클러스터 관리자가 사용자들에게 제공하는 저장소 종류를 나타내 동적으로 저장소를 제공합니다.
PersistentVolume
PersistentVolume(PV)은 데이터 저장소를 추상화시킨 리소스로,
클러스터 관리자가 데이터 저장을 위해 미리 마련한 저장 자원입니다.
예로
AWS - EBS
GCP - PersistentDisk
localhost - localhost volume path 정보
이처럼 다양한 환경에서 다양한 저장소 타입을 제공하기 위해 쿠버네티스에선 PersistentVolume이란 추상화된 리소스를 사용하여,
각 환경에 맞는 타입을 선택합니다.
hostPath PV
# volume.yaml
apiVersion: v1
kind: Pod
metadata:
name: volume
spec:
containers:
- name: nginx
image: nginx
# 컨테이너 내부의 연결 위치 지정
volumeMounts:
- mountPath: /container-volume
name: my-volume
# host 서버의 연결 위치 지정
volumes:
- name: my-volume
hostPath:
path: /home
위 내용을 PersistentVolume으로 표현하면 다음과 같습니다.
# hostpath-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-volume
spec:
storageClassName: manual
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /tmp
kubectl apply -f hostpath-pv.yaml
# persistentvolume/my-volume created
kubectl get pv
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
# my-volume 1Gi RWO Retain Available manual 12s
- storageClassName: 저장소 타입의 이름 정의. PVC에서 특정 저장소 타입을 지정하기 위해 사용
- capacity: 데이터 저장소의 크기 지정. 호스트 서버의 디스크를 1Gi를 이용
- accessModes: 접근 모드를 설정. ReadWriteOnce 동시에 1개의 Pod만 해당 볼륨에 접근 가능. NFS volume은 ReadWriteMany로 여러 Pod에서 동시에 접근 가능.
- hostPath: 호스트 서버에서 연결될 path를 나타냄
PV리소스는 네임스페이스에 국한되지 않은 클러스터 레벨의 리소스입니다.
kubectl로 PV의 STATUS 확인이 가능하고 현재 Available상태는 볼륨만 생성되었을 뿐 아직 아무도 데이터 저장소를 사용하고 있지 않다는 것을 의미합니다.
NFS PV
# my-nfs.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-nfs
spec:
storageClassName: nfs
capacity:
storage: 5Gi
accessModes:
- ReadWriteMany
mountOptions:
- hard
- nfsvers=4.1
nfs:
path: /tmp
server: <NFS_SERVER_IP>
로컬 호스트 볼륨 뿐 아니라 NFS볼륨도 사용 가능합니다.
- storageClassName: nfs라는 이름의 클래스 이름 정의
- capacity: 데이터 저장소의 크기를 지정
- accessModes: NFS volume같은 경우 ReadWriteMany로 설정 가능
- mountOptions: NFS 서버와 마운트하기 위한 옵션들을 설정할 수 있음
- nfs: 마운트할 NFS 서버 정보 입력
현재 NFS 서버가 없어 NFS PV 생성은 불가합니다.
Storage Class에서 NFS 서버를 구축 후 설정해보겠습니다.
awsElasticBlockStorage PV
AWS EBS를 요청하는 PV 예제입니다.
다음 예제는 AWS 플랫폼 위에서 적절한 권한이 부여된 환경에서만 동작합니다. 볼륨을 생성하여 <volume-id>를 입력해 주시기 바랍니다. (예제에서는 vol-1234567890abcdef0)
aws ec2 create-volume --availability-zone=eu-east-1a \
--size=80 --volume-type=gp2
# {
# "AvailabilityZone": "us-east-1a",
# "Tags": [],
# "Encrypted": false,
# "VolumeType": "gp2",
# "VolumeId": "vol-1234567890abcdef0",
# "State": "creating",
# "Iops": 240,
# "SnapshotId": "",
# "CreateTime": "YYYY-MM-DDTHH:MM:SS.000Z",
# "Size": 80
# }
# aws-ebs.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: aws-ebs
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
awsElasticBlockStore:
volumeID: <volume-id>
fsType: ext4
- capacity: 데이터 저장소의 크기
- accessModes: 접근 모드를 설정
- awsElasticBlockStore: AWS EBS 자원 정보를 입력
그 외 다른 PersistentVolume
- azureDisk
- employDir
- downward API
- configMap
PersistentVolume은 데이터 저장소를 추상화시킨 리소스이다~
PersistentVolumeClaim
PVC은 저장소 사용자가 PV를 요청하는 리소스입니다.
클러스터 관리자가 PV를 통해 데이터 저장소를 준비하면 쿠베 사용자가 PVC 요청을 통해 해당 리소스를 선점합니다.
한마디로 PV 사용을 요창하는 역할을 담당합니다.

1. 클러스터 관리자가 PV를 생성
2. PV 정의에 따라 볼륨 생성됨
3. 일반 사용자가 PV를 선점하기 위해 요청 (PVC 생성)
4. PV와 연결된 볼륨을 사용
# my-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
- storageClassName: 선점할 PV의 저장소 클래스를 지정
- accessModes: 선점할 PV의 접근모드를 설정
- resources: 요청할 저장소 크기를 지정
PVC 리소스를 생성하면 요청한 저장소 타입 클래스 이름에 맞는 PV를 연결해주지요.
kubectl apply -f my-pvc.yaml
# persistentvolumeclaim/my-pvc created
# 앞에서 생성한 my-volume을 선점하였습니다.
kubectl get pvc
# NAME STATUS VOLUME CAPACITY ACCESS MODES ...
# my-pvc Bound my-volume 1Gi RWO ...
kubectl get pv
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS
# my-volume 1Gi RWO Retain Bound
#
# CLAIM STORAGECLASS REASON AGE
# default/my-pvc manual 11s
my-pvc라는 PVC리소스를 생성한 이후 PV를 조회하면 STATUS가 Bound로 바뀌었고,
CLAIM에 my-pvc가 입력된 것을 확인할 수 있습니다.
이 PVC를 이용해 Pod에서 직접 사용해보겠습니다.
Pod를 싱행하고 /test-volume 위치에 파일을 하나 생성합니다.
# use-pvc.yaml
apiVersion: v1
kind: Pod
metadata:
name: use-pvc
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- mountPath: /test-volume
name: vol
volumes:
- name: vol
persistentVolumeClaim:
claimName: my-pvc
- volume: 연결할 볼륨을 설정
- persistentVolumeClaime: PVC볼륨 사용을 선언
- claimName: 사용할 PVC 이름 지정
kubectl apply -f use-pvc.yaml
# pod/use-pvc created
# 데이터 저장
kubectl exec use-pvc -- sh -c "echo 'hello' > /test-volume/hello.txt"
요청한 PVC에 데이터를 저장합니다.
Pod 삭제 후 똑같은 Pod를 다시 생성하여 해당 디렉터리에 기존 데이터가 남아있는지 확인해보겠습니다.
kubectl delete pod use-pvc
# pod/use-pvc deleted
kubectl apply -f use-pvc.yaml
# pod/use-pvc created
kubectl exec use-pvc -- cat /test-volume/hello.txt
# hello
이처럼 PVC는 사용자가 명시적으로 삭제하기 전까지 데이터 저장소가 유지됩니다.
왜 PV PVC 둘이 나눌까요?
Node와 Pod의 관계에서 Node는 Pod의 생명주기와 관계없이 지속적으로 유지되지만 Pod는 누구나 생성 가능합니다.
Node는 클러스터 관리지가 설벙해줘야 스케일 아웃같은게 가능합니다. 이는 자원을 소비합니다.
PV나 PVC관계도 이와 유사합니다.
데이터를 저장하는 PV와 이를 활용하는 PVC의 생명주기는 다릅니다.
PVC는 사용자의 요청에 의해 생성 삭제될 수 있지만 PV는 PVC 생명주기와 상관없이 지속적으로 데이터를 유지합니다.
쿠버네티스에서는 스토리지 자원을 책임과 역할에 따라 구분하여 제공하는 것입니다.
StorageClass
StorageClass 리소스는 클러스터 관리자에 의해 사용자들이 선택할 수 있는 스토리지 종류를 열거한 것입니다.
이를 이용해 사용자는 동적으로 볼륨을 제공 받을 수 있습니다.
원래 볼륨이 준비되지 않으면 Pod는 Pending상태가 되지만 StorageClass를 이용하면 볼륨을 생성하기를 기다릴 필요 없이 동적으로 데이터 저장소를 제공받을 수 있습니다.
k3s를 통해 쿠베를 설치하면 이미 local-apath라는 StorageClass가 설치되어 있습니다.
이는 노드이 로컬 저장소를 활용할 수 있게 해줘 명시적으로 로컬 호스트의 path를 지정할 필요없이 local-path provisioner가 볼륨을 관리해줍니다.
# local-path라는 이름의 StorageClass
kubectl get sc
# NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
# local-path (default) rancher.io/local-path Delete WaitForFirstConsumer false 20d
kubectl get sc local-path -oyaml
# apiVersion: storage.k8s.io/v1
# kind: StorageClass
# metadata:
# annotations:
# objectset.rio.cattle.io/id: ""
# ...
# name: local-path
# resourceVersion: "172"
# selfLink: /apis/storage.k8s.io/v1/storageclasses/local-path
# uid: 3aede349-0b94-40c8-b10a-784d38f7c120
# provisioner: rancher.io/local-path
# reclaimPolicy: Delete
# volumeBindingMode: WaitForFirstConsumer
StorageClass리소스는 클러스터 레벨의 리소스로써 조회 시, 네임스페이스 지정을 할 필요가 없습니다.
# my-pvc-sc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc-sc
spec:
storageClassName: local-path
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
- storageClassName: 사용자가 직접 정의한 manual StorageCalss가 아닌 클러스터 관리자가 제공한 StorageClass인 local-path를 지정
kubectl apply -f my-pvc-sc.yaml
# persistentvolumeclaim/my-pvc-sc created
kubectl get pvc my-pvc-sc
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
# my-pvc-sc Pending local-path 11s
위는 PVC가 Pending상태이지만 Pod가 PVC를 사용하는 경우 동작으로 볼륨이 생성됩니다.
# use-pvc-sc.yaml
apiVersion: v1
kind: Pod
metadata:
name: use-pvc-sc
spec:
volumes:
- name: vol
persistentVolumeClaim:
claimName: my-pvc-sc
containers:
- name: nginx
image: nginx
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: vol
# pod 생성
kubectl apply -f use-pvc-sc.yaml
# pod/use-pvc-sc created
# STATUS가 Bound로 변경
kubectl get pvc my-pvc-sc
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
# my-pvc-sc Bound pvc-479cff32-xx 1Gi RWO local-path 92s
# 기존에 생성하지 않은 신규 volume이 생성된 것을 확인
kubectl get pv
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
# pvc-479cff32-xx 1Gi RWO Delete Bound default/my-pvc-sc local-path 3m
# pv 상세 정보 확인 (hostPath 등)
kubectl get pv pvc-479cff32-xx -oyaml
# apiVersion: v1
# kind: PersistentVolume
# metadata:
# ...
# name: pvc-b1727544-f4be-4cd6-acb7-29eb8f68e84a
# ...
# spec:
# ...
# hostPath:
# path: /var/lib/rancher/k3s/storage/pvc-b1727544-f4be-4cd6-acb7-29eb8f68e84a
# type: DirectoryOrCreate
# nodeAffinity:
# required:
# nodeSelectorTerms:
# - matchExpressions:
# - key: kubernetes.io/hostname
# operator: In
# values:
# - worker
# ...

1. 일반 사용자가 StorageClass를 이용해 PVC 생성
2. StorageClass provisioner가 사용자의 요청을 인지
3. 사용자의 요청에 따라 PV를 생성
4. 볼륨 생성됨
5. PV와 연결되어 볼륨을 사용
NFS StorageClass 설정
NFS 볼륨을 동적으로 생성하는 StorageClass를 만들어봅시다.
먼저 NFS 서버가 필요합니다.
helm chart 중 NFS 서버를 생성하고 NFS StorageClass를 재공하는 nfs-server-provisioner라는 chart가 있습니다.
이로 서버를 생성하여 사용합니다.
helm install nfs stable/nfs-server-provisioner \
--set persistence.enabled=true \
--set persistence.size=10Gi \
--version 1.1.1 \
--namespace ctrl
# NAME: nfs
# LAST DEPLOYED: Wed Jul 8 13:19:46 2020
# NAMESPACE: ctrl
# STATUS: deployed
# REVISION: 1
# TEST SUITE: None
# NOTES:
# ...
# nfs-server-provisioner라는 Pod가 생성되어 있습니다.
kubectl get pod -n ctrl
# NAME READY STATUS RESTARTS AGE
# ...
# nfs-nfs-server-provisioner-0 1/1 Running 0 4m
# 이것은 StatefulSet로 구성되어 있습니다.
kubectl get statefulset -n ctrl
# NAME READY AGE
# nfs-nfs-server-provisioner 1/1 57s
# nfs-server-provisioner Service도 있습니다.
kubectl get svc -n ctrl
# NAME TYPE CLUSTER-IP ..
# nginx-nginx-ingress-default-backend ClusterIP 10.43.79.133 ..
# nginx-nginx-ingress-controller LoadBalancer 10.43.182.174 ..
# nfs-nfs-server-provisioner ClusterIP 10.43.248.122 ..
# 새로운 nfs StorageClass 생성
kubectl get sc
# NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
# local-path (default) rancher.io/local-path Delete WaitForFirstConsumer false 20d
# nfs cluster.local/nfs-nfs-server-provisioner Delete Immediate true 10s
nfs-server-provisioner는 크게 StatefulSet과 Service로 이루어져있습니다.
그리고 nfs라는 StorageClass가 생성된 것을 확인할 수 있습니다. 이를 이용해 NFS볼륨을 생성해보겠습니다.
# nfs-sc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-sc
spec:
# 기존 local-path에서 nfs로 변경
storageClassName: nfs
# accessModes를 ReadWriteMany로 변경
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
NFS StorageClass를 사용하는 nfs-sc라는 PVC를 만듭니다.
kubectl apply -f nfs-sc.yaml
# persistentvolumeclaim/nfs-sc created
# pvc 리소스 확인
kubectl get pvc
# NAME STATUS VOLUME CAPACITY ACCESS MODES ...
# my-pvc-sc Bound pvc-b1727544-xxx 1Gi RWO ...
# nfs-sc Bound pvc-49fea9cf-xxx 1Gi RWO ...
# pv 리소스 확인
kubectl get pv pvc-49fea9cf-xxx
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
# pvc-49fea9cf-xxx 1Gi RWX Delete Bound default/nfs-sc nfs 5m
# pv 상세 정보 확인 (nfs 마운트 정보)
kubectl get pv pvc-49fea9cf-xxx -oyaml
# apiVersion: v1
# kind: PersistentVolume
# metadata:
# ...
# spec:
# accessModes:
# - ReadWriteMany
# capacity:
# storage: 1Gi
# claimRef:
# apiVersion: v1
# kind: PersistentVolumeClaim
# name: nfs-sc
# namespace: default
# resourceVersion: "10084380"
# uid: 2e95f6c4-2b43-4375-808f-0c93e44a1003
# mountOptions:
# - vers=3
# nfs:
# path: /export/pvc-2e95f6c4-2b43-4375-808f-0c93e44a1003
# server: 10.43.248.122
# persistentVolumeReclaimPolicy: Delete
# storageClassName: nfs
# volumeMode: Filesystem
# status:
# phase: Bound
그럼 다음과 같이 자동으로 PV가 생성되고 사용자가 직접 NFS서버 정보를 몰라도 자동으로 연결이 됩니다.
이제 PVC를 사용하는 Pod를 생성해보겠습니다.
동일 NFS 저장소를 바라보는 nginx Pod를 2개 만듭니다. 이때 nodeSelector를 이용해 서로 다른 노드에 Pod를 배치합니다.
# use-nfs-sc.yaml
apiVersion: v1
kind: Pod
metadata:
name: use-nfs-sc-master
spec:
volumes:
- name: vol
persistentVolumeClaim:
claimName: nfs-sc
containers:
- name: nginx
image: nginx
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: vol
nodeSelector:
kubernetes.io/hostname: master
---
apiVersion: v1
kind: Pod
metadata:
name: use-nfs-sc-worker
spec:
volumes:
- name: vol
persistentVolumeClaim:
claimName: nfs-sc
containers:
- name: nginx
image: nginx
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: vol
nodeSelector:
kubernetes.io/hostname: worker
kubernetes.io/hostname으로 노드를 지정합니다.
kubectl apply -f use-nfs-sc.yaml
# pod/use-nfs-sc-master created
# pod/use-nfs-sc-worker created
kubectl get pod -o wide
# NAME READY STATUS RESTARTS AGE IP NODE
# ...
# use-nfs-sc-master 1/1 Running 0 19s 10.42.0.8 master
# use-nfs-sc-worker 1/1 Running 0 19s 10.42.0.52 worker
마스터 노드의 Pod에서 index.html을 생성하고 worker. 노드에서 html 파일을 요청해봅니다.
# master Pod에 index.html 파일을 생성합니다.
kubectl exec use-nfs-sc-master -- sh -c \
"echo 'hello world' >> /usr/share/nginx/html/index.html"
# worker Pod에서 호출을 합니다.
kubectl exec use-nfs-sc-worker -- curl -s localhost
# hello world
정상 호출이 됩니다.
이처럼 NFS StorageClass를 사용하면 여러 노드에서도 동일한 데이터 저장소를 바라볼 수 있게 구상 가능합니다.

'개발 > DevOps' 카테고리의 다른 글
| [Kubenetes] 접근제어 - Network Policy, Ingress & Egress, AND & OR (1) | 2025.12.24 |
|---|---|
| [Kubenetes] 접근제어 - HTTP Basic Authentication, X.509 인증서, RBAC (1) | 2025.12.23 |
| [kubernetes] Ingress 리소스 - Basic Auth, cert-manager, Issuer (0) | 2025.11.04 |
| [kubernetes] helm 패키지 매니저 (0) | 2025.10.06 |