
이번에 올릴 게시물은 이전 게시물(pv, pvc 생성)
에서 정적 pv/pvc를 생성했던 것과 달리, StorageClass를 이용해 PVC가 자동으로 PV를 생성하는 구조를 만들어본다.
nfs-subdir-external-provisioner/
├── rbac.yaml
├── deployment.yaml
├── storageclass.yaml
├── persistentvolumeclaim-dynamic.yaml
└── deploy-pvc.yaml
파일명: rbac.yaml
NFS 프로비저너가 쿠버네티스 리소스(PV, PVC)를 자동으로 생성하려면 ServiceAccount와 권한이 필요하다. 따라서 다음 rdbc를 설정하여 권한을 부여해준다.
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes", "persistentvolumeclaims", "events"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
다음 명령어를
kubectl apply -f rbac.yaml
하여 배포해준다.

파일명 : deployment.yaml
이 컨테이너가 실제로 PVC 요청이 들어올 때 NFS 서버에 디렉토리를 생성하고 PV를 자동으로 바인딩한다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
spec:
replicas: 1
selector:
matchLabels:
app: nfs-client-provisioner
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: k8s.gcr.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner
- name: NFS_SERVER
value: 192.168.219.103
- name: NFS_PATH
value: /Users/goorm/nfs_shared/dynamic-vol
volumes:
- name: nfs-client-root
nfs:
server: 192.168.219.103
path: /Users/goorm/nfs_shared/dynamic-vol
kubectl apply -f deployment.yaml
하여 적용시켜준다.

이 과정에서 deployment가 만든 pod가 running이 안되고 ContainerCreating상태인 것을 확인했다.
왜 아직도 ContainerCreating상태인지 확인하기 위해
차근차근 해결해보기로 했다.
kubectl logs deploy/nfs-client-provisioner
했는데,
goorm@guleum-ui-MacBookPro ~ % kubectl get pods
NAME READY STATUS RESTARTS AGE
deploy-pvc-7b97f867d4-nbzpd 0/1 Pending 0 7m51s
hello-docer 1/1 Running 1 (32h ago) 5d23h
hostpath-demo-pod 1/1 Running 16 (15m ago) 29h
nfs-client-provisioner-66cc74b564-mq2qn 0/1 ContainerCreating 0 33m
nginx-deploy-7ccccd94f7-7bs2n 1/1 Running 1 (32h ago) 5d4h
nginx-deploy-7ccccd94f7-7ppn8 1/1 Running 1 (32h ago) 5d4h
nginx-deploy-7ccccd94f7-bs88g 1/1 Running 1 (32h ago) 5d4h
nginx-pod 1/1 Running 1 (32h ago) 5d5h
pod-nfs 1/1 Running 0 5h50m
pod-tempstorage 2/2 Running 19 (9m36s ago) 32h
test-pod 0/1 Error 0 4d23h
goorm@guleum-ui-MacBookPro ~ % kubectl logs deploy/nfs-client-provisioner
Error from server (BadRequest): container "nfs-client-provisioner" in pod "nfs-client-provisioner-66cc74b564-mq2qn" is waiting to start: ContainerCreating
goorm@guleum-ui-MacBookPro ~ %
다음과 같이 나온것을 확인할 수 있었다.
이렇게 나오는 건, 아직 컨테이너 내부가 생성조차 안 된 상태란 뜻이다.
describe pod명령어를 통해 왜 ontainerCreating인지”를 이벤트 로그에서 확인해본다.
kubectl describe pod nfs-client-provisioner-66cc74b564-mq2qn | tail -n 30
goorm@guleum-ui-MacBookPro ~ % kubectl describe pod nfs-client-provisioner-66cc74b564-mq2qn | tail -n 30
Type Status
PodReadyToStartContainers False
Initialized True
Ready False
ContainersReady False
PodScheduled True
Volumes:
nfs-client-root:
Type: NFS (an NFS mount that lasts the lifetime of a pod)
Server: 192.168.219.103
Path: /Users/goorm/nfs_shared/dynamic-vol
ReadOnly: false
kube-api-access-44stv:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
Optional: false
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 37m default-scheduler Successfully assigned default/nfs-client-provisioner-66cc74b564-mq2qn to docker-desktop
Warning FailedMount 40s (x26 over 37m) kubelet MountVolume.SetUp failed for volume "nfs-client-root" : mount failed: exit status 32
Mounting command: mount
Mounting arguments: -t nfs 192.168.219.103:/Users/goorm/nfs_shared/dynamic-vol /var/lib/kubelet/pods/1888c494-d628-4cf3-9d53-4b63b9e4fc64/volumes/kubernetes.io~nfs/nfs-client-root
Output: mount.nfs: access denied by server while mounting 192.168.219.103:/Users/goorm/nfs_shared/dynamic-vol
goorm@guleum-ui-MacBookPro ~ %
다음과 같이 나오는 것을 확인했으며,
챗지피티와 함께 해당 로그를 분석해서 다음과 같은 결론을 도출할 수 있었다.
쿠버네티스(Docker Desktop 내부 VM)가
→ 맥의 NFS 서버(192.168.219.103) 로 접근하려 했는데
→ 맥이 접근을 거부했다 (Access Denied) 는 뜻이다.
즉,
macOS 쪽 /etc/exports 설정에서 Docker Desktop VM의 네트워크 대역(보통 192.168.65.x) 이 허용되지 않아서 생긴 문제다.
sudo vi /etc/exports
터미널에서 다음과 같이 입력해서 다음과 같이 대역대를 수정해준다.
/Users/goorm/nfs_shared/dynamic-vol -alldirs -mapall=0 -network 192.168.0.0 -mask 255.255.0.0
sudo nfsd restart
sudo exportfs -rv
그런 다음 nfs 데몬 재시작 및 expor를 갱신해준다.
goorm@guleum-ui-MacBookPro ~ % ls -ld /Users/goorm/nfs_shared/dynamic-vol
ls: /Users/goorm/nfs_shared/dynamic-vol: No such file or directory
goorm@guleum-ui-MacBookPro ~ % mkdir -p /Users/goorm/nfs_shared/dynamic-vol
goorm@guleum-ui-MacBookPro ~ % sudo chmod -R 777 /Users/goorm/nfs_shared
goorm@guleum-ui-MacBookPro ~ %
폴더가 실제 있는지 확인했는데, 없다고 나와서 mkdir 명령어를 통해 해당 파일을 만들어주었다.
또한 권한을 부여해주었으며 다음과 같은 명령어를 순차적으로 실행해주었다.
sudo nfsd disable
sudo nfsd enable
sudo nfsd restart
sudo nfsd checkexports
showmount -e
그러니 드디어 export가 정상적으로 등록되어
goorm@guleum-ui-MacBookPro ~ % showmount -e
Exports list on localhost:
/Users/goorm/nfs_shared/dynamic-vol 192.168.0.0
이렇게 떴다.
그런 후 , 다시 파드의 상태를 확인해보았다.
deployment로 만든 파드가 running 상태인 것을 확인할 수 있었다.
결론적으로, NFS export 설정이 이상하고 등록이 제대로 안되어 해당 문제가 생긴 것으로 확인이되었다.
파일명: storageclass.yaml
PVC가 이 StorageClass를 사용하면 자동으로 PV가 생성된다.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
parameters:
pathPattern: "${.PVC.namespace}/${.PVC.name}"
reclaimPolicy: Delete
mountOptions:
- nfsvers=4.1
kubectl apply -f storageclass.yaml
kubectl get storageclass
위 명령어를 통해 스토리지 클래스를 배포해준다.

파일명: persistentvolumeclaim-dynamic.yaml
이 pvc는 개발자가 pv를 할당받기 위해 pvc를 생성하는것이다.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-dynamic
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
storageClassName: managed-nfs-storage
kubectl apply -f persistentvolumeclaim-dynamic.yaml
이 명령어를 통해 배포한다.
kubectl get pvc
STATUS가 Bounding이면 NFS를 동적 프로비저닝을 성공한 것이다.
실제 NFS 서버의 /Users/goorm/nfs_shared/dynamic-vol 내부에
PVC 이름으로 된 폴더가 자동으로 생성된다.
위에서 에러를 겪고와서 그런가 또 에러가 생겼다.
pvc가 계속 pending상태인 것이다.
이 순서로 트러블슈팅을 진행했는데, 프로비저너 등록이 안된 것을 확인했다.
nfs-client-provisioner가 쿠버네티스에 ‘스토리지 프로비저너’로 등록되지 않은 상태야.
그래서 PVC가 “아무도 PV를 만들어주지 않는다”며 Pending인 것이다.
kubectl delete deploy nfs-client-provisioner
해당 명령어를 통해 기존 deployment를 삭제하고 새 deployment yaml 파일로 변경해줬다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
spec:
replicas: 1
selector:
matchLabels:
app: nfs-client-provisioner
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner
- name: NFS_SERVER
value: 192.168.219.103
- name: NFS_PATH
value: /Users/goorm/nfs_shared/dynamic-vol
volumes:
- name: nfs-client-root
nfs:
server: 192.168.219.103
path: /Users/goorm/nfs_shared/dynamic-vol
kubectl get csidrivers
kubectl get pods | grep nfs-client
등록 상태를 확인하기 위해 두 명령어를 통해 상태를 확인한다.
그런데 다음과 같이 떴다.
goorm@guleum-ui-MacBookPro ~ % vi deployment.yaml
goorm@guleum-ui-MacBookPro ~ % kubectl apply -f deployment.yaml
deployment.apps/nfs-client-provisioner created
goorm@guleum-ui-MacBookPro ~ % kubectl get csidrivers
No resources found
goorm@guleum-ui-MacBookPro ~ % kubectl get pods | grep nfs-client
nfs-client-provisioner-7b989f8499-hk8pk 1/1 Running 0 91s
goorm@guleum-ui-MacBookPro ~ %
이 순간 많이 긴장했다. 또 다른 오류가 있을까봐 무서웠다.
또한 kubectl get pvc 했는데, 여전히 pending상태여서
절망적이었다.
그렇지만 포기하지 않아야하니.. 계속 시도해본다.
지금 에러 없이 프로비저너가 돌아가지만,
macOS가 해당 NFS export를 쿠버네티스 내부 컨테이너에 실제로 열어주지 않고 있어서
“PV 생성 시도 → mount permission denied → 실패 → PVC Pending” 이렇게 반복중인 상태인 것을 확인할 수 있다.
showmount -e
했을 때 보이는 대역이, NFS가 다른 네트워크 대역에 열려 있어서
컨테이너 내부에서 마운트 접근이 막혀 있는 상태를 확인하여 다시 export 대역을 수정해주기로 했다.
/Users/goorm/nfs_shared/dynamic-vol -alldirs -mapall=goorm -network 192.168.219.0 -mask 255.255.255.0
다음과 같이 수정해주고 다시 재시작하기로 했다.
sudo nfsd disable
sudo nfsd enable
sudo nfsd restart
sudo nfsd checkexports
showmount -e
showmount 했을 때 바뀐 것을 확인한 후, 다시 pvc삭제 후 재생성해준다.
kubectl delete pvc pvc-dynamic
kubectl apply -f persistentvolumeclaim-dynamic.yaml
kubectl get pvc
그런데 여전히 pending 상태였고, 로그를 확인해보니 다음과 같이 떴다.
E1118 13:59:48.144785 1 leaderelection.go:320] error retrieving resource lock default/k8s-sigs.io-nfs-subdir-external-provisioner: endpoints "k8s-sigs.io-nfs-subdir-external-provisioner" is forbidden: User "system:serviceaccount:default:nfs-client-provisioner" cannot get resource "endpoints" in API group "" in the namespace "default"
로그를 분석해보니 NFS 접근이 아니라 RBAC 권한 부족 때문이며
즉, nfs-client-provisioner가 PV/PVC를 생성하려고 할 때
Kubernetes API 서버의 리더락(endpoints) 리소스에 접근 권한이 없어서 막힌 상태인 것을 확인할 수 있었다.
rbac.yaml파일을 다음과 같이 수정해준다. ("endpoints" 와 "update" 권한을 추가한 버전이다.)
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes", "persistentvolumeclaims", "endpoints", "events"]
verbs: ["get", "list", "watch", "create", "delete", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
kubectl delete -f rbac.yaml
kubectl apply -f rbac.yaml
kubectl delete pod -l app=nfs-client-provisioner
kubectl get pods
그런 다음, 이 순서로 실행하여 새로운 nfs-client-provisioner 파드가 재시작되고 Running 상태가 되는 것을 확인한다.
그 다음, 문제가 있었던 pvc 상태를 다시 확인한다.
goorm@guleum-ui-MacBookPro ~ % kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
pvc-dynamic Bound pvc-467bb223-06a7-4012-a519-acdef94ed8ef 5Gi RWX managed-nfs-storage <unset> 7m20s
pvc-nfs Bound pv-nfs 3Gi RWX manual <unset> 7h
goorm@guleum-ui-MacBookPro ~ %
이제는 감격스럽게도 해결된 것을 확인할 수 있다.
이제 pvc-dynamic이 Bound로 바뀌면서
자동으로 /Users/goorm/nfs_shared/dynamic-vol/default-pvc-dynamic/ 폴더가 생성된다.
이제 PVC가 정상적으로 바인딩되었는지, 실제 NFS 서버에서 폴더가 자동 생성되었는지 확인해보자.
PVC가 생성되면 NFS 서버의 /Users/goorm/nfs_shared/dynamic-vol 경로 아래에
PVC 이름(pvc-dynamic)으로 된 하위 폴더가 자동으로 만들어진다.
# NFS 서버(즉, NFS를 export한 노드)에서 확인
ls /Users/goorm/nfs_shared/dynamic-vol
정상적으로 동적 프로비저닝이 이루어졌다면 아래와 같은 결과를 볼 수 있다
default
이 폴더는 쿠버네티스에서 PVC 요청이 들어올 때 NFS 프로비저너가 자동으로 생성한 디렉토리이며,
해당 PVC를 사용하는 파드가 실제 데이터를 저장하는 경로이다.
구조 :
/Users/goorm/nfs_shared/dynamic-vol/
└── default/
└── pvc-dynamic/
└── (여기에 파드가 쓸 데이터가 들어감)
참고문서:
[쿠버네티스 공식 홈페이지 - Storage Classes]
https://kubernetes.io/docs/concepts/storage/storage-classes/
본 게시물의 트러블슈팅 및 최종 정리 과정은 ChatGPT의 도움을 받아 진행하였으며,
모든 YAML 구성은 쿠버네티스 공식 문서를 기반으로 작성하였습니다.