Minikube의 Dynamic Provisioning 이해하기

지금까지 Minikube를 사용하면서 PV를 미리 생성하지 않았는데, PVC 요청에 따라 알아서 생성되고 있다는 것에 의문을 가지신 적이 있나요?

저는 NFS로 볼륨을 생성하고 싶은데, PVC를 요청하면 PV가 자동으로 Host-Path로 생성되는 것을 보고 의문이 생겼습니다.

Minikube는 동적 프로비저닝을 지원하고 있는데, 나는 설정한 적이 없어요! 대체 어떤 기능이 동적 프로비저닝을 돕고 있는거죠? NFS로 프로비저닝 설정을 바꿀 순 없는걸까요?


기존의 PV 살펴보기

일단 기존에 (동적 프로비저닝을 통해)생성되었던 PV를 살펴보겠습니다.

  • annotations를 살펴보면 pv.kubernetest.io/provisioned-by: k8s.io/minikube-hostpath 를 통해 k8s.io/minikube-hostpath라는 기능으로 프로비저닝 되었음을 알 수 있네요.
  • 또 중요한 것은, 동적 프로비저닝 되었으니 자연스럽게 Claim이 bound되어 있다는 것입니다.
  • 그리고 Reclaim Policy가 Delete이군요. 조금 더 알아봅시다.
    - 저는 하나의 PV에 매일 한번씩 PVC를 마운트하며(PV와 PVC는 1:1마운트만 지원합니다.) 새로운 ML model을 저장하고 싶습니다. 따라서 PVC가 삭제되더라도 PV 볼륨 안에 저장되어 있던 기존의 데이터는 남아있어야 합니다. 그래서 Delete -> Retain(유지)으로 사용할 수 있는 방법도 알아보도록 하겠습니다.
    - Access Modes: RWX, Capacity: 20Gi는 제가 설정한 것입니다!
    - 마지막으로 중요한 것은 Source를 살펴보면 Hostpath 타입으로 자동 프로비저닝 되어 Path도 자동으로 /tmp/hostpath-provisioner/~으로 생성되었습니다. 이 부분은 제가 관여한 적이 없습니다. 그래서 NFS로 프로비저닝을 바꾸는 방법을 알아보고자 합니다.

기존에 PV를 생성했던 구문은 다음과 같습니다.

# PVC를 생성할 리소스 템플릿으로 변환될 작업을 나타냅니다. 
model_volume_op = dsl.VolumeOp(
    name="model-volume",
    resource_name="model-volume",
    size="20Gi",
    modes=dsl.VOLUME_MODE_RWM,
)

해당 문서를 읽어봤으나 Reclaim Policy에 대한 argument는 없네요.
다른 방법으로 pv의 내용을 직접 패치하는 방법이 있습니다.
kubectl patch pv <your-pv-name> -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'

StorageClasses 란?

StorageClass는 동적 프로비저닝의 핵심입니다.
스토리지 유형을 선언적으로 요청함으로써 Dynamic Provisioner는 런타임에 해당 스토리지를 이행하는 방법을 결정할 수 있습니다.
Minikube는 storageclass를 addons으로서 제공합니다.

보통 minikube를 정상적으로 설치한다면, default-storageclassstorage-provisioner가 자동으로 활성화 되어있습니다.

Kubernetes 내부에서 작동하는 방식을 이해하려는 경우에만 아래 작업을 수행하면 됩니다
(즉, PVC가 바인딩되고 바인딩 해제되는 방식과 볼륨 컨트롤러 자체가 스토리지 프로비저닝 도구를 트리거하는 통신 이벤트를 관리하는 방식의 관점에서)
sudo vim /etc/kubernetes/manifests/kube-controller-manager.yaml

kube-controller-manager(KCM)에서 로깅 섹션 끝에서 verbose에 대한 내용을 추가합니다. - -v=5

AdmissionController 란?

생성된 PVC를 살펴보면 우리가 설정하지 않은 storage-class가 standard로 설정되어 있습니다.

설정하지 않았는데 이러한 동작은 어디서 처리된 걸까요?

이것은 우리 클러스터에서 실행되고 Kubernetes API 서버에 대한 요청을 가로채고 들어오는 개체를 수정 하는 AdmissionController 에 의해 수행됩니다.
스토리지 클래스 를 제공 했다면 승인 컨트롤러가 이를 주입하지 않았을 것입니다.
스토리지 클래스는 아래와 같이 확인합니다.

StorageClass 해킹

minikube 자체를 시작하면 StorageClass가 생성됩니다.
이 스토리지 클래스는 Kubernetes 객체로 쉽게 수정할 수 있지만 파일로는 수정할 수 없습니다.
그 이유는 minikube 설치에서 부트스트랩되어 있기 때문입니다.
따라서 내용을 읽을 수 있지만 kubectl edit sc standard를 사용하여 Kubernetes 자체 내부에서만 수정할 수 있습니다

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"storage.k8s.io/v1","kind":"StorageClass","metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"},"labels":{"addonmanager.kubernetes.io/mode":"EnsureExists"},"name":"standard"},"provisioner":"k8s.io/minikube-hostpath"}
    storageclass.kubernetes.io/is-default-class: "true"
  creationTimestamp: "2022-03-21T02:28:05Z"
  labels:
    addonmanager.kubernetes.io/mode: EnsureExists
  name: standard
  resourceVersion: "283"
  uid: ec8bffb7-c149-4954-8088-daccf01ce9cc
provisioner: k8s.io/minikube-hostpath
reclaimPolicy: Delete
volumeBindingMode: Immediate

주목해야 할 점은 주석에 provisioner:k8s.io/minikube-hostpath 가 있다는 것 입니다.
minikube 코드베이스의 기존 Provisioner 컨트롤러를 살펴보면 이 키가 상수인 provisionerName 키에 매핑되는 것을 볼 수 있습니다.
const provisionerName = "k8s.io/minikube-hostpath"

자체 동적 프로비저닝 도구를 구축하는 방법

이제 프로비저닝 도구가 작동하는 방식을 이해했으므로 자체 동적 프로비저닝 도구를 구축하는 방법을 살펴보겠습니다.
실제로 그렇게 어렵지 않으며 빌릴 수 있는 유틸리티가 있습니다.
여기서 주목해야 할 핵심은 사용자 지정 볼륨을 작성하거나 CSI 드라이버를 구축할 필요가 없다는 것입니다.

동적 프로비저닝과 그 아래에 동적 스토리지 플레인이 있다는 의미인 CSI 간의 차이점에 대해 알아보겠습니다.
이 두 엔터티는 Kubernetes 클러스터에 대해 서로 다른 4가지 유형 의 스토리지 모델로 구성된 매트릭스를 제공합니다.(일반적으로 CSI 프로비저닝+다이내믹이 두 가지 모두에서 최고임)

  1. CSI 프로비저닝, 비동적
  2. CSI 프로비저닝, 동적
  3. 비 CSI 프로비저닝(트리 내), 동적
  4. 비 CSI 프로비저닝(트리 내), 비동적

CSI 프로비저닝, 동적

예: Kubernetes의 기존 트리 내 파일 시스템 외부의 공급업체로부터 공급자를 위한 PVC를 생성할 때 MiniKube가 즉시 생성한 파일 시스템.
이 경우 어느 시점에서 다음과 같은 YAML 개체가 작동하게 됩니다.
스토리지 클래스로서의 CSI 드라이버 및 CSI 드라이버에 대한 지식 이 없는 PersistentVolumeClaim도 있습니다.
PVC를 생성하면 궁극적으로 해당 볼륨을 생성하기 위해 CSI 제공자에 의존하는 StorageClass에 의해 PVC가 수행됩니다.

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: task-pv-claim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: csi-my-vendor
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: csi-my-vendor

비 CSI 프로비저닝(트리 내), 비동적

데이터 센터에서 수동으로 생성하고 PersistentVolume으로 하드 코딩한 NFS를 공유합니다.
그런 다음 해당 볼륨을 가리키는 PVC를 만듭니다.
이 경우 포드 자체 가 (PVC 추상화를 가리키는 대신) 볼륨을 선언합니다.
예를 들어, 포드 정의 외부에 이와 같은 스니펫이 있을 것입니다.
우리는 볼륨 정의 내부에 NFS 를 정의하고 특히 간접 지정 없이 볼륨을 생성한다는 점에 주목하십시오.

volumes:
    - name: nfs-volume
      nfs:
        server: 192.100.200.212
        path: /data

NFS 동적 프로비저닝 SC 설정하기

쿠버네티스에는 내장 NFS 프로비저너가 없습니다.
NFS를 위한 스토리지클래스를 생성하려면 외부 프로비저너를 사용해야 합니다.

따라서 NFS 외부 프로비저너인 NFS subdir external provisioner에 대해 알아보고 설정해보고자 합니다.

NFS 서버 구축

먼저 NFS로 사용하고자 하는 서버를 만들어야 합니다.
저는 우분투 18.04에서 진행했으며, 서버 내에 NFS 서비스를 활성화한 디렉토리를 만들겠다고 생각해주시면 되겠습니다.
이렇게 활성화 한 서버의 IP와 마운트 경로로, 쿠버네티스에서 NFS 서버를 활용할 예정입니다.

  1. (Ubuntu의 경우) 패키지 색인을 업데이트하고 NFS 서버 패키지를 설치합니다.
    sudo apt update
    sudo apt install nfs-kernel-server

  2. 공유 마운트 지점에 생성합니다.
    mkdir -p /mnt/nfs

  3. NFS 서버에서 내보낼 파일 시스템, 공유 옵션 및 이러한 파일 시스템에 액세스할 수 있는 클라이언트를 정의합니다. 이렇게 하려면 /etc/exports 파일을 엽니다.
    sudo vim /etc/exports
    <path> <접근할 클라이언트IP>/24(옵션)

  4. 파일을 저장하고 공유를 내보냅니다.
    sudo exportfs -ra

  5. 현재 설정을 확인합니다.
    sudo exportfs -v

  6. 방화벽을 엽니다.
    sudo ufw allow from 192.168.0.14/24 to any port nfs

NFS subdir external provisioner 설치 및 설정

본 포스팅은 헬름으로 구성을 진행합니다.
헬름은 이전 포스팅에서도 사용해왔기 때문에 따로 설명은 드리지 않겠으나, 헬름을 설치해야한다면 이전 Minikube GPU 구축 포스팅을 참고하시기 바랍니다!

헬름을 이용한 설치/헬름을 이용하지 않는 설치에 대한 더 자세한 내용은 공식 깃허브블로그를 참고하시기 바랍니다.

  1. nfs-subdir-external-provisioner를 kube-system namespace에서 생성하기위해 컨텍스트를 바꿔줍니다.
    kubectl config set-context --current --namespace=kube-system

  2. 헬름에 NFS subdir external provisioner 레포를 추가합니다.
    helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/

  3. 레포를 인스톨합니다. nfs.server와 nfs.path를 위에서 설정한 NFS 서버에 맞게 수정해주세요!
    (동적 프로비저닝되는 pv의 path나 recialm policy를 변경하고 싶다면 여기서 helm install을 진행하지 말고 아래 섹션을 반드시 확인해주세요!)

helm install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
    --set nfs.server=192.168.0.14 \
    --set nfs.path=/mnt/nfs

helm install config 설정하기

  • 아래의 내용을 진행하다보니, onDelete, pathPattern 파라미터를 변경해야 하는데 자꾸 불가능하다는 오류가 발생했습니다.
    여러 자료를 확인해보니 StorageClass는 생성된 이후에 수정이 아예 불가능한 것 같습니다.
    그래서 helm install로 생성할 때 config를 설정하는 법을 알려드리고자 합니다.
  1. 일단 저처럼 설치를 진행했다면, 삭제(롤백)합니다.
    helm list -A

    helm delete nfs-subdir-external-provisioner -n kube-system

  2. 이제 nfs-subdir-external-provisioner를 삭제했으니, 추가 설정엔 어떤 것이 있나 확인합니다. 링크
    저는 storageClass.defaultClass, storageClass.accessModes, storageClass.pathPattern, storageClass.onDelete, nfs.server, nfs.path 를 설정하도록 하겠습니다.

  3. 해당 옵션들을 추가해서 helm install을 진행해봅시다.

helm install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
    --set nfs.server=192.168.0.14 \
    --set nfs.path=/mnt/nfs \
    --set storageClass.defaultClass=true \
    --set storageClass.accessModes=ReadWriteMany \
    --set storageClass.pathPattern="" \
    --set storageClass.onDelete=retain \
    --set storageClass.reclaimPolicy=Retain

NFS StorageClass 수정하기

  1. 현재 sc를 확인해봅시다. 위에서 nfs-subdir-external-provisioner를 정상적으로 설치했다면 2개가 나올 겁니다.

  2. 기존 minikube(kubernetes) StorageClass 비활성화 하기.
    minikube에서 진행하지 않으셨다면 default(standard) sc가 있을 수도 없을 수도 있습니다.

  • 기존 스토리지 클래스의 어노테이션을 수정합니다.(기본값 비활성화)
    kubectl patch storageclass standard -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
  1. nfs-subdir-external-provisioner를 기본 StorageClass로 설정합니다.
    이제 PV가 동적 프로비저닝을 통해 생성될 때 NFS sc를 받아와서 설정됩니다.
    kubectl patch storageclass nfs-client -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

  2. NFS 공유 폴더의 권한을 변경해줍니다.
    chmod 777 -R /mnt/nfs
    ll -z /mnt/nfs

  3. 컨텍스트를 다시 kubeflow-user-example-com으로 변경합니다.
    kubectl config set-context --current --namespace=kubeflow-user-example-com

  4. pvc를 생성해보고, pvc 안에 임시 파일을 생성한 뒤 NFS 서버에 잘 저장이 되었나 확인해봅시다.

  • pvc 생성을 위한 yaml 파일을 하나 만들고, kubectl apply -f 로 리소스를 생성해봅시다. vim pvc_test.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-claim
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Mi
  • kubectl apply -f pvc_test.yaml
  1. PVC가 생성되고, 동적 프로비저닝된 NFS PV에 정상 바인딩 되었음을 확인할 수 있습니다.
    kubectl get pvc
    kubectl get pv

  2. nfs 서버에 정상적으로 폴더가 생성되었나 확인해봅시다.

    2개나 생겼군요..? 폴더 명은 pathPattern: "${.PVC.namespace}/${.PVC.annotations.nfs.io/storage-path}"에 의해 정해졌을 겁니다. StorageClass의 설정입니다.(나중에 바꾸어봅시다)

  3. pvc를 삭제하면 NFS에서 PV도 삭제되나 봅시다.
    kubectl delete pvc test-claim
    아하! pvc를 삭제하면 폴더명에 archived-가 붙고 내용은 유지되는 것 같군요.

  4. hostpath의 경우와 다르게 ReclaimPolicy가 Delete임에도 불구하고 완전히 삭제되지 않는 이유가 무엇일까요? NFS여서일텐데 자료를 더 찾아봐야 겠습니다.

Reclaim Policy 수정하기

  • 위에서 helm install config 설정하기 섹션을 확인하세요!! 생성된 sc는 수정이 불가능합니다. helm install을 진행할 때 미리 config를 설정해주서야 합니다.
  1. StorageClass를 수정해봅시다. PV가 삭제되더라도 PV에 저장됐던 내용이 사라지지 않고, NFS 디렉토리에 남아있도록 하려합니다.
    kubectl edit sc nfs-client

  2. ReclaimPolicy를 수정할건데 nfs-subdir-external-provisioner에서는 parameters.onDelete: retain을 추가해주면 됩니다.

  • archiveOnDeleteonDelete가 존재할 시 무시됩니다. 삭제해도 됩니다.
  • 다만 현재 프로비저닝되고 있는 PV/PVC가 있어서 그런가 스토리지 클래스 업데이트가 불가능하다고 나오네요. 해결되면 추가 포스팅해보겠습니다!(helm install config 설정하기 업데이트 완료)

좋은 웹페이지 즐겨찾기