0. Contents


1. Overview

EnvVar Configuration 패턴의 핵심설정을 코드/이미지에서 분리하라→ 가장 단순한 수단이 환경 변수다빌드 시 기본값Dockerfile ENV런타임 오버라이드docker run -ePod 스펙 env:값의 출처value (직접)configMapKeyRefsecretKeyRef
EnvVar Configuration의 핵심: 기본값, 런타임 오버라이드, 값의 출처

설정 외부화의 이유

  • 모든 비자명(nontrivial)한 애플리케이션은 설정이 필요하다
    • DB 접속 정보, 외부 서비스 주소, 프로덕션 튜닝 값
  • 이런 값을 코드에 하드코딩하는 대신 외부화하면 → 한 번 빌드한 애플리케이션을 수정 없이 여러 환경에서 재사용할 수 있다

EnvVar Configuration 패턴

  • 이 외부화를 가장 단순한 도구인 환경 변수로 구현한다
  • 어떤 OS든, 어떤 언어든 환경 변수를 지원한다 → 보편적으로 적용 가능
  • 다만 이 단순함에는 명확한 적용 한계가 있다
    • 그 한계선이 어디인지 아는 것이 이 패턴의 절반이다

2. Problem

2.1 하드코딩이 왜 문제인가

❌ 하드코딩된 설정

  • 코드 안에 DB 주소가 박혀 있음
  • 값이 바뀔 때마다: 코드 수정 → 재빌드 → 재배포
  • 환경(개발/운영)마다 다른 빌드가 필요

오래된 원칙: 설정 외부화

  • 설정을 코드에 하드코딩하는 것은 12요소 앱(twelve-factor app) 선언문이 나오기 훨씬 전부터 나쁜 관행으로 알려져 있었다
  • 설정은 외부화되어 빌드 이후에도 변경 가능해야 한다

2.2 불변 이미지와 설정 외부화

컨테이너 이미지의 핵심 철학: 불변성(immutability)

  • 컨테이너 세계에서 이 원칙은 더 중요해진다
  • 한 번 만든 이미지는 절대 고치지 않는다
  • 동일한 이미지를 모든 환경에서 재사용한다
❌ 설정이 이미지 안에 있으면my-app-dev:1.0my-app-prod:1.0...환경마다이미지 재빌드→ "테스트한 것"과"운영에 올라간 것"이 서로 다른 물건✅ 설정을 외부에서 주입하면my-app:1.0개발스테이징운영→ 테스트를 통과한 바로 그 이미지가운영에 올라감
설정을 밖에서 주입하면 이미지 하나가 모든 환경을 지난다

남는 질문

  • 컨테이너화된 세계에서 설정 외부화를 어떻게 구현하는 것이 최선일까?

3. Solution

3.1 핵심 패턴: 빌드 시 기본값 + 런타임 오버라이드

환경 변수의 결정적 강점: 보편성

  • 모든 OS가 환경 변수를 정의하고 자식 프로세스에 전파하는 방법을 알고 있다
  • 모든 언어가 한 줄로 읽을 수 있다
    • Java의 System.getenv(), Python의 os.environ, Node.js의 process.env
  • 라이브러리도, 설정 파일 파서도 필요 없다

전형적인 사용 패턴: 두 단계

① 빌드 시점이미지 안에 안전한 기본값을 심는다② 런타임배포 환경에 맞게 그 값을 덮어쓴다이미지LOG_LEVEL=info(기본값, 고정)개발 서버 실행LOG_LEVEL=debug 로 오버라이드운영 서버 실행기본값 그대로
빌드 때 기본값을 심고, 실행 때 필요한 환경만 덮어쓴다

런타임에 덮어쓰는 이유

이유설명
환경별 차이 흡수개발 DB와 운영 DB 주소가 다르다 — 이미지 재빌드 없이 값만 교체
동일 아티팩트 보장테스트한 이미지 = 운영 이미지, 빌드를 두 번 하지 않는다
민감 정보 분리비밀번호를 이미지에 박으면 이미지를 받는 누구나 볼 수 있다
빠른 대응로그 레벨 변경에 재빌드(수 분) 대신 재시작(수 초)

기본값의 역할

  • 아무 설정 없이 실행해도 일단 돌아가게 한다
  • 바뀌는 값만 덮어쓰게 만드는 안전한 출발점이다

3.2 Docker에서의 구현

빌드 시점 — Dockerfile의 ENV 지시어

# Example 19-1. 환경 변수가 있는 Dockerfile
FROM openjdk:11
ENV PATTERN "EnvVar Configuration"   # ① 한 줄에 하나씩 정의
ENV LOG_FILE "/tmp/random.log"
ENV SEED "1349093094"

# ② 또는 한 줄에 전부 (= 문법, 최근 권장 스타일 — ENV는 레이어 없이 이미지 설정만 바꿈)
ENV PATTERN="EnvVar Configuration" LOG_FILE=/tmp/random.log SEED=1349093094

애플리케이션에서 읽기

// Example 19-2. Java에서 환경 변수 읽기
public Random initRandom() {
    long seed = Long.parseLong(System.getenv("SEED"));  // ① 환경 변수는 항상 문자열 → 타입 변환 필요
    return new Random(seed);                            // ② EnvVar의 시드로 난수 생성기 초기화
}

런타임 — docker run -e 로 오버라이드

# Example 19-3. 컨테이너 시작 시 환경 변수 덮어쓰기
docker run -e PATTERN="EnvVarConfiguration" \
       -e LOG_FILE="/tmp/random.log" \
       -e SEED="147110834325" \
       k8spatterns/random-generator:1.0

실행 방식별 SEED 값

실행SEED비고
그냥 실행1349093094이미지 기본값
-e로 실행147110834325오버라이드 — 재빌드 없음 ✅

3.3 Kubernetes에서의 구현: env

Pod 스펙의 env: 필드

  • Docker의 -e에 해당하는 것이 Pod 스펙의 env: 필드다
  • 여기서 값을 지정하는 방법이 세 가지로 나뉜다
# Example 19-4. 환경 변수가 설정된 Pod
apiVersion: v1
kind: Pod
metadata:
  name: random-generator
spec:
  containers:
  - image: k8spatterns/random-generator:1.0
    name: random-generator
    env:
    - name: LOG_FILE
      value: /tmp/random.log            # ① 리터럴 값 직접 지정 — 가장 단순
    - name: PATTERN
      valueFrom:
        configMapKeyRef:                # ② ConfigMap에서 가져오기
          name: random-generator-config #    ③ 참조할 ConfigMap 이름
          key: pattern                  #    ④ 그 안에서 찾을 키
    - name: SEED
      valueFrom:
        secretKeyRef:                   # ⑤ Secret에서 가져오기 (조회 방식은 ConfigMap과 동일)
          name: random-generator-secret
          key: seed

ConfigMap은 Pod가 아니라 독립 리소스다

apiVersion: v1
kind: ConfigMap            # kind가 ConfigMap — 컨테이너도 프로세스도 없는 순수 데이터 리소스
metadata:
  name: random-generator-config
data:                      # spec이 아니라 data
  pattern: "EnvVar Configuration"
PodConfigMap
핵심 필드spec (뭘 실행할지)data (무슨 값을 담을지)
하는 일컨테이너 실행데이터 보관만
스코프네임스페이스네임스페이스

ConfigMap의 공유 범위

  • 네임스페이스 단위로 원하는 만큼 만들 수 있다
  • 여러 Pod이 하나를 공유하는 것이 일반적이다(Pod:ConfigMap = N:M)
  • 단, 다른 네임스페이스의 ConfigMap은 참조할 수 없다
  • 실무에서는 이 제약을 역이용한다
    • 네임스페이스마다 같은 이름의 ConfigMap을 두고 내용만 환경별로 다르게 채운다
    • 같은 배포 YAML이 어느 네임스페이스에서든 “자기 동네” 설정을 찾아 쓰게 된다
네임스페이스Podrandom-generatorLOG_FILEvalue: /tmp/random.logPATTERNvalueFrom: configMapKeyRefSEEDvalueFrom: secretKeyRefConfigMaprandom-generator-configkey: patternSecretrandom-generator-secretkey: seed여러 Pod이 공유 (Pod:ConfigMap = N:M)다른 네임스페이스의 ConfigMap은 참조할 수 없다
Pod는 값을 직접 갖거나, 같은 네임스페이스의 ConfigMap·Secret을 가리킨다

간접 참조(ConfigMap/Secret)의 장점

  • 설정을 Pod 정의와 독립적으로 관리할 수 있다
  • 명함에 집 주소를 인쇄하는 대신 “연락처는 홈페이지 참조"라고 적는 것과 같다
    • 이사를 가도 명함을 다시 찍을 필요가 없다
방식언제 쓰나
value (직접)단순하고 안 민감한, 이 Pod만 쓰는 값
configMapKeyRef여러 곳에서 공유하는 일반 설정
secretKeyRef민감한 값 — 단, 아래 3.4의 한계를 반드시 알고 쓸 것

3.4 환경 변수는 안전하지 않다

Secret에서 온 SEED의 함정

  • 위 예제에서 SEED는 Secret에서 온다
  • 유효한 사용법이지만, 여기서 반드시 짚어야 할 함정이 있다
위험의 본질은 "출발지"가 아니라 "도착지"다출발지도착지valueConfigMapSecretenvFrom환경 변수 ⚠️도착하면 전부동일하게 노출kubectl exec pod -- env로 즉시 조회자식 프로세스에무조건 자동 상속크래시 시 환경 변수 전체 덤프→ 로그 유출Secret볼륨(파일) ✅도착지가 다르면 위험도 다르다
위험은 출발지가 아니라 도착지에서 정해진다

투명 봉지에 담긴 Secret

  • Secret이라는 금고에서 꺼냈어도, 환경 변수라는 투명 봉지에 담는 순간 보호는 풀린다
  • kubectl exec my-pod -- env 한 줄이면 값이 그대로 보인다
  • 앱이 실행한 모든 자식 프로세스가 값을 물려받는다
  • 많은 프레임워크와 에러 수집 도구가 크래시 시 환경 변수 전체를 덤프한다

민감한 값은 Secret을 볼륨(파일)으로 마운트하는 것이 정석이다.

spec:
  containers:
  - name: random-generator
    volumeMounts:
    - name: secret-vol
      mountPath: /etc/secrets      # 이 경로 아래 키별로 파일이 생김
      readOnly: true
  volumes:
  - name: secret-vol
    secret:
      secretName: random-generator-secret
      defaultMode: 0400            # 파일이라 권한 제어가 가능 — 환경 변수엔 없는 개념
유출 경로환경 변수볼륨(파일)
env / /proc/1/environ 조회노출 ❌안 보임 ✅
자식 프로세스 자동 상속무조건 상속 ❌경로를 알고 직접 읽어야 함 ✅
크래시 로그 환경 덤프딸려 나감 ❌대상 아님 ✅
파일 권한 제어개념 없음 ❌0400 등 가능 ✅
값 갱신재시작 필요 ❌자동 갱신 (tmpfs, 앱이 재로딩 시 무중단 교체 — subPath 마운트는 갱신 안 됨) ✅

판단 기준 하나: “이 값이 로그에 찍혀도 괜찮은가?”

  • 괜찮으면(포트, 경로, 로그레벨) → 환경 변수
  • 안 되면(비밀번호, 토큰, API 키) → 볼륨

3.5 기본값에 대하여

기본값의 두 얼굴

  • 존재조차 모르는 설정을 골라야 하는 부담을 없애준다
  • Spring Boot의 포트 8080처럼, 설정보다 관례(convention over configuration) 패러다임의 핵심이기도 하다
  • 하지만 진화하는 애플리케이션에서는 안티패턴이 될 수 있다
😱 기본값 변경의 파장v1.0v2.0타임아웃 기본값 = 30초→ 수백 명이 아무 설정 없이 사용 중(암묵적으로 30초에 의존)기본값을 5초로 변경→ 아무것도 안 바꾼 사용자들의 앱이갑자기 타임아웃 폭발→ 설정 파일 어디에도 "30초"가 없었으므로원인 추적도 어려움
기본값에 조용히 의존하던 사용자는 기본값이 바뀌는 순간 깨진다

기본값 변경이 아픈 이유

  • 기본값은 코드/이미지 안에 하드코딩된 값이라 바꾸려면 재빌드가 필요하다
  • 무엇보다 기본값에 조용히 의존하던 사용자들이 변경 시 반드시 놀라게 된다
  • 그래서 책의 처방은 세 가지다
처방내용
메이저 버전업기본값 변경은 호환성 파괴다 — 시맨틱 버저닝에서 메이저 번호를 올려 요란하게 알려라
Fail fast어중간한 기본값은 제거하고, 값이 없으면 시작 시 에러를 내라 — 조용히 이상하게 도는 것보다 일찍 시끄럽게 죽는 게 낫다
90% 규칙오래 갈 거라 90% 확신 못 하는 값은 처음부터 기본값을 주지 마라

기본값을 주지 말아야 할 대표 후보: 비밀번호와 DB 접속 파라미터

  • 환경 의존적이라 예측이 불가능하다
  • 비밀번호에 예측 가능한 기본값이 있다는 것 자체가 보안 사고다
    • 공유기 admin/admin을 떠올려보라
  • 덤으로, 기본값이 없으면 모든 설정이 명시적으로 적힌다
    • → 설정 파일 자체가 살아있는 문서가 된다

3.6 편의 기능 3종: envFrom, Downward API, 종속 변수

① envFrom — 하나씩 말고, 통째로

# 키가 20개면 configMapKeyRef를 20번 반복? → envFrom으로 한 방에
envFrom:
- configMapRef:
    name: random-generator-config   # 안의 모든 키가 각각 환경 변수로 자동 주입

envFrom의 대가

  • YAML이 극적으로 짧아진다
  • 하지만 뭐가 들어오는지 YAML만 봐서는 안 보인다
  • 특히 Secret에 envFrom을 쓰는 것은 신중해야 한다
    • 필요 없는 비밀까지 전부 유입된다(최소 권한 위반)
    • Secret에 키가 추가되면 YAML 수정 없이도 다음 컨테이너 재시작 때 새 비밀이 흘러든다
    • 크래시 덤프 한 번에 비밀 전체 세트가 유출된다

② Downward API — 실행 시점에만 아는 “내 정보”

  • Pod 이름이나 IP는 Pod가 뜨기 전엔 존재하지 않는 값이다
    • → ConfigMap에 미리 적어둘 수 없다
  • Downward API는 Kubernetes가 실행 시점에 이런 메타데이터를 아래로 내려주는(downward) 메커니즘이다
env:
- name: POD_NAME
  valueFrom:
    fieldRef:
      fieldPath: metadata.name       # 내 Pod 이름 — Deployment가 붙인 랜덤 접미사까지 포함
- name: POD_IP
  valueFrom:
    fieldRef:
      fieldPath: status.podIP        # 내 IP — 스케줄링 후에야 배정되는 값

Downward API의 쓰임과 원칙

  • 로그에 자기 Pod 이름을 찍는 등, “내가 누구인지” 알아야 하는 앱에 유용하다
  • 단, 과한 정보까지 노출하면 위험하다
    • 노드 이름·서비스 어카운트·라벨 전체 등
    • 컨테이너 침해 시 공격자에게 클러스터 정찰 정보를 주는 셈이 된다
  • 앱이 실제로 필요한 필드만 내려주는 것이 원칙이다

③ 종속 변수 — 변수로 변수 만들기

# Example 19-5. 종속 환경 변수
apiVersion: v1
kind: Pod
metadata:
  name: random-generator
spec:
  containers:
  - image: k8spatterns/random-generator:1.0
    name: random-generator
    env:
    - name: PORT
      value: "8181"                  # ① 고정값 — 작성 시점에 아는 값
    - name: IP
      valueFrom:
        fieldRef:
          fieldPath: status.podIP    # ② Downward API — 실행 시점에만 아는 값
    - name: MY_URL
      value: "https://$(IP):$(PORT)" # ③ 앞서 정의된 IP, PORT를 조합 → https://10.1.2.3:8181

종속 변수의 묘미

  • 작성 시점에 알 수 없는 값(Pod IP)까지 재료로 삼아, 실행 시점에 완성품(URL)을 자동 조립한다
  • 앱 코드에서 IP를 조회하고 문자열을 이어붙이는 로직이 필요 없다

⚠️ 순서 함정 — 조용한 실패

# ❌ 잘못된 순서
env:
- name: MY_URL
  value: "https://$(IP):8181"   # IP가 아직 정의 안 됨
- name: IP
  value: "10.1.2.3"

# 결과: 에러가 나지 않는다!
# MY_URL = "https://$(IP):8181"  ← 치환 안 되고 글자 그대로 들어감
✅ 올바른 순서env 목록 위 → 아래PORT"8181"IPfieldRef: status.podIPMY_URL"https://$(IP):$(PORT)"참조 가능MY_URL = https://10.1.2.3:8181정상 치환❌ 잘못된 순서env 목록 위 → 아래MY_URL"https://$(IP):8181"IP"10.1.2.3"IP 아직 정의 안 됨MY_URL = "https://$(IP):8181"에러 없이 글자 그대로
$( )는 위에서 먼저 정의된 변수만 참조한다

참조 규칙과 문법

  • $( )는 목록에서 먼저(위에) 정의된 변수와 envFrom으로 가져온 변수만 참조할 수 있다
  • 순서가 틀려도 에러 없이 문자 그대로 들어간다
    • 한참 뒤 “왜 접속이 안 되지?“로 발견되는 전형적인 조용한 실패다
  • 쉘의 ${VAR}가 아니라 소괄호 $(VAR) 문법이다
  • 치환은 컨테이너 시작 시점에 단 한 번 일어난다

⚠️ 보안 관점: 종속 변수 자체는 배관일 뿐이다

  • 포트·IP 같은 공개 값을 흘리면 안전하다
  • postgres://$(USER):$(PASSWORD)@host 같은 접속 URL 조립은 위험하다
    • 비밀번호를 두 군데(원본 변수 + URL)에 노출시킨다
    • 접속 URL은 에러 로그에 특히 잘 찍힌다
  • 비밀이 섞인 조립은 하지 말 것

3.7 command에서의 환경 변수 치환

시작 명령어에서의 $( )

  • 같은 $( ) 문법을 컨테이너의 시작 명령어에도 쓸 수 있다
# Example 19-6. command 정의에서 환경 변수 사용
apiVersion: v1
kind: Pod
metadata:
  name: random-generator
spec:
  containers:
  - name: random-generator
    image: k8spatterns/random-generator:1.0
    command: [ "java", "RandomRunner", "$(OUTPUT_FILE)", "$(COUNT)" ]  # ① 시작 명령어에서 참조
    env:
    - name: OUTPUT_FILE
      value: "/numbers.txt"          # ② 명령어에 치환될 변수 정의
    - name: COUNT
      valueFrom:
        configMapKeyRef:
          name: random-config
          key: RANDOM_COUNT
작성java RandomRunner $(OUTPUT_FILE) $(COUNT)컨테이너 시작 직전, Kubernetes가 치환$(OUTPUT_FILE) ← env의 value "/numbers.txt"$(COUNT) ← ConfigMap random-config의 RANDOM_COUNT 키실제 실행java RandomRunner /numbers.txt 12345
컨테이너 시작 직전 Kubernetes가 $(VAR)를 값으로 바꿔 넣는다

진짜 가치: 환경 변수를 읽을 줄 모르는 앱

  • 명령줄 인자(args[0], args[1])로만 설정을 받는 레거시 앱·CLI 도구가 대상이다
  • command 치환을 통하면 코드 한 줄 수정 없이 ConfigMap 기반 설정 체계에 편입된다
  • 앱은 자기가 ConfigMap과 엮인 줄도 모른다

주의: 쉘을 거치지 않는 command

  • 이 command는 쉘을 거치지 않으므로 쉘의 ${VAR} 치환은 일어나지 않는다
  • Kubernetes 자신이 $(VAR)만 치환한다
  • 중괄호로 쓰면 역시 에러 없이 문자 그대로 들어간다

레퍼런스

종속 변수의 순서 규칙과 $(VAR_NAME) 문법은 Kubernetes 공식 문서 "Define Dependent Environment Variables"에 명시되어 있다.
"Note that order matters in the env list."
[해석] "env 목록에서는 순서가 중요하다는 점에 유의하라." — 공식 문서는 참조 대상이 목록의 더 아래에 정의되어 있으면 "정의된" 것으로 취급되지 않고, 해석되지 못한 참조는 일반 문자열로 처리되며, 잘못 파싱된 환경 변수가 있어도 컨테이너 시작 자체는 막히지 않는다고 설명한다. $$(VAR_NAME)으로 이스케이프하면 참조 여부와 무관하게 절대 확장되지 않는다는 규칙도 함께 다룬다.
공식 예제 Pod의 로그로 세 케이스가 한눈에 검증된다:
UNCHANGED_REFERENCE=$(PROTOCOL)://172.17.0.1:80   ← 순서 위반: 글자 그대로
SERVICE_ADDRESS=https://172.17.0.1:80              ← 올바른 순서: 정상 치환
ESCAPED_REFERENCE=$(PROTOCOL)://172.17.0.1:80      ← $$ 이스케이프: 의도적 미치환
→ kubernetes.io — Define Dependent Environment Variables
→ kubernetes.io — Define Environment Variables for a Container
→ 12factor.net — III. Config: Store config in the environment

4. Discussion

환경 변수의 장단점

  • 장점
    • 쉽고, 모두가 안다
    • 컨테이너 개념(불변 이미지 + 런타임 주입)에 매끄럽게 대응한다
    • 모든 런타임 플랫폼이 지원한다
  • 단점
    • 안전하지 않다
    • 적당한(decent) 개수의 설정까지만 감당한다

전체 메커니즘 정리

메커니즘문법시점용도 / 주의
Dockerfile 기본값ENV KEY value빌드안전한 출발점 — 값 변경엔 재빌드 필요
Docker 오버라이드docker run -e런타임재빌드 없이 환경별 값 교체
직접 지정env + value런타임단순·비민감·Pod 전용 값
ConfigMap 참조configMapKeyRef런타임공유 설정, Pod 정의와 분리 관리
Secret 참조secretKeyRef런타임YAML/Git 노출은 막지만 런타임 노출은 못 막음 ⚠️
통째 주입envFrom런타임키 많을 때 편리 — Secret엔 신중히
Downward APIfieldRef런타임Pod 이름·IP 등 실행 시점 정보
종속 변수$(VAR)시작 시 1회변수 조합 — 순서 위반 시 조용한 실패 ⚠️
command 치환command 내 $(VAR)시작 시 1회인자만 받는 앱을 무수정 편입
Secret 볼륨volumeMounts런타임민감 값의 정석 — 권한 제어·무중단 갱신(subPath 제외) ✅

한계 정리

  • 이제 한계를 세 가지 축으로 정리한다

4.1 규모의 한계와 Spring Boot 프로파일 안티패턴

설정이 수십 개로 늘 때 드러나는 구조적 약점

  • 계층 구조가 없다(전부 평평한 이름-값)
  • 타입이 없다(전부 문자열)
  • nginx.conf 같은 파일 형태의 복잡한 값은 사실상 담을 수 없다

흔한 우회책: Spring Boot 프로파일

  • 많은 팀이 쓰는 방식이다
  • 환경별 설정 파일을 이미지 안에 전부 넣고, 환경 변수 하나로 선택한다
이미지 안application-dev.yaml개발 설정 80개application-prod.yaml운영 설정 80개선택환경 변수는 스위치 1개만SPRING_PROFILES_ACTIVE=prod
스위치만 밖으로 나왔고, 설정 파일은 다시 이미지 안에 갇혔다

우회책의 함정: 설정이 다시 이미지 안으로

  • 환경 변수 80개가 1개로 줄었으니 똑똑해 보인다
  • 하지만 이것은 이 장이 처음부터 피하려던 상태로의 회귀다
    • 설정 파일들이 다시 이미지 안에 갇혔다
    • 스위치만 외부화됐지 알맹이는 내부화된 것이다

부작용 두 가지

  1. dev 설정 한 줄 수정 → 이미지 재빌드 → 운영까지 새 이미지
    • “빌드 없이 설정 변경"이라는 목표가 무너짐
  2. 운영에 배포된 이미지 안에 개발 설정이, 개발자 손의 이미지 안에 운영 정보가 동봉됨

올바른 방향: 파일을 이미지 밖으로

  • 책은 이 구성을 권장하지 않는다(설정은 항상 애플리케이션 외부에)
  • 파일 관리라는 발상은 유지하되 파일을 이미지 밖 — 즉 ConfigMap에 두고 볼륨으로 마운트한다(다음 장)
  • 이런 우회책이 존재한다는 사실 자체가 한계의 증거다
    • 환경 변수는 소~중규모 설정 전용

4.2 파편화 — “이 값 어디서 온 거야?”

장점이 뒤집힌 단점

  • 환경 변수는 어디서나 설정할 수 있다
  • 그 장점이 그대로 뒤집혀 단점이 된다 — 실제로 아무 데서나 설정되기 때문이다
LOG_LEVEL 하나가 설정될 수 있는 곳① 베이스 이미지의 Dockerfile ENV② 내 앱의 Dockerfile ENV①을 덮어씀③ Pod YAML의 env: value②를 덮어씀④ ConfigMap (configMapKeyRef)⑤ Secret (secretKeyRef)⑥ envFrom 통째 유입⑦ 컨테이너 시작 스크립트의 exportLOG_LEVEL중앙 정의 장소 없음
값 하나에 출처가 일곱 군데라 장애 때 출처 찾기가 어렵다

출처 추적: 중앙 정의 장소 부재

  • 중앙 집중된 정의 장소가 없으면, 장애 상황에서 값 하나의 출처를 찾느라 온 시스템을 뒤지게 된다
  • 이 문제의 처방 역시 다음 장으로 이어진다
    • 설정을 한 리소스(ConfigMap)에 모아 출처를 추적 가능하게 만드는 것

4.3 불변성의 역설 — 못 바꾸는 게 장점이다

실행 중 변경 불가: 단점이자 장점

  • 환경 변수는 컨테이너 시작 시점에 박제되어 실행 중에는 변경할 수 없다
  • 운영 중 로그 레벨을 “핫하게” 올려 튜닝할 수 없다는 건 분명 단점이다
  • 그런데 많은 사람이 이를 오히려 장점으로 본다
    • 설정에까지 불변성이 확장되기 때문이다
😱 핫 변경이 가능한 세상의 흔한 사고운영자가 급하다고 실행 중인 서버 설정을손으로 수정 (기록 없음)3주 뒤 Pod 재시작→ 손 수정분 증발"예전엔 됐는데 왜 안 되지???"아무도 원인을 모르는눈송이 서버(snowflake server) 탄생😊 불변 방식설정 변경의 유일한 경로YAML 수정 → 새 Pod으로 교체 (롤링 업데이트)YAML 수정은 Git에 기록됨지금 도는 모든 Pod의 설정= YAML에 적힌 그대로. 예외 없음.
고치지 말고 교체하면 YAML이 곧 운영 상태가 된다

“항상 정의되고 잘 알려진(defined and well-known) 설정 상태"의 의미

  • 몰래 고친 설정이 존재할 수 없다
  • “지금 운영에 뭐가 돌고 있어?“라는 질문의 답이 항상 “YAML 봐. 그게 전부야"가 된다
  • Kubernetes에서는 롤링 업데이트가 무중단으로 Pod을 교체해준다
    • “재시작"이 아프지 않다 → 불변성의 장점만 챙길 수 있다

이 장을 관통하는 철학

  • 이미지도 불변, 설정도 불변 → 바꾸고 싶으면 고치지 말고 교체하라

핵심 메시지

환경 변수의 몫

  • 적은 수의, 단순한, 안 민감한 키-값 설정
  • 그 너머는 다음 패턴들의 영역이다
다음 패턴맡는 영역
Configuration ResourceConfigMap/Secret을 제대로 (특히 볼륨 마운트)
Immutable Configuration설정 자체를 불변 이미지로 버전 관리
Configuration Template템플릿 + 환경별 값으로 실행 시점에 설정 생성

EnvVar Configuration은 “설정 외부화의 최소 비용 진입점” 이다. 빌드 시 기본값 + 런타임 오버라이드 패턴으로 불변 이미지 철학을 지키되, 보안(도착지가 환경 변수인 순간 노출), 규모(평평한 키-값의 한계), 파편화(출처 추적 불가)의 경계선을 넘는 순간 다음 패턴으로 넘어가야 한다.