PersistentVolume
개요
퍼시스턴트 볼륨.. 다시 말해 영속 쿠버네티스 볼륨.
영구 볼륨을 제공하는 방법 중 하나로서, 오브젝트로 제공된다.
이렇게 만들어진 오브젝트는 PersisetentVolumeClaim(PVC)이라는 오브젝트를 통해 파드에서 볼륨으로 사용한다.
이 문서에서는 PV와 PVC에 대해서 다룰 것이다.
이 둘 다 각각의 오브젝트이기는 하나, 매우 밀접한 관련이 있어서 같이 쓰는 게 좋다고 판단했다.
사용법
일반적으로는 다음의 흐름을 따른다.
- 관리자가 PV를 만든다.
- 사용할 수 있는 볼륨 유형 중 하나를 지정한 후 세부 스펙을 지정하면 된다.
- 사용자는 PVC를 만든다.
- 이때 원하는 사이즈와 접근 모드(한 파드만 접근할 수 있다던가, 한 노드에서만 접근 가능하다던가)만 명시하면 된다.
- 명시적으로 PV를 지정해주는 것도 가능하고, 동적 프로비저닝이 되게 할 수도 있다.
- 이후 사용할 파드에 PVC를 볼륨으로 명시한다.
얼핏 보면, 매우 거추장스러운 과정이 많아 보이기도 한다.
개념 정리
먼저 PV는 관리자에 의해 정적으로, 혹은 스토리지 클래스를 통해 동적 프로비저닝된 쿠버네티스 볼륨이다.
그리고 PVC는 스토리지에 대한 사용자의 요청이다.
그럼 왜 이런 구분이 생겨났을까?
이 내용은 쿠버네티스 아키텍처의 원론적인 내용을 다룬다.
그리고 상당한 뇌피셜을 담고 있다.
아래 [[#PV 라이프사이클]]을 읽으며 개념과 더 친숙해진 이후에 볼 것을 추천한다.
스토리지와 PV의 분리 필요성
PV는 클러스터에서 사용할 수 있는 영속 스토리지 자원의 추상화이다.
클러스터에서 NFS, Amazon EBS, 등 다양한 스토리지를 활용할 수 있다고 생각해보자.
각각의 스토리지마다 인증, 사용량 제한 등 개별적인 설정이 필요할 수 있다.
이것을 사용자들이 사용하기 편하도록, 가상의 볼륨 인터페이스를 통해 공급을 해줄 수 있다면 사용성이 크게 올라갈 것이다.
쿠버네티스에서는 PV를 통해 이러한 추상화를 제공해주는 것이다.
PV와 PVC의 분리 필요성
그럼, PVC는 왜 필요할까?
그냥 PV를 바로 사용자가 요청하는 식으로 중간 단계를 없애도 되는 게 아니었던 것인가?
이 디자인은 운영자와 개발자의 책임 영역 분리를 위한 추상화를 기반으로 하고 있다.
PVC 오브젝트가 따로 존재하지 않고 파드의 스펙으로 작성하는 방식으로 쿠버네티스가 개발되었다고 생각해보자.
이때, 디플로이먼트의 여러 파드가 한 PV를 사용하도록 설정했다.
그리고 PV가 더 이상 사용되지 않으면 자원을 자동으로 회수하도록 정책을 지정했다.
모든 파드가 어떠한 이유로든 종료되는 일이 발생하면, PV의 자원은 모두 날아갈 것이다.
결국 파드의 라이프사이클과 무관하게 있어야 할 PV가 파드에 영향을 받는 일이 생긴다는 것이다.
이를 막고자 무조건 PV는 사람이 직접 지우게 만들어야 하나?
그러면 운영 효율성이 어마무시하게 떨어지게 될 것이다.
아울러 PV는 네임스페이스에 종속되어있지 않다.
4.RESOURCE/KNOWLEDGE/Kubernetes/시큐리티/멀티 테넌시를 하며, 각 네임스페이스에서 사용할 수 있는 자원을 격리시키고 싶다면 네임스페이스 단위로 접근할 수 있는 오브젝트 단위가 추가적으로 필요하다.
물론, PV를 처음부터 네임스페이스 종속적으로 구현하는 방법도 있었을 것이다.
그러면 여러 PV를 만들 때 관리자가 신경써야 할 요소가 많아질 수 있다.
내 나름의 결론을 내린 후 쿠버 톡방에 이 질문을 올렸는데, 대선배님께서 의견을 주셨다.
그 답변이 내게도 확 와닿기에 여기에 정리하고자 한다.
파드는 쿠버네티스에서 불변한(immutable) 객체이며, 이 방침을 지키며 설계하는 것이 쿠버네티스를 개발하는 입장에서 주된 고려사항 중 하나일 것이다.
이때 볼륨 확장을 유연하게 하기 위해서, 혹은 이후에 추가될 여지가 있는 다양한 기능들에 대해 파드와 PVC와의 한 단계 추상화를 더 고려한 게 현재의 쿠버네티스이다.
만약 그저 PVC가 파드 스펙에 들어가 있다면, 볼륨 확장을 동적으로 할 때 불변 객체로서의 파드의 의미가 퇴색될 것이다.
이처럼 파드가, 내부의 컨테이너가 사용할 수 있는 자원들을 유연하게 관리할 수 있도록 개발자들은 초기에 디자인하고 구현했을 것이다.
나는 이 답변을 듣고 추가적인 생각이 하나 더 들었다.
동적으로 컨테이너의 메모리 제한을 확장하는 것이 가능한 것으로 아는데(확실친 않다), 그렇다면 이후에는 메모리, cpu 등의 자원에 대한 오브젝트가 추가로 분리될 수도 있지 않을까?
결국 PV-PVC-파드의 관계는 효율성과 확장성을 위한 추상화가 아니었나 생각한다.
계속 말이 나오는 스토리지 클래스는 무엇인가?
pv를 관리자가 pv를 만들 때 수요에 맞게 다양한 pv를 만들 수 있다.
이때 관리자는 실제 스토리지 구현에 대한 부분들을 숨기고 싶을 수 있는데, 이때 이 설정을 묶어서 관리하는 것이 스토리지 클래스다.
근데 이것 덕분에 StorageClass도 가능해지는 것이다!
PV 라이프사이클
그렇다면 클러스터의 자원인 PV와 해당 자원에 대한 요청인 PVC의 라이프사이클을 쭉 따라가보자.
provisioning
자원을 마련하고 제공할 수 있도록 세팅하는 과정, 즉 프로비저닝.
이 과정에는 계속 얘기하듯이 두 가지 방식이 존재한다.
- 정적(static)
- 관리자가 PV를 직접 만든다.
- 동적
- PVC에 매칭되는 PV가 없을 때, 클러스터에서 PVC에 대해 동적으로 볼륨을 제공한다.
- 스토리지 클래스에 의해 이뤄진다.
- PVC에서 클래스를
""
로 설정하여 동적 프로비저닝을 막을 수 있다. DefaultStorageClass
Admission Control가 활성화돼있어야 한다(보통 다 돼있다).
- PVC에서 클래스를
주의할 것은 PV를 만들었다고 해서 정말 볼륨 만들어진 것은 아니라는 것이다.
요청에 대한 인터페이스만 제공하는 것이라 할 수 있다.
이름이 참 헷갈리게 지어졌다고 느낀다.
binding
컨트롤러가 PVC에 엮일 PV를 매칭하고 묶어주는(bind) 단계이다.
위의 그림에서 PVC에 직접적으로 매칭할 볼륨 이름을 지정해주어 원하는 놈이 붙도록 유도했지만, PVC에는 사실 원하는 스펙만 명시해도 된다.
컨트롤러는 명시된 사이즈, 접근 모드, 스토리지 클래스 등을 고려하여 조건에 일치하는 PV를 붙여줄 것이다.
동적 프로비저닝에서는 실질적으로 거의 동시에 일어나는 과정이라 할 수 있겠다.
한번 연결되기만 하면 일단 해당 연결은 완전히 독점되어, 다른 PVC가 뺏어간다던가 하는 일은 발생하지 않는다.
달리 말하자면 PV와 PVC는 1대1 관계라는 것이다.
굳이 PV 스펙에 명시하지 않아도, 바인딩이 완료되면 PV 스펙에 바인딩된 PVC가 명시된다.
참고로 이 .spec.claimRef
필드를 직접 작성해도 된다.
내 PV에 특정 PVC만 연결됐으면 좋겠다! 싶다면 네임스페이스와 이름을 명시해서 다른 PVC는 바인딩되지 못하도록 만들자.
using
파드가 볼륨으로서 PVC를 사용한다.
PVC가 볼륨이기 때문에, 당연히 다양한 파드와 컨테이너가 접근 모드에 따라 이것을 사용할 수 있다.
파드가 삭제되더라도 사용된 PV, PVC는 알아서 삭제되지 않으며, 이를 통해 의도되지 않은 데이터 손실을 방지한다.
또한 파드가 PVC를 사용 중일 때 PVC와 PV가 삭제되지 않는 것도 보장된다.
일단 바운딩된 PVC에는 kubernetes.io/pvc-protection
라는 Finalizer가,
연결된 PV에는 kubernetes.io/pv-protection
가 붙는 것을 볼 수 있다.
reclaiming
파드가 종료되고 나면 비로소 해당 자원을 회수(reclaim)할 수 있다.
이때 PV에 회수 정책을 명시해 자원을 어떻게 할 것인지 규칙을 정할 수 있다.
이 규칙은 PV와 연관된 실제 데이터와 자원을 어떻게 할 것인지에 대한 정책이다.
원래는 Recycle이란 정책도 있었는데 더 이상 사용되지 않는다..
retain
Retain은 자원 회수가 항상 수동으로 이뤄지게 한다.
PVC가 삭제된 이후 PV는 풀린(released) 것으로 여겨지지만 데이터가 남아있어 다른 PVC가 접근할 수 없다.
회수를 직접 할 때는 다음처럼 하면 된다.
- 일단 PV를 지운다.
- 인터페이스만 삭제되는 과정이다.
- 실제 인프라에 남은 자원 속 데이터를 직접 지운다.
- 스토리지 관련 설정, 파일 등의 자원이 남을 수 있다.
- 그다음 해당 자원을 직접 지운다.
일단 pvc를 지워도 pv가 남는 것은 당연하게 느껴질 것이다.
그러나 이렇게 pv를 지우더라도..
해당 자원은 실제로 잘만 남아있는 것이 바로 retain이다.
(EKS에서 AWS EBS CSI Driver로 실습한 모습이다.)
이 방법의 장점은, PV를 지우더라도 같은 스펙으로 다시금 생성하면 이전의 볼륨을 그대로 쓸 수 있다는 것이다.
(CSI를 활용한 동적 프로비저닝된 PV였다면 csi.volumeHandle
필드를 잘 보고 작성해주면 될 것이다.)
매우 중요한 데이터가 보관되는 볼륨이라면 이 정책을 활용하는 것이 좋겠다.
혹은 볼륨의 데이터를 유지하면서 워크로드를 업데이트할 때도 활용할 수 있는 방법이다.
이 정책이 필요한 케이스라면 [[#binding]]에서 본 것처럼 아예 특정 PVC만이 PV에 바인딩되도록 하는 것도 좋을 것이다.
아울러 볼륨 스냅샷 등의 기능도 활용해주는 것이 좋을 것으로 생각된다.
delete
PV 제거시 관련 인프라 자원 역시 회수된다.
PV를 명시하지 않는 동적 프로비저닝에서는 스토리지 클래스의 정책에 따르며, 기본은 delete이다.
PV가 제거될 때, 인프라 자원이 먼저 확실하게 제거된 후 PV가 제거되도록 파이널라이저가 사용된다.
CSI 볼륨이라면 external-provisioner.volume.kubernetes.io/finalizer
가 붙는다.
내장 볼륨이라면 kubernetes.io/pv-controller
가 붙는데, 동적으로 프로비저닝된 경우에만 한정된다.
아무튼 데이터의 확실한 제거가 보장된다는 것이다.
PV의 상태
라이프사이클을 봤으니 PV의 각 상태도 이해할 수 있겠다.
- Available - 아직 PVC가 바인딩되지 않음
- Bound - PVC가 바인딩됨
- Released - PVC가 없어졌으나 아직 관련 자원이 회수되지 않음
- Failed - 회수 실패함
.status.lastPhaseTransitionTime
에는 언제 마지막으로 이 단계가 바뀌었는지 나온다.
PV 유형
영구 볼륨에 속하는 모든 볼륨이 PV로 만들어질 수 있다.
엄밀히 말해 완전히 동치되지는 않는다.
가령 persistentVolumeClaim은 애초에 PV를 사용하기 위해 사용하는 볼륨이니 당연히 PV로 만들 수 없다..
여기에서 간단하게 각 볼륨들을 명시만 하고, 조금 주의 깊게 봐야할 녀석들에 대해서만 추가 작성을 하겠다.
fc
fc를 본다.
iscsi
iscsi를 본다.
nfs
nfs 쪽에 써뒀는데, PV로 만들 때 비로소 추가적인 설정들을 할 수 있다.
hostPath
hostPath도 PV 오브젝트로 만들 수는 있다.
그러나 강력하게, 아래의 local을 사용하는 것이 권장된다.
local
노드에 이미 존재하고 있는 공간을 볼륨으로서 제공한다.
hostPath와 다르게 바로 파드의 스펙으로 명시할 수 없고, 무조건 PV로만 만들 수 있다.
apiVersion: v1
kind: PersistentVolume
metadata:
name: example-pv
spec:
volumeMode: Filesystem
local:
path: /mnt/disks/ssd1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- example-node
특이한 놈이라 먼저 여기에 양식을 써둔다.
local은 .spec.local.path
로 노드의 경로를 명시하고, .spec.nodeAffinity
로 Affinity를 명시해준다.
즉 이 볼륨이 어느 노드에 붙을지 미리 지정을 하는 것이다.
이 볼륨을 사용하는 파드는 해당 노드로 스케줄될 것이 보장된다.
그럼 파드에 다르게 쓰면 어떻게 되나?
몇 가지 알아야 할 사항들이 있다.
- 아래의 [[#volumeMode]]를
Block
으로 하여 파일시스템이 아닌 상태로 제공할 수도 있다. volumeBindingMode: WaitForFirstConsumer
필드를 가진 스토리지 클래스를 사용하는 게 좋다.- 다른 모드면 볼륨이 일단 먼저 생겨버리기에
- 동적 프로비저닝을 지원하지 않는다.
- 사용완료한 이후에는, 노드의 데이터를 직접 지원야 한다..
hostPath와 local의 차이
위에서 보았듯이 많은 차이가 있는 것이 보인다.
나는 이 두 방식이 존재하는 이유가 바로 용도의 차이라고 본다.
- hostPath
- 파드가 어느 노드에 붙던 그 노드에 대한 작업을 하는 것이 목적
- 그래서 흔히 데몬셋에서 활용된다.
- 사이즈 제한을 할 수 없다는 것도 이런 목적에서는 충분히 이해된다.
- local
- 노드의 저장 공간을 파드의 라이프사이클과 독립적으로 활용하는 것이 주목적
- 디스크 공간이 충분한 노드에 스케줄링되도록 유도하면 좋을 것이다.
csi
저쪽 문서에서는 영구 볼륨에 넣지 않았던 CSI를 통한 볼륨.
단순하게 파드의 볼륨 스펙으로 명시하는 csi 임시 볼륨는 영속 볼륨이 아니기 때문이다.
하지만 PV로서 csi를 쓴다면 의미가 당연히 다르다.
그런데 실상 csi로 직접 PV를 만드는 케이스는 거의 없다고 무방하다.
csi를 활용할 때는 동적 프로비저닝을 하는 게 대부분이기 때문이다.
이걸 직접 쓴다고 생각하면 귀찮은 지점을 생각해볼까.
- csi에 해당하는 공간에 직접 자원을 선정한다.
- 이후에 해당 자원을 PV로 지정한다.
- 이후에 해당 PV를 PVC로 가져온다.
- 이후에 해당 PVC를 파드에 마운팅한다.
할말하않이 된다 이 말입니다.
PV 양식 작성법
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv0003
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
storageClassName: slow
mountOptions:
- hard
- nfsvers=4.1
nfs:
path: /tmp
server: 172.17.0.2
NFS로 PV를 만드는 예시.
PV는 네임스페이스 종속적이지 않다는 점을 참고하자.
용량과 볼륨 모드, 접근 모드, 정책 등을 설정하는 것이 보인다.
nfs 볼륨을 쓰고 싶다면 이렇게 pv를 만들어서 하는 게 좋다.
여기에서는 /sbin/mount.nfs
헬퍼 프로그램을 사용한다.
...
spec:
csi:
driver: <프로비저너이름>
volumeHandle: <해당 자원을 고유하게 인식할 수 있는 식별자>
...
참고로 csi로 pv를 만들 때는 이렇게 해주면 된다.
capacity
capacity.storage
에 허용해줄 양을 명시하면 된다.
여기 값보다 작은 용량을 요청하는 PVC는 해당 PV에 매칭될 수 있을 것이다.
storage만 하위 필드로 쓸 수 있으나, 추후에는 IOPS, throughput 등도 넣을 수 있도록 개발될 수 있다고 한다.
volumeMode
어떤 유형의 스토리지를 쓸 것인가에 대한 필드이다.
현재는 Block
, Filesystem
인데, 블록은.. 진짜 특별한 케이스고 보통은 파일시스템이라 생각하자.
당연히 파일시스템으로 만들어진 볼륨이어야 파드에 붙을 때 디렉토리로 붙는다.
만약 내용물이 없다면 마운팅 직전에 쿠버네티스가 직접 파일시스템을 구성해줄 것이다(그럼 어떤 유형으로?).
블록이면 말 그대로 블록 디바이스를 붙일 수 있다.
PV, PVC 둘 다 volumeMode: Block
으로 명시를 해줘야만 바인딩이 된다.
volumeMode: Block
fc:
targetWWNs: ["50060e801049cfd1"]
lun: 0
readOnly: false
이런 식으로 조금 특별한 케이스다.
containers:
- name: fc-container
image: fedora:26
command: ["/bin/sh", "-c"]
args: [ "tail -f /dev/null" ]
volumeDevices:
- name: data
devicePath: /dev/xvda
블록 디바이스를 붙일 때는 파드 스펙에서 이렇게 volumeMounts
가 아니라 volumeDevices
를 써야 한다!
accessModes
사용 가능한 접근 모드를 지정하는 필드이다.
PVC에 대해 지원하고 싶은 방식들을 리스트로 작성하면 된다.
PVC는 나열된 리스트 중 한 방식으로 바인딩이 가능할 것이다.
- ReadWriteOnce(RWO) - 한 노드에서만 읽고쓰기 가능, 즉 같은 노드의 여러 파드에서 접근 가능!
- ReadOnlyMany(ROX) - 여러 노드에서 읽기 전용
- ReadWriteMany(RWX) - 여러 노드에서 쓰기까지 하용
- ReadWriteOncePod(RWOP) - 한 파드에서만 읽고 쓰기 가능
- 이 기능은 지원하는 CSI에서만 가능하다.
근데 유의할 게, 이건 사실 이건 PVC를 매칭할 때 사용하는 조건 중 하나에 불과하다..
여기에서 쓰기 허용을 넣더라도 실제 프로비저너는 읽기만을 허용하고 있을 수도 있다.
다만 RWOP 만큼은 한 파드에 붙는 것을 보장해준다고 한다.
storageClassName
StorageClass를 지정할 수도 있다.
이것도 컨트롤러가 PV와 PVC를 매칭하는 조건으로 쓰인다.
이 값을 지정하지 않은 PV는 스토리지 클래스를 사용하지 않는 PVC와만 바인딩 될 수 있다.
이게 주의점이 있는데, 아래의 [[#storageClassName]]에서 다루겠다.
과거에는 어노테이션을 이용했으며, 이 방식이 아직은 사용 가능하지만 곧 없어질 것이라고 한다.
persistentVolumeReclaimPolicy
위에서 말한 [[#reclaiming]], 즉 회수 정책을 지정할 수 있다.
참고로 스토리지 클래스에서 이 필드의 이름은 reclaimPolicy
이다.
왜 굳이 이름이 다르게 해서 작성할 때 헷갈리게 만들어뒀는지는 모르겠다.
잠깐 언급했던 Recycle
은 해당 볼륨을 rm -rf
해주는 놈인데..
Kubernetes v1.32 - Penelope 기준 [[#nfs]], [[#hostPath]]만 사용할 수 있다.
애초에 이 기능을 활용하는 케이스가 거의 없다 싶은 게 내 생각이다.
pv는 영속성을 바라는 리소스이다.
그래서 아예 자원이 남을지 말지만이 중요한 관건이 된다고 생각한다.
애매하게 실제 내용은 다 지워지고 껍데기 리소스만 남기는 게 무슨 의미가 있나 하는 것이다.
mountOptions
마운팅할 때 이뤄지는 옵션을 미리 지정할 수 있다.
그런데 모든 PV 유형이 지원하는 것도 아니고 양식도 저마다 다양할 것이다.
이 필드는 PV를 만들 당시 문제 여부를 검증하지 않는다.
문제가 있다면 나중에 파드에 마운팅되는 시점에 실패할 것이다.
nodeAffinity
이 볼륨을 사용하고자 하는 파드들의 위치를 Affinity로 제한할 수 있는 필드이다.
[[#local]]을 제외하면 스토리지 자체가 조건에 맞는 노드에 해당 생긴다는 의미는 아니라 그냥 선택적인 필드라고 생각하자.
PVC 양식 작성법
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myclaim
namespace: default
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 8Gi
storageClassName: slow
selector:
matchLabels:
release: "stable"
matchExpressions:
- {key: environment, operator: In, values: [dev]}
거의 모든 스펙이 [[#PV 양식 작성법]]과 똑같다.
구체적으로는, [[#accessModes]], [[#volumeMode]]가 같다.
PVC는 네임스페이스에 종속된다는 것을 유의하자.
이 말은 접근 모드가 뭐든 간에 PVC에는 같은 네임스페이스 오브젝트를 가진 노드에서만 사용이 가능하다는 것을 암시한다.
resources
할당 자원 관리에서 지정하는 방식을 사용하면 된다.
요청보다 작은 [[#capacity]]를 가진 PV는 매칭되지 않을 것이다.
볼륨 선택
어떤 PV와 연결되고 싶은지 정할 때는 다음의 두 가지 방법이 있다.
.spec.volumeName
- 그냥 PV 이름을 써주면 된다..spec.selector
- 라벨 셀렉터로 원하는 조건에 맞는 PV를 쓴다.
선택 관련 필드를 명시하지 않으면 컨트롤러가 알아서 매칭될 만한 PV를 찾아서 연결해준다.
당연하지만, PV를 고르려는 필드가 들어가는 PVC는 동적으로 프로비저닝되지 않는다!
storageClassName
이것도 PV랑 같이, 사용하라 스토리지 클래스를 명시하는 필드이다.
""로 필드를 명시하는 것은 클래스를 두지 않겠다는 말과 같긴 한데.. 이게 좀 PV의 케이스와 다르게 동작한다.
DefaultStorageClass Admission Control가 있다면 클래스가 명시되지 않은 PVC는 기본 스토리지 클래스가 스펙으로 자동으로 들어간다!
원래는 스토리지 클래스가 명시 안 된 PV에 바인딩을 하고 싶었는데 실패하는 케이스가 나올 수도 있다는 것...
이런 케이스도 있을 수 있다.
기본 스토리지 클래스가 없는 클러스터에서 먼저 PV를 만들었다.
PVC를 만드는 와중에 클러스터에 기본 스토리지 클래스가 설정이 돼버리면..
역행적 할당(retroactive assignment)라 하여 클러스터는 기존에 스토리지 클래스가 명시되지 않은 PVC에 전부 클래스를 할당해주기 때문에 의도하지 않은 동작을 일으킬 것이다.
정 PVC에 아무 값도 안 들어가게 하고 싶다면, 명시적으로 storageClassName: ""
과 같은 식으로 지정하자.
이렇게 하는 게 좋은 경우가 있기는 하다.
관리자는 동적으로 마구 기본 스토리지 클래스를 바꿔댈 수 있기에, 이러한 위험으로부터 자유로울 수 있는 것이다.
dataSource
볼륨 복제, 볼륨 스냅샷과 관련한 필드이다.
처음 볼륨을 마운팅할 때, 빈 볼륨이 아니라 무언가 채워진 채로 마운팅할 수 있게 해준다.
이 기능들은 이를 지원하는 CSI 플러그인을 사용할 때만 사용할 수 있다.
dataSource:
name: new-snapshot-test
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
볼륨 스냅샷은 이렇게 한다.
대충 봐도 스냅샷 떠둔 볼륨을 그대로 쓰는 것이다.
dataSource:
name: existing-src-pvc-name
kind: PersistentVolumeClaim
볼륨 복제는 이렇게 한다.
dataSource
는 이렇게 PVC나, VolumeSnapshot만을 받을 수 있다.
dataSourceRef
AnyVolumedataSource
피처 게이트가 활성화돼있을 때 사용할 수 있는 필드이다.
위의 dataSource
와 같이 빈 볼륨을 받을 때 내용을 채울 때 쓰는 것이며, 둘은 양립할 수 없다.
그러나 이 필드가 dataSource
와 다른 건, 같은 네임스페이스의 커스텀 오브젝트'도' 집어넣을 수 있다는 것이다!
문서에서는 볼륨을 채워주는 것을 volume populator라고 부른다.
볼륨 생성자? 볼륨 기입자? 어떻게 번역할지 모르겠어서 그냥 넘어간다.
여기에서 말하는 커스텀 오브젝트는 volume populator이다.
이것은 볼륨의 내용물을 채우는 컨트롤러로, CRD로 구현해야 할 것이다.
dataSource와의 비교
이 기능이 활성화된 클러스터라면, 이 필드를 사용하는 것을 강력하게 문서에서 추천한다.
이 필드가 좋은 이유는 다음과 같다.
- 설정에 따라 다른 네임스페이스(교차 네임스페이스, cross namespace)의 오브젝트도 쓸 수 있다.
AnyVolumeDataSource
,CrossNamespaceVolumeDataSource
피처 게이트가 kube-apiserver, kube-controller-manager에 활성화돼야 한다.CrossNamespaceVolumeDataSource
는 csi 프로비저너에서도 세팅돼야 한다.- 이 경우 Gateway API#ReferenceGrant를 통해 다른 네임스페이스의 오브젝트를 사용할 수 있게 된다.
- 유효하지 않은 세팅에 대해 검증 에러를 낸다.
이러면서 다른 오브젝트도 쓸 수 있으니 사옹만 할 수 있다면 안 좋은 이유가 없다는 것이다.
어차피 같은 기능을 하는 필드인데 둘 다 남겨둔 이유는 그저 과거와의 호환성 때문이다.
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-ns1-pvc
namespace: default
spec:
from:
- group: ""
kind: PersistentVolumeClaim
namespace: ns1
to:
- group: snapshot.storage.k8s.io
kind: VolumeSnapshot
name: new-snapshot-demo
이런 식으로 세팅하면 ns1
의 PVC가 default
의 볼륨 스냅샷을 사용할 수 있게 될 것이다.
말 그대로 해석해보자면 ns1
의 PVC에서 default
의 볼륨 스냅샷으로 참조하는 것을 허용하는 오브젝트라 할 수 있겠다.
볼륨 요청 확장
이건 PVC를 익히는데 필수적인 건 아니라 생각해서 뒤쪽으로 내용을 뺐다.
이런 상황을 생각해보자.
20메가만 요청하는 PVC를 만들어서 바인딩이 완료됐다.
근데 갑자기 50메가가 필요한 것 같다!
함부로 PVC를 지웠다 다시 만드는 것은 PV를 뺏기는 상황을 야기할 수 있다(물론 PV에 claimRef를 걸면 되긴 하다).
또한 이를 사용 중인 어플리케이션이 있다면 더더욱 불가능하다.
동적으로 50메가로 확장할 수는 없을까?
이를 위해 동적으로 볼륨 크기를 확장할 수 있도록 CSI에 정의되어 있다.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: example-vol-default
provisioner: vendor-name.example/magicstorage
allowVolumeExpansion: true
스토리지 클래스에 allowVolumeExpansion: true
필드가 설정돼야 한다.
이렇게 돼있다면, 그저 PVC의 스펙에서 사이즈를 키우는 것만으로 편하게 용량을 확장할 수 있다.
연결된 PV도 알아서 확장이 지원된 것이 반영될 것이다.
주의점
동적 확장 기술은 당연히 많은 주의점을 수반한다.
당연하지만.. 이걸 지원하지 않는 CSI 드라이버가 있을 수도 있으니 잘 확인해보자.
또한 파일시스템으로 제공되는 스토리지라면 xfs
나 ext3
,ext4
파일시스템에 대해서만 가능하다.
또한 이미 볼륨이 파일시스템을 가지고 있다면, 파드에서 쓰기가 허용됐을 때만 가능할 것이다.
파드에서 사용중인 요청도 리사이징이 가능하다.
직접 PV의 사이즈를 바꾸는 작업은 지양하는 게 좋다.
왜냐하면 볼륨 확장 작업은 컨트롤 플레인에서 PV와 PVC의 상태를 추적하면서 발생하는 것이기 때문이다.
그냥 PV를 직접 수정해버리면 컨트롤 플레인은 제대로 상황을 추적할 수 없고 실질적인 자원의 리사이징이 발생하지 않을 수 있다.
이거 구체적으로 어케 된다는 거임
볼륨을 확장하는데, 가용한 자원보다 큰 사이즈를 요청해버려서 계속 요청이 실패할 수도 있다.
그러면 관리자가 조치를 취할 때까지 PVC 확장은 계속 재시도될 것이다.
그럼 이 확장을 재조정을 해야 할 텐데, 여기에 두어 가지 전략을 생각할 수 있다.
- 관리자 수동 처리
- PV의 회수 정책을 Retain으로 바꿔 실제 자원이 유실되지 않도록 한다.
- PVC를 삭제한다.
- PV의 claimRef 필드에서 PVC를 지워 새로운 PVC가 붙을 수 있도록 한다.
- PVC를 조금 더 작은 사이즈로 다시 만들고, volumeName에 PV를 명시한다.
- 원래 해놨던 회수 정책으로 다시 바꾼다.
- 작은 사이즈로 재요청
- 쿠버네티스에서는 기본적으로 PVC의 확장은 지원해도 축소를 지원하진 않는다.
- 데이터 유실의 위험성도 크기도 하고, 기술적 난이도가 높은 게 아닐까 생각한다.
- 그러나, Kubernetes v1.32 - Penelope부터
RecoverVolumeExpansionFailure
피처 게이트를 통해 조금 더 편하게 이를 시도할 수 있다. - 그냥 단순하게 PVC 스펙을 작게 수정하는 방식으로 말이다!
- PVC의
.status.allocatedResourceStatuses
로 해당 상태를 모니터링할 수 있다. - 다만 첫 요청이던
.status.capacity
보다는 높게 요청해야 한다.
- 쿠버네티스에서는 기본적으로 PVC의 확장은 지원해도 축소를 지원하진 않는다.
이거 실험해보자.
관련 문서
이름 | noteType | created |
---|---|---|
StatefulSet | knowledge | 2024-12-26 |
PersistentVolume | knowledge | 2025-01-11 |