Kubernetes Volumes : Static & Dynamic Provisioning

1. Overview

이번 문서에서는 Kubernetes(k8s)의 여러 볼륨에 대해서 알아보겠습니다.

수정)
21.06.08 : Volume의 AccessMode에서 접근 가능한 대상을 Pod->Node로 변경
21.07.20 : nfs provisioner의 ServiceAccount namespace 설명 추가

2. Prerequisites

본문에서 사용한 spec :
OS : CentOS v7.6
Arch : x86

k8s클러스터는 1마스터 2노드로 구성했습니다.
Master : 4cpu, ram16G
Node : 2cpu, ram4G

3. Volumes

쿠버네티스에서의 볼륨은 pod에 종속되는 디스크입니다. 다시말해, 같은 pod에 있는 컨테이너들은 해당 디스크를 공유해서 사용할 수 있습니다.

EmptyDir

pod과 함께 생성되고 삭제되는 임시 볼륨입니다. 아무런 volume옵션을 주지 않았을 때 생성됩니다.

Host Path

로컬 디스크의 경로를 pod에 마운트해서 사용하는 방식입니다.

pod이 올라간 노드의 로컬디스크를 사용하는 것이기 때문에 해당 노드의 로컬 마운트 포인트가 존재하는지 확인해야 합니다.

또한 pod이 재시작되어 다른노드에서 기동될 경우에, 해당 노드의 로컬디스크를 사용하기 때문에 이전 노드에서 사용했던 로컬디스크는 액세스할 수 없게 됩니다.

노드의 파일시스템을 접근하는데 유용한 특성을 가지고 있으므로 노드의 로그파일을 읽어서 수집하는 로그 에이전트같은 경우에 유용하게 사용할 수 있습니다.

실습

$ vim hostpath-nginx.yaml

apiVersion: v1
kind: Pod
metadata:
  name: hostpath-nginx
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts: # pod의 path
    - name: volumepath
      mountPath: /usr/share/nginx/html
  volumes:
  - name : volumepath
    hostPath: # 반드시 노드에 해당 path가 있는지 확인할 것
      path: /root/hostdir
      type: Directory

pod 실행 :

$ kubectl apply -f hostpath-nginx.yaml
$ kubectl get pod

image

pod이 어디떠있는지 확인 :

$ kubectl describe pod hostpath-nginx

image

2번 노드에 떠있습니다.

2번 노드에 접속해서 해당 path에 파일을 하나 생성해봅시다.
image

nginx앱 안에 들어가서 마운트 포인트를 확인해봅니다.

$ kubectl exec -it hostpath-nginx /bin/bash

image
방금전 2번 노드에서 생성한 파일이 그대로 보이는것을 확인할 수 있습니다.

PersistentVolume & PersistentVolumeClaim

임시나 로컬 디스크가 아닌 디스크 볼륨을 설정하려면 물리적인 스토리지를 생성해야하고, 관리까지 할 줄 알아야합니다. 이는 개발자들에게 있어 상당한 부담이 될 수 있습니다.

쿠버네티스는 인프라에 대한 복잡성을 추상화시키고 개발자들이 손쉽게 인프라 자원을 사용할 수 있도록 하는 개념을 가지고 있습니다.

  • 시스템관리자 : 인프라에 대한 것들만
  • 개발자 : 개발에 관한 것만

각자의 역할에 집중할 수 있도록 PersistentVolume(pv)과 PersistentVolumeClaim(pvc)이라는 개념을 도입하였습니다.

기본 시나리오 :
Picture1

시스템 관리자가 실제 스토리지를 생성하고, 이 디스크를 PersistentVolume으로 쿠버네티스에 등록합니다.
개발자는 pod을 생성할 때, 볼륨을 정의하고 이 부분에 PersistentVolumeClaim을 지정하여 시스템 관리자가 생성한 pv와 연결합니다.

실습 : Static Provisioning (with NFS)

실습을 진행하기 전에, 물리적인 스토리지가 하나 반드시 필요합니다.
이 문서에서는 NFS를 사용하도록 하겠습니다.
참고링크 : 호롤리한하루/NFS 기초 연결 (LINUX)

실제 스토리지와 연동되는 PersistentVolume을 만들어봅시다.

$ vim create-pv.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv
spec:
  capacity:
    storage: 20Gi
  accessModes:
    - ReadWriteMany
  nfs:
    server: x.x.x.x
    path: /mount/point

accessMode :

  • ReadWriteOnce(RWO) : 하나의 노드에만 마운트되고 하나의 노드에서만 읽고쓰기 가능.
  • ReadOnlyMany(ROX) : 여러개의 노드에 마운트가능, 여러개의 노드에서 동시에 읽기 가능, 쓰기는못함.
  • ReadWriteMany(RWX) : 여러개의 노드에 마운트가능, 여러개의 노드에서 읽고쓰기 가능.

pod에서 어느정도의 볼륨을 사용할 것인지 PersistentVolumeClaim에 정의해줍니다.

$ vim claim-pvc.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 20Gi
$ kubectl create -f create-pv.yaml
$ kubectl create -f claim-pvc.yaml

image

pvc가 pv에 bound되어있는 것을 확인할 수 있습니다.

이제 pv,pvc를 사용할 pod을 생성해주겠습니다.

$ vim nginx-pv.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-demo
spec:
  selector:
    matchLabels:
      app: nginx-demo
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx-demo
    spec:
      containers:
      - name: master
        image: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: /mnt # pod에서의 mount point
          name: pvc-volume
      volumes:
      - name: pvc-volume
        persistentVolumeClaim: #생성했던 pvc의 이름
          claimName: pvc
$ kubectl apply -f nginx-pv.yaml

잘 올라갔는지 확인

$ kubectl describe pod nginx-demo
$ kubectl get pod

image
image

볼륨이 제대로 붙었는지 확인해봅시다.

$ kubectl exec -it nginx-demo-6857b465bd-d82tb bash
$ df -h

image
pvc에서 요청했던 20G 볼륨이 제대로 붙은 것을 확인할 수 있습니다.

나올 수 있는 에러 :

Warning  FailedMount  2s  kubelet, kube-n02  MountVolume.SetUp failed for volume "pv" : mount failed: exit status 32
Mounting command: systemd-run
Mounting arguments: --description=Kubernetes transient mount for /var/lib/kubelet/pods/677f3b6a-75c8-46b0-b2bf-06aba54ecf2e/volumes/kubernetes.io~nfs/pv --scope -- mount -t nfs fsf-dal1301k-fz.adn.networklayer.com:/IBM02SEV1869677_1/data01 /var/lib/kubelet/pods/677f3b6a-75c8-46b0-b2bf-06aba54ecf2e/volumes/kubernetes.io~nfs/pv
Output: Running scope as unit run-10679.scope.
mount: wrong fs type, bad option, bad superblock on fsf-dal1301k-fz.adn.networklayer.com:/IBM02SEV1869677_1/data01,
      missing codepage or helper program, or other error
      (for several filesystems (e.g. nfs, cifs) you might
      need a /sbin/mount.<type> helper program)

      In some cases useful info is found in syslog - try
      dmesg | tail or so.

필요한 util이 깔려있지 않기 때문.
노드마다 설치해주면 해결

$ yum install cifs-utils
$ yum install nfs-utils

실습 : Dynamic Provisioning (with NFS)

위에서의 방법은 pod하나를 만들때마다 물리적인 볼륨도 만들어야하고(pv) pv에 따른 pvc도 만들어야하니 굉장히 귀찮은 방법입니다.

누군가가 알아서 생성해주면 좋을텐데…

이 파트에서는 누군가(provisioner)가 알아서(dynamic) 볼륨을 만들어서 붙여주는(provisioning) 방법인 Dynamic Provisioning을 직접 해보도록 하겠습니다.

위 실습과 동일하게 nfs를 사용합니다.

먼저 Provisioner가 사용할 Service Account를 만들어줍니다.

$ vim nfs-prov-sa.yaml

kind: ServiceAccount
apiVersion: v1
metadata:
  name: nfs-pod-provisioner-sa
---
kind: ClusterRole # Role of kubernetes
apiVersion: rbac.authorization.k8s.io/v1 # auth API
metadata:
  name: nfs-provisioner-clusterRole
rules:
  - apiGroups: [""] # rules on persistentvolumes
    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-rolebinding
subjects:
  - kind: ServiceAccount
    name: nfs-pod-provisioner-sa 
    namespace: default
roleRef: # binding cluster role to service account
  kind: ClusterRole
  name: nfs-provisioner-clusterRole # name defined in clusterRole
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-pod-provisioner-otherRoles
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-pod-provisioner-otherRoles
subjects:
  - kind: ServiceAccount
    name: nfs-pod-provisioner-sa # same as top of the file
    # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: Role
  name: nfs-pod-provisioner-otherRoles
  apiGroup: rbac.authorization.k8s.io

주의) ClusterRoleBindingRoleBindingnamespaceProvisioner를 배포한 namespace와 동일해야함!!

$ kubectl apply -f nfs-prov-sa.yaml

image

그 다음으로는 스토리지 클래스를 만들어 줄겁니다.

pvc에서 pv의 name을 지정하지 않고, 이제 storageclass name으로 볼륨요청을 하게됩니다.

$ vim storageclass-nfs.yaml

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-storageclass # IMPORTANT pvc needs to mention this name
provisioner: nfs-test # name can be anything
parameters:
  archiveOnDelete: "false"
$ kubectl apply -f storageclass-nfs.yaml
$ kubectl get storageclass

image

일일히 pv를 생성했던것과 달리 자동으로 pv를 생성해주는 provisioner를 만들어줍니다.

$ vim pod-provision-nfs.yaml

kind: Deployment
apiVersion: apps/v1
metadata:
  name: nfs-pod-provisioner
spec:
  selector:
    matchLabels:
      app: nfs-pod-provisioner
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-pod-provisioner
    spec:
      serviceAccountName: nfs-pod-provisioner-sa # name of service account
      containers:
        - name: nfs-pod-provisioner
          image: quay.io/external_storage/nfs-client-provisioner:latest
          volumeMounts:
            - name: nfs-provisioner-v
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME # do not change
              value: nfs-test # SAME AS PROVISIONER NAME VALUE IN STORAGECLASS
            - name: NFS_SERVER # do not change
              value: x.x.x.x # Ip of the NFS SERVER
            - name: NFS_PATH # do not change
              value: /.../data01  # path to nfs directory setup
      volumes:
       - name: nfs-provisioner-v # same as volumemouts name
         nfs:
           server: x.x.x.x
           path: /.../data01
$ kubectl apply -f pod-provision-nfs.yaml
$ kubectl get pod

image

provisioner가 성공적으로 배포되었으니, 볼륨 요청을 해보도록 하겠습니다.

$ vim test-claim.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc-test
spec:
  storageClassName: nfs-storageclass # SAME NAME AS THE STORAGECLASS
  accessModes:
    - ReadWriteMany #  must be the same as PersistentVolume
  resources:
    requests:
      storage: 1Gi

1G만 요청해주고, storageClass를 명시해줍니다.

$ kubectl apply -f test-claim.yaml
$ kubectl get pv,pvc

image
만들지 않았던 pv가 자동으로 생성되면서 pvc가 bound되는것을 확인할 수 있습니다.

그리고 nfs가 마운트된 폴더로 가서 확인해보면
image
긴 이름의 폴더가 새로 생긴것을 확인할 수 있습니다.


댓글남기기