Certified Kubernetes Application Developer (CKAD) #13 ConfigMap과 Secret 깊이: volume vs env, 자동 갱신
#12 Observability에서 돌아가는 앱을 들여다보는 도구를 익혔다면, 이제 그 앱이 어떤 설정으로 돌아가는가를 다룰 차례입니다. 데이터베이스 주소, 기능 플래그, API 키, 비밀번호를 매니페스트나 이미지 안에 박아 넣으면 환경마다 이미지를 새로 빌드해야 하고 민감 정보가 코드 저장소에 노출됩니다. 쿠버네티스는 이 설정값을 코드 밖으로 빼내기 위해 ConfigMap(일반 설정)과 Secret(민감 정보)을 제공합니다.
CKAD에서 ConfigMap과 Secret은 가장 큰 도메인인 **Application Environment, Configuration and Security(25%)**의 핵심이며, 단순히 만드는 것을 넘어 env로 주입할지 volume으로 주입할지, 그리고 값이 바뀌면 언제 반영되는지까지 묻습니다. 기초 개념은 K8s 실무 트랙 #6에서 다뤘으니, 이번 글은 시험에서 곧바로 쓰는 실기 패턴에 집중하겠습니다.
ConfigMap 만들기 #
ConfigMap은 키-값 쌍으로 설정을 담는 오브젝트입니다. 시험에서는 손으로 YAML을 쓰기보다 kubectl create configmap의 세 가지 소스 옵션으로 즉시 만드는 편이 빠릅니다.
# 1) 리터럴: 키=값을 직접 지정
k create configmap app-config \
--from-literal=APP_COLOR=blue \
--from-literal=APP_MODE=prod
# 2) 파일: 파일 하나가 키 하나가 됨 (키=파일명, 값=파일 내용)
k create configmap nginx-conf --from-file=nginx.conf
# 3) env 파일: KEY=VALUE 줄들을 한꺼번에 키-값으로
k create configmap app-env --from-env-file=app.properties세 옵션의 결과 구조가 다릅니다. --from-literal과 --from-env-file은 각 줄이 개별 키가 되지만, --from-file=nginx.conf는 파일명이 키이고 파일 전체 내용이 하나의 값이 됩니다. 이 차이는 나중에 volume으로 마운트할 때 파일이 몇 개로 풀리는가를 결정하므로 정확히 구분해야 합니다.
만들어진 ConfigMap을 k get configmap app-config -o yaml로 확인하면 data 아래에 APP_COLOR: blue, APP_MODE: prod가 키-값으로 들어가 있습니다.
Secret 만들기 #
Secret은 ConfigMap과 구조가 거의 같지만, 비밀번호,토큰,인증서 같은 민감 정보를 담습니다. 타입에 따라 세 가지 generator를 자주 씁니다.
# generic: 일반 키-값 (비밀번호, API 키 등)
k create secret generic db-secret \
--from-literal=DB_USER=admin \
--from-literal=DB_PASS=s3cr3t
# docker-registry: 프라이빗 레지스트리 인증 (imagePullSecrets로 사용)
k create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username=ci \
--docker-password=token123
# tls: 인증서와 키 쌍 (Ingress TLS 등)
k create secret tls web-tls \
--cert=tls.crt --key=tls.keybase64는 암호화가 아닙니다 #
Secret을 k get secret db-secret -o yaml로 조회하면 값이 DB_PASS: czNjcjN0처럼 base64로 인코딩되어 보입니다. 여기서 반드시 짚어야 할 점은 base64는 암호화가 아니라 인코딩이라는 사실입니다. echo czNjcjN0 | base64 -d만 하면 누구나 원래 값을 복원합니다. base64는 바이너리 데이터를 텍스트로 옮기기 위한 형식일 뿐이며, 보안을 보장하지 않습니다. 실제 저장 시 암호화를 하려면 etcd encryption at rest나 외부 비밀 관리 도구가 필요합니다. 시험에서는 “base64는 암호화가 아니다"라는 개념을 묻는 문항이 자주 나옵니다.
YAML을 손으로 쓸 때 base64 인코딩이 번거로우면 data 대신 stringData를 쓰면 평문으로 적을 수 있고 쿠버네티스가 저장 시 인코딩합니다.
주입 방식 1: env로 개별 키 #
ConfigMap이나 Secret의 특정 키 하나를 환경 변수로 주입할 때는 valueFrom을 씁니다. ConfigMap은 configMapKeyRef, Secret은 secretKeyRef를 사용합니다.
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: nginx
env:
- name: APP_COLOR # 컨테이너 안 환경 변수 이름
valueFrom:
configMapKeyRef:
name: app-config # ConfigMap 이름
key: APP_COLOR # 그 안의 키
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: DB_PASS환경 변수 이름(name)과 소스의 키(key)는 다르게 지을 수 있습니다. 컨테이너 안에서는 APP_COLOR로 읽지만 ConfigMap의 키 이름은 별개라는 점을 의식하면 헷갈리지 않습니다.
주입 방식 2: envFrom으로 전체를 env로 #
키가 여러 개일 때 하나씩 valueFrom을 쓰면 길어집니다. ConfigMap이나 Secret의 모든 키를 한꺼번에 환경 변수로 풀려면 envFrom을 씁니다.
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: nginx
envFrom:
- configMapRef:
name: app-config # 모든 키가 그대로 env 이름이 됨
- secretRef:
name: db-secret이 경우 ConfigMap의 키 이름이 그대로 환경 변수 이름이 되므로, 키 이름은 APP_COLOR처럼 환경 변수로 써도 되는 형식이어야 합니다. 접두사를 붙이고 싶으면 항목에 prefix: CONFIG_를 추가하면 됩니다. envFrom은 configMapRef,secretRef이고 valueFrom은 configMapKeyRef,secretKeyRef라는 이름 차이를 정확히 기억해야 합니다.
주입 방식 3: volume으로 파일 마운트 #
env가 아니라 파일로 설정을 받아야 하는 경우가 있습니다. nginx 설정 파일이나 인증서처럼 애플리케이션이 경로에서 직접 읽는 값이 그렇습니다. 이때는 ConfigMap이나 Secret을 volume으로 마운트합니다.
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: config-vol
mountPath: /etc/app # 이 디렉터리에 키마다 파일 생성
volumes:
- name: config-vol
configMap:
name: app-config이렇게 마운트하면 /etc/app/ 아래에 ConfigMap의 키마다 파일이 하나씩 생깁니다. 즉 /etc/app/APP_COLOR(내용 blue), /etc/app/APP_MODE(내용 prod)가 만들어집니다. Secret도 configMap 대신 secret(필드는 secretName)으로 같은 방식으로 마운트합니다.
volumes:
- name: secret-vol
secret:
secretName: db-secretsubPath: 단일 파일만 마운트 #
volume을 통째로 마운트하면 mountPath 디렉터리에 있던 기존 파일이 가려집니다. 예를 들어 /etc/nginx에 설정 디렉터리를 마운트하면 그 안의 다른 파일이 모두 사라진 것처럼 보입니다. 파일 하나만 특정 경로에 끼워 넣고 나머지 디렉터리는 그대로 두려면 subPath를 씁니다.
volumeMounts:
- name: config-vol
mountPath: /etc/nginx/nginx.conf # 파일 경로까지 지정
subPath: nginx.conf # 이 키 하나만 이 경로에 마운트subPath로 마운트하면 ConfigMap의 nginx.conf 키만 해당 파일 경로에 들어가고 디렉터리의 다른 파일은 보존됩니다. 다만 subPath로 마운트한 파일은 자동 갱신이 되지 않는다는 제약이 있는데, 이 차이는 바로 다음 절에서 다루겠습니다.
env vs volume: 자동 갱신 (시험 단골) #
같은 ConfigMap을 env로 주입한 것과 volume으로 마운트한 것은 값이 바뀌었을 때 동작이 다릅니다. 이 차이가 CKAD에서 거듭 출제됩니다.
| 주입 방식 | ConfigMap/Secret 수정 시 반영 |
|---|---|
| env (valueFrom / envFrom) | 반영 안 됨. Pod 재시작 전까지 옛 값 유지 |
| volume 마운트 | 일정 시간(kubelet sync) 후 파일 내용 자동 갱신 |
| volume + subPath | 자동 갱신 안 됨 (전체 마운트와 다름) |
env로 주입한 값은 컨테이너가 시작될 때 프로세스 환경에 한 번 박히고 끝납니다. 그래서 ConfigMap을 수정해도 이미 돌아가는 컨테이너의 환경 변수는 바뀌지 않으며, 새 값을 보려면 Pod를 다시 만들어야 합니다(kubectl rollout restart로 Deployment를 재시작).
반면 volume으로 마운트한 파일은 kubelet이 주기적으로 동기화하므로, ConfigMap을 수정하면 일정 시간 뒤 마운트된 파일 내용이 자동으로 바뀝니다. 단 애플리케이션이 그 파일을 다시 읽어야 새 값이 적용되며(파일은 갱신돼도 프로세스가 재로딩하지 않으면 의미가 없음), 앞서 본 subPath 마운트는 이 자동 갱신 대상에서 빠집니다. “ConfigMap을 바꿨는데 왜 안 바뀌느냐"는 함정은 거의 항상 env 주입이거나 subPath 마운트입니다.
immutable과 optional #
immutable: 불변 ConfigMap/Secret #
설정이 자주 바뀌지 않는다면 immutable: true를 두어 수정 자체를 막을 수 있습니다. 불변으로 표시된 ConfigMap이나 Secret은 데이터를 바꿀 수 없고(삭제 후 재생성만 가능), 대신 kubelet이 변경을 감시할 필요가 없어 클러스터 성능에 이점이 있습니다.
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_MODE: prod
immutable: trueoptional: 없어도 기동되게 #
기본적으로 참조한 ConfigMap이나 키가 없으면 Pod가 시작되지 못하고 대기합니다. 없을 수도 있는 값이라면 optional: true를 두어 없을 때 건너뛰게 합니다.
env:
- name: FEATURE_FLAG
valueFrom:
configMapKeyRef:
name: app-config
key: FEATURE_FLAG
optional: true # 키가 없어도 기동downwardAPI 한 줄 #
env나 volume으로 주입하는 값이 ConfigMap,Secret만 있는 것은 아닙니다. Pod 자신의 메타데이터(이름, 네임스페이스, 라벨, 노드 이름, 리소스 한도 등)를 컨테이너에 주입하는 downwardAPI도 있습니다. 외부 설정이 아니라 Pod의 정보를 앱이 알아야 할 때 씁니다. 상세는 #17 Volumes에서 projected volume과 함께 다루겠습니다.
시험 포인트 #
- ConfigMap,Secret generator 세 소스를 구분합니다.
--from-literal(키=값),--from-file(파일명=키),--from-env-file(여러 줄 한꺼번에)입니다. - Secret 타입은
generic,docker-registry,tls세 가지를 손에 익힙니다. base64는 암호화가 아니라 인코딩이라는 개념을 묻는 문항이 자주 나옵니다. - env 주입 두 방식의 필드 이름을 정확히 씁니다. 개별 키는
valueFrom의configMapKeyRef,secretKeyRef, 전체는envFrom의configMapRef,secretRef입니다. - volume 마운트는 키마다 파일이 생기고,
subPath는 단일 파일만 끼워 넣어 디렉터리 나머지를 보존합니다. - 자동 갱신이 최대 단골입니다. env는 Pod 재시작 전까지 안 바뀌고, volume은 일정 시간 후 갱신되며, subPath 마운트는 갱신되지 않습니다.
- 빠른 검증은
kubectl exec로 합니다.k exec app -- env로 환경 변수를,k exec app -- cat /etc/app/APP_COLOR로 마운트된 파일을 확인합니다.
정리 #
이번 글에서 잡은 것:
- 설정과 민감 정보를 코드 밖으로. ConfigMap(일반)과 Secret(민감)으로 분리하고, Secret의 base64는 암호화가 아님을 인지합니다.
- 만들기. generator 세 소스(
--from-literal,--from-file,--from-env-file)와 Secret 세 타입(generic,docker-registry,tls)입니다. - 주입 세 방식. 개별 키 env(
valueFrom), 전체 env(envFrom), 파일 마운트(volume,subPath)입니다. - 자동 갱신 차이. env는 재시작 전까지 고정, volume은 자동 갱신, subPath는 갱신 제외입니다.
- immutable,optional. 불변 설정과 없어도 기동되는 선택적 참조입니다.
다음: ServiceAccount와 RBAC #
설정과 민감 정보를 컨테이너 안으로 넣는 법을 익혔습니다. 이제 그 앱이 클러스터 API에 무엇을 할 수 있는지, 즉 권한을 다룰 차례입니다.
#14 ServiceAccount와 RBAC (앱 관점)에서는 Pod가 어떤 ServiceAccount로 동작하는지, Role과 RoleBinding으로 네임스페이스 안 권한을 부여하는 법, ClusterRole과의 차이, 그리고 kubectl auth can-i로 권한을 검증하는 실기 패턴을 직접 만들어 보며 정리하겠습니다.