DevOps/Kubernetes

쿠버네티스(Kubernetes) PV, PVC

알로그 2022. 5. 20. 18:08
반응형

쿠버네티스(Kubernetes) PV, PVC

 

여태껏 각 포드는 별도의 데이터를 갖고 있지 않았고 단순히 요청에 대한 응답만을 반환했다.

DB처럼 포드 내부에서 특정 데이터를 보유해야 하는 상태가 있는(stateful) 애플리케이션의 경우 데이터를 어떻게 관리해야 할지 고민해야 한다.

 

MySQL 디플로이먼트를 통해 포드를 생성했더라도 디플로이먼트를 삭제하면 포드도 함께 삭제되고 데이터도 함께 삭제된다. 이를 해결하기위해 쿠버네티스에서도 호스트에 위치한 디렉토리를 각 포드와 공유함으로써 데이터를 보존하는 것이 가능하다.

 

로컬볼륨: hostPath, emptyDir

hostPath는 호스트와 볼륨을 공유하기 위해 사용하고 emptyDir은 포드의 컨테이너 간에 볼륨을 공유하기 위해 사용함

 

워커 노드의 로컬 디렉터리를 볼륨으로 사용: hostPath

포드의 데이터를 보존할 수 있는 가장 간단한 방법은 호스트의 디렉터리를 포드와 공유하는 것

<hostpath-pod.yaml>

apiVersion: v1
kind: Pod
metadata:
  name: hostpath-pod
spec:
  containers:
    - name: my-container
      image: busybox
      args: [ "tail", "-f", "/dev/null" ]
      volumeMounts:
      - name: my-hostpath-volume
        mountPath: /etc/data
  volumes:
    - name: my-hostpath-volume
      hostPath:
        path: /tmp

volumes 항목에 볼륨을 정의한 뒤, 이를 containers 항목에서 참조해서 사용함

호스트의 /tmp를 포드의 /etc/data에 마운트 함

 

$ kubectl apply -f hostpath-pod.yaml  
$ kubectl exec -it hostpath-pod touch /etc/data/mydata

포드가 생성된 워커 노드로 접속한 뒤, /tmp 디렉터리를 확인하면 확인 가능함

 

참고) volumes.hostPath.type에 DirectoryOrCreate를 명시하면 실제 경로가 없다면 새로 생성함

  • DirectoryOrCreate : 실제 경로가 없다면 생성
  • Directory : 실제 경로가 있어야됨
  • FileOrCreate : 실제 경로에 파일이 없다면 생성
  • File : 실제 파일이 었어야함

 

하지만 위와 같은 데이터 보존은 바람직하지 않다.

디플로이먼트의 포드에 장애가 생겨 다른 노드로 포드가 옮겨간 경우 이전 노드에 저장된 데이터를 사용할 수 없기 때문이다.

특정 노드에만 포드를 배치하는 방법도 생각할 수 있지만 이 방법 또한 호스트 서버에 장애가 생기면 데이터를 잃게 된다는 단점이 존재한다.

 

hostPath 볼륨은 모든 노드에 배치해야 하는 특수한 포드의 경우에 유용하게 사용할 수 있다.

만약 CAdvisor와 같은 모니터링 툴을 쿠버네티스의 모든 워커 노드에 배포해야 한다면 hostPath를 사용하는 것이 옳을 수 있지만 그 외에는 보안 및 활용성 측면에서 그다지 바람직하지 않으므로 사용을 고려해보는 것이 좋다.

 

 

포드 내의 컨테이너 간 임시 데이터 공유: emptyDir

포드가 실행되는 도중에만 필요한 휘발성 데이터를 각 컨테이너가 함께 사용할 수 있도록 임시저장 공간을 생성함

<empty-pod.yaml>

apiVersion: v1
kind: Pod
metadata:
  name: emptydir-pod
spec:
  containers:
    - name: content-creator
      image: alicek106/alpine-wget:latest
      args: ["tail" , "-f", "/dev/null"]
      volumeMounts:
        - name: my-emptydir-volume
          mountPath: /data 

    - name: apache-webserver
      image: httpd:2
      volumeMounts:
        - name: my-emptydir-volume
          mountPath: /usr/local/apache2/htdocs

  volumes:
    - name: my-emptydir-volume
      emptyDir: {}
$ kubectl apply -f emptydir-pod.yaml

 

content-creator 컨테이너 내부로 들어가 /data 디렉터리에 웹 컨텐츠를 생성하면 apache-webserver 컨테이너의 htdocs 디렉터리에도 동일하게 웹 컨텐츠 파일이 생성될 것이고(mount 명령어로 확인 가능), 이는 최종적으로 웹 서버에 의해 외부에 제공됨

 

 

네트워크 볼륨

NFS(Network File System)는 대부분의 운영체제에서 사용할 수 있는 네트워크 스토리지로 여러 개의 클라이언트가 동시에 마운트 해서 사용할 수 있는 특징이 있다. NFS는 NFS 서버와 NFS 클라이언트가 필요하며, 서버는 영속적인 데이터가 저장되는 네트워크 스토리지 서버이며, 클라이언트는 NFS 서버에 마운트해 스토리지에 파일을 읽고 쓰는 역할이다.

 

임시 NFS 서버 생성

<nfs-deployment.yaml>

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-server
spec:
  selector:
    matchLabels:
      role: nfs-server
  template:
    metadata:
      labels:
        role: nfs-server
    spec:
      containers:
        - name: nfs-server
          image: gcr.io/google_containers/volume-nfs:0.8
          ports:
            - name: nfs
              containerPort: 2049
            - name: mountd
              containerPort: 20048
            - name: rpcbind
              containerPort: 111
          securityContext:
            priviledged: true

<nfs-service.yaml>

apiVersion: v1
kind: Service
metadata:
  name: nfs-service
spec:
  ports:
    - name: nfs
      port: 2049
    - name: mountd
      port: 20048
    - name: rpcbind
      port: 111
  selector:
    role: nfs-server
$ kubectl apply -f nfs-deployment.yaml
$ kubectl apply -f nfs-service.yaml

<nfs-pod.yaml>

apiVersion: v1
kind: Pod
metadata:
  name: nfs-pod
spec:
  contaIners:
    - name: nfs-mount-container
      image: busybox
      args: [ "tail" , "-f" , "/dev/null" ]
      volumeMounts:
        - name: nfs-volume
          mountPath: /mnt 
  volumes:
    - name : nfs-volume
      nfs:
        path: /
        server: {NFS_SERVICE_IP}

포드 컨테이너의 /mnt 디렉터리에 파일을 저장하면 NFS 서버에 데이터가 저장됨

 

NFS 볼륨의 마운트는 컨테이너 내부가 아닌 워커 노드에서 발생하므로 서비스의 DNS 이름으로 NFS 서버에 접근할 수 없으므로 NFS 서비스의 Cluser IP를 직접 얻은 뒤 YAML 파일에 사용하는 방식으로 포드를 생성해보자.

$ export NFS_CLUSTER_IP=$(kubectl get svc/nfs-service -o jsonpath='{.spec.clusterIP}')
$ cat nfs-pod.yaml | sed "s/{NFS_SERVICE_IP}/$NFS_CLUSTER_IP/g" | kubectl apply -f -

혹시 ContainerCreating 상태에서 파드가 생성되지 않는다면 kubectl describe pods 명령어로 무엇이 문제인지 파악하고 포드가 할당된 워커노드에서 apt-get install nfs-common 명령어로 패키지를 설치해야 함

 

NFS 서버가 /mnt 디렉터리에 마운트 됐으므로 포드의 컨테이너 내부의 /mnt 디렉터리에 저장된 파일은 포드가 다른 노드로 옮겨가거나 포드를 재시작해도 삭제되지 않는다.

 

실제 운영 환경에서는 NFS 서버를 도입하려면 백업 스토리지를 구축해 NFS 데이터 손실에 대비하거나 서버의 설정 튜닝 및 접근하기 위한 DNS를 준비해야 할 수도 있다.

 

 

PV, PVC를 이용한 볼륨 관리

PV와 PVC는 포드를 생성하는 YAML 입장에서 네트워크 볼륨이 NFS인지, AWS의 EBS 인지 상관없이 볼륨을 사용할 수 있도록 추상화해주는 역할을 담당한다.

 

인프라 관리자는 네트워크 볼륨의 정보를 이용해 퍼시스턴트 볼륨 리소스를 미리 생성해두고 사용자(개발자)는 퍼시스턴트 볼륨 클레임을 명시하고 생성하면 된다.

 

AWS의 EBS를 퍼시스턴트 볼륨으로 사용해보는 예제(EBS 생성했다는 가정)

<ebs-pv.yaml>

apiVersion: v1
kind: PersistentVolume
metadata:
  name: ebs-pv
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce 
  awsElasticBlockStore:
    fsType: ext4
    volumeID: <VOLUME_ID>

5Gi 볼륨의 크기와 ReadWriteOnce는 해당 볼륨이 1:1로 마운트 가능함을 의미한다. 

 

이러한 항목들은 볼륨의 속성을 나타내며, 퍼시스턴트 볼륨 클레임의 요구 조건을 매칭할 때 사용한다.

$ cat ebs-pv.yaml | sed "s/<VOLUME_ID>/$VOLUME_ID/g" | kubectl apply -f -
$ kubectl get pv

아직 퍼시스턴트 볼륨 클레임을 생성하지 않았기 때문에 Status가 Available로 나오는 것을 확인할 수 있음

 

<ebs-pod-pvc.yaml>

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-ebs-pvc
spec:
  storageClassName: ""
  accessModes:
    - ReadWriteOnce 
  resources:
    requests:
      storage: 5Gi
 
-----
apiVersion: v1
kind: Pod
metadata:
  name: ebs-mount-container
spec:
  containers:
    - name: ebs-mount-container
      image: busybox
      args: ["tail", "-f", "/dev/null"]
      volumeMounts:
        - name: ebs-volume
          mountPath: /mnt
  volumes:
    - name: ebs-volume
      persistentVolumeClaim:
        className: my-ebs-pvc

 

포드와 퍼시스턴트 볼륨 생성

$ kubectl apply -f ebs-pod-pvc.yaml
$ kubectl get pv, pvc
$ kubectl get pods
$ kubectl exec ebs-mount-container -- df -h | grep /mnt

 

요약하자면..

AWS에서 EBS 볼륨을 생성하고 ebs-pv.yaml 파일을 통해 퍼시스턴트 볼륨을 등록한다.

ebs-pod-pvc.yaml 파일을 통해 퍼시스턴트 볼륨 클레임을 생성하고, 요구조건이 만족되면 퍼시스턴트 볼륨과 퍼시스턴트 볼륨 클레임이 bound 된다. 마지막으로 파드에서 볼륨 클레임을 사용한다고 명시하면 최종적으로 EBS 볼륨이 컨테이너 내부에 마운트된다.

 

 

퍼시스턴트 볼륨을 선택하기 위한 조건 명시로 accessMode, 볼륨 크기 등이 이런 조건에 해당한다.

accessMode와 볼륨 크기, 스토리지 클래스, 라벨 셀렉터를 이용한 퍼시스턴트 볼륨을 선택할 수 있다.

  • ReadWriteOnce(RWO) -> 1:1 마운트만 가능, 읽기 쓰기 가능
  • ReadOnlyMany(ROX) -> 1:N 마운트 가능, 읽기 전용
  • ReadWriteMany(RWX) -> 1:N 마운트 가능, 읽기 쓰기 가능

 

storageClassName 이름을 사용하면 일치하는 퍼시스턴트 볼륨과 퍼시스턴트 볼륨 클레임이 서로 연결된다.

또는 라벨 셀렉터를 사용할 수도 있다. labels <-> selector.matchLabels

 

 

Recliam Policy

퍼시스턴트 볼륨은 처음에 생성했을 때 Status가 Available 이었다가 퍼시스턴트 볼륨 클레임을 연결하면 Bound로 변경됐는데, 퍼시스턴트 볼륨 클레임을 삭제하면 다시 Available이 되는 것이 아니라 Released로 변경된다.

Released 상태의 퍼시스턴트 볼륨은 다시 사용할 수 없지만 데이터는 볼륨안에 보존되어 있다.

 

퍼시스턴트 볼륨의 사용이 끝났을 때 해당 볼륨을 어떻게 초기화할 것인지 별도로 설정할 수 있는데, 이를 Reclaim Policy라고 한다. Retain, Delete, Recycle 방식이 있으며, 기본적으로 데이터를 보존하는 방식인 Retain을 사용한다.

Delete는 PV도 함께 삭제되는 방식이며, Recycle은 deprecated 될 예정이다.

 

다이나믹 프로비저닝과 스토리지 클래스

다이나믹 프로비저닝은 퍼시스턴트 볼륨 클레임이 요구하는 조건과 일치하는 퍼시스턴트 볼륨이 존재하지 않는다면 자동으로 퍼시스턴트 볼륨과 외부 스토리지를 함께 프로비저닝 하는 기능이다.

 

<storageclass-slow.yaml>

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: slow
provisioner: kubernetes.io/aws-ebs
parameters:
  type: st1
  fsType: ext4
  zones: ap-northeast-2a

<storageclass-fast.yaml>

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: fast
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp2
  fsType: ext4
  zones: ap-northeast-2a

 

스토리지 클래스 생성

$ kubectl apply -f storageclass-slow.yaml
$ kubectl apply -f storageclass-fast.yaml
$ kubectl get sc

 

<pvc-fast-sc.yaml>

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-fast-sc
spec:
  storageClassName: fast
  accessModes:
    ReadWriteOnce
  Resources:
    requests:
      storage: 1Gi
 $ kubectl apply -f pvc-fast-sc.yaml

원래라면 퍼시스턴트 볼륨이 없기 때문에 퍼시스턴트 볼륨 클레임은 계속 pending 상태로 남아있어야 하지만 다이나믹 프로비저닝을 통해 동적으로 SSD 타입의 EBS가 생성되고, 이로부터 퍼시스턴트 볼륨이 생성된다.

 

 

인프런

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-03
spec:
  capacity:
    storage: 2G
  accessModes:
  - ReadWriteOnce
  local:
    path: /node-v
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - {key: kubernetes.io/hostname, operator: In, values: [k8s-node1]}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-01
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1G
  storageClassName: ""
apiVersion: v1
kind: Pod
metadata:
  name: pod-volume-3
spec:
  containers:
  - name: container
    image: kubetm/init
    volumeMounts:
    - name: pvc-pv
      mountPath: /mount3
  volumes:
  - name : pvc-pv
    persistentVolumeClaim:
      claimName: pvc-01

 

References:

http://www.yes24.com/Product/Goods/84927385

 

https://kubetm.github.io/k8s/03-beginner-basic-resource/volume/

 

Volume

emptyDir, hostPath, PV/PVC

kubetm.github.io

 

반응형