7.3 주입 패턴 정리
ConfigMap과 Secret을 컨테이너에 전달하는 통로는 env 변수와 volume 마운트 두 갈래다. 여기에 Pod가 자기 자신의 메타데이터를 받는 Downward API가 더해진다. 앞의 두 챕터가 오브젝트를 다뤘다면 이 챕터는 통로 쪽에서 정리한다. 어떤 값은 env로, 어떤 값은 파일로 넣어야 하는가. 기준은 세 가지다. 갱신이 필요한가, 크기와 형태가 어떤가, 노출되면 얼마나 위험한가.
env와 volume 사이의 선택
갱신 필요성이 첫 기준이다. 재배포 없이 설정 변경을 반영해야 하는 값은 volume으로 넣는다. env는 Pod 재시작 전까지 고정되기 때문이다. 반대로 immutable ConfigMap을 해시 이름으로 교체하며 rolling update를 일으키는 운영이라면, 갱신이 어차피 재배포와 함께 오므로 env의 약점이 사라진다. 갱신 전략을 먼저 정해야 통로를 정할 수 있다.
크기와 형태가 둘째 기준이다. 플래그 값 몇 개면 env가 간단하고, 애플리케이션 코드도 환경 변수 읽기 한 줄로 끝난다. 반면 nginx.conf나 application.yaml처럼 애초에 파일 형태인 설정은 volume이 자연스럽다. 여러 줄 텍스트를 env 변수 하나에 담으면 이스케이프와 파싱 문제만 늘어난다.
노출 위험이 셋째 기준이다. env는 프로세스 환경에 상주해 크래시 덤프, 진단 출력, 자식 프로세스 상속으로 퍼지기 쉽다. 민감한 값일수록 volume 쪽으로 기운다. 판단 근거는 7.2 Secret 챕터에서 정리한 것과 같다.
Downward API: 자기 메타데이터 주입
설정 파일에 없는 값도 컨테이너에 필요하다. 자기가 어느 Pod, 어느 namespace에서 실행 중인지, 리소스 limit이 얼마인지 같은 값이다. Deployment가 만드는 Pod는 이름이 실행 시점에 정해지므로 매니페스트에 미리 적을 방법이 없다. Downward API는 이런 값을 Pod 스스로에게서 꺼내 주입한다.
참조 방법은 두 가지다. fieldRef는 Pod의 필드를 가리키고, resourceFieldRef는 컨테이너의 리소스 requests와 limits를 가리킨다.
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: MEMORY_LIMIT_MB
valueFrom:
resourceFieldRef:
containerName: app
resource: limits.memory
divisor: 1MifieldPath로는 metadata.name, metadata.namespace, metadata.uid, spec.nodeName, spec.serviceAccountName, status.podIP 등을 참조한다. resourceFieldRef의 divisor는 단위 환산이다. limits.memory를 1Mi로 나눠 받으면 애플리케이션이 바이트 계산 없이 MiB 숫자를 읽는다. 로그 라인에 Pod 이름을 남기거나, JVM 힙 크기를 컨테이너 limit에 맞추는 데 흔히 쓴다.
resourceFieldRef로 CPU나 메모리 limit을 참조했는데 해당 컨테이너에 limit이 지정되어 있지 않으면, Downward API는 노드의 allocatable 값을 대신 반환한다. limit을 지정했다고 가정하고 힙 크기를 계산하는 애플리케이션이라면 노드 전체 메모리를 기준으로 삼는 사고가 난다. limit 지정과 함께 쓴다.env 말고 downwardAPI volume으로도 받는다. label과 annotation의 전체 목록은 volume으로만 받는 값이다.
volumes:
- name: podinfo
downwardAPI:
items:
- path: "labels"
fieldRef:
fieldPath: metadata.labels
- path: "annotations"
fieldRef:
fieldPath: metadata.annotations이 volume을 /etc/podinfo에 마운트하면 labels, annotations 두 파일이 생기고, 한 줄에 key 하나씩 담긴다. Pod가 실행되는 동안 label이 바뀌면 파일도 따라서 갱신된다. env 쪽은 metadata.labels['zone'] 형식으로 개별 key만 참조하며 시작 시점 값으로 고정된다. 갱신을 따라가야 하는 메타데이터라면 volume 쪽을 선택한다.
주입 방식별 갱신 전파
| 주입 방식 | 소스 변경 시 반영 |
|---|---|
env (configMapKeyRef, secretKeyRef) | 안 됨. Pod 재시작 필요 |
envFrom | 안 됨. Pod 재시작 필요 |
| volume 마운트 | kubelet 동기화 주기로 자동 갱신 |
volume + subPath | 안 됨 |
| downwardAPI volume (label·annotation) | 자동 갱신 |
| downwardAPI env | Pod 시작 시점 값으로 고정 |
갱신 주기와 subPath 예외의 배경은 7.1의 ConfigMap 동작과 동일하고, Secret volume도 같은 방식으로 전파된다. 그리고 표의 자동 갱신은 파일이 바뀐다는 뜻이지 애플리케이션이 새 값으로 동작한다는 보장이 아니다. 프로세스가 파일을 다시 읽는지는 애플리케이션의 몫이고, 설정 리로드 신호(SIGHUP)나 파일 감시 로직이 있어야 반영이 완성된다.
안티패턴 두 가지
거대 ConfigMap 하나에 모든 설정을 모으는 구성부터 피한다. 1MiB 제한은 생각보다 빨리 다가오고, 제한에 이르기 전에 운영 문제가 먼저 온다. key 하나를 바꿔도 이 ConfigMap을 마운트한 모든 Pod에 파일 갱신이 전파되고, 변경의 영향 범위를 매번 참조 Pod 전체 목록에서 다시 조사해야 한다. 여러 팀이 오브젝트 하나를 같이 수정하면 이력 추적도 얽힌다. 애플리케이션 단위, 관심사 단위로 나누고, 변경 통제가 필요한 것은 immutable과 해시 이름 교체로 묶는 편이 낫다.
둘째는 Secret을 env로 주입하고 로그로 유출하는 조합이다. 프레임워크 중에는 시작 시점에 환경 변수 전체를 로그에 남기는 것이 있고, 에러 리포팅 도구가 진단 정보에 환경을 포함하기도 한다. env로 들어간 데이터베이스 비밀번호는 이런 경로를 따라 로그 수집 파이프라인으로 들어가고, 로그 열람 권한은 대개 Secret 열람 권한보다 훨씬 넓다. RBAC로 Secret 접근을 아무리 제한해도 로그에서 유출되면 소용이 없다. 민감값은 volume으로 주입하고, 로그 파이프라인에는 마스킹을 추가한다.