Kubernetes Practice 12
PV와 PVC: 저장소의 공급과 요청, 누가 누구인가?
pv.yaml과 pvc.yaml이 따로 나오면 둘 다 저장소 설정처럼 보여서, 누가 공급자이고 누가 요청자인지 구분하기 어렵다.
근거 · Kubernetes 교안 p140-p146, yaml/volume/pv.yaml, pvc.yaml, pv-pod.yaml
1장: 루나: 겉모습에 속지 마, 저장소의 첫인상
솔라는 노트북 화면에 두 개의 파일을 나란히 띄워놓고 미간을 찌푸렸다. 왼쪽 창에는 pv.yaml이라는 이름의 파일이, 오른쪽 창에는 pvc.yaml이라는 이름의 파일이 열려 있었다. 한참 동안 두 파일을 번갈아 보던 솔라가 결국 참지 못하고 한숨을 내쉬었다.
“언니, 이거 대체 뭐야?”
방으로 들어서던 루나가 솔라의 부름에 다가와 노트북 화면을 들여다보았다.
“쿠버네티스 저장소 공부하는 중인데, pv.yaml이랑 pvc.yaml이 너무 헷갈려. 둘 다 그냥 저장소 설정하는 파일 같아 보이는데, 누가 공급자고 누가 요청자인지 도무지 모르겠어.”
솔라는 답답하다는 듯 화면을 가리켰다. 그녀의 말처럼 두 파일은 언뜻 보기에 상당히 비슷했다.
pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-pv
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data"
pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
“봐봐. 여기 storage: 1Gi라고 용량 지정하는 것도 비슷하고, accessModes도 똑같이 들어가잖아. 그냥 똑같이 1기가짜리 저장 공간 만드는 건데, 왜 파일을 두 개나 만들어야 하는 거야? 그냥 두 가지 다른 방법일 뿐인 거 아니야?”
솔라의 목소리에는 ‘이건 불필요하게 복잡하기만 한 것 같다’는 짜증이 섞여 있었다. 충분히 가질 만한 의문이었다. 중요한 설정값들이 양쪽에 모두 나타나니, 두 파일의 역할이 명확히 구분되지 않는 것은 당연했다.
루나는 잠시 솔라의 화면을 들여다보더니, 의자를 끌어와 옆에 앉았다.
“음, 정말 비슷해 보이네. 솔라 네가 본 것처럼 용량이나 접근 모드 같은 중요한 정보들이 둘 다 들어있고.”
루나의 말에 솔라는 ‘거봐, 내 말이 맞지?’ 하는 표정을 지었다. 하지만 루나는 바로 설명을 이어가는 대신, 질문의 방향을 살짝 틀었다.
“그런데 솔라, 한번 이렇게 생각해볼래? 만약 두 개가 정말 같은 일을 하는, 단순히 저장 공간을 만드는 두 가지 방법일 뿐이라면, 쿠버네티스를 만든 사람들은 왜 굳이 PersistentVolume과 PersistentVolumeClaim이라는, 이름까지 다른 두 종류의 객체를 만들었을까? 그냥 하나로 통일하는 게 더 간단하지 않았을까?”
“어…?”
솔라는 순간 말문이 막혔다. 당연히 같은 역할을 한다고 생각했지, 왜 굳이 나뉘어 있는지 그 ‘이유’에 대해서는 깊이 생각해보지 않았기 때문이다. 루나의 질문은 솔라의 관점을 ‘무엇이 같은가’에서 ‘왜 다른가’로 옮겨놓았다.
솔라는 다시 화면 속 두 파일을 찬찬히 뜯어보기 시작했다. kind: PersistentVolume, kind: PersistentVolumeClaim. 이름부터가 명백히 달랐다. 파일도 따로 만들어야 하고, 쿠버네티스가 인식하는 객체의 종류(Kind)도 다르다. 비슷한 내용을 담고 있는데도 이렇게 명확히 분리해 놓은 데에는 분명히 의도가 있을 터였다.
“그러게… 이름도 다르고, 종류도 다른데. 겉모습이 비슷하다고 그냥 같은 거라고 단정하면 안 되겠구나.”
솔라는 작게 중얼거렸다. 짜증은 사라지고 새로운 궁금증이 그 자리를 채우기 시작했다.
“겉모습에 속을 뻔했네. 비슷해 보이지만 실제로는 각자 다른 역할이 있으니까 분리해 놓은 거겠지. 그럼… 도대체 어떤 역할이 어떻게 다른 걸까? 왜 굳이 저장소를 ‘공급’하는 쪽과 ‘요청’하는 쪽으로 나눠야만 했을까?”
2장: 루나: PV, 클러스터의 저장소를 책임지는 자
솔라는 결심한 듯 노트북의 터미널 창을 활짝 열었다. 양쪽에 나란히 띄워뒀던 두 개의 YAML 파일 중 pvc.yaml 창은 닫아버리고, pv.yaml 파일만 남겨두었다. ‘왜 다른가’라는 질문에 답을 찾으려면, 한 번에 하나씩 역할을 파헤쳐보는 수밖에 없었다.
“좋아, 먼저 이 PersistentVolume이라는 것부터 정체를 밝혀보자고.”
솔라는 혼잣말을 하며 pv.yaml 파일을 하나 새로 만들었다. 이전과 비슷하지만, 실험용이라는 것을 명확히 하기 위해 이름과 경로를 조금 바꿨다.
# pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-lab
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/tmp/pv-data"
옆에서 솔라의 움직임을 조용히 지켜보던 루나가 입을 열었다.
“좋은 접근이야. 한 번에 하나씩. 그럼 그 파일, 클러스터에 적용해볼래? Pod나 다른 건 아무것도 만들지 말고, 딱 저것만.”
“응. 일단 이 PersistentVolume이란 게 뭔지, 이것만으로 무슨 일이 생기는지 보고 싶어.”
솔라는 망설임 없이 명령어를 입력했다.
$ kubectl apply -f pv.yaml
persistentvolume/pv-lab created
‘persistentvolume/pv-lab created’라는 메시지가 뜨자, 솔라는 잠시 화면을 응시했다. ‘만들어지긴 했는데… 이걸로 뭐가 달라진 거지? 그냥 볼륨 설정 하나가 생긴 건가?’ 솔라의 머릿속에는 여전히 이 파일이 애플리케이션(Pod)이 사용할 볼륨을 정의하는 설정 중 하나일 뿐이라는 생각이 남아 있었다.
“자, 이제 쿠버네티스에게 물어보자. 방금 만든 PersistentVolume이 지금 어떤 상태인지.”
루나의 말에 솔라는 kubectl get 명령어를 쳤다. 이번엔 pod가 아니라 pv라는, 조금은 낯선 대상을 조회했다.
$ kubectl get pv
결과는 즉시 터미널 화면에 나타났다.
| NAME | CAPACITY | ACCESS MODES | RECLAIM POLICY | STATUS | CLAIM | STORAGECLASS | REASON | AGE |
|---|---|---|---|---|---|---|---|---|
| pv-lab | 1Gi | RWO | Retain | Available | 15s |
솔라는 눈을 동그랗게 뜨고 STATUS 열을 가리켰다.
“Available? 사용 가능하다고 나오네. 근데… CLAIM 열이 텅 비어 있어.”
“그렇지. 그리고 Pod는?”
“Pod는 아예 만들지도 않았지.”
그 순간, 솔라는 무언가 깨달은 듯 작은 탄성을 내뱉었다.
“아! 알겠다. 이건… 이건 아직 아무도 사용하고 있지 않은, 그냥 클러스터에 미리 준비된 저장 공간 그 자체구나. 마치 주차장에 비어있는 주차 공간처럼. 차(Pod)가 아직 들어오지 않았고, 주차권(PVC)을 끊지도 않았지만, ‘여기에 1기가짜리 주차 공간이 하나 있습니다’ 하고 클러스터에 등록해놓은 상태인 거야.”
PV가 Pod와는 전혀 상관없이 독립적으로 존재한다는 사실을 눈으로 확인한 순간이었다. pv.yaml 파일은 애플리케이션을 위한 볼륨 설정이라기보다, 인프라 관리자가 클러스터 전체에 ‘우리에겐 이런 저장소를 쓸 수 있도록 준비해뒀습니다’라고 선언하고 공급하는 행위에 가까웠다.
“정확해. PV는 Pod가 아니라 클러스터의 리소스야. 관리자가 미리 준비해 둔 ‘저장소의 재고’ 같은 거지. 어떤 Pod가 이걸 사용할지는 아직 정해지지 않았어. 그저 ‘사용 가능한’ 상태로 대기하고 있을 뿐이야.”
루나의 설명에 솔라는 고개를 끄덕였다. 이제 PersistentVolume의 ‘Volume’이라는 단어에 더 이상 혼동되지 않았다. 이것은 Pod에 직접 연결되는 볼륨이 아니라, 그 볼륨으로 사용될 수 있는 물리적인 저장소의 추상화된 조각이었다. ‘공급자’의 역할이 명확해졌다.
솔라는 ‘Available’이라고 선명하게 찍힌 상태를 다시 한번 바라보았다. 궁금증 하나가 풀리자, 곧바로 다음 질문이 꼬리를 물었다.
“좋아, 그럼 이제 저장소 공급은 끝났네. 클러스터에 1기가짜리 공간이 준비됐어. 그럼 이제… 저 비어있는 CLAIM 열은 어떻게 채우는 거지? 저걸 사용하고 싶다고 요청하는 건 누구의 역할일까? 그게 바로 pvc.yaml의 역할인 건가?”
3장: 루나: PVC, Pod를 위한 저장소 요청 티켓
솔라의 손가락이 키보드 위를 빠르게 움직였다. 이전 실험을 깔끔하게 정리하기 위해서였다.
$ kubectl delete pv pv-lab
persistentvolume "pv-lab" deleted
터미널에 확인 메시지가 뜨자, 솔라는 비로소 안심하고 다음 단계로 넘어갔다. 방금 전까지만 해도 화면을 차지하고 있던 ‘Available’ 상태의 PV는 이제 클러스터에서 사라졌다. 지금은 텅 빈, 저장소 공급이 전혀 없는 깨끗한 상태다.
“좋아, 공급자 역할을 하는 PV는 확인했어. 그럼 이제 요청자 차례지?”
솔라는 이번엔 pvc.yaml이라는 이름의 새 파일을 열었다. 그녀의 질문은 이제 ‘이게 뭐지?’가 아니라 ‘이것 혼자서는 무슨 일을 할까?’로 바뀌어 있었다. PV와 PVC가 함께 있을 때가 아니라, 각각 독립적으로 어떤 상태를 갖는지 확인하는 것이 역할 분리를 이해하는 열쇠라고 직감했기 때문이다.
pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-lab
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
“흠. kind가 PersistentVolumeClaim… ‘볼륨을 요구한다’는 뜻이네.”
솔라는 YAML 파일의 내용을 입력하며 중얼거렸다. 지난번 PV 파일처럼 용량(1Gi)과 접근 모드(ReadWriteOnce)를 명시하고 있었다. ‘어쩌면 이것도 PV처럼 그 자체로 저장 공간을 만들어내는 또 다른 방법일지도 몰라. 개발자가 직접 저장소를 생성하는 방식 같은 거 아닐까?’ 하는 생각이 머리를 스쳤다. PV는 관리자가, PVC는 개발자가 만드는 저장소라는 가설이었다.
루나는 솔라의 혼잣말을 들었지만, 아무 말 없이 지켜보기만 했다. 솔라가 스스로 가설을 세우고 검증하는 과정을 방해하고 싶지 않았다.
“한번 해보자. 지금 클러스터에는 어떤 PV도 없어. 이 요청서만 덩그러니 내면 무슨 일이 벌어질까?”
솔라는 비장하게 명령어를 입력했다.
$ kubectl apply -f pvc.yaml
persistentvolumeclaim/pvc-lab created
생성되었다는 메시지는 PV 때와 똑같이 경쾌했다. 솔라는 즉시 PVC의 상태를 확인했다. kubectl get pv를 쳤을 때처럼, kubectl get pvc를 터미널에 입력했다.
$ kubectl get pvc
곧이어 나타난 결과는 솔라의 예상을 완전히 빗나갔다.
| NAME | STATUS | VOLUME | CAPACITY | ACCESS MODES | STORAGECLASS | AGE |
|---|---|---|---|---|---|---|
| pvc-lab | Pending | 5s |
“Pending?”
솔라는 STATUS 열에 찍힌 ‘Pending’이라는 단어에서 눈을 떼지 못했다. ‘대기 중’이라는 뜻이었다. PV 때처럼 Available(사용 가능) 상태가 될 것이라던가, 아니면 어쨌든 무언가 준비된 상태를 기대했는데, 이건 그저 무언가를 하염없이 기다리는 상태처럼 보였다. VOLUME과 CAPACITY 열도 텅 비어 있었다. 아무런 저장소도 할당받지 못한 것이다.
“저장소가… 안 만들어졌어. 그냥 ‘요청’만 생성되고 멈춰 있네.”
그 순간, 솔라는 자신의 가설이 틀렸음을 깨달았다. PVC는 저장소를 직접 생성하는 객체가 아니었다. PersistentVolumeClaim이라는 이름 그대로, 그저 ‘요구사항’을 담은 선언일 뿐이었다.
마치 놀이공원에 가서 ‘빅스윙 기구를 타고 싶어요!’ 하고 외치기만 한 상태와 같았다. 직원이 와서 티켓을 확인하고 놀이기구로 안내해주기 전까지는 아무 일도 일어나지 않는 것처럼, PVC는 자신이 원하는 저장소의 조건(1Gi, ReadWriteOnce)을 쿠버네티스에 알리고, 그 조건에 맞는 PV가 나타나길 기다리고 있었다.
“아하! 이게 바로 ‘요청 티켓’이구나! PV가 관리자가 준비해 둔 ‘저장소 재고’라면, PVC는 사용자가 ‘이런 사양의 저장소가 필요해요’ 하고 제출하는 ‘요청서’인 거야. 지금은 재고(PV)가 하나도 없으니까, 요청서만 덩그러니 남아서 대기하고 있는 거고.”
솔라의 목소리에 활기가 돌았다. 두 파일이 비슷해 보였던 이유, 하지만 명백히 다른 kind를 가졌던 이유가 퍼즐처럼 맞춰졌다. 공급자와 요청자의 역할이 명확하게 보이기 시작했다.
“이제 알겠어. PV는 공급자, PVC는 요청자. 둘은 서로 다른 역할을 해. 그럼 이제 남은 질문은 하나네.”
솔라는 화면의 Pending 상태를 가리키며 루나를 돌아보았다.
“이 대기 중인 티켓은, 어떻게 해야 자기가 원하는 놀이기구(PV)를 만나서 탈 수 있게 되는 걸까? 둘을 연결해주는 건 누구야?”
4장: 루나: 연결의 마법, 바인딩과 Pod의 마운트
솔라의 노트북 화면에는 Pending이라는 단어가 선명하게 떠 있었다. 클러스터에 홀로 남겨진 pvc-lab은, 조건에 맞는 저장소(PV)가 나타나기만을 하염없이 기다리는 중이었다. 공급자가 없는 시장에 덩그러니 놓인 구매 요청서와 같았다.
“자, 요청서는 이미 제출됐어. 지금 시장에 물건을 들여놓으면 어떻게 될까?”
루나의 말에 솔라는 화면의 Pending 상태와 이전에 만들었던 pv.yaml 파일을 번갈아 보았다. 요청이 대기 중인 상태에서 공급이 발생하면 어떤 일이 벌어지는지 직접 확인해보라는 뜻이었다. 솔라는 그 의도를 알아차리고, 이전에 작성했던 pv.yaml 파일을 클러스터에 적용했다.
# pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-lab
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/tmp/pv-data"
1기가바이트 용량의 저장소가 다시 클러스터에 ‘공급’되었다. 이제 클러스터 안에는 ‘1Gi 용량의 저장소를 원한다’는 요청(PVC)과 ‘1Gi 용량의 저장소를 제공한다’는 공급(PV)이 동시에 존재하게 된 셈이다.
“자, 이제 다시 한번 확인해봐. 요청서의 상태가 어떻게 변했는지.”
솔라는 마른침을 삼키고 아까와 동일한 명령어를 다시 입력했다.
$ kubectl get pvc
결과는 놀라웠다.
| NAME | STATUS | VOLUME | CAPACITY | ACCESS MODES | STORAGECLASS | AGE |
|---|---|---|---|---|---|---|
| pvc-lab | Bound | pv-lab | 1Gi | RWO | 2m |
“어! Bound? 묶였다는 뜻이잖아! 아까는 Pending이었는데!”
솔라의 눈이 커졌다. STATUS 열은 Pending에서 Bound로 바뀌어 있었고, 텅 비어 있던 VOLUME 열에는 방금 막 공급한 pv-lab의 이름이 선명하게 찍혀 있었다. 마치 대기하던 손님이 드디어 자기 표에 맞는 놀이기구를 찾아 탑승한 것 같았다.
혹시나 하는 마음에 솔라는 PV의 상태도 확인했다.
$ kubectl get pv
| NAME | CAPACITY | ACCESS MODES | RECLAIM POLICY | STATUS | CLAIM | STORAGECLASS | REASON | AGE |
|---|---|---|---|---|---|---|---|---|
| pv-lab | 1Gi | RWO | Retain | Bound | default/pvc-lab | 35s |
PV의 상태 역시 Available에서 Bound로 바뀌어 있었다. 그리고 비어 있던 CLAIM 열이 default/pvc-lab이라는 값으로 채워져 있었다. 두 객체가 서로를 정확히 찾아내 연결된 것이다.
“대박… 그냥 PV를 만들기만 했는데, 쿠버네티스가 알아서 기다리던 PVC랑 연결해줬어. 내가 뭘 한 게 없는데!”
이전의 질문, “누가 둘을 연결해주는가?”에 대한 답이 눈앞에 펼쳐졌다. 연결의 주체는 바로 쿠버네티스 자신이었다. 쿠버네티스의 컨트롤 플레인이 클러스터의 상태를 계속 지켜보다가, 조건에 맞는 PV와 PVC가 나타나자 둘을 자동으로 ‘바인딩’해준 것이다.
“이제 저장소를 사용할 준비는 끝났네. 그럼 이 ‘바인딩된 티켓’은 누가 사용하는 걸까?”
루나가 마지막 퍼즐 조각인 Pod 정의 파일을 화면에 띄웠다.
pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: my-container
image: redis
volumeMounts:
- mountPath: "/data"
name: my-storage
volumes:
- name: my-storage
persistentVolumeClaim:
claimName: pvc-lab # PV 이름이 아닌 PVC 이름을 사용한다!
솔라는 volumes 부분을 유심히 살펴보았다. 이전의 ConfigMap 실습처럼 volumeMounts를 통해 컨테이너 안의 특정 경로(/data)에 볼륨을 연결하는 구조였다. 하지만 결정적인 차이가 있었다. volumes의 상세 정의 부분에 persistentVolume 같은 이름 대신 persistentVolumeClaim이라는 필드가 사용되고 있었다.
“어? claimName이 pvc-lab이네? 실제 저장소인 pv-lab의 이름을 쓰는 게 아니고?”
솔라는 자신의 이전 생각이 틀렸음을 깨달았다. Pod가 저장소를 사용할 때, 공급자인 PV를 직접 선택하는 것이 아니었다. 사용자인 Pod는 오직 ‘요청서’인 PVC의 이름만 알고 있을 뿐이었다.
“바로 그거야. Pod는 자기가 사용할 저장소의 구체적인 사양이나 이름(pv-lab)에 대해선 전혀 몰라. 그저 ‘pvc-lab이라는 티켓을 사용할게요’라고 선언하기만 하면 돼. 그럼 쿠버네티스가 그 티켓과 연결된 실제 저장소를 찾아서 Pod에 붙여주는 거지.”
솔라는 고개를 끄덕이며, 준비된 Pod 파일을 클러스터에 배포했다. Pod가 성공적으로 생성되자, 솔라는 마지막 확인을 위해 Pod 내부로 들어가 보기로 했다.
$ kubectl exec -it my-pod -- /bin/bash
root@my-pod:/# ls /data
root@my-pod:/#
ls /data 명령을 치자, 아무것도 출력되지 않았지만 오류 없이 프롬프트가 돌아왔다. /data라는 디렉터리가 성공적으로 마운트되었다는 증거였다. 이제 이 Pod는 pv-lab이 가리키는 호스트의 /tmp/pv-data 디렉터리에 데이터를 영구적으로 저장할 수 있게 된 것이다.
솔라는 키보드에서 손을 떼고 의자에 등을 기댔다. 공급자(PV), 요청자(PVC), 그리고 사용자(Pod)의 역할과 관계가 하나의 흐름으로 완벽하게 정리되었다.
“알겠다. 관리자는 PV로 저장소 풀을 만들어 공급하고, 개발자는 PVC로 필요한 만큼 요청하고, Pod는 그 요청서(PVC)의 이름만 가져다 쓰는 거구나. 서로의 구체적인 내용을 전혀 알 필요 없이.”
모든 조각이 제자리를 찾았다. 하지만 솔라의 마음 한구석에는 새로운 질문이 피어오르고 있었다.
“그런데… 이게 왜 좋은 거지? 결국은 저장소를 쓰는 건데, 이렇게 한 다리 건너서 연결하는 게 그냥 직접 연결하는 것보다 뭐가 더 나은 걸까? 그냥 좀 더 복잡해진 거 아닌가?”
5장: 루나: 저장소 역할 분리의 진짜 이유
솔라의 질문이 방 안의 고요함을 가르고 있었다. 공급자(PV), 요청자(PVC), 사용자(Pod)의 연결고리는 이제 명확했지만, 그 구조가 주는 이점에 대해서는 아직 물음표가 남아 있었다. 왜 이 단순한 연결을 굳이 세 단계로 나누었을까? 복잡하기만 한 것 아닐까?
루나는 솔라의 질문에 바로 답하는 대신, 노트북 옆에 놓여 있던 빈 종이와 펜을 솔라 쪽으로 밀어주었다.
“좋은 질문이야. 그럼, 방금 우리가 확인한 것들을 한번 그려볼래? PV, PVC, 그리고 Pod가 지금 서로 어떻게 연결되어 있는지.”
“그려보라고?”
솔라는 잠시 의아했지만, 이내 펜을 집어 들었다. 복잡할 것도 없었다. 머릿속에 있는 그림을 그대로 종이 위에 옮겼다. 네모 상자 세 개를 그리고, 각 상자 안에 Pod, PVC, PV라고 적었다. 그리고 화살표로 그 관계를 이었다.
Pod → PVC → PV
그림을 완성한 솔라는 펜을 내려놓고 루나를 보았다.
“이거잖아. Pod는 PVC를 이름으로 부르고, PVC는 PV랑 바인딩되어 있고. 내 말은, 왜 이렇게 한 단계 더 거치냐는 거지. 그냥 Pod가 바로 PV를 부르면 더 간단하지 않아?”
솔라는 직접 Pod에서 PV로 이어지는 화살표를 손가락으로 그리며 덧붙였다. 그녀의 관점에서는 당연한 의문이었다. 중간 다리가 하나 빠지면 훨씬 직관적으로 보였다.
“만약 Pod가 PV를 직접 부르도록 설계되어 있다고 상상해보자.”
루나가 말했다.
“우리 pv-lab은 지금 어떤 종류의 저장소를 사용하고 있지?”
“음… hostPath. 이 노트북의 /tmp/pv-data 디렉터리.”
“맞아. 그럼 개발자인 네가 만든 my-pod.yaml 파일에 hostPath 볼륨을 직접 설정했다고 가정해봐. 지금은 잘 돌아가겠지. 그런데 어느 날, 우리 팀이 이 애플리케이션을 AWS 클라우드로 옮기기로 결정했어. AWS에는 hostPath 같은 게 없잖아. 대신 awsElasticBlockStore 같은 걸 써야 해.”
루나는 솔라가 그린 다이어그램의 PV 상자를 가리켰다.
“그럼 어떻게 해야 할까? 저장소의 종류가 hostPath에서 awsElasticBlockStore로 바뀌었으니….”
솔라의 눈이 잠시 흔들렸다. 루나가 의도하는 바를 즉시 알아차렸기 때문이다.
“…Pod의 YAML 파일을… 수정해야 하네. 개발자가 직접. hostPath 부분을 지우고 awsElasticBlockStore 설정을 넣어야겠지. 저장소 인프라가 바뀔 때마다 애플리케이션 정의 파일 자체가 바뀌는 거야.”
“바로 그거야.”
루나는 말을 이었다.
“개발자는 이제 그냥 애플리케이션 개발만 하는 게 아니야. 이 클러스터가 어떤 저장소 기술을 쓰는지, 그 설정은 어떻게 하는지까지 알아야만 해. 반대로, 인프라 관리자가 더 좋은 성능의 새 저장소 시스템을 도입하고 싶어도, ‘저거 사용하는 모든 Pod들 YAML 파일 다 바꾸셔야 합니다’라고 모든 개발팀에 요청하고 다녀야겠지.”
그 말을 듣는 순간, 솔라의 머릿속에서 PV와 PVC를 분리해 놓은 그림이 전혀 다른 의미로 보이기 시작했다. 이것은 단순히 연결을 복잡하게 만든 것이 아니었다. 의도적으로 중간에 벽을 세워 서로를 보호하는 구조였던 것이다.
솔라는 자신이 그렸던 Pod → PVC → PV 다이어그램을 다시 들여다보았다.
“아…! 알겠다. Pod는 PVC라는 ‘티켓’만 알면 되는구나. ‘1Gi 용량에 ReadWriteOnce 접근이 가능한 저장소 티켓을 주세요’라고 요청만 하는 거야. 그 티켓이 실제로 어떤 놀이기구(hostPath, awsElasticBlockStore, NFS…)와 연결될지는 Pod의 관심사가 아니야. 그건 전적으로 관리자와 쿠버네티스의 역할인 거고.”
역할의 분리. 솔라는 이 단어의 진짜 무게를 깨달았다. 개발자는 애플리케이션의 요구사항(PVC)에만 집중하고, 인프라 관리자는 클러스터의 저장소 공급(PV)에만 집중할 수 있다. 둘은 PVC라는 추상적인 ‘요구사항 명세서’를 통해 소통한다. 서로의 구체적인 구현 내용을 알 필요가 전혀 없어진다. 이것이 바로 ‘디커플링(Decoupling)’의 가치였다.
“만약 AWS로 이사 간다면, 개발자는 my-pod.yaml 파일을 한 줄도 바꿀 필요가 없어. 그냥 그대로 배포하면 돼. 대신 관리자가 AWS 환경에 맞는 새로운 PV, 예를 들어 awsElasticBlockStore를 사용하는 PV를 만들어서 클러스터에 공급해주기만 하면, 쿠버네티스가 알아서 똑같은 PVC 티켓을 그 새로운 PV에 착 붙여주겠구나.”
솔라의 목소리에는 감탄이 섞여 있었다. 복잡해 보였던 구조가 이제는 지극히 논리적이고 우아한 설계로 보였다.
루나는 만족스러운 표정으로 고개를 끄덕였다.
“이제 PV와 PVC가 왜 필요한지, 왜 역할을 나누는지 확실히 이해한 것 같네.”
솔라는 자신이 그렸던 다이어그램을 보며 마지막으로 정리했다.
“응. PV는 인프라 관리자의 영역, PVC는 애플리케이션 개발자의 영역. 그리고 Pod는 개발자의 PVC를 가져다 쓰는 사용자. 이 역할 분리 덕분에 애플리케이션은 어떤 환경으로 옮겨가도 코드를 바꿀 필요 없는 ‘이식성’을 얻게 되고, 관리자는 저장소 인프라를 자유롭게 교체하거나 확장할 수 있는 ‘유연성’을 얻는 거구나.”
솔라는 펜을 들어 방금 그린 다이어그램 옆에 새로운 시나리오를 적기 시작했다. 마치 새로운 애플리케이션의 요구사항을 설계하는 것처럼.
New App: my-db
- 필요한 것 (개발자, 나):
pvc-db.yaml파일 만들기.storage: 10GiaccessModes: ReadWriteOnce
- 해줘야 할 것 (관리자, 언니): 위 PVC를 만족시키는
pv-db.yaml파일 만들기.- 실제 저장소는
nfs든ceph든 뭐든 상관없음!
- 실제 저장소는
그것은 더 이상 학습 노트가 아니었다. PV/PVC 모델의 핵심을 꿰뚫고, 자신의 역할을 정확히 인지한 개발자가 작성한 첫 번째 설계안이었다.