[MacOS 환경 #15] NFS Dynamic Provisioning – StorageClass를 이용한 자동 PV 생성 실습(트러블 슈팅 두 번을 곁들인)

도람·2025년 11월 18일
post-thumbnail

이번에 올릴 게시물은 이전 게시물(pv, pvc 생성)
에서 정적 pv/pvc를 생성했던 것과 달리, StorageClass를 이용해 PVC가 자동으로 PV를 생성하는 구조를 만들어본다.


실습 파일 구조

nfs-subdir-external-provisioner/
├── rbac.yaml
├── deployment.yaml
├── storageclass.yaml
├── persistentvolumeclaim-dynamic.yaml
└── deploy-pvc.yaml

1. RBAC 설정 (관리자 권한 부여)

파일명: 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

하여 배포해준다.


2.NFS 프로비저너 배포

파일명 : 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상태인 것을 확인했다.

트러블 슈팅1. Pod : 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) 이 허용되지 않아서 생긴 문제다.


해결방법 1.NFS export 설정 수정

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를 갱신해준다.

해결방법 2.폴더가 실제 존재하는지 확인하고 없으면 생성

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 설정이 이상하고 등록이 제대로 안되어 해당 문제가 생긴 것으로 확인이되었다.

3.StorageClass 생성

파일명: 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

위 명령어를 통해 스토리지 클래스를 배포해준다.


4.PVC 생성 (개발자 요청)

파일명: 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 이름으로 된 폴더가 자동으로 생성된다.

트러블 슈팅2. Bounding 말고 Pending상태 유지

위에서 에러를 겪고와서 그런가 또 에러가 생겼다.
pvc가 계속 pending상태인 것이다.

  • 프로비저너 이름 확인 (kubectl get sc managed-nfs-storage -o yaml | grep provisioner)
  • Deployment 환경변수 확인 (kubectl get deploy nfs-client-provisioner -o yaml | grep -A 2 PROVISIONER_NAME)
  • 이벤트 로그 확인 (kubectl describe pvc pvc-dynamic | tail -n 10)
  • RBAC(권한) 누락 또는 namespace mismatch 확인 (kubectl get sa | grep nfs-client-provisioner)
  • 프로비저너 등록 상태 확인 (kubectl get csidrivers)

이 순서로 트러블슈팅을 진행했는데, 프로비저너 등록이 안된 것을 확인했다.

nfs-client-provisioner가 쿠버네티스에 ‘스토리지 프로비저너’로 등록되지 않은 상태야.
그래서 PVC가 “아무도 PV를 만들어주지 않는다”며 Pending인 것이다.

해결방법 1.기존 Deployment 삭제 및 교체

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

그런 다음, apply해준다.
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상태여서
절망적이었다.

그렇지만 포기하지 않아야하니.. 계속 시도해본다.


해결방법 2.대역값 다시 변경

지금 에러 없이 프로비저너가 돌아가지만,
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) 리소스에 접근 권한이 없어서 막힌 상태인 것을 확인할 수 있었다.


해결방법3. RBAC 설정 수정

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/ 폴더가 생성된다.


5.NFS 서버에서 PVC 폴더 자동 생성 확인

이제 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 구성은 쿠버네티스 공식 문서를 기반으로 작성하였습니다.

profile
정도를 걷는 엔지니어

0개의 댓글