Kubernetes StorageClass와 NFS 통합 가이드

StorageClass란?

Kubernetes에서는 Dynamic Provisioning이라는 기능을 통해 PV를 자동으로 생성할 수 있습니다. 이 메커니즘의 핵심은 StorageClass API 객체입니다.

StorageClass는 다음 두 가지 내용을 정의합니다:
1. PV 속성: 스토리지 유형, 볼륨 크기 등.
2. PV 생성에 필요한 스토리지 플러그인.
이 정보를 바탕으로 Kubernetes는 사용자가 제출한 PVC에 맞는 StorageClass를 찾아 해당 플러그인을 호출하여 필요한 PV를 생성합니다.
사용 방법은 간단하며, 요구사항에 따라 YAML 파일을 작성하고 kubectl create 명령어로 실행하면 됩니다.

왜 StorageClass가 필요한가?

대규모 Kubernetes 클러스터에서는 수천 개의 PVC가 존재할 수 있으며, 이를 위해 관리자는 많은 PV를 미리 준비해야 합니다. 새로운 PVC가 추가될 때마다 새로운 PV도 계속 추가해야 하며, 그렇지 않으면 새로운 Pod가 PVC를 바인딩하지 못해 생성에 실패할 수 있습니다. 또한, 애플리케이션의 다양한 저장소 요구사항(읽기/쓰기 속도, 동시성 성능 등)을 충족시키기 위해 StorageClass를 사용하여 다양한 종류의 스토리지 리소스를 정의하고 사용자에게 제공할 수 있습니다.

StorageClass 동작 원리 및 배포 절차

NFS를 사용하는 경우 nfs-client라는 프로비저너를 설치해야 합니다. 이 프로그램은 이미 설정된 NFS 서버를 사용하여 PV를 자동으로 생성합니다.

1. 자동 생성된 PV는 ${namespace}-${pvcName}-${pvName} 형식으로 NFS 서버의 공유 데이터 디렉토리에 생성됩니다.
2. PV가 해제되면 archived-${namespace}-${pvcName}-${pvName} 형식으로 NFS 서버에 유지됩니다.
1. 동작 원리 및 배포 절차 설명

다음과 같이 StorageClass+NFS를 설정하는 주요 단계는 다음과 같습니다:

1. NFS 서버 설정
2. Service Account 생성: NFS 프로비저너의 권한 관리를 위한 계정
3. StorageClass 생성: PVC 생성 및 NFS 프로비저너 호출을 담당하며 PV와 PVC 연결 관리
4. NFS 프로비저너 생성: NFS 공유 디렉토리에 마운트 포인트 생성 및 PV 생성 후 NFS 마운트 포인트와 연결

StorageClass 생성

1. NFS 공유 서비스 설정

설정 과정은 간단하므로 생략하며, 필요 시 검색을 통해 확인 가능합니다.

현재 환경에서의 NFS 서버 및 공유 디렉토리 정보

IP: 192.168.100.10
Export PATH: /data/volumes/
2. RBAC 관련 설정 문서

rbac.yaml: namespace만 수정하면 됩니다.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-provisioner-sa
  namespace: default # 실제 환경에 맞게 수정
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-provisioner-role
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-provisioner-binding
subjects:
  - kind: ServiceAccount
    name: nfs-provisioner-sa
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-provisioner-role
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-provisioner
  namespace: default
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-provisioner-binding
subjects:
  - kind: ServiceAccount
    name: nfs-provisioner-sa
    namespace: default
roleRef:
  kind: Role
  name: leader-locking-nfs-provisioner
  apiGroup: rbac.authorization.k8s.io
3. StorageClass 생성

nfs-storage-class.yaml

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-sc
provisioner: my-nfs-provisioner # provisioner 설정 파일의 PROVISIONER_NAME 환경 변수와 일치해야 함
parameters:
  archiveOnDelete: "false"
4. NFS 프로비저너 생성

nfs-provisioner-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-provisioner-deploy
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nfs-provisioner-app
  template:
    metadata:
      labels:
        app: nfs-provisioner-app
    spec:
      serviceAccountName: nfs-provisioner-sa
      containers:
        - name: nfs-provisioner-container
          image: quay.io/external_storage/nfs-client-provisioner:latest
          env:
            - name: PROVISIONER_NAME
              value: my-nfs-provisioner # provisioner 이름, nfs-storage-class.yaml 파일의 provisioner 이름과 일치해야 함
            - name: NFS_SERVER
              value: 192.168.100.10 # NFS 서버 IP 주소
            - name: NFS_PATH
              value: /data/volumes # NFS 마운트 볼륨
          volumeMounts:
            - name: nfs-root
              mountPath: /persistentvolumes
      volumes:
        - name: nfs-root
          nfs:
            server: 192.168.100.10 # NFS 서버 IP 주소
            path: /data/volumes # NFS 마운트 볼륨

테스트 Pod 생성 및 검증

1. Pod + PVC

test-claim.yaml

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-pvc
  annotations:
    volume.beta.kubernetes.io/storage-class: "managed-nfs-sc" # nfs-storage-class.yaml의 metadata.name과 일치해야 함
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Mi

PVC 상태가 Bound인지 확인

[root@k8s-master ~]# kubectl get pvc
NAME         STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
test-pvc     Bound    pvc-abc12345-1234-5678-abcd-1234567890ab   1Mi        RWX            managed-nfs-sc       2m48s
[root@k8s-master ~]# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS          AGE
pvc-abc12345-1234-5678-abcd-1234567890ab   1Mi        RWX            Delete           Bound    default/test-pvc     managed-nfs-sc        4m13s

테스트 Pod 생성 및 정상적으로 마운트되는지 확인

test-pod.yaml

kind: Pod
apiVersion: v1
metadata:
  name: test-pod
spec:
  containers:
  - name: test-container
    image: busybox:latest
    command:
      - "/bin/sh"
    args:
      - "-c"
      - "touch /mnt/SUCCESS && exit 0 || exit 1" # SUCCESS 파일 생성 후 종료
    volumeMounts:
      - name: nfs-volume
        mountPath: "/mnt"
  restartPolicy: Never
  volumes:
    - name: nfs-volume
      persistentVolumeClaim:
        claimName: test-pvc # PVC 이름과 일치해야 함

검증 결과:

[root@nfs-server ~]# ls /data/volumes/default-test-pvc-pvc-abc12345-1234-5678-abcd-1234567890ab/
total 0
-rw-r--r-- 1 root root 0 Jan 15 17:51 SUCCESS # SUCCESS 파일이 존재함으로써 검증 성공
2. StatefulSet + volumeClaimTemplates로 PV 자동 생성

nginx-statefulset.yaml

---
apiVersion: v1
kind: Service
metadata:
  name: nginx-headless-service
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None # None 값은 Headless 서비스를 의미
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web-statefulset
spec:
  serviceName: "nginx"
  replicas: 2 # 2개의 복제본
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx-container
        image: nginx:1.14.2
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www-data
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www-data
      annotations:
        volume.beta.kubernetes.io/storage-class: "managed-nfs-sc" # 생성한 storage-class 이름
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

검증 결과:

클러스터 노드에서:

[root@k8s-master ~]# kubectl delete -f nginx-statefulset.yaml
[root@k8s-master ~]# kubectl get pods -l app=nginx # Pod 상태 확인
NAME        READY   STATUS    RESTARTS   AGE
web-0       1/1     Running   0          115m
web-1       1/1     Running   0          114m
[root@k8s-master ~]# kubectl get pvc # PVC 확인
NAME             STATUS   VOLUME                                      CAPACITY   ACCESS MODES   STORAGECLASS          AGE
test-pvc         Bound    pvc-abc12345-1234-5678-abcd-1234567890ab    1Mi        RWX            managed-nfs-sc       19h
www-data-web-0   Bound    pvc-1234abcd-5678-1234-abcd-1234567890ab    1Gi        RWO            managed-nfs-sc       115m
www-data-web-1   Bound    pvc-5678efgh-1234-5678-abcd-1234567890ab    1Gi        RWO            managed-nfs-sc       115m
[root@k8s-master ~]# kubectl get pv # PV 확인
NAME                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                       STORAGECLASS          AGE
pvc-1234abcd-5678-1234-abcd-1234567890ab    1Gi        RWO            Delete           Bound    default/www-data-web-0      managed-nfs-sc        115m
pvc-5678efgh-1234-5678-abcd-1234567890ab    1Gi        RWO            Delete           Bound    default/www-data-web-1      managed-nfs-sc        115m
pvc-abc12345-1234-5678-abcd-1234567890ab    1Mi        RWX            Delete           Bound    default/test-pvc            managed-nfs-sc        19h

NFS 서버에서:

[root@nfs-server ~]# cd /data/volumes/
[root@nfs-server volumes]# ll # 디렉토리 명명 규칙 확인
total 0
drwxrwxrwx 2 root root 21 Jan 15 17:51 default-test-pvc-pvc-abc12345-1234-5678-abcd-1234567890ab
drwxrwxrwx 2 root root  6 Jan 16 11:28 default-www-data-web-0-pvc-1234abcd-5678-1234-abcd-1234567890ab
drwxrwxrwx 2 root root  6 Jan 16 11:28 default-www-data-web-1-pvc-5678efgh-1234-5678-abcd-1234567890ab
[root@nfs-server volumes]# echo "web-00" > default-www-data-web-0-pvc-1234abcd-5678-1234-abcd-1234567890ab/index.html # 각각 index 파일 생성
[root@nfs-server volumes]# echo "web-01" > default-www-data-web-1-pvc-5678efgh-1234-5678-abcd-1234567890ab/index.html

클러스터 임의 노드에서:

[root@k8s-master ~]# kubectl exec -it pod-cm-1 -- /bin/sh # 임의의 Pod에서 nginx-headless 서비스 확인
/ # nslookup nginx-headless-service
nslookup: can't resolve '(null)': Name does not resolve

Name:      nginx-headless-service
Address 1: 172.17.136.7 172-17-136-7.nginx-headless-service.default.svc.cluster.local # 두 개의 주소가 있음을 확인
Address 2: 172.17.248.5 172-17-248-5.nginx-headless-service.default.svc.cluster.local
[root@k8s-master ~]# curl 172.17.248.5 # 각각 접속하여 결과 확인
web-00
[root@k8s-master ~]# curl 172.17.136.7
web-01

StatefulSet의 Pod 복제본 수를 변경하면서 PV/PVC 상태 변화를 관찰할 수 있습니다.

StorageClass 재활용 정책이 데이터에 미치는 영향

1. 첫 번째 구성
archiveOnDelete: "false"
reclaimPolicy: Delete # 기본값은 Delete

테스트 결과:

1. Pod 삭제 후 재생성 시 데이터 유지, 이전 Pod 이름 및 데이터 유지됨
2. StorageClass 삭제 후 재생성 시 데이터 유지, 이전 Pod 이름 및 데이터 유지됨
3. PVC 삭제 시 PV 및 NFS 서버 데이터 삭제됨
2. 두 번째 구성
archiveOnDelete: "false"
reclaimPolicy: Retain

테스트 결과:

1. Pod 삭제 후 재생성 시 데이터 유지, 이전 Pod 이름 및 데이터 유지됨
2. StorageClass 삭제 후 재생성 시 데이터 유지, 이전 Pod 이름 및 데이터 유지됨
3. PVC 삭제 시 PV는 삭제되지 않고 Released 상태로 변환되며, NFS 서버 데이터 유지됨
4. 새로운 StorageClass 생성 후 새로운 PVC 생성 시 이전 데이터는 새로운 PV로 복사 가능
3. 세 번째 구성
archiveOnDelete: "true"
reclaimPolicy: Retain

결과:

1. Pod 삭제 후 재생성 시 데이터 유지, 이전 Pod 이름 및 데이터 유지됨
2. StorageClass 삭제 후 재생성 시 데이터 유지, 이전 Pod 이름 및 데이터 유지됨
3. PVC 삭제 시 PV는 삭제되지 않고 Released 상태로 변환되며, NFS 서버 데이터 유지됨
4. 새로운 StorageClass 생성 후 새로운 PVC 생성 시 이전 데이터는 새로운 PV로 복사 가능
4. 네 번째 구성
archiveOnDelete: "true"
reclaimPolicy: Delete

결과:

1. Pod 삭제 후 재생성 시 데이터 유지, 이전 Pod 이름 및 데이터 유지됨
2. StorageClass 삭제 후 재생성 시 데이터 유지, 이전 Pod 이름 및 데이터 유지됨
3. PVC 삭제 시 PV는 삭제되지 않고 Released 상태로 변환되며, NFS 서버 데이터 유지됨
4. 새로운 StorageClass 생성 후 새로운 PVC 생성 시 이전 데이터는 새로운 PV로 복사 가능

요약: 첫 번째 구성 외에는 나머지 구성 모두에서 PV/PVC 삭제 후 데이터 유지됨

태그: kubernetes nfs StorageClass RBAC deployment

7월 16일 17:42에 게시됨