BACKEND/Docker & Kubernetes

Kubernetes Storage, 제대로 이해하기

gngsn 2024. 6. 10. 00:00

 

본 포스팅에서는 Kubernetes의 저장소를 구성하는 방법인 PV & PVC 의 개념을 이해하고 적용할 수 있도록 하는 목표를 가집니다.

 

🔗  Kubernetes Series

모든 Kubernetes 시리즈를 확인하시려면 위를 참고해 주세요.

 

 


 

 

쿠버네티스의 저장소에 대해서 알아보도록 하겠습니다.

 

 

 

Volumes

Pod는 내부에서 컨테이너 들이 실행되며, 서로 CPU, RAM, Network Interface 등 리소스를 공유합니다.

그렇다면, 파일 시스템도 동일할까요?

 

다른 리소스와 다르게, Pod 내부의 각 컨테이너는 각각의 분리된 파일 시스템을 가집니다.

컨테이너 마다의 파일 시스템은 컨테이너 이미지에서 제공하기 때문입니다.

 

Docker Volume과 동일하게, 

쿠버네티스의 컨테이너 내 디스크에 있는 파일은 임시적입니다. 

이미지 레이어으로 부터 Copy-On-Write 전략을 통한 컨테이너 레이어를 생성하기 때문이죠.

자세한 내용은 Docker Volume, 제대로 이해하기 를 참고하실 수 있습니다.

 

 

Pod 생명 주기 == Contianer Layer의 파일 생명 주기

 

 

 

컨테이너 레이어의 데이터는 수정할 수는 있지만 영구적으로 저장되지 않습니다.

이로 인해, 컨테이너가 크래시하면 파일이 손실되거나,

여러 컨테이너 간 파일 공유 불가능하다는 문제점이 있죠.

 

Kubernetes는 Volume을 통해 위 두 문제를 해결합니다.

 

Pod 생명 주기 ≠ Contianer Layer의 파일 생명 주기

 

 

쿠버네티스의 볼륨은 Pod의 구성요소로 컨테이너와 동일하게 Pod spec에서 정의됩니다.

볼륨은 Pod의 모든 컨테이너에서 사용 가능하지만 접근하려는 컨테이너에서 각각 마운트 되어야 합니다.

각 컨테이너의 어떤 경로에나 볼륨을 마운트할 수 있습니다.

 

예를 들어, 아래와 같이 볼륨을 정의하고 Pod에서 사용합니다.

 

apiVersion: v1
kind: Pod
metadata:
  name: volume-demo
spec:
  containers:
    - image: alpine
      name: alpine
      volumeMounts:
        - mountPath: /opt
          name: data-volume
  volumes:
    - name: data-volume
      hostPath:
        path: /data
        type: Directory

 

 

.spec.volumes 하위에 data-volume 볼륨을 정의하고,

.spec.containers.volumeMounts 를 통해 생성한 볼륨을 마운트합니다.

 

 

 

 

위 볼륨은 hostPath 타입으로, 호스트 노드의 파일 시스템Pod의 파일을 연결하는 볼륨 타입입니다.

단일 노드에서는 문제가 없지만, 다중 노드 클러스터에서는 권장되지 않습니다.

다중 노드에서는 각 서버, 즉 host가 다르기 때문에 데이터를 공유할 수 사용할 수 없기 때문이죠.

 

그럼, 어떤 Volume Types 들을 사용해야 할까요?

 

 

 

Volume Types

Kubernetes는 다양한 Volume Types을 제공합니다.

 

 

https://www.linkedin.com/posts/mmumshad_kodekloud-kubernetes-cloudcomputing-activity-7155899563218759680-QYWM/

 

 

 

자세한 내용은 🔗 공식 문서 - volume types 를 참고할 수 있으며, 간단히 훑어 보자면 아래와 같습니다.

 

- emptyDir: 일시적인 데이터를 저장하는 데 사용되는 간단한 빈 디렉터리
hostPath: 워커 노드의 파일 시스템을 파드의 디렉터리로 마운트하는데 사용
nfs: NFS 공유 파드에 마운트
gcePersistentDisk(GCE Persistent Disk), awsElasticBlockStore(AWS EBS Volume), azureDist(MS Azure Disk Volume): 클라우드 제공자의 전용 스토리지를 마운트
cinder, cephfs, iscsi, glusterfs, quobyte, rbd, flexVolume, vsphereVolume, photonPersistentDisk: 다른 유형의 네트워크 스토리지를 마운트
configMap, secret, downwardAPI: 쿠버네티스 리소스나 클러스터 정보를 파드에 노출하는 데 사용되는 특별한 유형의 볼륨
- persistentVolumeClaim: 사전에 혹은 동적으로 프로비저닝된 퍼시스턴트 스토리지를 사용하는 방법

 


클라우드 사용하는 예를 들어보자면, AWS EBS를 사용하려면 다음과 같이 설정할 수 있습니다.

 

...
volumes:
  - name: data-volume
    awsElasticBlockStore:
      volumeID: <volume-id>
      fsType: ex4

 

 

 

 

 

 

Persistent Volumes

하지만 이렇게 매번 Pod 정의 파일에 Volume을 지정하면,

많은 포드를 배포할 때마다 각각의 Pod에 저장소를 구성해야 합니다.

변경 사항이 있을 때마다 모든 Pod에 수정 사항을 작성하는 대신, 중앙에서 관리하는 것이 더 효율적입니다.

 

Persistent Volume을 사용하여 큰 저장소 풀을 생성하고,

필요에 따라 각 Pod에서 이를 사용할 수 있도록 설정할 수 있습니다.

 

Kubernetes에서 Persistent Volumes(이하 Persistent Volumes / PV )를 설정하는 방법은 아래와 같습니다.

 

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-vol1
spec:
  accessModes:
    - ReadWriteOnce
  capacity:
    storage: 1Gi
  hostPath:
    path: /tmp/data

 

 

PV 객체를 생성하기 위해 kubectl create 명령어를 사용할 수 있습니다.

 

❯ kubectl create -f pv-definition.yaml
persistentvolume/pv-vol1 created
❯ kubectl get persistentvolume
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
pv-vol1   1Gi        RWO            Retain           Available                          <unset>

 

 

혹은, 외부 클라우드 저장소를 사용하려면 다음과 같이 설정할 수 있습니다.

 

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-vol1
spec:
  accessModes:
    - ReadWriteOnce
  capacity:
    storage: 1Gi
  awsElasticBlockStore:
    volumeID: <volume-id>
    fsType: ext4

 

 

 

관리자가 Persistent Volume 이라는 스토리지 객체를 만들어 두면,

원하는 사용자(개발자)가 이를 가져다 사용할 수 있게 만들어 둡니다.

 

 

 

 

 

 

클러스터 내에서는 관리자에 의해 만들어진 Persistent Volume가 여러 개 존재할 수 있습니다.

그럼, 사용자는 어떤 Persistent Volume 를 사용하고 싶은지 요청을 해야하죠.

이를 Persistent Volume Claim을 통해 구현합니다.

일종의 스토리지 요청서와 같습니다.

 

 

 

 

 

 

 

Persistent Volume Claim

Persistenet Volume Claim 는 사용자가 요구하는 일종의 저장소 청구서입니다.

(이하 Persistenet Volume Claim / PVC ) 

 

비유를 해보자면, PVC는 Pod와 비슷한 개념으로,

Pod가 Node 리소스를 소비하는 것처럼,

PVC는 PV 리소스를 소비합니다.

 

Pod     request →  Node Resources
PVC    ⎯ request →  PV

 

 

관리자는 Persistent Volume 세트를 만들고,

사용자는 해당 저장소를 사용하기 위해 persistent volume claims을 생성합니다.

 

 

 

 

 

 

Persistent Volume Claims 이 생성되면

쿠버네테스가 볼륨에 설정된 요청과 속성에 따라 claims 에 persistent volume을 묶습니다.

 

 

 

 

 

 


PVC는 특정 크기와 액세스 모드를 요청할 수 있습니다.

 

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 500Mi

 

 

위의 예시는 읽기 모드(ReadWriteOnce)로 설정 및 리소스를 설정해 500Mi의 저장 공간 요청합니다.

accessMode 는 아래 PV & PVC 섹션에서 다룹니다.

 

이후 kubectl get persistentvolumeclaim 명령어로 PVC를 생성합니다.

PVC를 생성하고 나서,kubectl get persistentvolumeclaim 명령어로 확인할 수 있습니다.

 

❯ kubectl create -f pvc-definition.yaml
persistentvolumeclaim/myclaim created

❯ kubectl get persistentvolumeclaim
NAME      STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
myclaim   Pending                                      standard       <unset>                 9s

 

이전에 생성한 PV의 accessModes가 일치하며, storage 용량이 충분하면 매칭됩니다.

 

 

 

 

PV & PVC

이 때, Persistent Volume 과 Persistent Volume Claim 은 어떻게 매칭될까요?

 

클레임은 특정 크기 및 액세스 모드를 요청할 수 있는데, 

ReadWriteOnce, ReadOnlyMany, ReadWriteMany, ReadWriteOncePod 가 될 수 있습니다.

 

 

Access Modes

✔️ ReadWriteOnce

: RWO. 볼륨은 읽기/쓰기 모드에서 단일 작업자 노드로 적용 가능,

하나의 워커 노드에 마운트되면 다른 노드는 마운트할 수 없음

 

✔️ ReadOnlyMany

: ROX. 볼륨은 읽기 전용 모드로 여러 작업자 노드에 동시에 적용 가능

 

✔️ ReadWriteMany

: RWX. 볼륨은 동시에 여러 작업자 노드에 읽기/쓰기 모드로 적용 가능

 

✔️ ReadWriteOncePod

: RWOP. 하나의 Pod만 읽기/쓰기 모드로 적용 가능, Kubernetes 1.22+ 버전부터 지원

 

 

 


PVC은 단 하나의 PV에 묶이게 됩니다.

바인딩 과정에서 쿠버네티스는 사용자의 요청 만큼 PV가 충분한 용량을 가졌는지 확인하는데요.

용량 뿐만 아니라, Sufficient Capacity, Access Modes, Volume Modes, Storage Class 등을 체크합니다.

 

PVC가 생성되면, Kubernetes는 설정된 요청과 속성에 따라 PV를 매칭합니다.

모든 PVC는 단일 PV로 매칭되며, 매칭 조건이 맞으면 바인딩됩니다.

사용 가능한 PV가 없으면, PVC는 보류 상태로 대기합니다.

 

 

 

Kubernetes in Action

 

 

 

PVC를 Pod, ReplicaSets, Deployments에 정의하여 사용할 수 있습니다.

 

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: myfrontend
      image: nginx
      volumeMounts:
        - mountPath: "/var/www/html"
          name: mypd
  volumes:
    - name: mypd
      persistentVolumeClaim:
        claimName: myclaim

 

 

이와 같이 설정하면, Pod가 PVC를 통해 PV를 사용하게 됩니다.

 

 

만약, 단일 클레임에 매치 가능한 게 여러 개 있는데,

특정 볼륨을 지정하고 싶다면 Label을 사용할 수 있습니다.

 

 

 

 

 

마지막으로, 다른 모든 매칭 조건이 일치하고 더 나은 옵션이 없는 경우, 더 작은 Claim이 더 큰 Volume 으로 묶일 수 있습니다.

Claim과 Volume 은 일대일 관계이기 때문에, 다른 클레임은 볼륨의 남은 용량을 활용할 수 없습니다.

 

 

 

Delete PVC

PVC를 삭제하려면 kubectl delete 명령어를 사용합니다.

 

❯ kubectl delete persistentvolumeclaim myclaim

 

 

이 때, PVC 가 삭제되면 PV는 어떻게 될까요?

기본 값은, PV는 유지되고 관리자에 의해 삭제될 때까지 유지됩니다.

이 설정은 변경할 수 있는데요.

PersistentVolumeReclaimPoliy를 설정하여 PVC 삭제 후 PV를 관리 정책을 설정합니다.

 

 

 

PersistentVolumeReclaimPolicy

🔗 kubernetes.io - Persistent Volumes: Reclaiming

 

 

PVC가 삭제된 후 PV를 어떻게 처리할지는 persistentVolumeReclaimPolicy에 정의할 수 있습니다.

 

- Retain: 기본값. PV가 유지되며 관리자가 수동으로 삭제할 때까지 남아 있습니다.

- Delete: PVC가 삭제되는 순간 PV도 자동으로 삭제됩니다.

- Recycle: Deprecated. 기본 스크럽을 수행하고 새 클레임에 대해 다시 사용할 수 있도록 합니다.

 

 

⚠️ The Recycle reclaim policy is deprecated. Instead, the recommended approach is to use dynamic provisioning.

 

 

 



CSI - Container Storage Interface

🔗 CSI Spec

 

CSI 컨테이너 스토리지 관리를 위한 API 공통 사양을 정의입니다.

다양한 컨테이너 오케스트레이션 시스템에서 호환성을 유지하기 위해 만들어졌습니다.

Kubernetes는 CSI를 구현하여 기본 스토리지 인프라에 대한 특정 지식 없이도 스토리지 볼륨을 관리할 수 있습니다.

 

쿠버네티스 CSI은 기본적으로 프로비저너(Provisioner), 어태쳐(Attacher), 컨트롤러(Controller), 노드서버(NodeServer)로 이루어져 있습니다.

 

Provisioner

프로비저너는 클러스터에 PVC가 생성되는 걸 모니터링하고 있다가 PVC가 생성되면 PV를 생성하는 걸 담당합니다.

 

Attacher

어태처는 파드가 PVC를 사용하려할 때 해당 컨테이너에서 PV를 마운트하는 걸 담당합니다.

 

Controller

컨트롤러는 쿠버네티스 컨테이너에서 사용할 볼륨을 스토리지 서버에서 생성 및 삭제하는 걸 담당합니다.

 

NodeServer

노드서버는 파드가 배포될 노드에서 스토리지 볼륨에 마운트할 수 있게 환경을 만드는 걸 담당합니다.

 

 

 

 

Storage Class

Static Provisioning Volumes

 

위 방식에서는 PV 를 작성하고, PVC를 생성한 후, Pod 에 사용할 PVC를 명시하는 과정을 거쳤습니다.

하지만, 여전히 실제 스토리지를 미리 프로비저닝 해야하는 번거로운 작업이 필요합니다.

 

가령 Google Cloud에 연결한다고 하면,

1. PV를 생성하기 전에 Google Cloud에서 디스크를 먼저 생성하고

2. 저장소가 필요할 때마다 Google Cloud에서 수동으로 디스크를 프로비저닝해야 하며,

3. 생성한 디스크와 같은 이름을 사용하여 수동으로 PersistentVolume 정의 파일을 만들어야 합니다.

 

지금까지 살펴본 내용,

PV를 정의해서 PVC에 명시해 사용하는 방식Static Provisioning Volumes라고 합니다.

 

 

 

Dynamic Provisioning

쿠버네티스는 PV의 동적 프로비저닝(Dynamic Provisioning)을 지원합니다.

덕분에 이 일련의 작업을 자동으로 수행할 수 있습니다.

관리자가 PV를 생성하는 대신 PV 프로비저너(Provisioner)를 배포하고,

사용자가 선택 가능한 PV 타입을 하나 이상의 Storage Class(SC) 객체로 정의할 수 있습니다.

 

사용자가 PVC에서 PV가 아닌 StorageClass를 참조하면,

Provisioner가 퍼시스턴트 스토리지를 프로비저닝할 때 이를 자동으로 처리합니다.

 

사실 대부분의 클라우드에서 Provisioner를 포함하는데,

덕분에 PVC에서 StorageClass만 정의하면 클레임 요청시 시스템이 알아서 PV를 생성합니다.

 

이제 PV를 직접 생성할 필요가 없습니다.

StorageClass에 의해 자동으로 생성됩니다.

 

 

PersistentVolume 을 정의하는 대신 StorageClass를 정의

 

 

StorageClass를 정의하게 되면 아래의 순서로 실제 Pod에 매핑됩니다.

 

StorageClass → PersistentVolumeClaim

PVC에서 정의한 StorageClass를 이름을 지정합니다.

그래야 PVC가 어떤 StorageClass를 사용할지 알 수 있습니다.

 

PersistentVolumeClaim → Pod
PVC가 생성되면, StorageClass는 정의된 프로비저너를 통해 요청된 사이즈에 맞게 디스크를 프로비저닝합니다.

그 다음 PersistentVolume을 생성해 그 볼륨에 PVC를 묶습니다.

 

⚠️ 수동으로 PV를 생성할 필요는 없지만, 여전히 StorageClass에 의해 PV가 생성됩니다.

 

 

 

쿠버네티스에서는 AWS EBS, Azure File, Azure Disk, CephFS, Portworx, ScaleIO 같은 다양한 프로비저너를 사용할 수 있습니다.

 

각 프로비저너에 추가적인 매개 변수를 전달할 수 있습니다.

예를 들어, 프로비저닝 디스크 유형이나 복제 유형 등을 지정할 수 있습니다.

 

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: google-storage
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-standard
  replication-type: none

 


Google Persistent Disk에서는 표준 또는 SSD로 유형을 지정할 수 있습니다.

각각 다른 유형의 디스크를 사용하여 다양한 StorageClass를 만들 수 있습니다.

 

가령,
- 표준 Disk인 silver StorageClass

- SSD 드라이브가 포함된 gold StorageClass

- SSD 드라이브 및 복제가 포함된 platinum StorageClass

 

StorageClass

 

 


이렇게 다양한 서비스 클래스를 만들 수 있기 때문에 StorageClass라고 합니다.
다음에 PVC를 생성할 때, 필요한 저장소 클래스를 지정하기만 하면 됩니다.