0. Contents


1. Overview

Immutable Configuration 패턴의 핵심설정을 "컨테이너 이미지"로 굽는다앱 이미지는 그대로, 설정 이미지만 갈아 끼운다Docker설정 컨테이너가 볼륨을 소유앱이 --volumes-from으로 빌려 씀(도커가 자동으로 꺼내줌)KubernetesPod이 emptyDir을 소유Init 컨테이너가 cp로 채우고 퇴장앱이 같은 볼륨을 마운트해서 읽음(아무도 안 꺼내주니 직접 옮김)
같은 목표, 다른 연결 방법: Docker는 빌려 쓰고 Kubernetes는 복사해 둔다

20장에서 만난 두 개의 벽

  • 20장에서 ConfigMap과 Secret은 “설정 외부화의 표준 답안"이었다
  • 그러나 마지막에 두 개의 벽을 만났다
    • 1MB 크기 제한
    • YAML 안에 YAML을 중첩하는 고통

Immutable Configuration 패턴: 벽 너머의 답

  • Immutable Configuration 패턴은 그 벽 너머의 답이다
  • 환경별 설정 데이터를 통째로 수동적(passive) 데이터 이미지에 넣는다
  • 일반 컨테이너 이미지처럼 레지스트리로 배포하고 버전을 붙인다
  • 런타임에 이 데이터 이미지와 애플리케이션 컨테이너를 연결해 설정을 꺼내 쓴다

이 장의 재미: “연결하는 방법”

  • Docker에서는 한 줄로 끝나던 볼륨 연결이 Kubernetes에서는 왜 안 되는지
  • 그리고 Init 컨테이너와 emptyDir로 어떻게 같은 결과를 만들어내는지가 핵심이다

2. Problem

2.1 Environment Variable과 ConfigMap이 멈추는 지점

19장과 20장이 해결한 것

  • 19장의 환경 변수는 개수가 일정 수준을 넘는 순간 관리가 무너졌다
  • 20장의 ConfigMap은 그 파편화를 한곳에 모아 해결했다
    • 1.21부터는 immutable: true 한 줄로 수정까지 막을 수 있게 됐다
  • 여기까지 오면 대부분의 설정 문제는 끝난다
  • 그런데 현실의 설정은 계속 자란다

ConfigMap이 버거워지는 세 가지 순간

  • ① 중첩 지옥
    • WildFly 서버 설정처럼 XML/YAML을 YAML 안에 다시 넣어야 할 때
    • 편집기 지원이 약해 들여쓰기 한 칸에 전체가 깨진다
    • 저자들도 “여러 번 망쳤다(believe us!)“고 고백하는 부분
  • ② 항목 폭발
    • 앱이 설정 파일을 수십·수백 개 요구할 때
    • ConfigMap 하나에 수백 개 키를 유지하는 것은 악몽이다
  • ③ 크기의 벽
    • 사전 학습된 ML 모델 같은 대용량 데이터
    • etcd 기반 1MB 제한은 immutable로 잠가도 그대로다
    • 아예 못 넣는다

완화할 수 있는 것과 없는 것

  • ①·②는 도구로 어느 정도 완화된다
  • ③은 근본적으로 넘을 수 없다

2.2 불변성이 뜻하는 것

불변성(immutability)의 정의

  • 여기서 말하는 불변성(immutability) 은 “애플리케이션이 시작된 뒤에는 설정을 바꿀 수 없다"는 뜻이다
  • 그래야 구성 데이터가 언제나 명확하게 정의된 상태(well-defined state)에 있다
  • “지금 이 앱이 어떤 설정으로 도는가"에 확실히 답할 수 있다

가변 설정 vs 불변 설정

가변 설정불변 설정
실행 중 누군가 슬쩍 수정봉인. 바꾸려면 새 버전을 만들어 갈아 끼움
“누가 언제 바꿨지?” 추적 불가Git에 올려 버전 관리 + 변경 관리 절차 적용
20장 3.7의 드리프트가 여기서 발생누가·언제·왜 바꿨는지 기록이 남는 정식 작업

이 장의 목표 두 가지

  • 불변이면서 버전 관리되는 설정
  • 크기 제한이 없는 설정

3. Solution

3.1 첫 번째 선택지는 여전히 immutable ConfigMap

책이 먼저 못 박는 원칙

  • 가장 간단하고 권장되는 방법은 immutable로 표시한 ConfigMap/Secret이다
  • 설정이 1MB 안에 들어가고 YAML에 잘 담기면 그것으로 끝이다
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  demo.properties: |
    greeting=hello
    port=8080
immutable: true          # 한 줄. 이후 kubectl edit은 거부되고, 변경은 새 이름으로만 (20장 3.8)

나머지 내용의 대상

  • 이 장의 나머지는 이걸로 안 되는 경우를 위한 것이다

3.2 데이터 이미지 — 설정을 컨테이너 이미지로 굽는다

아이디어: 설정만 담긴 이미지

  • 환경별 설정 파일들을 통째로 설정만 담긴 컨테이너 이미지로 만든다
  • 앱 없이 파일만 들고 있어서 아무것도 실행하지 않는다
  • 이 이미지를 수동적(passive) 데이터 이미지라 부른다
앱 이미지welcome-servlet:1.0(하나. 절대 안 바뀜)설정 이미지환경별로 하나씩config-dev:1.0.1config-staging:1.0.1config-prod:1.0.1일반 이미지처럼 빌드·푸시·풀, 버전 태그, 롤백
앱 이미지는 하나, 설정 이미지는 환경별로 하나씩

비유: 설정 파일만 담은 USB 메모리

  • 개발용 USB, 운영용 USB를 따로 만들어둔다
  • 앱을 켤 때 어떤 USB를 꽂느냐로 환경이 정해진다
  • 크기 제한도 없다

어려운 단계: 시작 시점의 연결

  • 이미지를 만드는 것은 쉽다
  • 어려운 부분은 시작 시점에 앱과 연결하는 단계다
    • 이것이 플랫폼마다 다르다

3.3 Docker Volume — 컨테이너가 볼륨을 소유한다

순수 Docker의 두 도구

  • Kubernetes를 보기 전에 순수 Docker부터 본다
  • VOLUME 지시어
    • 컨테이너 안의 디렉터리를 밖으로 노출한다
  • --volumes-from
    • 다른 컨테이너가 그 볼륨을 통째로 빌려 쓴다
# Example 21-1. Dockerfile-config — 설정 이미지
FROM scratch                                    # ① 운영체제도 없는 완전히 빈 이미지
ADD app-dev.properties /config/app.properties   # ② 개발용 설정 파일 하나를 넣는다
VOLUME /config                                  # ③ "이 폴더는 나중에 다른 컨테이너가 가져다 쓸 수 있다" 표시

scratch를 쓰는 이유

  • 할 일이 없기 때문이다
  • 이 이미지는 파일 하나 든 껍데기다
    • 프로그램이 단 하나도 없다
# Example 21-2. 설정 이미지 빌드 + 컨테이너 생성
docker build -t k8spatterns/config-dev-image:1.0.1 -f Dockerfile-config .
docker create --name config-dev k8spatterns/config-dev-image:1.0.1   # run이 아니라 create

# Example 21-3. 앱 실행 — 설정 컨테이너의 볼륨을 붙여서
docker run --volumes-from config-dev k8spatterns/welcome-servlet:1.0

create이지 run이 아닌 이유

  • 실행할 프로그램이 애초에 없다
  • 컨테이너를 만드는 목적은 딱 하나, /config를 볼륨으로 등록해두는 것이다
  • 그리고 이 순간에 중요한 일이 일어난다

docker create 순간, Docker가 자동으로 하는 두 가지

  1. 호스트 디스크에 실제 볼륨 폴더 생성
    • /var/lib/docker/volumes/xxx/_data
  2. 이미지 안 /config의 내용을 그 볼륨으로 복사
    • VOLUME 규칙: 마운트가 원본을 가리므로 처음 한 번 채워준다

결과: config-dev “소유"의 볼륨

  • 볼륨은 config-dev 컨테이너 “소유”
  • config-dev 안 /config = 호스트 볼륨 폴더

공유의 조건: 컨테이너 밖으로 꺼내기

  • 컨테이너끼리는 서로의 파일시스템이 안 보인다
    • 다른 컴퓨터나 마찬가지다
  • 그래서 config-dev 안에 박힌 파일을 앱이 직접 가져다 읽을 수는 없다
  • Docker가 컨테이너 밖(호스트 볼륨)으로 꺼내놔야 공유가 가능하다
  • docker create가 그 꺼내는 작업을 대신 해준다

--volumes-from config-dev의 의미

  • “config-dev가 VOLUME으로 표시해둔 폴더를 내 앱에도 같은 경로로 붙여줘"가 된다
호스트 디스크실제 파일은 한 곳/var/lib/docker/volumes/xxx/_data/app.propertiesconfig-dev의 /config(소유자, 가만히 서 있음)앱(welcome-servlet)의 /config(빌려 씀, 읽고 동작)
실제 파일은 호스트 볼륨 한 곳, 두 컨테이너가 같은 경로로 본다

운영 전환: 시작 명령만 교체

  • 앱은 “내 설정은 /config에 있겠지"라고만 알고 있다
  • 운영으로 옮길 때는 시작 명령만 바꾼다
# Example 21-4. 운영 환경 — 앱 이미지는 그대로, 설정 컨테이너만 교체
docker build -t k8spatterns/config-prod-image:1.0.1 -f Dockerfile-config .
docker create --name config-prod k8spatterns/config-prod-image:1.0.1
docker run --volumes-from config-prod k8spatterns/welcome-servlet:1.0
#                          ────────── 여기만 다르다

3.4 Kubernetes에는 컨테이너 볼륨이 없다

Kubernetes에 없는 것: 컨테이너 소유 볼륨

  • Kubernetes에서 Pod 안 컨테이너끼리 볼륨을 공유하는 것은 원래 잘 된다
  • 그런데 Docker의 --volumes-from처럼 “저 컨테이너 이미지 안에 든 디렉터리를 그대로 볼륨으로 쓰기” 는 없다
  • Kubernetes 볼륨은 emptyDir, ConfigMap, PVC 같은 종류다
    • “컨테이너 소유 볼륨"이라는 개념 자체가 없다
  • Dockerfile의 VOLUME 지시어에 대응하는 개념이 Kubernetes API에는 없다
    • 런타임이 컨테이너마다 익명 볼륨을 만들 수는 있다
    • 하지만 다른 컨테이너와 공유할 수 없고 Kubernetes가 관리하지도 않는다

볼륨 소유 구조 비교

DockerKubernetes
볼륨의 주인컨테이너 (config-dev)Pod
컨테이너의 위치다른 컨테이너가 “빌려 씀”컨테이너들은 전부 “이용자"일 뿐
이미지 안 폴더이미지 안 폴더 = 볼륨 (직접 노출)이미지 안 폴더 ≠ 볼륨. 꺼내주는 사람 없음

지원 전망과 우회로

  • 논의는 오래됐지만 구현 복잡도 대비 이점이 작아 가까운 시일 내 지원될 가능성은 낮다
    • 책 기준 2023년, 실험적 CSI만 존재
    • 이후 Image Volume 기능이 알파/베타로 들어오기 시작했다
  • 그래서 우회로가 필요하다

3.5 Init Container — 복사로 우회하기

핵심: 방향을 뒤집는 것

  • 아무도 남의 컨테이너 안을 들여다보지 않는다
  • 대신 Pod 차원에서 빈 공유 폴더(emptyDir) 를 하나 만든다
  • 15장의 Init 컨테이너가 앱보다 먼저 뜬다
    • 자기 이미지 안의 파일을 그 폴더로 복사해놓고 퇴장한다

비유: 서랍과 복도 게시판

  • Docker: 옆방 사람 서랍을 직접 열어 서류를 꺼내옴
    • → Kubernetes에선 금지
  • K8s: 옆방 사람이 먼저 와서 복도 게시판에 서류를 붙여놓고 감
    • → 내가 나중에 게시판에서 읽음
Pod 생성emptyDir 생성노드 디스크에 빈 폴더. Pod 소유, Pod 죽으면 같이 소멸Init 컨테이너 (설정 이미지)/config에 emptyDir 마운트컨테이너 켜지기 전에 마운트 완료cp /config-src/* /config자기 이미지 안 파일을 밖(emptyDir)으로종료컨테이너가 사라져도 파일은 이미 밖에 있음앱 컨테이너/var/config에 같은 emptyDir 마운트역시 켜지기 전에/var/config/demo.properties 읽음앱은 emptyDir이 뭔지 모름. 그냥 파일이 있을 뿐
Pod 생성 → Init 컨테이너가 복사 후 종료 → 앱이 같은 볼륨에서 읽음

Init 컨테이너를 쓰는 이유

  • 일반 컨테이너(containers)는 spec에 적힌 순서대로 하나씩 시작되지만, 앞 컨테이너가 준비될 때까지 기다리지 않는다
  • 복사 컨테이너와 앱을 나란히 두면 앱이 먼저 떠서 빈 폴더를 볼 수 있다
  • Init 컨테이너는 일반 컨테이너보다 먼저, 순서대로, 성공 종료할 때까지 실행된다
  • “복사 끝남"이 보장된 상태에서 앱이 켜진다

emptyDir을 쓰는 이유

  • 어차피 Pod가 시작할 때마다 Init이 새로 채우니 데이터가 남을 필요가 없다
  • 원본은 이미지에 있으니 재시작 시 다시 복사하면 그만이다
  • 오히려 임시 폴더라 “실행 중에 밖에서 손댈 수 없다"는 불변성이 자연스럽게 지켜진다

Docker 자동 복사 = Kubernetes 수동 복사

  • Docker의 docker create 자동 복사 = Kubernetes의 Init 컨테이너 수동 복사
  • 결과는 같다
  • 과정만 “직접 빌리기"에서 “중간에 복사"로 바뀐다

3.6 배달원의 Dockerfile 해부

보관함에서 배달원으로

  • Docker 때의 이미지는 “보관함"이었다
  • Kubernetes 때의 이미지는 “배달원"이어야 한다
    • 그래서 Dockerfile이 달라진다
# Example 21-5. 개발 설정 이미지 (Kubernetes용)
FROM busybox                                          # ① scratch 대신 busybox — cp와 sh가 필요하니까
ADD dev.properties /config-src/demo.properties        # ② 원본은 /config가 아니라 /config-src에
ENTRYPOINT [ "sh", "-c", "cp /config-src/* $1", "--" ] # ③ 켜지면 복사하고 끝

Docker용과 Kubernetes용 Dockerfile 비교

Docker (21-1)Kubernetes (21-5)왜
베이스scratch (완전 빈 것)busybox (~1MB, cp/sh 포함)이제 “복사"라는 동작을 직접 해야 한다
마지막 줄VOLUME /config (선언)ENTRYPOINT cp ... (명령)도커가 꺼내주던 일을 이미지가 스스로
파일 위치/config//config-src/아래 참고

/config-src인 이유: 마운트 그림자

  • Kubernetes에서 emptyDir이 /config에 마운트되면 그 자리에 있던 이미지 원본은 빈 폴더에 가려진다
    • 20장 3.5의 실무 함정과 같은 리눅스 마운트 동작
  • 원본을 /config에 뒀다면 복사할 것이 사라진다
  • 그래서 원본은 src에 두고, 마운트된 /config로 복사한다

ENTRYPOINT의 각 조각

shbusybox의 셸-c "cp /config-src/* $1"실행할 문자열* 와일드카드를 풀려면 셸이 필요(cp를 직접 부르면 *가 문자 그대로)--sh -c 문법상 $0 자리를채우는 더미이게 있어야 다음 인자가 $1/configPod의 args에서 넘어옴→ $1실행 결과 = cp /config-src/* /config
네 조각이 합쳐져 cp /config-src/* /config가 된다

줄별 역할과 비유

줄역할비유
FROM busybox도구 챙기기배달원 채용
ADD설정 파일 싣기짐 싣기
ENTRYPOINT도착지 받으면 내려놓고 퇴장배달 지시서

빌드와 배포

  • 이 이미지도 Docker 때와 마찬가지로 docker build + docker push로 레지스트리에 올린다
  • 다만 docker create는 없다
    • 컨테이너를 띄우는 건 이제 Kubernetes가 한다

3.7 Deployment 명세 — 볼륨 하나, 컨테이너 둘

# Example 21-6. Init 컨테이너로 설정을 복사하는 Deployment (.template.spec 부분)
initContainers:
- image: k8spatterns/config-dev:1      # ① 방금 만든 배달원 이미지 — 환경 바꿀 땐 여기만
  name: init
  args:
  - "/config"                          # ② ENTRYPOINT의 $1 → cp /config-src/* /config
  volumeMounts:
  - mountPath: "/config"               # ③ 배달원이 짐 내려놓을 곳 = emptyDir
    name: config-directory
containers:
- image: k8spatterns/demo:1            # ④ 앱 이미지 — dev든 prod든 절대 안 바뀜
  name: demo
  ports:
  - containerPort: 8080
    name: http
    protocol: TCP
  volumeMounts:
  - mountPath: "/var/config"           # ⑤ 같은 볼륨을 다른 경로에. 앱은 여기서 읽는다
    name: config-directory
volumes:
- name: config-directory               # ⑥ Pod 소유의 빈 공유 폴더
  emptyDir: {}
Pod볼륨 하나, 컨테이너 둘initInit 컨테이너image: k8spatterns/config-dev:1args: "/config"① 먼저 실행 → 복사 후 종료환경 바꿀 땐 여기만demo앱 컨테이너image: k8spatterns/demo:1containerPort: 8080② Init 성공 종료 후 시작dev든 prod든 절대 안 바뀜mountPath: /configcp로 채움mountPath: /var/config읽음config-directoryemptyDir: {} · Pod 소유의 빈 공유 폴더Init 컨테이너가 죽어도 볼륨은 남는다 — 볼륨은 Init의 것이 아니다
볼륨은 Pod에 딸려 있고, 두 컨테이너가 각자 경로로 마운트한다

volumes:의 위치: containers:와 같은 레벨

  • YAML 구조가 그대로 소유 관계다
  • 볼륨은 컨테이너 안에 있지 않고 Pod에 딸려 있다
  • 컨테이너 쪽 volumeMounts:는 “Pod가 가진 볼륨 중 저것을 내 이 경로에 붙여줘"라는 연결 표시일 뿐이다
  • 그래서 Init 컨테이너가 죽어도 볼륨은 남는다
    • 볼륨은 Init의 것이 아니니까

마운트 경로: Init은 /config, 앱은 /var/config

  • 같은 볼륨을 각자 원하는 위치에 건다
  • 같은 USB를 A 컴퓨터는 D:에, B 컴퓨터는 E:에 꽂는 것과 같다
  • 안에 든 파일은 하나다

마운트 시점: 항상 컨테이너 프로세스 시작 전

  • Init이 cp를 실행하는 시점에 이미 /config는 emptyDir이다
  • 앱이 첫 줄을 실행하는 시점에 이미 /var/config/demo.properties가 보인다
  • “폴더 없네” 할 틈이 없다

앱 쪽: 바뀌는 것 없음

  • demo:1은 그냥 /var/config/demo.properties를 읽는 평범한 앱이다
  • 설정이 어디서 오는지, 누가 갖다 놓는지 모른다
  • 이 패턴의 복잡함은 전부 “갖다 놓는” 쪽에 있다
    • 앱은 로컬 개발 때처럼 파일을 읽을 뿐이다
  • 그래서 앱 이미지 하나로 모든 환경을 돈다

3.8 환경 전환과 Template — 이미지 이름만 갈아 끼우기

개발 → 운영 전환: image 한 줄

  • 개발 → 운영은 Init 컨테이너의 image 한 줄이다
  • YAML을 고쳐 kubectl apply하거나 kubectl set image로 바꾸면 된다
  • 그러나 환경이 다섯 개면 YAML 다섯 개를 따로 관리하게 된다
    • 이는 실수의 온상이다

해법: 바뀌는 부분만 빈칸으로

  • 해법은 바뀌는 부분(이미지 이름)만 빈칸으로 뚫어두는 것
  • 책은 OpenShift Template을 예로 든다
# Example 21-7. OpenShift Template — 설정 이미지를 매개변수로
apiVersion: v1
kind: Template                              # ⚠️ 일반 Kubernetes에는 없는 kind. OpenShift 전용
metadata:
  name: demo
parameters:
- name: CONFIG_IMAGE                        # ① 빈칸 선언
  description: Name of configuration image
  value: k8spatterns/config-dev:1           #    기본값 — 아무 말 없으면 dev
objects:
- apiVersion: apps/v1
  kind: Deployment
  spec:
    template:
      spec:
        initContainers:
        - name: init
          image: ${CONFIG_IMAGE}            # ② 빈칸 사용 — 예제 21-6과 다른 곳은 여기뿐
          args: [ "/config" ]
          volumeMounts:
          - mountPath: /config
            name: config-directory
        containers:
        - image: k8spatterns/demo:1
          volumeMounts:
          - mountPath: /var/config
            name: config-directory
        volumes:
        - name: config-directory
          emptyDir: {}

Template의 정체: YAML을 찍어내는 틀

  • 이 파일 그대로는 Kubernetes에 못 먹인다
  • kind: Template은 Kubernetes가 모르는 종류다
  • ${CONFIG_IMAGE}는 아직 채워지지 않은 빈칸이다
    • Kubernetes는 변수 치환을 하지 않는다
  • Template은 YAML을 찍어내는 틀이고, 찍어낸 결과물이 클러스터에 들어간다
Template 파일 (빈칸 있음)oc new-app demo -p CONFIG_IMAGE=...빈칸 채우기 + 적용완성된 Deployment (빈칸 없음)클러스터
Template은 YAML을 찍어내는 틀이고, 찍어낸 결과물이 클러스터에 들어간다
# Example 21-8. 템플릿으로 앱 생성
oc new-app demo                                             # 기본값 → dev
oc new-app demo -p CONFIG_IMAGE=k8spatterns/config-prod:1   # prod

매개변수의 범위

  • 이 예제는 CONFIG_IMAGE 하나만 뚫었다
  • 실무에선 앱 이미지·레플리카 수·네임스페이스처럼 환경마다 달라지는 것들을 함께 매개변수로 뺀다
  • 이 패턴의 맥락에서 핵심 변수는 CONFIG_IMAGE 하나다
    • 이것만 바꾸면 환경이 바뀐다는 것을 보여주는 것이 목적이다
  • 일반 Kubernetes에서는 Helm({{ .Values.configImage }})이나 Kustomize(patch)가 같은 역할을 한다

레퍼런스

Init 컨테이너의 실행 보장은 Kubernetes 공식 문서 "Init Containers"에 명시되어 있다.
"Init containers always run to completion. Each init container must complete successfully before the next one starts."
[해석] "Init 컨테이너는 항상 완료될 때까지 실행된다. 각 Init 컨테이너는 다음 것이 시작되기 전에 성공적으로 완료되어야 한다." — 이 순차·완료 보장이 3.5의 "복사 끝남이 보장된 상태에서 앱이 켜진다"의 근거다. 같은 문서는 Init 컨테이너가 앱 컨테이너와 다른 이미지를 쓸 수 있어 셋업 유틸리티를 앱 이미지에 넣지 않아도 된다는 점을 강조하는데, busybox 배달원이 정확히 그 사례다.
emptyDir 볼륨 문서는 이 볼륨이 Pod이 노드에 할당될 때 생성되고 Pod이 노드에서 제거되면 데이터도 영구 삭제된다고 명시한다 — 3.5의 "Pod 소유, Pod 죽으면 소멸"이 이것이다.
Docker 문서의 --volumes-from은 다른 컨테이너의 볼륨을 마운트하는 옵션으로, VOLUME 지시어로 선언된 경로가 컨테이너 생성 시 익명 볼륨으로 만들어지고 이미지의 해당 경로 내용으로 초기화된다는 동작이 3.3의 "docker create 순간 자동 복사"의 근거다.
→ kubernetes.io — Init Containers
→ kubernetes.io — Volumes: emptyDir
→ docs.docker.com — Volumes

4. Discussion

책의 솔직한 평가

  • 데이터 컨테이너를 쓰는 것은 솔직히 좀 번거롭다(admittedly a bit involved)
  • 이미지를 굽고, 레지스트리에 올리고, Init 컨테이너를 짜야 한다
  • immutable ConfigMap/Secret이 맞지 않을 때만 써야 한다

전체 메커니즘 정리

두 플랫폼 나란히 보기

  • 같은 목표를 두 플랫폼이 어떻게 다르게 달성하는지 나란히 놓으면 이렇다
Docker볼륨의 주인이 컨테이너다 — 도커가 알아서 꺼내준다FROM scratch빈 껍데기. 실행할 게 없으니 OS도 필요 없다ADD app-dev.properties설정 파일을 /config 에 그대로VOLUME /config"이 폴더 가져다 쓸 수 있다"고 선언만docker build →docker create --name config-dev호스트에 볼륨 폴더 생성 + 이미지 내용 자동 복사← 도커가 대신 해주는 부분볼륨 소유자 = config-dev 컨테이너docker run --volumes-from config-dev앱이 그 볼륨을 빌려 씀앱이 /config 를 읽는다설정 컨테이너는 파일 들고 가만히 서 있기만Kubernetes볼륨의 주인이 Pod다 — 아무도 안 꺼내주니 직접 옮긴다FROM busyboxcp 와 sh 가 필요하다. 이제 직접 일해야 하니까ADD dev.properties /config-src원본은 /config 가 아니라 src 에 (마운트 그림자 회피)ENTRYPOINT cp /config-src/* $1선언이 아니라 "명령". 도착지는 Pod 이 알려준다docker build → docker push레지스트리를 반드시 거친다Pod 생성 → emptyDir 빈 폴더← 볼륨 소유자 = PodInit 컨테이너가 /config 에 마운트하고cp 실행 후 종료파일은 이미 볼륨(밖)에 있으므로배달원이 사라져도 남는다앱이 같은 볼륨을 /var/config 에 마운트앱이 /var/config 를 읽는다설정 컨테이너는 파일 옮겨놓고 퇴장
Docker는 도커가 꺼내 주고, Kubernetes는 Init 컨테이너가 직접 옮긴다

두 경로가 끝내 같아지는 지점

  • 환경 전환
    • Docker: –volumes-from 대상 이름 교체
    • K8s: Init 컨테이너의 image 한 줄 교체 (템플릿 변수로)
  • 앱 이미지
    • 둘 다 그대로. 건드리지 않는다
    • ★ 이 패턴의 존재 이유

4.1 데이터 컨테이너가 주는 것

① 봉인 + 버전

  • 환경별 설정이 이미지 안에 봉인
  • config-prod:1 → :2 처럼 앱 이미지와 같은 방식으로 버전 관리
  • 문제 시 이전 태그로 롤백

② 레지스트리 배포

  • Docker Hub 같은 곳에 올려두면 클러스터에 접속하지 않고도 docker pull로 “prod 설정 뭐였지?“를 로컬에서 확인
  • ConfigMap은 클러스터 안에 들어가야 보인다

③ 진짜 불변

  • 이미지는 원래 수정이 안 된다
  • 바꾸려면 새 이미지·새 버전
  • 몰래 손댈 수가 없다

④ 크기 무제한

  • 1MB의 벽이 없다
  • ML 모델 몇 GB도 담을 수 있다

4.2 대가와 판단 기준

① 복잡도

  • 이미지 빌드·푸시·Init 작성
  • 단계가 많다

② 보안 미해결

  • 이미지 안에 DB 비밀번호를 넣으면 그 이미지를 받는 누구나 본다
  • 이 패턴은 “안 바뀌게"이지 “안 보이게"가 아니다
  • Secret은 별도

③ 복사는 우회책

  • 진짜 원하는 건 “이미지를 볼륨으로 바로 꽂기"인데 그게 안 돼서 복사한다
  • 파일이 크면 복사 시간과 디스크 두 배
  • 이미지 볼륨이 정식화되면 없어질 오버헤드

④ Deployment 분기

  • Init 이미지가 다르니 환경별 Deployment 객체가 갈라진다
  • 템플릿으로 완화되지만 사라지진 않는다

설정의 모습별 도구 선택

설정의 모습올바른 도구
작고 단순, 1MB 이하immutable ConfigMap/Secret (20장) ✅
크거나, 파일 수십 개거나, 레지스트리로 배포하고 싶음Immutable Configuration (이 장)
크지만 환경마다 조금씩만 다름Configuration Template (22장)
비밀번호·인증서Secret + 앱 수준 암호화 / 외부 비밀 관리

다음 장으로: Configuration Template

  • “환경마다 조금씩만 다른 큰 설정 파일"이라면
    • 이미지를 통째로 여러 개 만드는 대신
    • 파일 하나에 빈칸을 뚫어 환경별로 채우는 쪽이 낫다
  • 그것이 다음 장 Configuration Template이다

핵심 메시지

Immutable Configuration의 몫

  • 1MB를 넘는 설정을 컨테이너 이미지로 버전 관리

연결 방법은 플랫폼이 정한다

  • Docker → 설정 컨테이너가 볼륨을 소유, 앱이 –volumes-from으로 빌림
  • Kubernetes → 컨테이너 볼륨이 없으므로 Init 컨테이너가 emptyDir로 복사해 앱에 넘김

Immutable Configuration은 “ConfigMap이 멈춘 자리에서 시작하는 답” 이다. 설정을 이미지로 구워 버전과 크기 문제를 한 번에 풀되, Kubernetes에는 이미지 안 폴더를 직접 꽂는 길이 없으니 Init 컨테이너라는 배달원이 emptyDir 게시판에 파일을 붙이고 퇴장하게 하고, 앱은 그 게시판만 읽게 하며, 환경 전환은 배달원 이미지 이름 한 줄로 끝내라. 그러나 이 모든 것은 번거로우니, immutable ConfigMap 한 줄로 되는지를 먼저 확인할 것.