본문 바로가기
프로그래밍/Kubernetes

EKS + EBS로 이해하는 Kubernetes 스토리지

by Hwan2 2026. 2. 19.
반응형

Kubernetes를 “좀 깊게” 파기 시작하면 스토리지에서 한 번은 멈춥니다.

  • “Pod 재시작하면 데이터가 사라지나?”
  • “emptyDir은 기본이야?”
  • “PV/PVC는 왜 둘로 나뉘어?”
  • “EBS 하나를 여러 노드가 동시에 마운트해도 돼?”
  • “Retain인데 PVC 삭제하니 Terminating/Released는 뭐지?”
  • “100Gi를 200Gi로 늘리면 무중단이야?”
  • “AZ는 어떻게 유지돼? 노드 죽으면 같은 AZ로 왜 다시 떠?”

이 글은 EKS + EBS(=블록 스토리지) 기준으로, 위 질문들을 개념 → 동작 원리 → 운영 시나리오 순서로 한 번에 정리합니다.
중간중간 AWS/EKS 얘기가 나오지만, 핵심은 Kubernetes 스토리지의 일반 원리입니다.

근거 문서(중요 레퍼런스)

Kubernetes Volumes / PV / StorageClass / kube-scheduler / VolumeBinding
https://kubernetes.io/docs/concepts/storage/volumes/
https://kubernetes.io/docs/concepts/storage/persistent-volumes/
https://kubernetes.io/docs/concepts/storage/storage-classes/
https://kubernetes.io/docs/concepts/scheduling-eviction/kube-scheduler/
https://kubernetes.io/docs/reference/scheduling/config/

kube-scheduler VolumeBinding 플러그인 동작(“bound PVC면 PV nodeAffinity 체크”)
https://pkg.go.dev/k8s.io/kubernetes/pkg/scheduler/framework/plugins/volumebinding

CSI topology + external-provisioner가 PV nodeAffinity를 자동 설정하는 코드
https://kubernetes-csi.github.io/docs/topology.html
https://raw.githubusercontent.com/kubernetes-csi/external-provisioner/master/pkg/controller/controller.go

AWS EBS는 같은 AZ 인스턴스에만 attach 가능
https://docs.aws.amazon.com/ebs/latest/userguide/ebs-attaching-volume.html

Karpenter: PV topology를 고려해 노드 생성(스케줄링 요구사항에 zone 포함)
https://karpenter.sh/docs/concepts/scheduling/

 

1. Kubernetes에서 “디스크”는 어디에 있나: 컨테이너 파일시스템 vs Volume

https://www.mdpi.com/2079-9292/14/17/3393

1.1 컨테이너의 writable layer(루트 파일시스템)

Pod 스펙에 volumes:를 아무 것도 안 붙여도 컨테이너는 / 아래에 파일을 쓸 수 있습니다.
이는 컨테이너 이미지 위에 얹힌 writable layer(컨테이너 런타임 레이어)입니다.

  • 컨테이너 프로세스 크래시 → 재시작 정도는 같은 Pod 오브젝트/같은 노드에서 이루어질 수 있어, “데이터가 남아있는 것처럼” 보일 때가 많습니다.
  • 하지만 이걸 영속 스토리지로 믿으면 위험합니다.

정확히 말하면, “남아 보일 때도 있다”이지, Kubernetes가 영속성을 보장해주는 메커니즘은 아닙니다.

1.2 “Pod 재시작”이 섞여 쓰이는 게 문제

현업에서 “재시작”은 흔히 아래가 섞입니다.

  • 컨테이너 재시작: 같은 Pod 오브젝트, 같은 노드에서 컨테이너만 다시 뜸
  • Pod 재생성/재스케줄: 기존 Pod가 사라지고 새 Pod가 생김(노드도 바뀔 수 있음)

두 번째(재생성/재스케줄) 상황이면 컨테이너 writable layer는 새로 만들어진다고 보는 게 안전합니다.
그래서 Kubernetes는 데이터를 안전하게 다루려면 명시적 Volume을 쓰라고 개념을 구성합니다.
참고: https://kubernetes.io/docs/concepts/storage/volumes/


2. “볼륨 마운트 방법” 전체 지도: Volume 타입 분류

Kubernetes의 Volume은 종류가 정말 많습니다. 공식 문서도 “다양한 목적의 다양한 Volume 타입”을 전제로 설명합니다.
참고: https://kubernetes.io/docs/concepts/storage/volumes/

 

실무에서 이해하기 쉬운 분류는 아래 2축입니다.

  • 수명(lifecycle): ephemeral(임시) vs persistent(영속)
  • 저장 위치: 노드 로컬 vs 외부 스토리지(네트워크/클라우드)

2.1 Ephemeral(임시) 계열: Pod와 생명주기를 같이함

대표적으로:

 

2.2 Persistent(영속) 계열: Pod가 바뀌어도 데이터를 남기려는 목적

표준 패턴은 persistentVolumeClaim(PVC)로 마운트하는 방식입니다.
https://kubernetes.io/docs/concepts/storage/persistent-volumes/

  • NFS 같은 네트워크 파일시스템
  • 클라우드 스토리지(EBS/EFS 등)를 CSI 드라이버로 붙이는 구조

3. emptyDir은 “기본(default)”인가?

결론부터:

  • emptyDir은 기본으로 자동 생성되는 것이 아니라, Pod spec에 명시할 때만 생깁니다.
  • 다만 “볼륨을 안 쓰면” 컨테이너 writable layer가 있으니, 체감상 “그냥 디스크가 있는 것처럼” 보일 수 있습니다.
  • 둘의 차이점은 emptyDir은 volume의 형태라 파드 안의 컨테이너들 끼리 공유가 가능합니다.(volumeMounts: 를 통해)

emptyDir의 핵심은:


4. emptyDir을 “쓰는 것”과 “안 쓰는 것”의 차이

4.1 emptyDir을 쓰면 얻는 것

  • Pod 내 컨테이너 간 공유 디렉터리가 명확해짐(예: sidecar가 만든 파일을 app이 읽음)
  • 수명주기 정의가 명확해짐: “Pod가 노드에서 사라지면 데이터 삭제”
    https://kubernetes.io/docs/concepts/storage/volumes/#emptydir
  • medium: Memory로 tmpfs(메모리) 기반 초고속 scratch도 구성 가능

4.2 emptyDir을 안 쓰고 writable layer에 쓰면 생기는 문제

  • 데이터 수명이 명시적으로 보장되지 않음(운영 이벤트/재스케줄 시 예기치 않은 소실로 보일 수 있음)
  • 컨테이너 간 공유도 애매해짐(각 컨테이너 FS는 기본적으로 분리)

정리하면: emptyDir은 “영속 데이터”가 아니라 scratch 공간을 명시적으로 만들기 위한 장치입니다.


5. PV/PVC/StorageClass: 왜 이렇게 복잡하게 나눴을까?

Kubernetes의 표준 영속 스토리지는 다음 3단으로 이해하면 깔끔합니다.

  • PV (PersistentVolume): 클러스터 입장에서 “실제 스토리지 한 조각”
  • PVC (PersistentVolumeClaim): 사용자가 “이만큼 스토리지 주세요”라고 요청하는 청구서
  • StorageClass: “어떤 방식/성능/정책으로 PV를 만들지” 정의한 템플릿(동적 프로비저닝의 핵심)

Kubernetes 문서에서도 PV/PVC는 바인딩(일반적으로 1:1)을 전제로 설명합니다.
참고: https://kubernetes.io/docs/concepts/storage/persistent-volumes/


6. StorageClass란 무엇인가? (EKS+EBS 예시 포함)

StorageClass는 “정해진 몇 개 중에서 고르는 메뉴”라기보다,

  • 클러스터 관리자가 정의하는 리소스
  • 내부/외부 provisioner(드라이버)에 어떤 파라미터로 볼륨을 만들지 결정하는 정책

입니다.
참고: https://kubernetes.io/docs/concepts/storage/storage-classes/

 

또한 “기본(default) StorageClass” 개념이 있어, PVC에 storageClassName을 생략하면 default로 자동 채워질 수 있습니다.
참고: https://kubernetes.io/docs/concepts/storage/storage-classes/#default-storageclass

6.1 EBS(gp3) StorageClass 예시

아래 예시는 EBS CSI(ebs.csi.aws.com) + gp3 + WaitForFirstConsumer + 확장 허용을 보여줍니다.

 
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: ebs-gp3
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"  # (선택) default SC
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
allowVolumeExpansion: true
parameters:
  type: gp3
  csi.storage.k8s.io/fstype: ext4

포인트:


7. “EBS 하나를 여러 노드가 동시에 마운트 가능?” (가장 많이 하는 착각)

7.1 기본 답: 일반적인 EBS + ext4/xfs 조합은 “한 번에 한 노드(RWO)가 표준”

EBS는 블록 스토리지입니다. Kubernetes에서 EBS CSI로 붙이는 일반적인 패턴은 사실상:

  • ReadWriteOnce(RWO): “동시에 한 노드에 attach/mount”

를 기본으로 봅니다.

여기서 많이 놓치는 디테일:

  • RWO는 “Pod 1개만”이 아니라, “한 노드” 기준입니다.
    즉, 같은 노드 위의 여러 Pod가 같은 PVC를 마운트하는 건 가능할 수 있습니다(워크로드/마운트 방식에 따라).

7.2 예외: EBS Multi-Attach (하지만 운영 난이도/제약이 큼)

AWS에는 EBS Multi-Attach 기능이 있지만(특정 볼륨 타입/조건),
여러 노드가 동일 블록 디바이스를 동시에 쓰려면 파일시스템/애플리케이션 레벨에서 동시 쓰기를 안전하게 처리할 설계가 필요합니다.

그래서 “여러 노드가 동시에 읽고/쓰는 공유 스토리지”가 필요하면 보통은:

  • EBS Multi-Attach를 억지로 쓰기보다
  • EFS 같은 RWX 파일시스템으로 가는 게 정석

입니다.


8. PV 100Gi 만들고 PVC 10Gi 요청하면 10Gi만 쓰나, 100Gi까지 쓰나?

정리:

실제로 얼마나 쓸 수 있는지는 스토리지 백엔드/파일시스템이 결정합니다.

  • EBS 동적 프로비저닝이면 보통 “PVC 요청 = EBS 생성 크기”가 되어 PV도 같이 맞춰짐
  • 수동 PV로 PV가 더 크면, 파일시스템이 그 크기만큼 열려 있는 한 더 쓸 수도 있음

즉 “무조건 10Gi로 제한된다”라고 단정하기는 어렵습니다. 엄격한 quota가 필요하면 별도 기능/구성이 필요합니다.


9. PV 1개를 여러 PVC가 참조할 수 있나?

원칙적으로 안 됩니다.

PV는 특정 PVC와 바인딩되고, PV에는 그 바인딩을 가리키는 claimRef가 들어갑니다.
참고: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#binding

공유가 필요하면 보통은:

  • PV를 여러 PVC가 나눠쓰는 게 아니라
  • 하나의 PVC를 여러 Pod가 마운트하는 구조(RWX 가능 스토리지)로 갑니다.

10. “Pod 100개가 같은 PVC를 쓰는데 용량이 다 차면 무슨 일이?”

일반적으로:

  • 애플리케이션에서 ENOSPC(No space left on device) 에러
  • 로그/DB/임시파일 쓰기 실패 → 앱 에러/크래시 루프 가능

Kubernetes가 자동으로 “디스크 찼으니 확장해줄게”를 하진 않습니다(운영자가 확장하거나 별도 자동화 필요).

그리고 더 중요한 함정:

  • PVC AccessMode가 RWO인지 RWX인지
  • EBS(RWO) 기반인지, EFS(RWX) 기반인지

에 따라 “처음부터 성립 가능한 구조인지”가 갈립니다.


11. Retain/Delete: PVC 삭제했을 때 PV/EBS는 어떻게 되나?

Reclaim Policy는 크게 두 가지가 핵심입니다.

  • Delete: PVC가 지워지면 PV와 백엔드 스토리지도 정리되는 방향
  • Retain: PVC가 지워져도 PV/데이터는 남겨서 수동으로 회수(reclaim)

개념은 PV reclaimPolicy 문서에 정리되어 있습니다.
참고: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#reclaim-policy

11.1 Retain일 때 흔히 보는 상태: Released

PVC를 삭제하면 PV는 보통 Released 상태로 갑니다.

중요:

  • Released는 “바로 재사용 가능”을 의미하지 않습니다.
  • PV 안에 이전 PVC를 가리키는 claimRef가 남아있고,
  • 데이터가 남아있기 때문에 운영자가 수동으로 정리해야 합니다.

12. Terminating에서 안 지워지는 이유: “스토리지 오브젝트 보호(finalizer)”

PVC/PV가 “삭제 요청했는데 Terminating에서 멈춤”은 대부분 finalizer 때문입니다.

Kubernetes는 “Storage Object in Use Protection”으로 PVC/PV에 보호(finalizer)를 붙여,
Pod가 사용 중인 PVC를 실수로 삭제하는 사고를 막습니다.
참고: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#storage-object-in-use-protection

실무적으로는:

  • 어떤 Pod가 spec.volumes에서 그 PVC를 참조하고 있으면 “사용 중”으로 판정될 수 있고
  • 그 Pod가 완전히 없어져야(삭제되어 API에서 사라져야) 보호가 풀리면서 PVC 삭제가 완료됩니다.

13. Released PV는 “못 쓰는 거야?” 그리고 claimRef 제거 패치는 뭐야?

13.1 /spec/claimRef는 무엇인가?

PV 오브젝트 입장에서 “나는 어떤 PVC에 붙어있다”를 기록하는 필드가 claimRef입니다.
PV-PVC 바인딩 구조의 핵심 포인터입니다.

13.2 claimRef 제거 패치의 의미

Retain으로 남은 PV가 Released 상태일 때,
운영자가 PV를 재사용 가능한 상태로 돌리기 위해 claimRef를 제거하는 경우가 있습니다.

예:

 
kubectl patch pv <pv-name> --type json \ -p='[{"op":"remove","path":"/spec/claimRef"}]'

주의:

  • claimRef만 제거하고 재사용하면 데이터 섞임/유출 사고가 날 수 있습니다.
  • Retain을 쓰는 이유가 “데이터를 남겨서 수동 처리”이므로, 데이터 wipe/복구/백업 절차를 런북으로 분리하는 게 안전합니다.

14. StatefulSet: “템플릿을 보고 PVC를 자동 생성”은 무슨 말인가?

StatefulSet은 “상태가 있는 워크로드”를 위해 만들어진 컨트롤러이고, 중요한 특징이 두 개입니다.

  • Pod에 안정적인 이름/순서/정체성을 부여
  • Pod마다 안정적인 스토리지를 붙일 수 있게 설계됨

여기서 스토리지의 핵심이 volumeClaimTemplates입니다.

Kubernetes 문서는 StatefulSet 예시에서:

즉 “차트에 pvc.yaml이 없다”는 말은 이런 뜻입니다.

  • Helm 차트가 직접 PVC 오브젝트를 배포한 게 아니라
  • StatefulSet의 volumeClaimTemplates에 “PVC 스펙 템플릿”만 적어두었고
  • StatefulSet 컨트롤러가 web-0, web-1… Pod를 만들면서
  • 거기에 대응하는 PVC를 자동으로 생성했다

15. volumeClaimTemplates는 왜 쓰나? PVC를 그냥 만들면 안 되나?

둘 다 가능하지만 목적이 다릅니다.

15.1 PVC를 직접 만들면 좋은 케이스

  • 특정 이름의 PVC를 여러 리소스가 공유해야 함(공유/고정 이름 필요)
  • Helm/GitOps에서 PVC를 명시적으로 관리하고 싶음(드리프트 최소화 목적)

15.2 volumeClaimTemplates가 빛나는 케이스(StatefulSet의 핵심)

  • 레플리카(Pod)마다 자기 전용 디스크가 필요함(예: DB, 큐, stateful 캐시)
  • 스케일 아웃하면 자동으로 PVC가 늘어나야 함
  • Pod가 재스케줄되어도 “그 Pod의 디스크”가 따라다녀야 함

16. EBS 100Gi를 200Gi로 늘리면 무중단(Zero downtime)인가?

“무중단”은 층을 나눠야 합니다.

  • (A) 클라우드 레벨: EBS 디바이스 크기 증가
  • (B) 노드/파일시스템 레벨: 파일시스템 resize 반영
  • (C) 애플리케이션 레벨: 쓰기/읽기 실패 없이 체감 무중단인지

16.1 EBS 볼륨 수정(크기 증가)은 인스턴스에 붙은 상태에서도 가능(조건부)

AWS 문서(attach 개념/제약 포함):
https://docs.aws.amazon.com/ebs/latest/userguide/ebs-attaching-volume.html

16.2 Kubernetes에서 정석 루트는 PVC를 늘리는 것

StorageClass에 allowVolumeExpansion이 있고 드라이버가 지원하면,
PVC 요청 용량을 늘려 확장할 수 있습니다.
참고: https://kubernetes.io/docs/concepts/storage/storage-classes/#allow-volume-expansion
PVC 확장 개념: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#expanding-persistent-volumes-claims

16.3 파일시스템 확장은 즉시 끝나지 않을 수 있음

Kubernetes는 확장 과정에서 FileSystemResizePending 같은 상태를 언급하고,
경우에 따라 Pod 재시작/재마운트가 필요할 수 있다고 설명합니다.
참고: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#expanding-persistent-volumes-claims

결론:

  • EBS 크기 증가 자체는 “꽤 무중단에 가깝게” 진행될 수 있지만,
  • 앱 관점에서 “완전 무중단으로 usable space가 즉시 늘어난다”는 건 환경에 따라 달라질 수 있습니다.

17. allowVolumeExpansion: true면 “PVC만” 올려도 EBS/PV가 같이 늘어나나?

일반적인 EKS + EBS CSI 구성에서는 보통 아래 흐름으로 이해하면 됩니다.

  • StorageClass: allowVolumeExpansion=true
  • PVC spec.resources.requests.storage를 증가
  • CSI resizer가 백엔드(EBS) 확장 요청
  • PV capacity 업데이트
  • 노드에서 파일시스템 확장 완료(필요 시 Pod 재시작/재마운트)

참고:


18. “PV는 100Gi인데 PVC를 200Gi로 늘리면 드리프트 나지 않나?” — Helm/StatefulSet에서 자주 터지는 포인트

특히 StatefulSet + volumeClaimTemplates 조합에서 더 그렇습니다.

  • 템플릿(values)은 100Gi
  • 운영 중 PVC만 200Gi로 확장

이면:

  • 실제 PVC: 200Gi
  • 차트 템플릿: 100Gi

로 GitOps/Helm 관점에서 괴리가 생깁니다.

실무에서 흔한 대응은:

  • 템플릿(values)은 200Gi로 올려 “미래 기준”을 맞추고
  • 이미 생성된 PVC는 운영 작업으로 patch/edit 해서 확장하고
  • 필요 시 Pod 재시작으로 filesystem resize를 마무리

처럼 “템플릿 관리”와 “기존 리소스 확장”을 분리하는 방식입니다.


19. (중요) AZ는 어떻게 유지돼? 노드 죽으면 왜 같은 AZ로 다시 떠?

핵심은 이것입니다.

  • “노드가 AZ에 고정”되는 게 아니라,
  • “EBS(PV)가 AZ 종속(zonal)”이고,
  • 그 PV를 쓰는 Pod는 그 AZ 노드에서만 스케줄 가능해서,
  • 결과적으로 Karpenter가 같은 AZ로 노드를 띄우게 됩니다.

근거 흐름:

  1. AWS EBS는 같은 AZ 인스턴스에만 attach 가능
    https://docs.aws.amazon.com/ebs/latest/userguide/ebs-attaching-volume.html
  2. PV는 nodeAffinity로 “이 볼륨을 접근 가능한 노드 제약”을 표현할 수 있고,
    스케줄러는 bound PVC의 PV nodeAffinity가 노드에서 만족되는지 검사합니다.
    (VolumeBinding 플러그인 설명에 명시)
    https://pkg.go.dev/k8s.io/kubernetes/pkg/scheduler/framework/plugins/volumebinding
  3. 동적 프로비저닝(CSI + external-provisioner)에서는 PV nodeAffinity가 사람이 수동으로 넣지 않아도 자동으로 채워질 수 있습니다.
    (external-provisioner 코드에서 topology로 PV nodeAffinity를 설정)
    https://raw.githubusercontent.com/kubernetes-csi/external-provisioner/master/pkg/controller/controller.go
    CSI topology 개념: https://kubernetes-csi.github.io/docs/topology.html
  4. Karpenter는 Pending Pod의 스케줄링 요구사항(토폴로지 포함)을 보고,
    그 요구사항을 만족하는 노드를 프로비저닝하려고 합니다.
    https://karpenter.sh/docs/concepts/scheduling/

결론적으로:

  • 노드가 죽으면 Pod는 재스케줄되고,
  • PV가 특정 AZ로 핀돼 있으면 Pod는 그 AZ 노드에서만 스케줄 가능,
  • 그 AZ에 노드가 없으면 Pod는 Pending,
  • Karpenter가 그 AZ에 노드를 띄우려고 시도합니다(조건이 맞는 경우).

20. 전체 흐름을 한 장으로 요약

20.1 EKS + EBS(동적 프로비저닝) 기본 플로우

StorageClass(ebs.csi.aws.com, allowVolumeExpansion=true, WaitForFirstConsumer)

PVC(100Gi) → (동적 프로비저닝) → PV(100Gi) + EBS(100Gi)

Pod mounts PVC (실제 attach는 “노드(EC2)”에)

20.2 Retain + PVC 삭제 시 상태 변화

PVC 삭제 요청
↓ (pvc-protection finalizer: 사용 중이면 Terminating 유지)
PVC 실제 삭제 완료

PV 상태: Released (Retain) → 데이터 남아있음 → 수동 회수 필요

20.3 100Gi → 200Gi 확장(정석)

StorageClass: allowVolumeExpansion=true

PVC requests.storage = 200Gi 로 수정

CSI resizer가 백엔드(EBS) 확장

PV capacity 업데이트

노드에서 파일시스템 확장 완료(필요 시 Pod 재시작/재마운트)

 

반응형

댓글


스킨편집 -> html 편집에서