본문 바로가기
DevOps/Kubernetes

[9] 쿠버네티스 볼륨

by 비어원 2022. 2. 20.
728x90

Volume

컨테이너(파드)가 뜨면 컨테이너 만의 공간이 생성되고 이 공간은 임시적으로 사용된다. 만약 파드가 죽는다면 해당 공간은 사라진다. 웹 애플리케이션과 같은 Stateless한 애플리케이션인 경우에는 문제가 없지만, 애플리케이션이 특정 데이터를 저장해야 하는 경우나 파드에서 같이 실행되는 컨테이너 간에 임시 파일을 공유해야 하는 경우에 문제가 발생하게 된다.

 

컨테이너 애플리케이션이 특정 데이터를 저장해야 하는 경우에는 애플리케이션이 죽으면 쿠버네티스가 컨테이너를 다시 띄우겠지만 컨테이너 공간이 사라지기 때문에 해당 데이터는 당연히 삭제된다.

 

파드에서 같이 실행되는 컨테이너 간에 임시 파일을 공유해야 하는 경우, 파일을 저장하는 컨테이너가 죽어버리면 위와 똑같은 이유로 임시파일에 저장된 데이터는 당연히 삭제된다.

 

그래서 컨테이너 (또는 파드)의 라이프사이클에 관계없이 지속되는 공간이 필요한데 쿠버네티스는 볼륨 이라는 이름으로 해당 공간을 사용할 수 있도록 지원한다. 물론 도커에도 볼륨이라는 개념을 가지고 있지만, 쿠버네티스는 더 다양한 볼륨의 유형을 지원한다. 쿠버네티스에는 크게 두 가지 형식의 임시 볼륨 (Ephemeral Volume)퍼시스턴트 볼륨 (Persistent Volume) 을 지원한다.

 

임시 볼륨

임시볼륨은 파드가 재시작되었을 때 파드 내에서 저장된 데이터가 삭제되어도 상관없는 데이터를 저장할 때 사용하는 볼륨이다. 예를 들어 캐시서비스에서 캐시 메모리가 꽉 찰 경우 자주 사용하지 않는 데이터를 임시 볼륨에 저장하는 경우에 사용할 수 있다. 또는 설정 등 여러 파드에서 사용하는 읽기 전용의 데이터를 불러와야 할 때 임시 볼륨을 사용한다.

임시볼륨은 파드의 수명을 따르기 때문에 영구 볼륨을 사용할 수 있는 노드에 제한되지 않고 파드를 (재)스케줄링할 수 있다. 임시 볼륨은 PodSpec에 inline으로 지정할 수 있다.

 

임시 볼륨 종류

쿠버네티스에서는 여러가지의 임시 볼륨을 제공한다.

  • emptyDir: 말그대로 비어있는 볼륨, 해당 스토리지는 kubelet base directory공간이나 램을 사용한다.
  • configMap, downwardAPI, secret: 여러 종류의 쿠버네티스 데이터를 파드로 주입하는 경우 사용
  • CSI ephemeral volumes: 특수목적으로 사용되며 특정 CSI driver에 의해 제공되는 볼륨
  • generic ephemeral volumes: 영구 볼륨도 지원하는 모든 스토리지 드라이버에서 제공하는 볼륨

그 중 가장 많이 사용하는 empryDir, configMap, secret 에 대해 알아보자.

 

emptyDir

emptyDir 볼륨은 파드가 노드에 할당될 때 생성되며 파드와 동일한 라이프사이클을 따른다. 즉, 파드가 죽으면 emptyDir도 삭제된다. 하지만 파드 내 일부 컨테이너가 죽으면 emptyDir은 삭제되지 않는다. 이 볼륨은 파드 내 컨테이너끼리 공유되며 컨테이너는 해당 볼륨을 마운트하여 접근할 수 있다.

 

설정에 따라 emptyDir.mediumMemory로 설정하면 emptyDir 볼륨은 RAM 기반 파일 시스템인 tmpfs를 마운트하도록 할 수 있다. 대신 노드가 재부팅되면 tmpfs가 지워진다. 그래도 Memory로 설정하면 빠르기 때문에 캐시용 볼륨을 사용하고 싶을 때 설정하면 좋을 듯 하다.

 

emptyDir 구성은 다음과 같이 할 수 있다.

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
  - name: cache-volume
    emptyDir: {}

emptyDir을 사용하는 경우는 다음과 같다.

  • 웹 서버 컨테이너가 데이터를 처리하는 동안 컨텐츠 매니저 컨테이너가 가져오는 파일을 보관할 때
  • initContainer에서 설정한 값을 본 컨테이너로 전달하려고 할 때
  • 그 외 컨테이너 끼리 파일을 공유할 때?

 

configMap

configMap은 구성 파일을 파드에 주입할 때 사용한다. configMap에 저장된 데이터는 configMap 유형의 볼륨에서 참조되고 파드에서 실행되는 컨테이너가 해당 볼륨을 마운트하여 구성파일을 사용한다.

볼륨 이름에 configMap 이름을 설정하여 configMap의 데이터를 볼륨으로 만들 수 있다. 그리고 컨테이너 설정 중 volumeMounts에서 namemountPath를 사용하여 configMap 볼륨을 마운트할 경로를 지정한다.

apiVersion: v1
kind: Pod
metadata:
  name: configmap-pod
spec:
  containers:
    - name: test
      image: busybox
      volumeMounts:
        - name: config-vol
          mountPath: /etc/config
  volumes:
    - name: config-vol
      configMap:
        name: log-config
        items:
          - key: log_level
            path: log_level

예시에서는 log-config 컨피그맵 데이터는 /etc/config/log_level 경로로 마운트된다.

 

secret

secret은 configMap과는 유사하지만 암호화 같은 민감한 정보를 파드에 전달하는데 사용된다. secret에 저장되는 값은 Base64로 인코딩되어 저장된다. 그리고 secret은 tmpfs만을 지원하기 때문에 비휘발성 스토리지에 저장되지 않는다.

 

퍼시스턴트 볼륨

임시볼륨은 PodSpec의 volumes 을 통해 정의하여 파드와 같은 라이프사이클을 가진다. 하지만 퍼시스턴트 볼륨은 파드와 같은 라이프사이클을 가지지 않는다. 파드가 죽어도 퍼시스턴트 볼륨의 데이터가 영구적으로 남아있어야 하기 때문이다.

그리고 퍼시스턴트 볼륨(PV) 을 사용하는 파드가 완전히 삭제되어도 퍼시스턴트 볼륨은 삭제되지 않는다. 쿠버네티스에서 파드와 퍼시스턴트는 직접적으로 관계가 형성되지 않고 퍼시스턴트볼륨클레임(PVC) 이라는 오브젝트를 통해 간접적으로 관계가 형성되기 때문이다.

간단히 말하면 PVC는 원하는 스펙의 PV를 바인딩하며, 파드는 PVC를 볼륨으로 사용한다.

 

PV (Persistent Volume)

퍼시스턴트 볼륨 (Persistent Volume, PV) 은 파드와 별개의 라이프사이클을 가지는 볼륨이며 PV는 클러스터 리소스이다. PV는 수동으로 프로비저닝하거나 동적 프로비저닝을 통하여 생성한다. 수동으로 프로비저닝을 하는 경우 간단히 매니페스트 파일로 생성하면 되고, 동적으로 프로비저닝 하려면 동적 프로비저닝을 하기 위한 프로비저너StorageClass를 생성하고, StorageClass에서 프로비저너에게 전달할 파라미터를 정의해야 한다.

프로비저닝은 사용자의 요구에 맞게 시스템 자원을 할당, 배치, 배포해 두었다가 필요 시 시스템을 즉시 사용할 수 있는 상태로 미리 준비해 두는 것을 말한다. (wikipedia)

 

PVC (Persistent Volume Claim)

퍼시스턴트 볼륨 클레임 (Persistent Volume Claim, PVC) 은 사용자가 사용할 PV를 요청하는 명세이다. 여기서는 사용할 특정 PV를 명시하여 PV를 예약하거나 StorageClass를 지정하여 원하는 용량의 볼륨을 동적으로 프로비저닝을 받도록 할 수 있다.

 

PV, PVC 라이프사이클

실제로 PV를 사용할 때는 쿠버네티스 관리자가 PV를 먼저 프로비저닝 한 다음에 사용자가 파드를 생성할 때 PVC를 정의하여 파드가 사용할 PV를 요청하게 된다. PVC로 PV를 요청하면 쿠버네티스에서 파드가 사용하기 적당한 PV를 할당해준다. 그리고 시간이 지나고 파드를 더 이상 사용하지 않는다면 PV를 반납해도 된다.

 

파드와 마찬가지로 PV도 별도의 라이프사이클이 있는데, 파드의 라이프사이클과는 조금 다를 것이다. PV와 PVC의 라이프사이클에 대해 알아보자. 일단은 PV를 수동으로 프로비저닝하는 경우만 생각해보자. (동적 프로비저닝의 경우는 추후에 설명해보도록 하겠다.)

 

프로비저닝 (Provisioning)

먼저 쿠버네티스 클러스터 관리자가 직접 PV를 생성한다. 로컬 스토리지나 NFS와 같은 네트워크 스토리지를 생성할 수 있다. 그리고 클러스터 사용자가 사용할 실제 스토리지의 세부사항을 PVC를 통해 생성한다.

 

수동 프로비저닝의 경우 직접 매니페스트 파일을 만들어서 프로비저닝한다. 매니페스트 예시는 아래와 같다.

 

로컬 스토리지

apiVersion: v1
kind: PersistentVolume
metadata:
  name: local-pv-test
spec:
  capacity:
    storage: 3Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-storage
  local:
    path: /mnt/data
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: pv-local
          values:
          - "test"
          operator: In
  • 로컬 스토리지의 경우 storageClassNamelocal-storage로 설정해야 한다.
  • 로컬 스토리지는 spec.local 에서 로컬 스토리지로 사용할 path를 설정할 수 있다.
  • 로컬 스토리지를 사용하기 위해서는 볼륨으로 사용할 노드를 지정해야 하기 때문에 nodeAffinity를 설정해야 한다. 여기서는 pv-local=test 라는 레이블을 가지는 노드에 볼륨을 할당한다. (이 레이블을 가지는 노드는 1개여야 한다. 1개가 아니더라도 생성은 되겠지만.....)

 

이 매니페스트로 PV를 생성하면 다음과 같이 PV가 생성되었음을 알 수 있다.

$ kubectl get pv local-pv-test
NAME            CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS    REASON   AGE
local-pv-test   3Gi        RWO            Retain           Available           local-storage            8s

 

네트워크 스토리지

 

네트워크 스토리지 중 NFS를 사용하여 PV를 만들어보겠다. 일단 NFS 서버가 있어야 한다.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv-test
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: slow
  mountOptions:
    - hard
    - nfsvers=4.1
  nfs:
    path: /mnt/data/pv-test
    server: {nfs-server-ip}
  • spec.mountOptions에 NFS 서버에 맞는 nfsvers를 입력해야 한다.
  • NFS로 PV를 만드는 경우에는 spec.nfs 필드가 필요하다.

 

이 매니페스트로 PV를 생성하면 다음과 같이 PV가 생성되었음을 알 수 있다.

$ kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv-test   1Gi        RWO            Recycle          Available           slow                    1m

 

바인딩 (Binding)

클러스터 관리자가 PV를 프로비저닝하면 클러스터 사용자가 사용할 실제 스토리지의 세부사항을 PVC를 통해 생성하여 PV를 요청한다. 사용자가 PV를 요청하기 위해 원하는 스토리지의 용량과 접근모드를 PVC에 적어 생성한다면 컨트롤플레인의 컨트롤 루프는 새로 생성된 PVC를 감시하고 PVC가 원하는 스펙과 일치하는 PV를 찾아 서로 바인딩한다.

 

일치하는 PV가 생성되면 PVC가 바인딩된다. 하지만 PVC가 원하는 스펙과 일치하는 PV가 없다면 PVC는 무한정 바인딩되지 않은 상태로 남아있는다.

 

앞에서 생성한 nfs PV를 바인딩하기 위해 PVC를 생성해보자.

 

pvc-test.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-test
spec:
  storageClassName: slow
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 512Mi # < 1Gi
$ kubectl apply -f pvc-test.yaml

 

PVC를 생성한 후에 컨트롤 플레인은 PVC의 요구사항을 만족하는 PV를 찾는다.

  • 컨트롤 플레인이 동일한 스토리지클래스를 갖는 PV를 찾는다. (spec.stroageClassName=slow)
  • 용량 등 PVC의 스펙 요구사항을 만족하는 PV를 찾는다. (spec.resource.requests.storage >= 512Mi)

 

PVC를 생성한 후 다시 PV를 확인하면 PV가 바운드된 것을 확인할 수 있다. (STATUS=Bound)

$ kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM              STORAGECLASS   REASON   AGE
pv-test   1Gi        RWO            Recycle          Bound    default/pvc-test   slow                    27m

 

마찬가지로 PVC를 확인하면 PVC에 pv-test 볼륨이 바인딩 된 것을 확인할 수 있다.

$ kubectl get pvc
NAME        STATUS    VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS       AGE
pvc-test    Bound     pv-test   1Gi        RWO            slow               8m16s

 

그 다음은 볼륨으로 PVC를 사용하는 파드를 만들어보자. 파드가 PV를 사용하고 있는지 확인하기 위해 사용할 PV에 데이터를 넣고 실험해보자. (필자는 html이라는 아무 파일을 넣었다.)

 

pv-pod-test.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pv-pod-test
spec:
  volumes:
    - name: pv-storage-test
      persistentVolumeClaim:
        claimName: pvc-test
  containers:
    - name: pv-container-test
      image: nginx
      ports:
        - containerPort: 80
          name: "http-server"
      volumeMounts:
        - mountPath: "/usr/share/nginx/html"
          name: pv-storage-test
$ kubectl apply -f pv-pod-test.yaml

 

그 다음, 파드에서 구동되고있는 컨테이너에 접근한다.

$ kubectl exec -it pv-pod-test -- /bin/bash

 

그 후 파일이 공유되어있는지 확인한다.

$ ls /usr/share/nginx
html

 

반환 (Reclaiming)

사용자가 볼륨을 다 사용하고 나면 리소스를 반환할 수 있는 API를 사용하여 PVC 오브젝트를 삭제할 수 있다. 쿠버네티스에서 제공하는 리소스 반환 정책은 Retain, Recycle, Delete 가 있다.

 

Retain

Retain 반환 정책은 리소스를 수동으로 반환할 수 있게 한다. PVC가 삭제되더라도 PV는 여전히 존재하며 해당 볼륨은 Released 로 간주된다. 여기서는 이전에 저장했던 데이터가 여전히 볼륨에 남아있기 때문에 다른 PVC는 이 볼륨을 바인딩 할 수 없다. 관리자는 다음 단계에 따라 볼륨을 수동으로 반환할 수 있다.

  1. PV를 삭제한다.
  2. 관련 스토리지의 데이터를 수동으로 삭제한다.
  3. 연결된 스토리지를 수동으로 삭제한다.

만약 같은 스토리지 자산을 재사용하려는 경우 동일한 스토리지 자산으로 새 PV를 수동으로 생성해야 한다.

 

Delete

Delete 반환 정책을 지원하는 볼륨 플러그인의 경우 PVC를 삭제하는 경우 쿠버네티스에서 PV 오브젝트와 외부 인프라의 관련 스토리지 자산을 모두 삭제한다. 동적으로 프로비저닝된 볼륨은 StorageClass의 반환 정책을 상속하며 기본값은 Delete이다.

 

Recycle

기본 볼륨 플러그인에서 지원하는 경우 Recycle 반환 정책은 볼륨에서 기본 스크럽 (rm -rf /volume/*) 을 수행하고 새 PVC에 다시 사용할 수 있도록 한다.

그런데 Recycle 반환 정책은 더 이상 사용하지 않고 대신 권장되는 방식은 동적 프로비저닝을 사용하는 것이라고 한다.

728x90

'DevOps > Kubernetes' 카테고리의 다른 글

[10] 쿠버네티스 Ingress  (0) 2022.07.21
쿠버네티스 Static Pods  (0) 2022.07.16
[8] 쿠버네티스 서비스  (0) 2022.02.09
[7] 쿠버네티스 디플로이먼트  (0) 2022.01.23
[6] 쿠버네티스 파드  (0) 2022.01.05

댓글