Storage - 정리

2026. 1. 5. 22:08·CKA

이 글은 지금까지 다룬 스크립트(자막) 흐름을 중심으로, Docker의 스토리지 구조에서 시작해 Kubernetes의 PV/PVC, 그리고 StorageClass(동적 프로비저닝)와 volumeBindingMode까지 한 번에 이어서 정리한 “총 편집본”입니다.
중간중간 학습 과정에서 헷갈렸던 질문 포인트(예: src=data_volume은 어떻게 아는지, --mount는 어떻게 동작하는지, RWO 의미, PVC 50이 PV 100에 붙으면 100을 쓰는지 등)는 문맥에 자연스럽게 녹여 설명합니다.


1. Docker는 데이터를 어디에 저장할까? /var/lib/docker의 정체

Docker 설치 시 호스트에는 기본적으로 Docker의 작업 디렉터리가 생깁니다.

  • 일반적으로: /var/lib/docker
  • 하위에 예시로 다음과 같은 디렉터리들이 존재:
    • containers/ : 컨테이너별 메타데이터, 로그 등
    • image/ : 이미지 관련 데이터
    • volumes/ : Docker Volume 데이터(영구 데이터)
    • (환경에 따라) overlay2/, aufs/, btrfs/ 등: 스토리지 드라이버가 쓰는 영역

즉, Docker가 관리하는 “이미지/컨테이너/볼륨” 데이터가 이 루트 아래에 쌓입니다.


2. Docker 이미지/컨테이너의 핵심: 레이어드 파일시스템 + Copy-on-Write

2.1 Dockerfile 한 줄 = 이미지 레이어 하나

Docker는 이미지를 만들 때 레이어(layer) 방식으로 빌드합니다.

예:

  • FROM ubuntu → base layer
  • RUN apt-get ... → 패키지 레이어
  • RUN pip install ... → 파이썬 의존성 레이어
  • COPY . /app → 소스 코드 레이어
  • ENTRYPOINT ... → 엔트리포인트 레이어

각 레이어는 “이전 레이어 대비 변경분”만 저장하므로,

  • 재빌드 시 캐시를 활용해 빨라지고
  • 여러 이미지가 레이어를 공유해 디스크를 절약합니다.

2.2 컨테이너 실행 시: 읽기 전용 이미지 레이어 + 쓰기 가능 컨테이너 레이어

이미지는 빌드 완료 후 불변(immutable) 에 가깝습니다. 즉 이미지 레이어는 읽기 전용입니다.

컨테이너를 실행하면 Docker는:

  • 이미지 레이어들(RO) 위에
  • 컨테이너 전용 쓰기 가능 레이어(RW) 를 하나 얹습니다.

컨테이너 내부에서:

  • 로그 파일 생성
  • 임시 파일 생성
  • 파일 수정
    같은 모든 변경사항은 이 RW 레이어에 기록됩니다.

컨테이너 삭제하면 왜 데이터가 날아가나?

RW 레이어의 수명은 “컨테이너 수명”과 같습니다.
컨테이너를 지우면 RW 레이어도 함께 사라지므로 컨테이너 내부에서 만든 데이터도 같이 날아갑니다.

2.3 이미지의 파일을 수정할 수는 없나? → 가능은 한데 “Copy-on-Write”

이미지 레이어는 RO라서 “원본 자체”는 바뀌지 않습니다.
하지만 컨테이너 안에서 파일을 수정하려고 하면 Docker/스토리지 드라이버가:

  1. 해당 파일을 RW 레이어로 복사해오고
  2. 이후 수정은 RW 레이어의 복사본에 적용합니다.

이게 Copy-on-Write 메커니즘입니다.


3. 데이터 영속성: Docker Volume이 필요한 이유

예: MySQL 컨테이너가 /var/lib/mysql에 DB 파일을 쓴다고 할 때,
그대로 컨테이너만 쓰면 DB 파일은 컨테이너 RW 레이어에 생기고 컨테이너 삭제 시 같이 삭제됩니다.

그래서 Volume을 붙입니다.


4. Docker 볼륨 실전: Volume mount vs Bind mount, -v vs --mount

4.1 Volume mount (Docker가 관리하는 볼륨)

  1. 볼륨 생성:
docker volume create data_volume
  1. 컨테이너 실행하며 마운트:
docker run -v data_volume:/var/lib/mysql mysql

이때 Docker는 호스트의:

  • /var/lib/docker/volumes/data_volume/_data
    에 실제 데이터를 저장하고, 이를 컨테이너의 /var/lib/mysql로 마운트합니다.

질문: src=data_volume에서 data_volume은 어떻게 아는 거지?

  • 내가 만든 볼륨 이름입니다.
  • docker volume create data_volume로 만들었으면 그 이름이 data_volume.
  • 또는 사전에 만든 볼륨 목록을 보고 결정할 수 있습니다:
docker volume ls
docker volume inspect data_volume

질문: src=mysql_data는 미리 만들어놔야 해?

  • “볼륨 마운트” 관점에서는 두 케이스가 있습니다.
    1. 미리 만들기: docker volume create mysql_data
    2. 안 만들고 써도 됨: docker run -v mysql_data:/var/lib/mysql ...
      • 이 경우 Docker가 자동으로 볼륨을 생성합니다.

즉 “Docker 볼륨 이름”을 쓰는 경우엔 반드시 미리 만들 필요는 없습니다(자동 생성 가능).

4.2 Bind mount (호스트의 임의 경로를 마운트)

호스트의 특정 디렉터리(예: /data/mysql)를 컨테이너로 붙이고 싶다면:

docker run -v /data/mysql:/var/lib/mysql mysql

이건 Docker가 /var/lib/docker/volumes 아래에 관리하는 “볼륨 객체”가 아니라,
그냥 호스트 파일시스템의 특정 경로를 그대로 바인딩합니다.

정리:

  • Volume mount: Docker가 만든 볼륨 객체(저장 위치는 /var/lib/docker/volumes/...)
  • Bind mount: 호스트의 “원하는” 경로(/data/..., /home/...)를 직접 마운트

4.3 -v vs --mount 차이

-v는 오래된 스타일(짧고 간단하지만 의미가 섞임),
--mount는 명시적이고 키=값 형태라 실무에서 선호됩니다.

Bind mount 예시 (--mount)

docker run \
  --mount type=bind,src=/data/mysql,dst=/var/lib/mysql \
  mysql

Volume mount 예시 (--mount)

docker run \
  --mount type=volume,src=mysql_data,dst=/var/lib/mysql \
  mysql

질문: -v는 “컨테이너 경로를 VM 경로에 마운트”인데, --mount는 어떻게 동작?

동작은 같습니다. 차이는 “표기 방식과 명확성”입니다.

  • -v는 한 줄에 “소스/대상/옵션”이 콜론으로 섞여 들어가고,
    소스가 “볼륨 이름인지/호스트 경로인지”가 문맥으로 해석됩니다.
  • --mount는
    • type=bind 또는 type=volume
    • src=...
    • dst=...
      를 명시하므로 실수 가능성이 줄고 읽기 쉽습니다.

5. 스토리지 드라이버 vs 볼륨 드라이버: 역할 분리

5.1 스토리지 드라이버(Storage Driver)

  • 이미지 레이어 관리
  • 컨테이너 RW 레이어 생성
  • Copy-on-Write 구현
    을 담당합니다.

예: overlay2, aufs, btrfs, devicemapper 등
운영체제/커널/환경에 따라 최적 드라이버가 선택됩니다.

5.2 볼륨 드라이버(Volume Driver Plugin)

볼륨은 스토리지 드라이버가 아니라 볼륨 드라이버 플러그인이 담당합니다.

  • 기본: local 드라이버
    • 볼륨 데이터를 /var/lib/docker/volumes에 저장
  • 외부: EBS, Azure Disk/File, GCE PD, NetApp, Portworx 등 다양한 플러그인

컨테이너 실행 시 특정 드라이버를 지정해 클라우드 스토리지에 자동으로 붙이는 방식도 가능합니다(드라이버 구현에 따라).


6. Kubernetes로 넘어오기: “Pod도 일시적이다” → Volume이 필요하다

Docker와 마찬가지로 Kubernetes에서 Pod는 기본적으로 일시적입니다.

  • Pod가 삭제되면 컨테이너 FS에 있던 데이터도 기본적으로 사라집니다.
  • 그래서 Pod에도 Volume을 붙입니다.

7. Kubernetes Volume (Pod 수준)부터: 가장 단순한 형태

예: Pod가 /opt/number.out에 랜덤 값을 쓰는데 Pod 삭제해도 남기고 싶다.

가장 단순한 예로 hostPath를 쓰면:

  • “노드의 디렉터리”를 Pod에 마운트할 수 있습니다.

하지만 중요한 경고:

  • hostPath는 멀티 노드에서 권장되지 않습니다.
    • Pod가 다른 노드로 가면, 다른 노드의 디렉터리를 보게 됩니다.
    • “모든 노드가 같은 데이터”를 기대하면 깨집니다.
  • 멀티 노드/프로덕션은 보통 NFS/Ceph/EBS/GCE PD 등 외부 스토리지를 씁니다.

8. PersistentVolume(PV) & PersistentVolumeClaim(PVC): 스토리지의 “중앙 관리”

Pod 스펙에 매번 스토리지 상세를 쓰는 건 대규모 환경에서 관리가 어렵습니다.
그래서 Kubernetes는 스토리지를 두 계층으로 나눕니다.

  • PV (PersistentVolume): 관리자가 미리 준비한 “스토리지 풀(자원)”
  • PVC (PersistentVolumeClaim): 사용자가 “필요한 스토리지”를 요청(Claim)

8.1 PV 생성 예시(개념)

  • capacity, accessModes, 스토리지 타입(hostPath/EBS/NFS 등) 정의

hostPath PV는 실습용으로만(프로덕션 비권장)

8.2 PVC 생성과 바인딩

PVC가 생성되면 Kubernetes는 조건에 맞는 PV를 찾아 1:1로 바인딩합니다.

매칭 기준:

  • 용량(capacity)이 요청 이상인지
  • accessModes가 일치하는지
  • (있다면) storageClassName
  • (있다면) selector/label 매칭 등

질문: pvc50이 pvc100사이즈에 맵핑되면 실제로 100 쓸 수 있게 되나?

일반적으로는 “아니오” 로 이해하는 게 안전합니다.

  • PV가 100Gi이고 PVC가 50Gi를 요청해 PV 100Gi에 바인딩되더라도,
    PVC의 “요청량(requested)”은 50Gi입니다.
  • 많은 CSI/스토리지 구현에서는 PVC 요청 크기 기준으로 쿼터/리사이즈 정책이 적용됩니다.
  • 그리고 중요한 점: PV-PVC는 1:1 관계라서
    • 남는 50Gi를 다른 PVC가 “같은 PV에서 나눠 쓰는” 구조가 아닙니다.

정리:

  • “물리적으로 100Gi 디스크를 붙였는데 논리 요청은 50Gi”일 수 있고,
  • 실제 파일시스템/스토리지 구현, 쿼터, 리사이즈 설정에 따라 사용 가능/불가능이 갈립니다.
  • 운영 관점에서는 PVC 요청 크기 = 내가 쓸 수 있는 크기로 설계하는 게 안전합니다.

9. Access Modes: 특히 RWO를 정확히 이해하기

PVC/PV에서 자주 등장하는 accessModes:

  • ReadWriteOnce (RWO)
  • ReadOnlyMany (ROX)
  • ReadWriteMany (RWX) 등

질문: “RWO: 한 노드에만 Read/Write로 마운트 가능 (파드 여러 개여도 같은 노드면 가능)”이 무슨 말?

핵심은 “동시에 붙는 범위”가 노드 기준이라는 점입니다.

  • RWO는 “한 번에 하나의 노드에만 read/write로 attach/mount 가능”
  • 하지만 그 한 노드 안에서는:
    • 여러 Pod가
    • 같은 PVC를
    • 동시에 마운트하는 것이 “가능한 구현/상황”이 많습니다(파일시스템/스토리지와 workload 특성에 따라 동시 쓰기 충돌은 별도 고려).

즉 문장의 의미는:

  • 다중 노드에 걸쳐 동시에 RW로 마운트는 불가
  • 단일 노드 내부에서 여러 Pod가 공유 마운트는 가능할 수 있음

주의:

  • “가능”은 “스토리지 타입/드라이버/파일시스템/애플리케이션 동시쓰기 안전성”에 좌우됩니다.
  • DB 같은 경우 “한 데이터 디렉터리를 여러 인스턴스가 동시에 RW”하면 망가질 수 있어, RWO라도 일반적으로 단일 writer 패턴을 씁니다.

10. Pod에서 PVC를 쓰는 방법: volumes + volumeMounts

다음 예시를 기준으로 “볼륨 관련 필드”를 해부합니다.

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: myfrontend
      image: nginx
      volumeMounts:
      - mountPath: "/var/www/html"
        name: mypd
  volumes:
    - name: mypd
      persistentVolumeClaim:
        claimName: myclaim

10.1 volumes는 “어떤 볼륨을 쓸지” 정의(컨테이너 밖, Pod 레벨)

volumes:
  - name: mypd
    persistentVolumeClaim:
      claimName: myclaim
  • name: mypd
    • Pod 내부에서 이 볼륨을 참조할 “별칭”
  • persistentVolumeClaim.claimName: myclaim
    • “myclaim PVC를 이 볼륨으로 쓰겠다”
    • PVC는 이미 어떤 PV에 바인딩되어 있을 것이고, 결국 그 PV의 실제 스토리지를 사용합니다.

10.2 volumeMounts는 “컨테이너 안의 어디에 붙일지” 정의(컨테이너 레벨)

volumeMounts:
  - mountPath: "/var/www/html"
    name: mypd
  • name: mypd로 volumes에서 정의한 볼륨을 참조
  • mountPath: /var/www/html에 마운트

질문: 결국 컨테이너 내부의 /var/www/html 파일들은 myclaim에 연결된 PV 경로에 쌓이게 되는거지?

네. 정확히는:

  • 컨테이너가 /var/www/html 아래에 쓰는 데이터는
  • PVC myclaim이 바인딩된 PV(및 그 스토리지)로 저장됩니다.

즉, 컨테이너 관점에서는 /var/www/html이지만,
실제로는 “PV가 가리키는 스토리지(EBS/NFS/hostPath 등)”에 기록됩니다.


11. PVC 삭제 시 PV는 어떻게 되나? Reclaim Policy

PVC를 지우면 PV의 처리 정책은 PV의 persistentVolumeReclaimPolicy에 좌우됩니다.

대표:

  • Retain(기본으로 많이 보게 됨): PV와 실제 데이터 유지(관리자가 수동 정리)
  • Delete: PVC 삭제 시 PV 및 실제 스토리지도 함께 삭제(동적 프로비저닝에서 흔함)
  • Recycle: 과거 방식(Deprecated). 단순 rm -rf 수준이라 안전/이식성/기능 부족으로 사실상 사용하지 않음

12. StorageClass: “PV를 미리 만들어두는 방식(정적)”에서 “PVC 요청 시 자동 생성(동적)”으로 넘어가기

여기까지는 PV를 미리 만들어두고 PVC가 그 PV를 가져가는 “정적 프로비저닝” 흐름이었습니다.
하지만 클라우드 환경에서 이 방식은 다음 문제가 있습니다.

  • 애플리케이션이 스토리지를 필요로 할 때마다
    1. 클라우드 콘솔/CLI에서 디스크(EBS/GCE PD 등) 만들고
    2. 그 디스크를 참조하는 PV YAML을 만들고
    3. PVC가 그걸 바인딩하도록 기다려야 함

이 과정을 자동화하기 위한 장치가 StorageClass 입니다.

12.1 StorageClass가 하는 일

StorageClass에는 보통:

  • 어떤 프로비저너(CSI driver 등)를 쓸지
  • 어떤 타입/성능/파라미터(SSD, 복제, IOPS 등)를 쓸지
    를 정의합니다.

그리고 PVC에서 storageClassName을 지정하면:

  1. PVC 생성
  2. StorageClass가 프로비저너를 통해 “실제 디스크/볼륨”을 자동 생성
  3. PV 자동 생성
  4. PVC ↔ PV 자동 바인딩

즉, 사용자는 “PVC만 만들면” 되고 PV/디스크는 자동입니다.

12.2 volumeBindingMode까지 포함해서 이해하기

StorageClass에는 volumeBindingMode라는 중요한 옵션이 있습니다.
이 옵션은 “PVC가 만들어졌을 때 PV(및 실제 볼륨)를 언제 확정(바인딩/프로비저닝)할지”를 결정합니다.

대표 값:

(1) Immediate

  • PVC가 생성되는 즉시 PV를 만들고 바인딩을 진행합니다.
  • 단점(특히 멀티 AZ/토폴로지 환경):
    • 어떤 노드에 Pod가 스케줄될지 아직 모르는 상태에서
    • 볼륨이 특정 AZ에 먼저 만들어져버리면
    • Pod 스케줄링이 꼬일 수 있습니다(노드는 AZ A인데 볼륨은 AZ B 같은 상황).

(2) WaitForFirstConsumer

  • “처음으로 그 PVC를 사용하는 Pod가 스케줄될 때까지” 바인딩/프로비저닝을 미룹니다.
  • 장점:
    • Pod가 어느 노드(어느 존)에 배치되는지 결정된 후
    • 그 토폴로지에 맞게 볼륨을 생성/선택하므로
    • 스케줄링 실패/비효율을 줄입니다.

실무 감각으로 정리하면:

  • 단일 존/단순 환경이면 Immediate도 무난한 편
  • 멀티 존(특히 클라우드) + 상태ful 워크로드면 WaitForFirstConsumer가 문제를 줄이는 경우가 많습니다.

마무리: 전체 흐름 한 장 요약

  • Docker
    • 이미지 레이어(RO) + 컨테이너 레이어(RW) + Copy-on-Write
    • 컨테이너 삭제 시 RW 레이어 삭제 → 데이터 유실
    • 영속 데이터는 Volume(volume mount / bind mount)
    • -v는 축약형, --mount는 명시형
    • 스토리지 드라이버는 레이어/CoW, 볼륨 드라이버는 영속 볼륨
  • Kubernetes
    • Pod도 일시적 → Volume 필요
    • 단순히 Pod에 직접 붙이는 방식(예: hostPath)은 한계
    • PV(관리자) / PVC(사용자) 로 스토리지를 중앙 관리
    • AccessMode(RWO 등)와 ReclaimPolicy 이해가 중요
    • StorageClass로 동적 프로비저닝(자동 PV/자동 볼륨 생성)
    • volumeBindingMode로 “언제 바인딩할지” 제어(Immediate vs WaitForFirstConsumer)

'CKA' 카테고리의 다른 글

Network - 네트워크 기초(DNS)  (0) 2026.01.05
Network - 네트워크 기초(스위치, 라우터, 게이트웨이)  (0) 2026.01.05
Storage - Storage Class  (0) 2026.01.03
Storage - PV & PVC  (0) 2026.01.03
Storage - Volume  (0) 2026.01.03
'CKA' 카테고리의 다른 글
  • Network - 네트워크 기초(DNS)
  • Network - 네트워크 기초(스위치, 라우터, 게이트웨이)
  • Storage - Storage Class
  • Storage - PV & PVC
5jyan5
5jyan5
  • 5jyan5
    jyan
    5jyan5
  • 전체
    오늘
    어제
    • 분류 전체보기 (243)
      • 김영한의 스프링 핵심 원리(기본편) (8)
      • 김영한의 스프링 핵심 원리 - 고급편 (11)
      • 김영한의 스프링 MVC 1편 (1)
      • 김영한의 스프링 DB 1편 (3)
      • 김영한의 스프링 MVC 2편 (3)
      • 김영한의 ORM 표준 JPA 프로그래밍(기본편) (9)
      • 김영한의 스프링 부트와 JPA 활용2 (2)
      • 김영한의 실전 자바 - 중급 1편 (1)
      • 김영한의 실전 자바 - 고급 1편 (9)
      • 김영한의 실전 자바 - 고급 2편 (9)
      • Readable Code: 읽기 좋은 코드를 작성.. (2)
      • 김영한의 실전 자바 - 고급 3편 (9)
      • CKA (119)
      • 개발 (37)
      • 경제 (4)
      • 리뷰 (1)
      • 정보 (2)
  • 블로그 메뉴

    • 링크

    • 공지사항

    • 인기 글

    • 태그

      Target
      @within
      hibernate5module
      JPQL
      빈 후처리기
      @discriminatorcolumn
      김영한
      페치 조인
      jdk 동적 프록시
      스레드
      cglib
      gesingleresult
      자바
      조회 성능 최적화
      @discriminatorvalue
      WAS
      @args
      requset scope
      jpq
      typequery
      프록시 팩토리
      양방향 맵핑
      log trace
      락
      Thread
      reentarantlock
      단방향 맵핑
      고급
      프록시
      버퍼
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.2
    5jyan5
    Storage - 정리
    상단으로

    티스토리툴바