[kuber-study] Chapter6. Volumes

볼륨이란?

pod안의 각 컨테이너는 컨테이너 이미지에서 가져온 각자의 파일시스템을 갖는다. pod의 컨테이너가 재시작 된다면, 새 컨테이너는 이전 컨테이너에 의해 공유된 어떤 기록도 볼 수 없다.
데이터를 보관하는 디렉토리를 보존하기 위해 쿠버네티스는 볼륨 디렉토리를 제공한다. pod의 구성요소로서 컨테이너와 같이 pod의 사양에 정의되어있고, pod와 생명주기를 함께 한다. 볼륨은 독립된 쿠버네티스의 객체가 아니라서 스스로 생성되거나 삭제될 수 없다.

볼륨 설명

  • Container WebServer : /var/htdocs 디렉토리에서 HTML 페이지를 제공하고, /var/logs에 액세스 로그를 저장하는 웹 서버를 실행
  • Container ContentAgent : HTML 파일을 작성하고 /var/html에 저장하는 에이전트를 실행
  • Container LogRotator : /var/logs에서 찾은 로그들(회전, 압축, 분석 등)을 처리

 좌측의 각 컨테이너는 잘 정의된 단일 작업을 갖지만 웹 서버가 각 파일에 접근할 수 없다. 공유 디스크가 없는 세 컨테이너로 pod를 생성하는 것은 의미가 없다.
 우측과 같이 2개의 볼륨을 pod에 추가하고 컨테이너 내부 경로에 마운트하면 파일시스템의 내용을 마운트된 디렉토리에서 액세스할 수 있다. 이렇게 하면, 3개의 컨테이너는 함께 일할 수 있다.

  • Volume publicHtml : 웹 서버가 서비스할 콘텐츠가 저장되는 볼륨
  • Volume logVol : 로그를 저장하는 볼륨

볼륨 타입

  • emptyDir : 임시 데이터를 저장하는 데 사용되는 간단한 빈 디렉토리
  • hostPath : 워커 노드의 파일 시스템에서 pod로 디렉토리를 마운트할 때 사용
  • gitRepo : Git 저장소의 내용으로 초기화된 볼륨
  • nfs : 기존 NFS 볼륨을 pod에 마운트
  • gcePersistentDisk(Google Compute Engine Persistent Disk), awsElasticBlockStore(Amazon Web Services Elastic Block Store Volume), azureDisk(Microsoft Azure Disk Volume) : 클라우드 공급자별 스토리지를 마운트하는 데 사용
  • cinder, cephfs, iscsi, flocker, glusterfs, quobyte, rbd, flexVolume, vsphereVolume, photonPersistentDisk, scaleIO : 다른 유형의 네트워크 스토리지를 마운트하는 데 사용
  • configMap, secret, downwardAPI : 특정 Kubernetes 리소스 및 클러스터 정보를 pod에 노출하는 데 사용되는 특수 유형
  • persistentVolumeClaim : 사전 또는 동적으로 프로비저닝된 영구 스토리지를 사용하는 방법

단일 pod는 서로 다른 유형의 여러 볼륨을 동시에 사용할 수 있다.


다중 컨테이너 사이 데이터 공유를 위한 볼륨

emptyDir 볼륨

emptyDir 볼륨은 빈 디렉토리로 시작한다. 생명주기를 pod와 함께하기 때문에 볼륨의 내용은 pod가 삭제될 때 같이 사라진다. 같은 pod 안의 컨테이너 사이에서 파일을 공유할 때, 데이터를 일시적으로 기록할 때 사용한다.

  • fortune-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: fortune
spec:
  containers:
  
 - image: luksa/fortune
   name: html-generator
   volumeMounts:
   - name: html
     mountPath: /var/htdocs
 - image: nginx:alpine 
   name: web-server 
   volumeMounts: 
   - name: html 
     mountPath: /usr/share/nginx/html 
     readOnly: true 
   ports:
   - containerPort: 80
     protocol: TCP
 volumes: 
 - name: html 
   emptyDir: {} 

 html이름을 가진 단일 emptyDir 볼륨이 위의 두 컨테이너에 마운트 된다. html-generator에서는 /var/htdocs에, web-server에서는 /usr/share/nginx/html에 읽기 전용으로 마운트 된다.

 출력물을 보려면 로컬에서 pod로 포트포워딩 후 접근
$ kubectl port-forward fortune 8080:80
$ curl http://localhost:8080

  • emptyDir 볼륨의 매체 설정
volumes:
  - name: html
    emptyDir:
      medium: Memory

워커 노드의 실제 디스크 대신 메모리에 볼륨을 생성할 수 있다.

gitRepo 볼륨 : Git 저장소를 볼륨 시작점으로 사용

gitRepo 볼륨은 pod가 시작되고 컨테이너가 생성되기 전에 git 저장소를 복제하는 emptyDir 볼륨의 종류이다. 볼륨이 생성된 후에는 저장소와 동기화되지 않는다. pod가 재생성됐을 때 최신 commit이 포함된 볼륨이 생성된다. emptyDir처럼 pod가 삭제되면 볼륨과 콘텐츠도 함께 삭제된다.

  • gitrepo-volume-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: gitrepo-volume-pod
spec:
  containers:
  - image: nginx:alpine
    name: web-server
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
      readOnly: true
    ports:
    - containerPort: 80
      protocol: TCP
  volumes:
  - name: html
    gitRepo: 
      repository: https://github.com/junghyun326/kubia-website-example.git 
      revision: master 
      directory: . 

pod를 생성하면 볼륨이 빈 디렉토리로 초기화되고, 지정된 git 저장소가 복제된다. master 브랜치를 쿠버네티스가 체크하도록 지정해주고, 볼륨의 root 디렉토리로 복제하고자 .으로 설정해준다.

사이드카 컨테이너

git repo가 변경될 때마다 pod를 삭제할 필요 없이 동기화하는 방법에는 사이드카 컨테이너가 있다. 사이드카 컨테이너는 pod의 메인 컨테이너의 변경 없이 독립적으로 동작하는 컨테이너를 붙여 기능을 향상시키는 컨테이너이다.
Docker Hub에서 git sync를 검색해서 로컬 디렉토리가 git 저장소와 동기화된 상태로 유지되는 컨테이너 이미지를 찾는다. git sync 컨테이너를 구성하여 파일이 git 저장소와 동기화되도록 한다.

  • private git 저장소에서의 gitRepo 사용
    현재 gitRepo 볼륨은 private git 저장소와는 사용할 수 없다. gitRepo 볼륨 대신 git sync 사이드카 또는 유사한 다른 방법을 사용해야 한다.

지속되는 스토리지

대부분의 pod는 호스트 노드를 인식하지 못해서 노드의 파일 시스템에 액세스할 일이 없지만, 특정 시스템 레벨의 pod는 노드의 파일을 읽거나 액세스할 필요가 생긴다.

hostPath 볼륨


hostPath 볼륨은 노드의 파일시스템에 있는 특정 파일 또는 디렉토리를 가리킨다. 같은 노드에서 실행되고 hostPath 볼륨에서 같은 경로를 사용하는 pod들은 같은 파일을 볼 수 있다. pod를 종료할 때 hostPath 볼륨은 삭제되지 않는다.
그러나 hostPath 볼륨은 pod가 설정된 노드에 민감하게 작용해서 동일한 노드에서만 데이터가 유효하다. 때문에 단일 노드 클러스터의 지속되는 스토리지로 시범적으로 사용하는 경우가 많다.

모든 클러스터 노드에서 액세스 하려면 특정한 유형의 NAS(Network-Attached Storage)에 저장해야 한다.

GCE Persistent Disk 볼륨

GCE(Google Compute Engine)에서 클러스터 노드를 실행하는 GKE(Google Kubernetes Engine)에서 pod를 생성한다면, gcePersistentDisk를 스토리지 매커니즘으로 사용한다.

  • GCE persistent disk 생성
    $ gcloud compute disks create --size=1GiB --zone=europe-west1-b mongodb
    1GB사이즈의 디스크를 쿠버네티스 클러스터와 같은 존에 생성한다.

  • mongodb-pod-gcepd.yaml

apiVersion: v1
kind: Pod
metadata:
  name: mongodb 
spec:
  volumes:
  - name: mongodb-data 
    gcePersistentDisk: 
      pdName: mongodb 
      fsType: ext4 
  containers:
  - image: mongo
    name: mongodb
    volumeMounts: 
    - name: mongodb-data 
      mountPath: /data/db 
    ports:
    - containerPort: 27017
      protocol: TCP

persistent disk의 이름은 위에서 만든 PD와 같아야 한다. /data/db에서 컨테이너의 볼륨을 마운트하는데, 그 곳이 MongoDB가 데이터를 저장하는 곳이다.
(minikube에서는 gce Persistent Disk 사용 불가)

이제 MongoDB에 데이터를 저장하면, persistent disk에 저장이 된다. pod를 다른 노드에서 재생성해도 동일한 데이터를 유지할 수 있다. (kubectl get po -o wide로 노드 확인)

gcePersistentDisk 볼륨을 사용한 이유는 쿠버네티스 클러스터가 GKE에서 실행되기 때문이다. 클러스터를 다른 곳에서 실행하면 인프라에 따라 다른 유형의 볼륨을 사용하면 된다.

awsElasticBlockStore 볼륨

AWS Elastic Block Storage에 사용

  • mongodb-pod-gcepd.yaml 수정
apiVersion: v1
kind: Pod
metadata:
  name: mongodb 
spec:
  volumes: 
  - name: mongodb-data 
    awsElasticBlockStore:
       volumeId: my-volume 
       fsType: ext4 
  containers:
  - ...

volumeId는 EBS의 ID로 설정한다.

nfs 볼륨

단순 NFS를 마운트하려면 NFS 서버와 서버에서 노출된 경로를 지정한다.

  • mongodb-pod-gcepd.yaml 수정
  volumes: 
  - name: mongodb-data 
    nfs: 
      server: 1.2.3.4 
      path: /some/path 

이외의 스토리지 기술

  • ISCSI 디스크를 마운트하기 위한 iscsi
  • GlusterFS를 위한 glusterfs
  • RADOS block device를 위한 rbd
  • flexVolume, cinder, cephfs, flocker, fc(fibre channel) 등

볼륨 유형에 대한 자세한 정보는 kubernetes API reference에서 kubernetes API 정의 참고 or `kubectl explain`으로 조회

pod 정의에 스토리지 정보를 정의하는 것의 문제점

인프라 관련 정보를 pod 정의에 포함한다는 것은 특정 쿠버네티스 클러스터에 상당히 연결되어 있음을 의미한다. 또한 동일한 pod 정의를 다른 pod 생성에서 사용하기 어렵다.


스토리지 기술에서 pod 분리

위의 볼륨 타입들은 pod 개발자가 실제 네트워크 스토리지 인프라에 대한 지식을 갖추어야 했다. 쿠버네티스에 애플리케이션을 배포하는 개발자는 시스템 아래에서 어떤 종류의 스토리지 기술이 사용되는지 알 필요가 없다. 개발자는 그저 필요한 스토리지를 쿠버네티스에 요청하면 된다.

PersistentVolumes 와 PersistentVolumeClaims

: 애플리케이션을 통해 인프라 세부 사항을 처리하지 않고도 쿠버네티스 클러스터에 스토리지를 요청할 수 있게 하는 리소스

1) 클러스터 관리자가 기본 스토리지를 설정
2) kubernetes API 서버를 통해 PersistentVolume(PV) 리소스를 생성하여 쿠버네티스에 스토리지를 등록
3) 클러스터 사용자는 필요한 최소 크기 및 액세스 모드를 지정하는 PersistentVolumeClaim(PVC) 매니페스트를 생성
4) 매니페스트를 kubernetes API 서버에 제출하면 쿠버네티스가 적절한 PV를 찾아 PVC를 바인딩
5) PVC를 참조하는 볼륨을 pod 내부 볼륨 중 하나로 사용
바인딩된 PVC를 삭제하여 해제할 때까지 다른 사용자는 동일한 PV를 사용할 수 없다.

PersistentVolume 생성

  • mongodb-pv-gcepd.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mongodb-pv
spec:
  capacity: 
    storage: 1Gi 
  accessModes: 
  - ReadWriteOnce 
  - ReadOnlyMany 
  persistentVolumeReclaimPolicy: Retain 
  gcePersistentDisk: 
    pdName: mongodb 
    fsType: ext4 

해당 볼륨은 단일 클라이언트에게 읽기 쓰기 허용으로 마운트되거나 여러 클라이언트에게 읽기 허용으로 마운트될 수 있다. claim이 해제되어도 PV는 삭제되지 않고 유지(retain)된다. gcePersistentDisk 기반으로 생성된다.

  • PV 목록 확인
    $ kubectl get pv
    - pv : persistentvolume 축약형
    아직 PVC를 만들지 않아서 STATUS가 Available일 것

  • PV는 클러스터 레벨 리소스

    PV는 네임스페이스에 속하지 않아서 클러스터에서 공용으로 사용이 가능하다. PVC는 네임스페이스 객체이기 때문에 네임스페이스에 속한다. 특정 네임스페이스에서만 생성이 가능하고 동일한 네임스페이스에 있는 pod에서만 사용이 가능하다.

PersistentVolumeClaim으로 PersistentVolume 요청

PV를 할당하는 것은 pod를 만드는 것과는 완전히 별개의 프로세스이다.

  • PersistentVolumeClaim 생성을 위한 mongodb-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mongodb-pvc 
spec:
  resources:
    requests: 
      storage: 1Gi 
  accessModes: 
  - ReadWriteOnce 
  storageClassName: "" 

단일 클라이언트에서 읽기 쓰기 가능한 1GB 용량의 스토리지 요청
claim을 생성하는 즉시 쿠버네티스는 요청을 만족하는 PV를 찾아 claim에 바인딩한다.

  • PVC 목록 출력
    $ kubectl get pvc
    • pvc : persistentvolumeclaim의 축약형
    • ACCESSMODES에 사용되는 약어 (pod수가 아니라 워커노드 수와 관련)
      • RWO(ReadWriteOnce) : 단일 노드만 읽기 및 쓰기용 볼륨을 마운트
      • ROX(ReadOnlyMany) : 여러 노드가 읽기용 볼륨을 마운트
      • RWX(ReadWriteMany) : 여러 노드가 읽기 및 쓰기용 볼륨을 마운트

이제 PV의 STATUS는 Bound로 변경되고, CLAIM에 mongodb-pvc가 바인딩 되어있을 것이다.

pod에서 PersistentVolumeClaim 사용

pod에서 PVC를 사용하려면 pod의 볼륨 내에서 PVC를 이름으로 참조한다.

  • mongodb-pod-pvc.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mongodb 
spec:
  containers:
  - image: mongo
    name: mongodb
    volumeMounts:
    - name: mongodb-data
      mountPath: /data/db
    ports:
    - containerPort: 27017
      protocol: TCP
  volumes:
  - name: mongodb-data
    persistentVolumeClaim: 
      claimName: mongodb-pvc 

이전에 MongoDB를 통해 볼륨에 저장한 데이터가 있다면, MongoDB 셸 실행 시 확인이 가능하다.

PersistentVolumes와 Claims 사용의 장점


위 그림은 pod가 GCE Persistent Disk를 사용하는 두 가지 방법(직접 연결과 Claim을 통한 연결)

  • 개발자는 실제 스토리지 기술에 대해 전혀 알 필요가 없다.
  • pod와 claim 매니페스트가 인프라 고유의 어떤 것도 참조하지 않기 때문에 다른 쿠버네티스 클러스터에서도 사용될 수 있다.

PersistentVolumes 재사용

$ kubectl delete pod mongodb
$ kubectl delete pvc mongodb-pvc
$ kubectl get pvc
claim의 STATUS가 Bound가 아닌 Pending으로 표시된다.

$ kubectl get pv
볼륨의 STATUS는 available이 아닌 released로 표시된다.
한 번 사용된 볼륨은 데이터를 포함할 수 있기 때문에 관리자에게 볼륨을 정리할 기회를 주기 위해 released로 표시되고 즉시 바인딩되지 않는다.

  • PV 수동 반환
    persistentVolumeReclaimPolicy를 Retain으로 설정한다. 재활용하기 위해서는 PV를 삭제한 뒤 재생성한다. 기본 스토리지의 파일을 삭제하거나 재사용하도록 그대로 둘 수 있다는 장점이 있다.

  • PV 자동 반환

    • Recycle
      볼륨의 내용을 삭제하고 볼륨을 다시 할당할 수 있도록 한다. PV를 서로 다른 PVC나 pod에서 재사용할 수 있다.
    • Delete
      스토리지를 삭제한다.
    • 각 볼륨 타입은 지원하는 옵션이 다르기 때문에 PV 생성 전에 기본 스토리지에 대해 반환 정책을 확인해야 한다. 지원옵션 안에서 반환 정책 변경이 가능하다.

PersistentVolumes의 동적 프로비저닝

스토리지를 초기에 프로비저닝하려면 클러스터 관리자가 필요한데, 다행히 쿠버네티스에서는 PV의 동적 프로비저닝을 통해 해당 작업을 자동으로 수행할 수 있다.
클러스터 관리자가 PV provisioner를 배포하고 하나 이상의 StorageClass 객체를 정의해서 개발자가 작성한 PVC요청이 있을 때 마다 새 PV를 생성할 수 있게 한다. PV와 마찬가지로 StorageClass 리소스도 namespace에 속하지 않는다.

StorageClass를 통해 스토리지 유형 정의

StorageClass 리소스는 PVC가 이 StorageClass를 요청할 때, PV를 프로비저닝하는 데 사용할 프로비저너를 지정한다.

  • storageclass-fast-gcepd.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast
provisioner: kubernetes.io/gce-pd 
parameters:
  type: pd-ssd 
  zone: europe-west1-b

(minikube 사용 시엔 storageclass-fast-hostpath.yaml 배포)
매개 변수는 프로비저너에게 전달된다.

PersistentVolumeClaim에서 스토리지 클래스 요청

스토리지 클래스 리소스를 생성하면 PVC에서 스토리지 클래스를 이름으로 참조할 수 있다. 존재하지 않는 클래스를 참조할 경우 프로비저닝이 실패한다(ProvisioningFailed 이벤트 발생).

  • mongodb-pvc-dp.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mongodb-pvc
spec:
  storageClassName: fast 
  resources:
    requests:
      storage: 100Mi
  accessModes:
    - ReadWriteOnce

이제 동적 프로비저닝이 가능하다. 기존에 수동으로 프로비저닝된 PV가 일치하는 경우에도 provisioner가 사용된다.

$ gcloud compute disks list 명령어를 사용하여 볼륨 뿐만 아니라 실제 디스크도 프로비저닝된 것을 확인할 수 있다.

스토리지 클래스의 장점

 claim이 이름으로 참조하기 때문에 클래스 이름이 동일한 클러스터 간에는 PVC 정의의 이동이 가능하다.

스토리지 클래스를 지정하지 않은 동적 프로비저닝

PVC를 생성할 때, storageClassName 속성을 지정하지 않고 매니페스트 파일 작성이 가능하다. 그럴 때도 동적 프로비저닝은 가능하다.

  • 스토리지 클래스 목록 확인
    $ kubectl get sc

    • sc : storageclass의 약자
  • 스토리지 클래스의 자세한 정보 확인
    $ kubectl get sc <sc-name> -o yaml

  • Standard 스토리지 클래스
    $ kubectl get sc standard -o yaml 명령어로 standard 클래스 정의를 확인하면, annotation에 default 클래스임을 볼 수 있다. PVC에서 사용할 SC를 지정하지 않은 경우 PV를 프로비저닝하는 데 사용되는 클래스이다.

PVC를 사전에 수동 프로비저닝된 PV에 강제로 바인딩

  • storageClassName을 빈 문자열로 설정
kind: PersistentVolumeClaim
spec:
  storageClassName: ""

storageClassName 속성을 빈 문자열로 설정하지 않으면 프로비저너는 사전에 프로비저닝된 PV이 있어도 새 PV를 프로비저닝했을 것이다.

요약

지속적인 스토리지를 pod에 연결하는 가장 좋은 방법은 PVC(필요하다면 storageClassName을 가진)와 pod(이름으로 PVC를 참조하는)만을 생성하는 것이다. 다른 작업은 동적 PV provisioner가 처리한다.

  • 동적 프로비저닝 이해

    1) 클러스터 관리자가 PV 프로비저너를 설정
    2) 관리자가 하나 이상의 SC를 생성하고 하나는 default로 설정
    3) 클러스터 사용자가 SC를 참조하는 PVC를 생성
    4) 쿠버네티스는 SC와 프로비저너를 참조해서 PVC 요청을 기반으로 새 PV를 생성
    5) 프로비저너가 실제 스토리지를 공급, PV 생성, PVC에 바인드

출처
Kubernetes in Action

Q. persistentVolume 반환 정책 중 볼륨의 내용을 삭제하고 다시 할당할 수 있도록 하는 정책은 무엇인가? 그리고 해당 정책은 자동인가 수동인가?

Q. PersistentVolumeClaim을 새로 생성된 PV가 아닌 사전에 수동 프로비저닝한 PersistentVolume에 강제로 바인딩 하고자한다. 어떻게 설정 가능한가?

좋은 웹페이지 즐겨찾기