쿠버네티스를 처음 공부하면서 가장 머리가 아팠던 부분이 바로 스토리지였습니다. 컨테이너가 죽으면 데이터가 날아간다는 걸 처음 직접 경험했을 때, 솔직히 이건 예상 밖이었습니다. 파드를 재시작했더니 아무것도 없는 깨끗한 상태로 돌아와 있는 것을 보고 '이게 왜 이러지?' 싶었는데, 그게 컨테이너의 휘발성 스토리지 특성이었습니다. 이 글은 그 문제를 어떻게 풀어나갔는지, 그리고 실제로 가상머신 3대에 Longhorn을 올려보면서 알게 된 것들을 정리한 내용입니다.

컨테이너 데이터가 사라지는 이유와 볼륨의 세 가지 선택지
컨테이너는 기본적으로 휘발성 스토리지를 씁니다. 쉽게 말해 컨테이너를 끄면 그 안에 저장된 데이터도 같이 사라지는 구조입니다. 처음엔 왜 이렇게 설계했을까 의아했는데, 생각해보면 컨테이너는 원래 가볍고 빠르게 켰다 끄는 게 목적이라 그게 맞는 방향이긴 합니다.
쿠버네티스에서는 이 문제를 해결하기 위해 볼륨(Volume)이라는 개념을 제공합니다. 볼륨이란 컨테이너가 사용할 가상의 디스크 공간으로, 파드와 별도로 데이터를 유지할 수 있게 해주는 저장 단위입니다. 제가 공부하면서 접한 볼륨의 종류는 크게 세 가지였습니다.
- emptyDir: 파드가 살아있는 동안에만 존재하는 임시 볼륨입니다. 파드가 삭제되면 같이 사라지지만, 파드 안의 컨테이너가 재시작되는 경우엔 데이터가 남습니다. 잘 쓰이는 방식은 아니고, 제 경험상 실습에서도 거의 쓸 일이 없었습니다.
- hostPath: 쿠버네티스 노드의 특정 디렉토리를 파드에 직접 연결하는 방식입니다. 파드가 삭제되어도 노드에는 데이터가 남지만, 파드가 어떤 노드에 배치될지 보장이 안 되기 때문에 실무에서는 잘 쓰지 않습니다.
- PVC/PV: 실무에서 거의 이것만 쓴다고 봐도 무방합니다. 전용 스토리지 서버와 연동해 영구적으로 데이터를 보관하는 방식입니다.
PV(PersistentVolume)란 관리자가 쿠버네티스에 미리 등록해둔 실제 스토리지 자원입니다. 그리고 PVC(PersistentVolumeClaim)란 개발자가 파드를 생성할 때 "나는 이 정도 크기의 스토리지가 필요합니다"라고 요청하는 일종의 신청서입니다. 파드는 PV에 직접 접근하는 게 아니라 PVC를 통해 간접적으로 연결되는 구조라, 스토리지 환경이 바뀌어도 파드 설정을 건드리지 않아도 된다는 점이 실용적으로 느껴졌습니다. PV와 PVC는 1대1로 연결됩니다.
Longhorn으로 분산 스토리지를 직접 구축해보니
클라우드 환경이라면 AWS EBS나 Google Persistent Disk 같은 서비스를 PV로 연결하면 됩니다. 그런데 제가 공부하는 환경은 로컬 가상머신 3대가 전부였습니다. 전용 스토리지 서버를 따로 구성할 사양이 안 되니, 가상머신 3대에 직접 분산 스토리지를 올려보는 방향으로 진행했습니다. 이때 선택한 것이 Longhorn입니다.
Longhorn은 쿠버네티스 전용으로 설계된 오픈소스 분산 블록 스토리지 시스템입니다. 여기서 분산 블록 스토리지란 여러 노드에 걸쳐 데이터를 나눠 저장하고 관리하는 방식으로, 특정 노드 하나가 죽어도 데이터를 유지할 수 있게 해주는 구조입니다. CNCF(Cloud Native Computing Foundation)에서 인큐베이팅 프로젝트로 관리하고 있어 신뢰성 면에서도 인정받고 있습니다(출처: CNCF).
설치 순서는 크게 어렵지 않았습니다. 모든 노드에 open-iscsi를 설치하고 서비스를 활성화한 뒤, 마스터 노드에서 kubectl apply 명령 하나로 Longhorn을 배포합니다. 여기서 iSCSI(Internet Small Computer Systems Interface)란 네트워크를 통해 스토리지 장치에 접근하기 위한 프로토콜로, Longhorn이 각 노드의 디스크를 블록 단위로 관리할 때 사용합니다.
설치 후에는 Longhorn 프론트엔드 서비스의 타입을 ClusterIP에서 LoadBalancer로 변경해서 대시보드에 접근했습니다. ClusterIP란 클러스터 내부에서만 접근 가능한 기본 서비스 타입이고, NodePort나 LoadBalancer로 바꿔야 외부에서 접속이 가능해집니다. 대시보드에서 Storage Schedulable 수치를 확인할 수 있는데, 이게 실제로 파드에게 빌려줄 수 있는 용량입니다. 처음엔 이 수치가 생각보다 낮아서 당황했는데, Edit Node and Disks 메뉴에서 디스크를 추가해주면 Schedulable 용량이 늘어난다는 걸 직접 확인했습니다.
StorageClass를 생성할 때는 numberOfReplicas를 "1"로 설정했습니다. 원래는 3 정도로 설정해서 복제본을 여러 노드에 유지하는 게 이상적이지만, 가상머신 사양 한계상 1로 낮춘 것입니다. 실제 운영 환경이라면 etcd와 마찬가지로 복제 설정을 충분히 해두는 게 맞습니다. 참고로 etcd란 쿠버네티스 클러스터의 모든 구성 정보와 상태를 저장하는 핵심 데이터베이스인데, 이게 날아가면 운영 DB가 통째로 날아간 것과 같습니다. 그만큼 스토리지 이중화는 중요합니다.
MetalLB로 외부 접근을 가능하게 만드는 과정
파드에 PVC를 연결해서 데이터를 영구 보관할 수 있게 됐다면, 다음은 외부에서 그 서비스에 접근할 수 있어야 합니다. 쿠버네티스에서 서비스를 외부로 노출하는 방법 중 가장 안정적인 것이 LoadBalancer 타입입니다.
그런데 쿠버네티스를 온프레미스(자체 서버)로 직접 구축하면 LoadBalancer 타입이 기본으로 제공되지 않습니다. 클라우드 환경에서는 AWS ELB나 GCP Load Balancer 같은 서비스가 자동으로 연동되지만, 로컬 환경에선 그게 없습니다. 제가 직접 구축한 가상머신 환경에서 LoadBalancer 타입 서비스를 만들었더니 EXTERNAL-IP가 영원히 pending 상태로 머물러 있었습니다. 이 문제를 해결하기 위해 설치하는 것이 MetalLB입니다.
MetalLB는 베어메탈(물리 서버 또는 로컬 가상머신처럼 클라우드 로드밸런서가 없는 환경) 쿠버네티스 클러스터에서 LoadBalancer 서비스를 구현해주는 오픈소스 플러그인입니다. 여기서 베어메탈이란 클라우드 공급자의 인프라 없이 직접 구성한 서버 환경을 뜻합니다. MetalLB를 설치하고 사용할 IP 대역을 IPAddressPool로 지정해두면, LoadBalancer 타입 서비스가 생성될 때 그 대역에서 자동으로 외부 IP를 하나 뽑아서 할당해줍니다.
쿠버네티스 서비스 타입을 정리하면 다음과 같습니다.
- ClusterIP: 클러스터 내부에서만 접근 가능한 기본 타입. 내부 서비스 간 통신에 사용합니다.
- NodePort: 외부에서 노드 IP와 포트 번호로 접근 가능. 데모나 테스트 용도에 적합하지만, 노드가 사라지면 접근이 끊길 수 있습니다.
- LoadBalancer: 외부에서 단일 IP로 안정적으로 접근 가능. 실제 서비스 운용에 사용하며, 온프레미스 환경에서는 MetalLB 같은 플러그인이 필요합니다.
마리아DB 파드를 구성할 때는 ConfigMap으로 환경변수를 분리하고, PVC로 /var/lib/mysql 경로를 마운트한 뒤, LoadBalancer 타입 서비스를 붙이는 구조로 진행했습니다. ConfigMap(컨피그맵)이란 파드 설정과 환경변수를 분리해서 관리하기 위한 쿠버네티스 오브젝트로, 같은 이미지를 환경에 따라 다르게 운영할 수 있게 해줍니다. 이 구조 덕분에 파드가 재시작되거나 다른 노드로 옮겨가도 데이터베이스 데이터는 그대로 유지되는 것을 확인할 수 있었습니다. CNCF의 쿠버네티스 문서에서도 스테이트풀한 워크로드는 PVC를 통한 영구 스토리지 연결을 권장하고 있습니다(출처: Kubernetes 공식 문서).
쿠버네티스를 처음 접하면 네트워크와 스토리지 개념이 한꺼번에 쏟아져서 막막하게 느껴질 수 있습니다. 제 경험상 이건 한 번에 다 이해하려고 하면 오히려 더 어렵습니다. PVC와 PV의 관계를 먼저 손으로 만들어보고, 그다음 Longhorn으로 실제 스토리지를 붙여보고, 마지막으로 MetalLB로 외부 접근까지 열어보는 순서로 따라가면 각 개념이 왜 필요한지 자연스럽게 납득이 됩니다. 사양이 부족한 로컬 환경이더라도 직접 손을 움직여보는 것이 결국 가장 빠른 이해 방법이었습니다.