kubernetes - Storage/OpenEBS

2026. 1. 25. 15:55·Container & DevOps

쿠버네티스 스토리지

출처: https://medium.com/@martin.hodges/adding-persistent-storage-to-your-kubernetes-cluster-5e12adb81592

컨테이너 환경에서 별도의 설정을 하지 않으면 데이터는 노드의 임시 디스크(ephemeral disk)에 저장된다. 해당 임시 디스크에 저장된 데이터는 컨테이너가 종료되거나 파드가 삭제되면 함께 사라진다.

쿠버네티스에서는 해당 문제를 해결하기 위해 파드와 데이터를 분리하는 개념을 사용한다.

 

즉, 파드는 애플리케이션 실행 단위이고, 볼륨은 데이터를 보관하는 독립된 리소스로 본다. 이 두 개념을 분리함으로써 파드가 재생성되더라도 데이터를 유지할 수 있게 되는 것이다.

쿠버네티스 스토리지의 핵심 리소스

쿠버네티스에서 스토리지를 구성할 때 주요하게 등장하는 리소스는 다음과 같다.

  • PersistentVolume(PV)
    • 클러스터 전체에서 사용 가능한 실제 스토리지 자원이다
    • 관리자가 정적으로 직접 영구 PV를 생성할 수 있지만, 운영 환경에서는 대부분 스토리지 클래스를 통해 동적으로 PV를 할당한다.
    • NFS, iSCSI, 클라우드 디스크, 로컬 디스크 등이 여기에 해당한다
    • 클러스터 관리자 관점의 리소스다
  • PersistentVolumeClaim(PVC)이다
    • 사용자가 필요한 스토리지를 요청하는 객체이다
    • 용량, 접근 모드(ReadWriteOnce, ReadWriteMany 등)를 명시한다
    • 개발자 관점의 리소스다
  • StorageClass이다
    • PV를 동적으로 생성하기 위한 템플릿이다
    • 스토리지 종류, 성능, 프로비저닝 방식 등을 정의한다
    • 관리자는 StorageClass를 준비하고, 개발자는 PVC만 사용한다

정리해보면 결국 다음과 같이 역할이 분담된다.

  • 클러스터 관리자: 어떤 스토리지를 쓸지 결정하고, StorageClass와 PV를 설계한다.
  • 개발자: 필요한 용량과 접근 방식만 정의하고, PVC를 통해 애플리케이션에 연결한다.

OpenEBS?

출처: https://openebs.io/blog/openebs-node-device-management-(ndm)%E2%80%8A%E2%80%94%E2%80%8Atroubleshooting-tips

쿠버네티스는 스토리”를 직접 제공하지 않고, StorageClass라는 동적 프로비저닝 규칙만 있으면 PV를 자동으로 만들어주는 구조이다.
클라우드(AWS/EBS, GCP/PD 등)는 StorageClass가 기본 제공되지만, 온프레미스/로컬은 기본이 없으니 OpenEBS 같은 스토리지 솔루션을 올려서 StorageClass를 만들어줘야 한다.

 

참고로, 현업에서 많이 사용되는 오픈소스 스토리지 솔루션에는 Ceph, GlusterFS, OpenEBS이 존재한다.

그 중 OpenEBS hostpath는 네트워크 스토리지가 아니라 노드 로컬 디스크의 특정 디렉터리(기본: /var/openebs/local)를 PV의 실제 저장소로 쓰는 방식이다.

 

즉, 높은 성능의 스토리지를 빠르고 간단하게 구축이 가능하지만, 노드에 종속된다는 단점이 존재한다.

OpenEBS 로컬 호스트 패스 설치

OpenEBS 설치에 앞서, 네임스페이스를 먼저 생성해준다. 오픈소스 스토리지 컴포넌트들을 기본 네임스페이스에 깔지 않고 전용 네임스페이스로 격리하는 것이 좋다.

kubectl create ns openebs

 간단한 구성이기에 헬름 차트를 이용하지 않고, 매니패스트 파일 기반으로 openebs를 설치해준다.

kubectl apply -f https://openebs.github.io/charts/openebs-operator-lite.yaml
kubectl apply -f https://openebs.github.io/charts/openebs-lite-sc.yaml

위와 같이 성공적으로 리소스들이 생성된 것을 확인할 수 있다.

  • openebs-device: 호스트 노드에서 마운트하지 않은 별도의 디바이스(ex. /dev/sdb 등) 기반으로 PV를 만들 수 있는 타입이다.
  • openebs-hostpath: 호스트 노드의 특정 디렉토리리(예: /var/openebs/local) 기반으로 PV를 만드는 타입이다.
cf. OpenEBS에서 사용하는 볼륨이 호스트 노드의 파일 시스템 용량에 영향을 끼치지 않도록 기본 디렉토리 경로가 아니라 별도의 마운트 포인트를 사용하는 것이 권장된다.

PVC & PV 생성

쿠버네티스 스토리지 모델은 아래 순서가 기본적이다.

  • PVC(PersistentVolumeClaim): “나 이만큼(예: 1Gi) 디스크 필요하다”는 요청서이다. 네임스페이스 리소스이다.
  • PV(PersistentVolume): 실제 디스크(볼륨) 그 자체이다. 클러스터 범위 리소스이다.

개발자는 PV를 직접 만들기보다 PVC만 만들고, StorageClass가 알아서 PV를 자동 생성하도록 쓰는 게 표준이다.

 

PVC를 만드는 매니패스트를 다음과 같이 작성한다.

vi date-pvc.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: default-pvc
  namespace: default
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 1Gi
  storageClassName: "openebs-hostpath"
  • kind: PersistentVolumeClaim
    PVC 리소스라는 뜻이다.
  • metadata.namespace: default
    PVC는 default 네임스페이스에 속한다.
  • spec.accessModes: ReadWriteOnce (RWO)
    한 번에 하나의 노드에서만 RW로 마운트 가능하다는 의미이다. hostpath/로컬 디스크 계열은 보통 RWO가 기본이다.
    (ReadWriteMany 같은 공유 스토리지는 NFS/CephFS 같은 네트워크 스토리지에서 주로 나온다.)
  • spec.volumeMode: Filesystem
    볼륨을 파일시스템으로 마운트한다는 뜻이다. (Block 모드도 가능하지만 지금은 FS이다.)
  • spec.resources.requests.storage: 1Gi
    1Gi 필요하다는 요청량이다.
  • spec.storageClassName: openebs-hostpath
    해당 PVC는 openebs-hostpath라는 규칙(프로비저너)을 써서 PV를 자동으로 만들어 달라는 의미이다.

위 매니패스트를 통해 PVC를 생성한다.

kubectl apply -f date-pvc.yml

정상적으로 pod는 생성되었지만, Status가 Pending 상태인 것을 확인할 수 있다. 로그를 확인하기 위해 describe 명령어를 사용한다.

위와 같은 로그가 찍혀있는 것을 확인할 수 있다. 해당 로그는 StorageClass의 volumeBindingMode가 WaitForFirstConsumer 설정으로 인해 발생한 것이다. 해당 설정의 의미는 다음과 같다.

  • 로컬 스토리지는 어느 노드의 디스크를 쓸지가 중요하다
  • 근데 PVC 단계에서는 파드가 어느 노드에 스케줄될지가 아직 모른다
  • 그래서 파드가 실제로 생성되어 노드가 결정된 뒤에, 그 노드에 맞춰 PV를 만들고 바인딩하도록 한다.

즉, Pending은 오류가 아니라 정상 대기인 경우가 많다. 반대로 Immediate면 PVC 생성 시점에 바로 PV를 잡아버리는데, 로컬 스토리지에서는 스케줄링 꼬임이 생기기 쉬워서 WaitForFirstConsumer가 자주 쓰인다.

PVC를 사용하는 Deployment 생성

PVC를 사용하는 Deployment를 생성하기 위해, 다음과 같이 매니패스트를 작성해준다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: date-pod
  namespace: default
  labels:
    app: date
spec:
  replicas: 1
  selector:
    matchLabels:
      app: date
  template:
    metadata:
      labels:
        app: date
    spec:
      containers:
        - name: date-pod
          image: busybox
          command:
            - "/bin/sh"
            - "-c"
            - "while true; do date >> /data/pod-out.txt; cd /data; sync; sync; sleep 30; done"
          volumeMounts:
            - name: date-vol
              mountPath: /data

      volumes:
        - name: date-vol
          persistentVolumeClaim:
            claimName: default-pvc
  • spec.template.spec.containers.volumeMounts
    컨테이너에서 사용할 볼륨의 정보를 지정한다. 파드는 볼륨을 사용하기 위해 volumeMounts와 volumes의 2가지를 사용한다.
  • spec.template.spec.containers.volumeMounts.name
    컨테이너 마운트 포인트에 사용할 볼륨의 이름을 지정한다
  • spec.template.spec.containers.volumeMounts.mountPath
    컨테이너 내부의 마운트 포인트를 지정한다. 지정된 마운트 포인트로 볼륨이 할당된다
  • spec.template.spec.volumes.name
    컨테이너 마운트 포인트 이름과 동일하게 매핑한다. YAML 파일 내에 볼륨 마운트가 여러 개 있으면 해당하는 볼륨 마운트 이름과 일치시킨다
  • spec.template.spec.volumes.persistentVolumeClaim.claimName
    볼륨에 사용할 PVC 이름을 지정한다. PVC는 파드와 같은 네임스페이스에 있어야 한다.

다음 명령어를 통해 디플로이먼트를 생성한다.

kubectl apply -f date-pvc-deploy.yml

위와 같이 정상적으로 리소스들이 생성된 것을 확인할 수 있다.

 

파드 안에 들어가서 PVC로 마운트한 파일(/data/pod-out.txt)에 정상적으로 데이터가 저장되고 있는지 확인한다.

kubectl exec -it deploy/date-pod -- cat /data/pod-out.txt

정상적으로 저장되고 있는 것이 확인되고, 이제 파드를 삭제하고 재시작해서 이전 데이터가 정상적으로 저장되어 있는지를 확인한다.

kubectl delete pod date-pod-5f749fb548-xr7gv
kubectl exec -it deploy/date-pod -- cat /data/pod-out.txt

이전 데이터들이 그대로 존재하는 것을 확인할 수 있다. 왜냐하면 파드를 삭제하면 컨테이너 파일시스템은 날아가지만, /data는 컨테이너 내부가 아니라 PV에 연결된 외부 저장소이기에 데이터가 보존되기 때문이다.

그래서 파드를 삭제하고 새 파드가 다시 올라와도, 같은 PVC를 마운트하면 같은 PV(=같은 노드 디렉터리)에 다시 붙으므로 이전에 쌓인 /data/pod-out.txt가 그대로 남는 것이다.

리소스 정리

PV는 파드에서 볼륨으로 사용하고 있기에, PV를 삭제하려면 먼저 볼륨을 사용하고 있는 파드를 삭제해야한다. 생성한 date-pod 디플로이먼트를 먼저 삭제해준다.

cf. PV 리소스 정리는 Deployment 삭제 → 파드 종료 → PVC 삭제 → (정책에 따라) PV 삭제 단계로 진행하는 것이 안전하다.
kubectl delete deployment date-pod

PVC를 삭제할 때는 PV의 삭제와 관련된 정책은 해당 스토리지 클래스 정책을 따르며 kubectl get pv 출력 결과에서 확인할 수 있다.

현재에는 ReclaimPolicy가 Delete로 잡혀 있다면, PVC를 삭제하면 연결된 PV도 같이 삭제된다는 의미이다. 반대로 ReclaimPolicy가 Retaim으로 잡혀 있다면, PVC를 삭제해도 PV가 남아있으며 PV 정리는 관리자가 수동으로 해줘야 한다.

 

운영에서는 실수로 PVC 삭제했는데 데이터까지 손실되는 것을 막기 위해 Retain을 쓰는 케이스도 있다.
다만 로컬 hostpath는 구조상 “노드 디렉터리에 남은 찌꺼기”까지 같이 관리해야 해서, Retain을 쓰면 운영 규칙이 더 엄격해야 한다.

 

다음 명령어를 통해 PVC를 삭제해준다.

kubectl delete pvc default-pvc

위 명령어로 PVC를 삭제하면, ReclaimPolicy의 Delete 설정으로 인해 PV도 자동으로 삭제된다. 아래 명령어를 통해 확인해본다.

kubectl get pv,pvc

PVC와 함께 PV도 삭제된 것을 확인할 수 있다.

OpenEBS 방식의 한계

OpenEBS hostpath는 편하지만, 다음과 같은 제약사항이 존재한다.

  1. 노드 종속이다
    PV 데이터가 특정 노드 디렉터리에 있으니, 파드가 다른 노드로 옮겨가면 같은 데이터를 못 볼 수 있다.
  2. HA/공유 스토리지에 약하다
    ReadWriteMany 같은 여러 노드에서 동시에 붙는 공유 디스크는 아니다.
  3. 노드 장애 시 복구가 곤란하다
    노드 디스크가 죽으면 데이터도 같이 위험해진다.

그래서 학습/개발/단일 노드/간단한 상태 저장에는 좋고, 실제 운영에서 고가용성이 필요하면 Ceph(Rook), NFS, 클라우드 블록스토리지 같은 쪽을 고려하는 게 일반적이다.

Reference

  • https://kubernetes.io/ko/docs/home/
  • 이정훈, ⌜24단계 실습으로 정복하는 쿠버네티스⌟, 위키북스, 2022, 492쪽
'Container & DevOps' 카테고리의 다른 글
  • Kubernetes - 스토리지 볼륨 스냅샷
  • Kubernetes - Helm을 이용한 MariaDB StatefulSet 구성
  • kubernetes - 사용자 SSL/TLS 인증서 적용
  • Kubernetes - Ingress 테스트용 애플리케이션 설치 및 설정 테스트
SummerToday
SummerToday
summertoday 님의 블로그 입니다.
  • SummerToday
    SummerToday
    SummerToday
  • 전체
    오늘
    어제
  • 인기 글

  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
    • 관리자
    • 분류 전체보기 (62)
      • OS & Network (4)
      • Cloud (11)
      • Container & DevOps (41)
      • Database (4)
      • Develop (0)
      • IaC (2)
  • 태그

    container
    Grafana
    EIP
    점프 계정
    cloud
    Kubernetes
    Eni
    tailscale
    K8S
    MariaDB
    gitops
    argocd
    AmazonSNS
    s2s vpn
    aws
    openebs
    계정 관리
    CI/CD
    Galera Cluster
    CloudWatch
  • hELLO· Designed By정상우.v4.10.3
SummerToday
kubernetes - Storage/OpenEBS
상단으로

티스토리툴바