Dev Notes
Web Vitals
Web Vitals
Dev Notes
Docs홈 k3s 클러스터 구축기 (6) — Longhorn 분산 스토리지
Web Vitals
Web Vitals
GitHub
Infra40분

홈 k3s 클러스터 구축기 (6) — Longhorn 분산 스토리지

CSI 기반 분산 스토리지 개념과 Longhorn ArgoCD 배포, 데이터베이스 워크로드 적용

2026년 2월 11일
k3skuberneteslonghorndistributed-storagecsiargocdpvc

환경: k3s v1.31+, Longhorn v1.8.1, ArgoCD v3.3+, Ubuntu Server 24.04 LTS

들어가며

4편에서 Bitnami Helm 차트로 Redis를 배포할 때, storageClassName을 별도로 지정하지 않았습니다. k3s의 기본 StorageClass인 local-path가 자동으로 사용되었고, PVC는 특정 노드의 로컬 디스크에 바인딩되었습니다. Redis처럼 자체 복제를 지원하고 데이터 유실이 허용되는 캐시 워크로드라면 이것으로 충분하지만, 모든 워크로드가 그렇지는 않습니다.

local-path로 생성된 PVC는 해당 노드의 디스크에 직접 연결되므로, Pod도 그 노드에서만 스케줄링됩니다. 노드가 장애를 겪으면 디스크에 접근할 수 없고, Pod은 다른 노드에서 재시작되더라도 데이터에는 도달하지 못합니다. 데이터베이스처럼 유실이 허용되지 않는 워크로드에서는 이 구조가 치명적입니다.

이 문제를 해결하려면 스토리지 레이어를 특정 노드에서 분리해야 합니다. 여러 노드의 디스크를 하나의 풀로 묶고, 데이터를 복제본으로 분산 저장하면 노드 한 대가 죽어도 나머지 노드에서 데이터를 제공할 수 있습니다. 이것이 분산 스토리지이며, 이 편에서는 그중 가장 가벼운 솔루션인 Longhorn을 ArgoCD로 배포하고, 데이터베이스 워크로드에 적용하는 방법을 다룹니다.

분산 스토리지의 구조

분산 스토리지는 CSI(Container Storage Interface) 기반으로 동작합니다. 각 노드에 에이전트(DaemonSet)가 실행되면서 로컬 디스크의 일부를 스토리지 풀에 기여하고, PVC가 생성되면 풀에서 볼륨을 할당하는 방식입니다.

Pod볼륨이 로컬인지 네트워크인지 모름
PVC용량과 AccessMode만 요청
StorageClass어떤 provisioner를 쓸지 결정
Provisionerlocal-path 또는 분산 스토리지 엔진

Pod 입장에서는 달라지는 것이 없습니다. 동일하게 PVC를 만들고 마운트해서 사용할 뿐, 볼륨이 로컬 디스크에 있는지 네트워크 분산 스토리지에 있는지 알지 못합니다. 바뀌는 건 PVC 아래쪽, 즉 StorageClass가 가리키는 provisioner입니다. local-path는 노드의 디스크에 직접 볼륨을 만들고, 분산 스토리지(Longhorn 등)는 네트워크 스토리지 엔진을 경유하여 여러 노드에 복제본을 분산 저장합니다.

StorageClass

StorageClass는 볼륨을 어떻게 프로비저닝할지 정의하는 템플릿입니다. PVC가 용량을 요청하면, StorageClass가 어떤 provisioner를 써서 어떤 설정으로 볼륨을 만들지 결정합니다.

YAML
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: longhorn-replicated
provisioner: driver.longhorn.io
reclaimPolicy: Retain
volumeBindingMode: Immediate
parameters:
  numberOfReplicas: "3"
  staleReplicaTimeout: "2880"
필드설명
provisioner볼륨을 생성하는 CSI 드라이버. driver.longhorn.io, rbd.csi.ceph.com 등
reclaimPolicyPVC 삭제 시 볼륨 처리 방식. Retain이면 보존, Delete면 함께 삭제
volumeBindingModeImmediate는 PVC 생성 즉시 할당, WaitForFirstConsumer는 Pod 스케줄링 시점에 할당
parametersprovisioner별 세부 설정. 복제본 수, 디스크 타입 등

StorageClass는 여러 개를 만들어두고 용도별로 나눠 쓸 수 있습니다. 하나를 default로 지정하면, PVC에서 storageClassName을 생략했을 때 자동으로 사용됩니다. 현재 클러스터의 구성은 다음과 같습니다.

StorageClassprovisionerdefault용도
local-pathrancher.io/local-pathO캐시, 임시 데이터 등 유실 가능한 데이터
longhorndriver.longhorn.ioXDB 등 복제본이 필요한 중요 데이터

storageClassName 생략 시 동작

PVC에서 storageClassName을 생략하면 default StorageClass가 사용됩니다. 4편에서 Redis values.yaml에 storageClass를 지정하지 않은 것이 바로 이 경우이며, k3s 기본 default인 local-path가 적용되었습니다.

스토리지 유형

분산 스토리지를 이해하려면, 스토리지가 어떤 레벨에서 동작하는지를 먼저 파악해야 합니다.

애플리케이션파일 읽기/쓰기 요청
파일시스템 (ext4, xfs)파일, 디렉토리 개념이 여기서 생김
블록 스토리지블록 번호 + 데이터만 다룸
물리 디스크 또는 네트워크 볼륨

Kubernetes에서 사용하는 스토리지는 크게 세 유형으로 나뉩니다.

유형설명특징
블록 스토리지고정 크기 블록 단위로 데이터를 저장. 디스크처럼 동작하며 그 위에 파일시스템을 올림가장 low-level, 성능이 좋음, DB에 적합, 보통 RWO
파일 스토리지NFS, CephFS처럼 파일시스템 레벨로 공유여러 노드에서 동시 마운트 가능 (RWX)
오브젝트 스토리지S3처럼 HTTP API로 파일 저장/조회마운트가 아닌 API 방식, 비정형 데이터에 적합

세 유형 모두 분산 스토리지 위에서 동작하면 여러 노드의 디스크를 풀링하여 사용할 수 있습니다. Longhorn은 이 중 블록 스토리지를 중심으로 제공하며, RWX가 필요한 경우에는 NFS를 경유하는 방식으로 지원합니다.

AccessMode와 복제본

AccessMode

PVC를 생성할 때 지정하는 AccessMode는 볼륨에 동시에 접근할 수 있는 노드 수를 제한하는 설정입니다. 데이터가 어디에 저장되는지와는 별개의 개념이므로, 분산 스토리지와 혼동하지 않아야 합니다.

모드설명
ReadWriteOnce (RWO)한 번에 하나의 노드에서만 마운트
ReadWriteMany (RWX)여러 노드에서 동시 읽기/쓰기
ReadOnlyMany (ROX)여러 노드에서 읽기 전용

RWO 볼륨이라 해도 분산 스토리지를 사용하면 데이터 복제본은 여러 노드에 분산 저장되어 있습니다. "하나의 노드에서만"은 마운트에 대한 제약이지 저장 위치에 대한 제약이 아닙니다. 따라서 노드 장애가 발생하면 다른 노드에서 Pod이 재시작되고, 동일한 볼륨에 재연결되어 서비스를 계속할 수 있습니다.

복제본

복제본(Replica)은 노드 장애 시 데이터를 보호하기 위해 존재합니다. 복제본이 1개이면 해당 노드가 죽었을 때 데이터가 유실되고, 3개이면 서로 다른 노드 3곳에 동일한 데이터가 저장되어 노드 하나가 죽어도 서비스가 지속됩니다. 쓰기가 발생하면 모든 복제본에 동시에 기록하고, 읽기는 하나의 복제본에서 수행합니다. 노드가 복구되면 자동으로 복제본을 재동기화합니다.

복제본 수만큼 실제 용량이 소모됩니다

복제본 3개 설정에서 10Gi PVC를 만들면 실제로 30Gi(각 노드에 10Gi씩)가 소모됩니다. 가용성과 용량의 트레이드오프를 고려하여, 데이터 중요도에 따라 복제본 수를 결정해야 합니다. 노드 2대인 홈 클러스터에서는 복제본 2개가 현실적인 최대치입니다. 또한 노드 장애 시 자동 복구는 다른 노드에 볼륨을 수용할 여유 용량이 있을 때 성립하므로, 용량 계획 시 이 점을 감안해야 합니다.

Longhorn vs Ceph

Kubernetes에서 분산 스토리지로 가장 많이 언급되는 두 선택지는 Longhorn과 Ceph(Rook-Ceph)입니다.

항목LonghornCeph (Rook-Ceph)
아키텍처볼륨 단위로 독립 엔진 프로세스클러스터 레벨 분산 (OSD + CRUSH 알고리즘)
지원 스토리지블록(RWO) 중심, RWX는 NFS 경유블록(RBD) + 파일(CephFS) + 오브젝트(RGW) 네이티브
운영 복잡도낮음. 웹 UI 직관적, 컴포넌트 적음높음. MON/OSD/MGR/MDS 등 컴포넌트 다수, CRUSH map·PG 이해 필요
성능볼륨마다 엔진 오버헤드, 볼륨 수 증가 시 리소스 소모 증가대규모에서 효율적, OSD가 디스크 직접 접근
리소스 요구가벼움. 노드당 수백 MB무거움. OSD당 최소 2~4GB RAM, MON 별도 리소스
적합한 상황블록 스토리지 중심, 운영 부담 최소화블록/파일/오브젝트 통합 필요, 대규모 환경

블록 스토리지만 필요하고 운영 부담을 최소화하고 싶다면 Longhorn이 적합합니다. 특히 홈 클러스터처럼 노드가 2~3대이고 자원이 제한된 환경에서는, Ceph의 무거운 컴포넌트 구성이 부담이 됩니다. 오브젝트 스토리지(S3 호환)나 네이티브 RWX 파일시스템이 필요해지는 시점에 Ceph 도입을 고려하면 됩니다.

Longhorn 배포

3편에서 구축한 ArgoCD App-of-Apps 패턴으로 Longhorn을 배포합니다. Longhorn은 Helm 차트로 제공되므로, 4편의 Redis와 동일하게 multi-source로 공식 차트와 커스텀 values.yaml을 조합합니다.

사전 요구사항 — iSCSI, NFS 설치

Longhorn이 블록 디바이스를 노드에 마운트할 때 iSCSI 프로토콜을 사용합니다. 각 노드에 iSCSI와 NFS 클라이언트를 설치해야 합니다. SSH 접근이 불가능한 환경에서는 kubectl debug node로 노드 셸에 진입할 수 있습니다.

Bash
# 각 노드에서 실행
kubectl debug node/<노드명> -it --image=ubuntu -- chroot /host bash
 
apt update
apt install open-iscsi nfs-common -y
systemctl enable iscsid --now
exit

클러스터의 모든 노드에서 이 작업을 반복합니다.

values.yaml 작성

YAML
# longhorn/values.yaml
defaultSettings:
  defaultDataPath: /var/lib/longhorn       # 각 노드에서 스토리지로 사용할 경로
  defaultReplicaCount: 2                   # 볼륨당 복제본 수 (노드 수에 맞게 설정)
  storageMinimalAvailablePercentage: 15    # 노드 디스크 최소 여유 비율
  storageOverProvisioningPercentage: 200   # 오버프로비저닝 허용 비율
 
persistence:
  defaultClassReplicaCount: 2              # StorageClass 기본 복제본 수
  defaultClass: false                      # local-path를 기본으로 유지
 
longhornUI:
  enabled: true                            # 웹 UI 활성화
 
preUpgradeChecker:
  jobEnabled: false                        # ArgoCD 호환: pre-upgrade hook 비활성화

defaultClass: false로 설정하여 기존 local-path의 default 지위를 유지합니다. Longhorn을 쓰려면 PVC에서 storageClassName: longhorn을 명시적으로 지정해야 하며, 이것이 의도적인 선택을 강제하는 안전한 구성입니다.

복제본 수는 노드 수를 초과할 수 없습니다

복제본은 서로 다른 노드에 분산되어야 하므로, 노드 2대면 최대 2개, 노드 3대면 최대 3개까지만 설정할 수 있습니다.

preUpgradeChecker.jobEnabled: false

ArgoCD 환경에서는 이 설정이 필수입니다. ArgoCD가 Helm hook을 PreSync 단계에서 실행하는데, 이 시점에는 ServiceAccount가 아직 생성되지 않아 Job이 실패합니다. Longhorn 공식 ArgoCD 가이드에서도 이 설정을 권장합니다.

ArgoCD Application 작성

Longhorn은 외부 Helm 차트이므로, sources(복수형)로 공식 차트와 Git 저장소의 values.yaml을 조합합니다.

YAML
# argocd/longhorn-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: longhorn
  namespace: argocd
spec:
  project: default
  sources:
    - repoURL: https://charts.longhorn.io
      chart: longhorn
      targetRevision: 1.8.1
      helm:
        valueFiles:
          - $values/longhorn/values.yaml
    - repoURL: https://github.com/mirunamu00/helm-chart.git
      targetRevision: master
      ref: values
  destination:
    server: https://kubernetes.default.svc
    namespace: longhorn-system
  ignoreDifferences:
    - group: apiextensions.k8s.io
      kind: CustomResourceDefinition
      jsonPointers:
        - /metadata/annotations
        - /spec/conversion
        - /spec/preserveUnknownFields
        - /status
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true
      - RespectIgnoreDifferences=true

이 Application에서 가장 중요한 부분은 ignoreDifferences와 syncOptions입니다. Longhorn 컨트롤러는 CRD에 conversion webhook, status, preserveUnknownFields 등을 런타임에 자동으로 추가합니다. ArgoCD는 이를 "Helm 차트 원본과 다르다"고 판단하여 OutOfSync를 발생시키는데, ignoreDifferences로 해당 필드를 무시하고 ServerSideApply + RespectIgnoreDifferences로 sync 시에도 이 설정이 반영되도록 해야 합니다.

git push → 자동 설치

설정 저장소에 커밋하고 push하면 App-of-Apps가 변경을 감지하여 자동으로 설치를 시작합니다.

Bash
git add longhorn/ argocd/longhorn-app.yaml
git commit -m "feat: add Longhorn distributed storage"
git push

App-of-Apps가 argocd/ 폴더의 변경을 감지하면 longhorn-app.yaml에서 Application 리소스를 생성하고, ArgoCD가 Longhorn Helm 차트를 values.yaml과 함께 longhorn-system 네임스페이스에 배포합니다.

설치 확인

Bash
# ArgoCD 앱 상태
kubectl get app longhorn -n argocd
 
# Pod 상태 (모두 Running이면 정상)
kubectl get pods -n longhorn-system
 
# StorageClass 생성 확인
kubectl get storageclass

longhorn StorageClass가 목록에 나타나면 준비가 완료된 것입니다. Longhorn은 설치 시 디스크를 미리 점유하지 않으며, 지정한 경로(/var/lib/longhorn)의 디스크를 사용 가능한 풀로 인식만 해둡니다. 실제 용량 할당은 PVC가 생성될 때 요청한 크기만큼 이루어집니다. 노드별 세부 설정(특정 노드의 기여 용량 조정, 스토리지 제외 등)은 Helm values만으로 한계가 있어 Longhorn 웹 UI에서 조정합니다.

Longhorn 볼륨 활용

Longhorn이 배포되었으니, 실제 워크로드에 활용할 차례입니다. 4편에서 배포한 Redis는 캐시 용도이므로 local-path로 충분합니다. Redis는 master-replica 간 애플리케이션 레벨 복제를 자체적으로 수행하고, 캐시 데이터는 유실되더라도 원본에서 다시 채워지기 때문입니다.

하지만 데이터베이스는 사정이 다릅니다. 저장된 데이터가 파생물이 아니라 원본 그 자체이므로, 노드 장애로 인한 유실은 곧 서비스 장애를 의미합니다. 이런 워크로드에 Longhorn이 필요합니다. PostgreSQL을 Bitnami Helm 차트로 배포한다면, values.yaml에서 storageClass를 longhorn으로 지정하는 것만으로 분산 스토리지 위에서 동작하게 됩니다.

YAML
# postgresql/values.yaml
primary:
  persistence:
    storageClass: longhorn
    size: 5Gi
 
readReplicas:
  replicaCount: 1
  persistence:
    storageClass: longhorn
    size: 5Gi

이렇게 설정하면 PVC가 Longhorn provisioner를 통해 생성되며, 볼륨 데이터가 설정된 복제본 수만큼 여러 노드에 분산 저장됩니다. primary가 위치한 노드에 장애가 발생하더라도, Pod이 다른 노드에서 재시작되면 동일한 볼륨에 재연결되어 데이터를 그대로 사용할 수 있습니다. local-path였다면 PVC가 특정 노드에 묶여 있어 Pod 재스케줄링 자체가 불가능했을 상황입니다.

ArgoCD Application은 4편의 Redis와 동일한 multi-source 패턴을 사용하면 됩니다. 차트 소스를 Bitnami의 postgresql로, values 파일 경로만 변경하면 동일한 구조로 배포할 수 있습니다.

StorageClass 선택 기준

모든 워크로드에 Longhorn을 쓸 필요는 없습니다. 캐시처럼 유실이 허용되고 애플리케이션 레벨에서 복제를 처리하는 워크로드는 local-path가 적합하고, 데이터베이스처럼 원본 데이터를 직접 저장하는 워크로드에만 longhorn을 지정하는 것이 리소스를 효율적으로 사용하는 방법입니다.

트러블슈팅

정리

6편 요약

  1. 분산 스토리지: 여러 노드의 디스크를 하나의 풀로 묶어, 노드 장애에도 데이터를 보호합니다
  2. StorageClass: provisioner만 다를 뿐, Pod과 PVC의 사용법은 기존과 동일합니다
  3. Longhorn: 블록 스토리지 중심의 가벼운 솔루션. 웹 UI로 직관적인 운영이 가능합니다
  4. ArgoCD 배포: CRD ignoreDifferences + ServerSideApply 설정이 필수입니다
  5. StorageClass 선택: 캐시는 local-path, 데이터베이스는 longhorn으로 워크로드 성격에 따라 구분합니다

다음 편에서는 Prometheus와 Grafana를 배포하여, 클러스터와 워크로드의 상태를 실시간으로 모니터링할 수 있는 환경을 구축합니다.

참고 자료

  • Longhorn 공식 문서
  • Longhorn ArgoCD 설치 가이드
  • Kubernetes StorageClass 공식 문서
  • Kubernetes Persistent Volumes
  • Rook-Ceph 공식 문서
Written by

Mirunamu (Park Geonwoo)

Software Developer

관련 글 더보기

Infra

홈 k3s 클러스터 구축기 (1) — Ubuntu 설치부터 워커 노드 조인까지

4코어 8GB 미니PC 2대로 시작하는 경량 Kubernetes 홈 클러스터 구축 과정

읽기
Infra

홈 k3s 클러스터 구축기 (2) — 외부 노출과 TLS 인증서

공유기 포트포워딩, 도메인 연결, cert-manager를 통한 HTTPS 적용 과정

읽기
Infra

홈 k3s 클러스터 구축기 (3) — ArgoCD와 GitOps 배포 파이프라인

ArgoCD App-of-Apps, Helm Chart 분리 설계, GitHub Actions CI를 통한 GitOps 자동 배포 구축 과정

읽기
Infra

홈 k3s 클러스터 구축기 (7) — Prometheus + Grafana 모니터링

kube-prometheus-stack 기반 클러스터 모니터링 환경의 ArgoCD 배포와 Grafana 대시보드 활용

읽기
이전 글홈 k3s 클러스터 구축기 (5) — Next.js Redis 캐시 연동
다음 글홈 k3s 클러스터 구축기 (7) — Prometheus + Grafana 모니터링
목차
  • 들어가며
  • 분산 스토리지의 구조
  • StorageClass
  • 스토리지 유형
  • AccessMode와 복제본
    • AccessMode
    • 복제본
  • Longhorn vs Ceph
  • Longhorn 배포
  • Longhorn 볼륨 활용
  • 트러블슈팅
  • 정리
  • 참고 자료