ConfigMapとSecretで設定を注入する
한국어 원문으로 표시합니다.
목표
같은 이미지를 환경마다 다르게 동작시키는 네 가지 주입 경로(전체 환경변수, 개별 환경변수, 파일 볼륨, subPath 파일)를 구분해서 쓰고, Secret 과 ConfigMap 의 차이를 필드 수준에서 다룰 수 있게 된다.
왜 중요한가
설정 주입에서 실무 사고가 나는 지점은 대부분 "갱신이 반영되는가"이다. 환경변수로 넣은 값은 프로세스가 시작할 때 한 번 읽히고 끝이라, ConfigMap 을 고쳐도 파드를 다시 만들기 전에는 절대 바뀌지 않는다. 볼륨으로 넣은 파일은 kubelet 이 주기적으로 갱신해 주지만, subPath 로 얹은 파일만은 예외라 갱신되지 않는다. 이 세 가지 동작 차이를 모르면 "설정을 바꿨는데 왜 안 먹지"에서 시간을 다 쓴다.
defaultMode 도 실무에서 자주 걸린다. Secret 을 마운트하면 기본 권한이 0644 라 같은 노드의 다른 프로세스가 읽을 수 있다. TLS 키처럼 민감한 파일은 0400 으로 좁힌다. YAML 에서 0400 을 따옴표 없이 쓰면 8진수로 해석되지만, 400 만 쓰면 10진수 400 으로 읽혀 엉뚱한 권한이 된다.
immutable: true 는 보안 기능이 아니라 성능 기능이다. kubelet 은 마운트된 ConfigMap/Secret 의 변경을 감시하려고 API 서버를 계속 두드리는데, immutable 이면 감시를 멈춘다. 수천 파드 규모에서 apiserver 부하가 눈에 띄게 줄어든다.
단계
- 네임스페이스
ckad-config를 만들고, 그 안에 ConfigMapapp-config를 리터럴로 만든다. 키는APP_MODE=production,LOG_LEVEL=info. - 디렉터리
/root/ckad-config를 만들고 파일/root/ckad-config/app.properties에timeout=30한 줄을 쓴 뒤, 그 파일로 ConfigMapapp-props를 만든다(키 이름은app.properties). - 파드
cfg-env를 만든다. 이미지busybox:1.36,command: ["/bin/sh","-c","sleep 3600"].envFrom으로app-config전체를 주입하고, 추가로env에LOG라는 이름으로app-config의LOG_LEVEL키를 참조한다. - Secret
db-secret을 만든다. 타입은generic(Opaque), 키는DB_USER=appuser,DB_PASSWORD=s3cr3t. - 파드
secret-consumer를 만든다. 이미지busybox:1.36,command: ["/bin/sh","-c","sleep 3600"]. 환경변수DB_PASSWORD를db-secret의DB_PASSWORD키에서 가져오고, 같은 Secret 을 볼륨dbcreds로/etc/db에 마운트하되defaultMode를0400으로 준다. - 파드
cfg-vol을 만든다. 이미지nginx:1.27. ConfigMapapp-props를 볼륨props로 두 번 마운트한다 — 하나는/etc/app에 디렉터리째, 다른 하나는subPath: app.properties로/opt/app/app.properties에. - ConfigMap
feature-flags를 만든다. 키NEW_UI=on, 그리고immutable: true. 파드optional-cm을 만들어(이미지busybox:1.36,command: ["/bin/sh","-c","sleep 3600"]) 존재하지 않는 ConfigMapmaybe-config를envFrom으로 참조하되optional: true를 준다. - Deployment
web-config를 만든다. 레플리카 2, 라벨app=web-config, 이미지nginx:1.27.envFrom에app-config(configMapRef)와db-secret(secretRef)을 모두 넣고, ConfigMapapp-props를 볼륨props로/etc/app에 마운트한다.
참고
kubectl create configmap app-config -n ckad-config --from-literal=APP_MODE=production --from-literal=LOG_LEVEL=infokubectl create secret generic db-secret -n ckad-config --from-literal=DB_USER=appuser --from-literal=DB_PASSWORD=s3cr3t- YAML 로 직접 쓸 때 평문을 넣으려면
data가 아니라stringData를 쓴다. 저장되면 자동으로data로 바뀐다. - 흔한 실수 1:
defaultMode를 컨테이너의volumeMounts밑에 쓰는 것. 볼륨 정의(spec.volumes[].secret.defaultMode) 쪽이다. - 흔한 실수 2:
immutable을data안에 넣는 것. ConfigMap 최상위 필드다. - 확인:
kubectl get secret db-secret -n ckad-config -o jsonpath='{.data.DB_USER}' | base64 -d
리터럴로 ConfigMap 만들기
네임스페이스 ckad-config 를 만들고, 그 안에 ConfigMap app-config 를 리터럴로 만든다. 키는 APP_MODE=production, LOG_LEVEL=info.
kubectl create configmap 에 --from-literal=키=값 을 여러 번 줄 수 있다. 네임스페이스를 먼저 만들어 두고 -n 을 빠뜨리지 않는다.
파일에서 ConfigMap 만들기
디렉터리 /root/ckad-config 를 만들고 파일 /root/ckad-config/app.properties 에 timeout=30 한 줄을 쓴 뒤, 그 파일로 ConfigMap app-props 를 만든다(키 이름은 app.properties).
--from-file=경로 를 쓰면 파일 이름이 키가 되고 파일 내용 전체가 값이 된다. 키 이름을 바꾸고 싶으면 --from-file=키이름=경로 형태를 쓴다.
envFrom 과 configMapKeyRef 를 같이 쓰기
파드 cfg-env 를 만든다. 이미지 busybox:1.36, command: ["/bin/sh","-c","sleep 3600"]. envFrom 으로 app-config 전체를 주입하고, 추가로 env 에 LOG 라는 이름으로 app-config 의 LOG_LEVEL 키를 참조한다.
envFrom[].configMapRef 는 모든 키를 그대로 변수명으로 만들고, env[].valueFrom.configMapKeyRef 는 키 하나를 골라 다른 이름으로 넣는다. 두 방식을 한 컨테이너에 함께 쓸 수 있다.
Secret 만들기와 인코딩 확인
Secret db-secret 을 만든다. 타입은 generic(Opaque), 키는 DB_USER=appuser, DB_PASSWORD=s3cr3t.
kubectl create secret generic 의 --from-literal 값은 자동으로 base64 인코딩된다. YAML 로 직접 쓸 때 평문을 넣고 싶으면 data 대신 stringData 를 쓴다.
Secret 을 환경변수와 파일로 함께 주입
파드 secret-consumer 를 만든다. 이미지 busybox:1.36, command: ["/bin/sh","-c","sleep 3600"]. 환경변수 DB_PASSWORD 를 db-secret 의 DB_PASSWORD 키에서 가져오고, 같은 Secret 을 볼륨 dbcreds 로 /etc/db 에 마운트하되 defaultMode 를 0400 으로 준다.
환경변수는 env[].valueFrom.secretKeyRef, 파일은 volumes[].secret.secretName 과 볼륨 마운트다. 파일 권한은 볼륨 쪽 defaultMode 로 정하고, YAML 에서는 8진수 앞의 0 을 빠뜨리기 쉬우니 주의한다.
ConfigMap 볼륨과 subPath
파드 cfg-vol 을 만든다. 이미지 nginx:1.27. ConfigMap app-props 를 볼륨 props 로 두 번 마운트한다 — 하나는 /etc/app 에 디렉터리째, 다른 하나는 subPath: app.properties 로 /opt/app/app.properties 에.
디렉터리에 통째로 마운트하면 그 디렉터리의 원래 내용이 가려진다. 파일 하나만 얹고 싶으면 volumeMounts[].subPath 에 키 이름을 적는다. subPath 로 얹은 파일은 ConfigMap 을 고쳐도 갱신되지 않는다.
optional 참조와 immutable ConfigMap
ConfigMap feature-flags 를 만든다. 키 NEW_UI=on, 그리고 immutable: true. 파드 optional-cm 을 만들어(이미지 busybox:1.36, command: ["/bin/sh","-c","sleep 3600"]) 존재하지 않는 ConfigMap maybe-config 를 envFrom 으로 참조하되 optional: true 를 준다.
immutable: true 는 ConfigMap 스펙의 최상위 필드다(데이터 안이 아니다). 존재하지 않는 ConfigMap 을 참조해도 파드가 뜨게 하려면 참조하는 쪽에 optional: true 를 준다.
Deployment 에 세 가지를 한꺼번에 (종합)
Deployment web-config 를 만든다. 레플리카 2, 라벨 app=web-config, 이미지 nginx:1.27. envFrom 에 app-config(configMapRef)와 db-secret(secretRef)을 모두 넣고, ConfigMap app-props 를 볼륨 props 로 /etc/app 에 마운트한다.
앞에서 만든 ConfigMap 둘과 Secret 하나를 한 Deployment 에서 쓴다. envFrom 배열에는 configMapRef 와 secretRef 를 나란히 넣을 수 있다. 볼륨 마운트도 잊지 않는다.