'2026/02/11'에 해당되는 글 1건

영구 볼륨(Persistent Volume)은 컨피그 & 스토리지 API가 아닌, 클러스터 API 카테고리에 해당하는 리소스이다. 별도의 매니페스트로 PV 리소스를 생성한 후 사용한다. 과거에는 영구 볼륨의 대표적인 플러그인 종류가 아래와 같이 다양하게 존재했지만, 현재는 CSI 방식으로 통일되었다.

  • GCE Persistent Disk
  • AWS Elastic Block Store
  • Azure File
  • nfs
  • iSCSI
  • Ceph(RBD, CephFS)
  • OpenStack Cinder
  • GlusterFS
  • Container Storage Interface(CSI)

자세한 것은 공식 문서 를 참고하며, 현재 표준인 Container Storage Interface 에 대해서만 좀 더 알아본다.

 

1. Container Storage Interface

초창기 kubernetes 는 Google, AWS, Azure 같이 유명한 kubernetes provider 에 대해서 각각의 스토리지 드라이버 소스코드를 kubernetes 소스코드 저장소에 함께 관리하고, 실제 쿠버네티스 바이너리에도 포함하여 배포하였다. 쿠버네티스 사용자가 별도의 벤더사 제공 드라이버 설치 없이도 바로 스토리지를 사용할 수 있다는 편리함이 있었지만, 아래와 같은 치명적인 단점이 존재했다.

  • 특정 벤더사의 스토리지 드라이버에 버그가 있더라도, 쿠버네티스 전체 코드를 함께 검토하고 버저닝을 해야 함
  • 수십개의 벤더사 코드를 포함하여 바이너리가 너무 무거워짐
  • 핵심 코드 내에 외부 벤더사의 코드가 섞여 보안 검증이 힘듦

이러한 단점들 때문에 Container Storage Interface 라는 영구 볼륨 플러그인이 생겼고, 현재는 이 CSI 의 표준에 맞추어 각 벤더사가 자체의 스토리지 드라이버를 개발하여 제공하는 형식이 되었다. 즉, 벤더사의 드라이버는 이제 스토리지 개발사가 직접 개발/관리하도록 된 것이다.

 

2. 볼륨으로 사용할 파일 시스템 구성하기(영구 디스크 설정)

퍼블릭 클라우드 프로바이더가 아닌 직접 로컬 PC에 구축한 k8s 환경을 기준으로 생성해본다. 퍼블릭 클라우드처럼 스토리지 클래스를 지정하면 자동으로 볼륨이 생성되는 "동적 프로비저닝"을 사용하기 위해서 Longhorn 이라는 오픈소스 분산 블록 스토리지 시스템을 사용한다. 이를 사용하면 여러 노드의 여유 디스크 공간을 논리적으로 묶어 하나의 Storage Pool로 만든다. 파드가 띄워진 노드가 변경되어도 변함없이 데이터에 접근이 가능하며, 복제, 백업, 스냅샷, 고가용성을 챙길 수 있다. 위에서 언급했듯이 쿠버네티스에서는 CSI 인터페이스를 통해서만 요청하며, 쿠버네티스 사용자가 Longhorn CSI 드라이버를 직접 설치하여야 한다.

 

2.1 Longhorn 으로 설치(리소스가 충분한 경우)

 

2.1.1 종속성 설치(모든 노드)

Longhorn 은 노드끼리 데이터를 주고받기 위해 NFS 와 iSCSI 프로토콜을 사용한다. control plane 을 포함한 모든 노드에서 아래 명령어를 실행한다.

$ sudo apt update
$ sudo apt install -y open-iscsi nfs-common

 

2.1.2 각 노드에서 iscsid 서비스가 실행중인지 확인

$ sudo systemctl enable --now iscsid

 

2.1.3 환경 체크(control-plane 에서만)

Longhorn 에서 제공하는 아래 환경 체크 스크립트를 실행한다. 에러가 나오지 않아야 한다.

$ curl -sSfL https://raw.githubusercontent.com/longhorn/longhorn/v1.5.3/scripts/environment_check.sh | bash

 

2.1.4 Longhorn 설치(control-plane 에서만)

$ kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.5.3/deploy/longhorn.yaml

 

2.1.5 설치 확인

아래 커맨드로 CSI 관련 파드들이 정상적으로 떴는지 확인한다. 모든 파드가 Running 상태가 될 때까지 대기한다.

$ kubectl get pods -n longhorn-system -w

 

2.1.6 대시보드 접속

아래 명령어로 port-forward 하여 브라우저에 접속할 수 있게 구성 후, http://<마스터노드-IP>:8080 접속해본다.

$ kubectl port-forward -n longhorn-system svc/longhorn-frontend 8080:80 --address 0.0.0.0

 

2.1.7 StorageClass 확인

longhorn 네임스페이스에 StorageClass 가 생성되었는지 확인한다. 앞으로 PV 생성시 이 StorageClass 를 사용하면 된다.

$ kubectl -n longhorn get storageclasses
NAME                 PROVISIONER          RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
longhorn (default)   driver.longhorn.io   Delete          Immediate           true                   2m21s
longhorn-static      driver.longhorn.io   Delete          Immediate           true                   2m17s

 

2.1.8 동적 프로비저닝 테스트

(나는 여기까지 하다가 맥북의 메모리가 부족하여 NFS 방식으로 전환하였다.)

 

 

2.2 NFS(Network File System) 로 설치(리소스가 부족한 경우)

 

2.2.1 NFS 서버 설치(control-plane 에서만)

NFS 서비스 패키지를 설치하고, 공유할 디렉토리를 생성한다.

# 1. 패키지 설치
$ sudo apt update
$ sudo apt install -y nfs-kernel-server

# 2. 공유할 디렉토리 생성 (예: /srv/nfs/kubedata)
$ sudo mkdir -p /srv/nfs/kubedata
$ sudo chown nobody:nogroup /srv/nfs/kubedata
$ sudo chmod 777 /srv/nfs/kubedata

 

2.2.2 NFS 설정(control-plane 에서만)

공유를 허용할 네트워크 대역을 설정 파일에 등록한다.(실습환경이므로 * 으로 전체 허용)

우분투에서 /etc/exports 파일은 NFS 서버가 어떤 디렉터리를 어떤 클라이언트에게, 어떤 권한으로 공유할지 정의하는 핵심 설정 파일이다. 자세한건 검색해보자.

# 설정 파일 열기
$ sudo vi /etc/exports

# 아래 내용 작성
# 문법: <공유할_디렉터리> <클라이언트>(옵션)
# 예시: /srv/nfs 192.168.1.0/24(rw,sync,no_subtree_check)
/srv/nfs/kubedata *(rw,sync,no_subtree_check,no_root_squash)

 

2.2.3 NFS 서비스 적용(control-plane 에서만)

아래 커맨드로 NFS 설정을 활성화한다.

# 수정된 exports 설정 반영
$ sudo exportfs -ra

# NFS 서버 서비스 재시작 및 활성화
$ sudo systemctl restart nfs-kernel-server
$ sudo systemctl enable nfs-kernel-server

 

2.2.4 클라이언트 패키지 설치(모든 worker 노드에서만)

각 worker 노드에 NFS 관련 도구를 설치한다.

$ sudo apt update
$ sudo apt install -y nfs-common

 

2.2.5 연결 테스트(worker 노드 중 한곳에서)

worker 노드에서 master 노드의 NFS 공유 디렉토리가 정상적으로 접근되는지 확인한다.

$ showmount -e <master노드IP>
Export list for 192.168.0.36:
/srv/nfs/kubedata *

 

3. 영구 볼륨 생성

k8s 에서 영구 볼륨을 생성할 때는 아래와 같은 항목들을 설정할 수 있다.

  • 레이블: metadata.labels
  • 용량: spec.capacity.storage
  • 접근 모드: spec.accessModes
  • Reclaim Policy: spec.persistentVolumeReclaimPolicy
  • 마운트 옵션: spec.mountOptions
  • 스토리지 클래스: spec.storageClassName
  • 각 플러그인 고유 설정: spec.nfs 등

아래는 nfs 볼륨 플러그인을 사용하여 영구 볼륨을 구성하는 매니페스트이다.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: sample-pv-nfs
  labels: # 파드가 특정 PV를 찾을 수 있게 돕는 이름표
    type: nfs
    env: practice
spec:
  capacity:
    storage: 500Mi # 사용할 용량
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual # 수동으로 만든 PV임을 명시 (중요)
  mountOptions: # NFS 마운트 최적화 옵션
    - nfsvers=4.1 # NFS 버전 명시
    - hard # 서버 장애 시 응답이 올 때까지 대기 (데이터 보호)
    - timeo=600 # 타임아웃 설정
    - retrans=2 # 재전송 횟수
  nfs:
    path: /srv/nfs/kubedata # 마스터 노드에서 만든 경로
    server: 192.168.0.36 # 마스터 노드 IP 주소

PV 생성 후 아래 커맨드로 조회해본다. STATUS 항목이 Available 로 뜨면 사용 준비가 된 것이다.

$ kubectl get pv sample-pv-nfs
NAME            CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
sample-pv-nfs   500Mi      RWO            Retain           Available           manual         <unset>                          41s

 

PV 에 설정하는 각 항목을 살펴보자

 

3.1 레이블

동적 프로비저닝(StorageClass 를 지정하여 PVC 리소스를 생성하는 타이밍에 PV 를 동적으로 생성하여 할당하는 기법)을 사용하지 않고, 미리 영구 볼륨을 생성해두고 나중에 사용하려는 경우 영구 볼륨을 식별하기 어렵기 때문에 type, env, environment, speed 등의 레이블을 사용하면 편리하다.

 

3.2 용량

여기서 지정하는 용량은 실제 디스크의 공간을 "예약" 하지도, "보장" 하지도 않는다. PVC <- PV 할당을 위한 스케쥴링/바인딩을 위한 논리값일 뿐이다. 실제 파일시스템의 용량과 무관하다. 파일을 실제 write 할 때 그만큼만 디스크 공간이 사용된다.

PV에 용량을 지정할 때 주의할 점은, PVC 에서 요청하는 용량 이상 중 가장 낮은 용량을 가진 PV 가 할당된다는 것이다. 만약 1Gi, 20Gi 의 PV 가 있고, PVC 에서 2Gi 를 요청하는 경우에 무려 20Gi 의 PV 가 할당된다는 점이다.

 

3.3 접근 모드

접근 모드는 아래 3가지가 있다.

단축어 설명
ReadWriteOnce RWO 단일 노드에서 Read/Write 가능
ReadWriteMany RWX 여러 노드에서 Read/Write 가능
ReadOnlyMany ROX 여러 노드에서 Read 가능

이 중에서 ReadOnlyMany 를 사용할때는 주의할 점이 있다. 여러 노드에서 해당 볼륨에 읽기 작업을 해야하는데, 그 중 하나라도 쓰기 요청하는 파드가 있다면 다른 노드에서 볼륨 마운트가 불가능해진다. 따라서 이 볼륨을 사용하는 모든 파드에서는 영구 볼륨 클레임을 지정할 때 readOnly: true 로 지정해야 한다. 헷갈릴 수 있는데, 컨테이너 템플릿의 볼륨 마운트 설정인 spec.containers[].volumeMounts[].readOnly 항목이 "아니라" 볼륨 설정인 spec.volumes[].persistentVolumeClaim.readOnly 를 true 로 지정해야 한다.

ReadWriteMany 는 nfs 같은 볼륨 플러그인에서 주로 사용한다. ReadWriteMany 를 지원하지 않는 볼륨 플러그인도 있음에 주의하자.

 

3.4 Reclaim Policy

PVC(영구 볼륨 클레임)에서 PV를 사용하고 난 후 PVC가 삭제되었을 때 PV의 처리 정책을 설정하는 것이다. 즉, 바인딩이 해제된 뒤의 처리 방식을 결정한다. spec.persistentVolumeReclaimPolicy 항목으로 설정하며, 아래 값이 올 수 있다.

설명 상태 변경 흐름
Delete 삭제한다. 동적 프로비저닝에서 사용하는 경우가 많다. 단, 동적 프로비저닝을 사용하지 않으면서 삭제 컨트롤러가 존재하지 않으면 자동 삭제되지 않는다.(Static NFS 같은 경우) Bound → PVC 삭제 → Released → PV 삭제 → 스토리지 데이터 삭제
Retain 유지한다. Released 상태가 된다. 하지만 또다른 PVC에 의해 자동으로 재사용(마운트)되지는 않는다. Bound → PVC 삭제 → Released → (수동 처리)
Recycle 유지한다. 단, 스토리지 데이터는 삭제(rm -rf ./*)한다. Available 상태가 되므로 또다른 PVC에 의해 자동으로 재사용될 수 있다. 하지만, deprecated 이므로 사용 지양. Bound → PVC 삭제 → 스토리지 데이터 삭제 → Available

Released 상태의 PV 는 PVC 에서 바인딩하지 않는다는 사실을 기억하자. Retain 의 경우에는 실제 스토리지 데이터도 삭제되지 않기 때문에, 새로운 PV 를 생성하여 데이터를 유지할 수 있다.

 

3.5. 마운트 옵션

마운트 옵션은 영구 볼륨 플러그인에 따라 다르기 때문에 각 사양을 확인해보자.

 

3.6 스토리지 클래스

PV의 스토리지 클래스 항목은 해당 PV가 어떤 스토리지 클래스를 통해서 바인딩되는지에 대한 항목이다. 동적 프로비저닝시에는 해당 스토리지 클래스의 CSI provisioner 에 의해 PV 가 자동 생성된다. 수동으로 PV를 관리하는 경우에는 해당 스토리지 클래스의 provisioner 는 kubernetes.io/no-provisioner 가 될 것이며, PVC 와 PV 모두에 스토리지 클래스 이름을 지정해주어야 한다.

아래는 스토리지 클래스 자체에 대해서 더 설명하겠다.

StorageClass 는 동적 프로비저닝을 위한 스토리지 정책을 담고 있는 리소스이다. CSI 드라이버를 통해서 PV 를 생성/바인딩 하므로 스토리지 정책을 입힐 수 있으며, 아래와 같은 항목들을 설정할 수 있다.

항목 설명
provisioner 프로비저너 CSI 드라이버를 지정한다. 
parameters 프로비저닝 파라미터를 지정한다. 디스크 타입, 복제 개수, 파일시스템 유형, NFS 서버 주소, 스토리지 풀 이름 등을 프로비저너 CSI 드라이버로 전달할 수 있다.
reclaimPolicy PVC 삭제시 PV 처리 정책을 지정한다.(Delete / Retain)
volumeBindingMode PV 바인딩 시점을 지정한다.(Immediate / WaitForFirstConsumer)
allowVolumeExpansion PVC 용량 확장 허용 여부를 지정한다.

PVC의 spec.storageClassName 에 사용할 스토리지 클래스 이름을 지정하면 PVC가 생성될 때 해당 스토리지 클래스를 사용하여 PV를 자동으로 생성하여 바인딩하게 된다.(동적 프로비저닝)

PVC 생성 → StorageClass 참조 → CSI provisioner 호출 → PV 자동생성 → PVC 와 바인딩

동적 프로비저닝을 사용하지 않고도 스토리지 클래스를 사용하려면, 스토리지 클래스의 provisioner 에 kubernetes.io/no-provisioner 를 지정하고, PV 와 PVC 의 spec.storageClassName 에 해당 스토리지 클래스 이름을 지정하면 된다.

아래는 동적 프로비저닝을 사용하지 않는 StorageClass 매니페스트 예시이다.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: manual
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: Immediate

 

3.7 각 플러그인 고유 설정

각 영구 볼륨 플러그인의 사양을 확인해보자.

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,