Certified Kubernetes Administrator (CKA) #12 ConfigMap과 Secret 깊이
#11까지 워크로드 객체를 차례로 살펴봤습니다. 이제 그 워크로드에 설정과 비밀값을 주입하는 두 객체 ConfigMap과 Secret을 운영자 관점에서 깊이 들여다보겠습니다.
앱 개발자 관점의 입문은 K8s 기초 #6에서 한 사이클 다뤘습니다. 이번 글은 그 위에서 시험과 운영에서 실제로 손이 가는 부분에 집중하겠습니다. 빈 터미널에서 빠르게 만드는 법, 주입 방식별 동작 차이, base64가 암호화가 아니라는 사실, 그리고 immutable의 효과까지입니다.
두 객체의 역할 구분 #
ConfigMap과 Secret은 설정값을 워크로드 정의에서 떼어 내는 같은 목적을 가집니다. 매니페스트에 포트나 도메인을 직접 박지 않고 별도 객체에 두면, 같은 이미지를 환경마다 다른 설정으로 띄울 수 있습니다.
| 구분 | ConfigMap | Secret |
|---|---|---|
| 용도 | 비밀이 아닌 설정값 | 비밀번호, 토큰, 키, 인증서 |
| 저장 형태 | 평문 | base64 인코딩 |
| etcd 저장 | 평문 | 기본은 평문(인코딩만 됨) |
| 타입 | 단일 | Opaque, kubernetes.io/tls 등 다수 |
표의 마지막 두 줄이 운영자가 가장 자주 오해하는 지점입니다. Secret이라는 이름 때문에 암호화된다고 생각하기 쉽지만, 기본 설정에서 Secret은 암호화되지 않습니다. 이 부분은 뒤에서 따로 다루겠습니다.
ConfigMap 만들기: 세 가지 소스 #
시험에서는 빈 터미널에서 ConfigMap을 빠르게 만드는 일이 잦습니다. 소스는 세 가지입니다.
–from-literal: 키와 값을 직접 #
# 키=값을 직접 나열
k create configmap app-config \
--from-literal=APP_COLOR=blue \
--from-literal=APP_MODE=prod키가 두세 개면 가장 빠릅니다. 여기에 --dry-run=client -o yaml을 붙여 YAML을 먼저 확인하는 습관이 안전합니다.
–from-file: 파일에서 #
# 파일 하나를 통째로 (키는 파일명, 값은 내용)
k create configmap nginx-config --from-file=nginx.conf
# 키 이름을 직접 지정
k create configmap nginx-config --from-file=conf=nginx.conf
# 디렉터리 전체 (디렉터리 안 각 파일이 키가 됨)
k create configmap all-config --from-file=./config-dir/설정 파일 전체를 그대로 담아야 할 때 씁니다. 키를 지정하지 않으면 파일명이 그대로 키가 됩니다.
–from-env-file: env 형식 파일에서 #
# KEY=VALUE 줄들이 각각 별도 키가 됨
k create configmap app-config --from-env-file=app.properties--from-file은 파일 하나가 키 하나가 되지만, --from-env-file은 파일 안 각 줄이 별도 키가 됩니다. 이 차이가 envFrom으로 통째 주입할 때 결과를 가릅니다.
YAML로 직접 정의 #
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_COLOR: blue
APP_MODE: prod
nginx.conf: |
server {
listen 80;
}| 블록으로 여러 줄짜리 설정 파일을 그대로 담을 수 있습니다.
Secret 만들기: 타입을 의식한다 #
Secret은 ConfigMap과 만드는 법이 거의 같지만, 타입이 추가됩니다. 시험과 운영에서 세 가지를 알아 두면 충분합니다.
generic (Opaque) #
가장 흔한 범용 타입입니다. ConfigMap과 같은 소스 옵션을 그대로 씁니다.
k create secret generic db-secret \
--from-literal=DB_USER=admin \
--from-literal=DB_PASS=s3cr3tdocker-registry #
비공개 레지스트리에서 이미지를 받을 때 쓰는 자격 증명입니다. Pod의 imagePullSecrets가 이 타입을 참조합니다.
k create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username=admin \
--docker-password=s3cr3t \
--docker-email=ops@example.comtls #
TLS 인증서와 키를 담습니다. Ingress의 TLS 종료(#19)에서 참조합니다.
k create secret tls web-tls \
--cert=tls.crt \
--key=tls.keyYAML로 정의할 때: data와 stringData #
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
data:
DB_USER: YWRtaW4= # base64로 인코딩된 값
stringData:
DB_PASS: s3cr3t # 평문으로 적으면 apiserver가 인코딩data에는 base64로 인코딩한 값을 넣어야 하고, stringData에는 평문을 넣으면 apiserver가 저장 시 인코딩합니다. 손으로 인코딩할 때는 다음을 씁니다.
echo -n 's3cr3t' | base64 # 인코딩
echo 'czNjcjN0' | base64 -d # 디코딩echo에 -n을 빼면 줄바꿈 문자까지 인코딩되어 값이 틀어집니다. 이 한 글자가 시험에서 자주 점수를 깎습니다.
base64는 암호화가 아니다 #
Secret을 다룰 때 운영자가 반드시 새겨야 할 한 가지입니다. base64는 인코딩이지 암호화가 아닙니다. 누구나 base64 -d로 즉시 평문으로 되돌립니다. 즉 Secret을 YAML로 보거나 etcd 덤프를 손에 넣은 사람은 비밀값을 그대로 읽습니다.
# Secret 값을 평문으로 보는 데 특별한 권한이 필요 없다
k get secret db-secret -o jsonpath='{.data.DB_PASS}' | base64 -d그렇다면 Secret이 ConfigMap보다 나은 점은 다음입니다.
- RBAC 분리. Secret 읽기 권한을 ConfigMap과 따로 걸어 비밀값 접근을 좁힐 수 있습니다(#9).
- 노출 면 축소. 일부 컴포넌트가 로그나 환경 덤프에 ConfigMap은 찍어도 Secret은 가리도록 설계됩니다.
- etcd 암호화의 대상. Secret만 골라 저장 시 암호화를 켤 수 있습니다.
etcd 암호화(encryption at rest) 한 단락 #
기본 클러스터에서 Secret은 etcd에 base64 상태로, 즉 사실상 평문으로 저장됩니다. 이를 막으려면 apiserver에 --encryption-provider-config로 EncryptionConfiguration을 걸어 **저장 시 암호화(encryption at rest)**를 켜야 합니다. 그러면 etcd 디스크를 탈취당해도 비밀값이 바로 읽히지 않습니다. 이 설정과 키 회전, 그리고 KMS provider는 control plane 보안 주제라 #24와 후속 CKS 트랙에서 따로 다루겠습니다. 여기서는 Secret이 기본적으로 암호화되지 않으며, 그것을 켜는 별도 설정이 있다는 사실만 잡아 두면 됩니다.
주입 1: 환경 변수로 #
만든 값을 컨테이너에 넣는 방식이 본론입니다. 첫 번째는 환경 변수입니다.
env valueFrom: 키 하나씩 #
spec:
containers:
- name: app
image: nginx
env:
- name: APP_COLOR
valueFrom:
configMapKeyRef:
name: app-config
key: APP_COLOR
- name: DB_PASS
valueFrom:
secretKeyRef:
name: db-secret
key: DB_PASS특정 키만 골라, 컨테이너 안에서 쓸 변수 이름을 따로 지정할 때 씁니다. ConfigMap은 configMapKeyRef, Secret은 secretKeyRef입니다.
envFrom: 통째로 #
spec:
containers:
- name: app
image: nginx
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: db-secret객체의 모든 키를 환경 변수로 한 번에 주입합니다. 이때 키 이름이 그대로 환경 변수명이 되므로, 키를 환경 변수로 쓸 이름에 맞춰 두는 것이 중요합니다. 앞에서 --from-env-file로 만든 ConfigMap이 envFrom과 잘 맞물리는 것도 이 때문입니다.
키 이름에 접두사를 붙이고 싶으면 prefix를 씁니다.
envFrom:
- configMapRef:
name: app-config
prefix: CONF_ # CONF_APP_COLOR 식이 됨주입 2: 볼륨으로 #
두 번째 방식은 볼륨 마운트입니다. 설정 파일 자체를 컨테이너 파일 시스템에 올려야 할 때 씁니다.
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: config-vol
mountPath: /etc/app
volumes:
- name: config-vol
configMap:
name: app-config이 경우 /etc/app/ 아래에 각 키가 파일로 생깁니다. APP_COLOR 키는 /etc/app/APP_COLOR 파일이 되고 그 내용이 값입니다. Secret도 같은 모양으로 secret: 볼륨을 씁니다.
subPath: 파일 하나만 #
볼륨을 그냥 마운트하면 mountPath 디렉터리의 기존 내용이 가려집니다. 디렉터리는 두고 파일 하나만 끼워 넣고 싶을 때 subPath를 씁니다.
volumeMounts:
- name: config-vol
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
volumes:
- name: config-vol
configMap:
name: nginx-config이렇게 하면 /etc/nginx/ 디렉터리는 그대로 두고 nginx.conf 파일만 ConfigMap의 값으로 덮습니다. 단, subPath에는 중요한 함정이 있습니다.
env와 volume의 자동 갱신 차이 #
운영에서 반드시 알아야 할 핵심입니다. ConfigMap이나 Secret의 내용을 나중에 바꿨을 때, 컨테이너에 반영되는 방식이 주입 방식마다 다릅니다.
| 주입 방식 | 원본 변경 시 |
|---|---|
| env / envFrom | 자동 반영 안 됨. Pod 재시작 필요 |
| volume mount | 잠시 뒤 파일이 자동 갱신됨 |
| volume + subPath | 자동 갱신 안 됨 |
환경 변수는 컨테이너가 시작할 때 한 번 주입되고 그 뒤로는 바뀌지 않습니다. 그래서 env로 주입한 설정을 바꾸려면 Pod를 다시 띄워야 합니다(#10의 kubectl rollout restart가 이때 쓰입니다).
볼륨 마운트는 kubelet이 주기적으로 동기화하므로 잠시 뒤 파일 내용이 바뀝니다. 다만 앱이 그 파일을 다시 읽어야 실제로 반영되니, 파일을 watch하지 않는 앱이라면 결국 재시작이 필요합니다. 그리고 subPath로 마운트한 파일은 이 자동 갱신에서 제외되므로, 설정을 자주 바꾸며 무중단 반영을 원한다면 subPath를 피하는 편이 맞습니다.
immutable: 성능과 안전을 함께 #
자주 바뀌지 않는 설정이라면 ConfigMap과 Secret을 immutable로 선언할 수 있습니다.
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_MODE: prod
immutable: trueimmutable: true로 두면 두 가지 효과가 있습니다.
- 안전. 실수로 운영 설정을 바꾸는 사고를 막습니다. 한번 immutable이면
data를 수정할 수 없고, 바꾸려면 객체를 지우고 다시 만들어야 합니다. - 성능. kubelet이 변경을 감시할 필요가 없어, 대규모 클러스터에서 apiserver 부하가 줄어듭니다. 객체 수가 많을수록 효과가 큽니다.
immutable로 만든 뒤에는 이름을 바꿔 새 객체로 배포하고 워크로드가 그 새 이름을 참조하도록 롤아웃하는 운영이 자연스럽습니다.
시험 포인트 #
- 세 가지 소스를 손에 익힙니다. 키 몇 개면
--from-literal, 파일 통째면--from-file, env 형식 파일이면--from-env-file입니다. - Secret 타입을 문제에 맞춰 고릅니다. 범용은
generic, 레지스트리 자격은docker-registry, 인증서는tls입니다. - base64 인코딩 시
echo -n을 빠뜨리지 않습니다.-n누락이 자주 점수를 깎습니다. - 주입 방식을 문제 문구로 구분합니다. “환경 변수로"면
env/envFrom, “파일로"면 volume mount, “특정 파일만 덮기"면 subPath입니다. - 설정을 바꾼 뒤 반영이 안 되면 env 주입은 Pod 재시작이 필요하다는 점을 떠올립니다.
- dry-run으로 YAML을 먼저 뽑아 손편집하는 흐름이 빈 매니페스트를 처음부터 쓰는 것보다 빠르고 정확합니다.
정리 #
이번 글에서 잡은 것:
- ConfigMap과 Secret은 설정을 워크로드에서 분리합니다. 같은 이미지를 환경마다 다르게 띄우는 토대입니다.
- 생성 소스는
--from-literal,--from-file,--from-env-file세 가지이며, env 형식 파일은 각 줄이 별도 키가 됩니다. - Secret 타입은
generic,docker-registry,tls를 기억합니다. base64는 암호화가 아니라 인코딩이며, 기본 etcd 저장은 사실상 평문입니다. 암호화하려면 별도로 encryption at rest를 켭니다(#24,CKS). - 주입은
env valueFrom(키 하나),envFrom(통째), volume mount(파일), subPath(파일 하나)로 나뉩니다. - env는 자동 갱신이 안 되어 재시작이 필요하고, volume mount는 자동 갱신되며, subPath는 갱신에서 제외됩니다.
immutable: true는 실수 방지와 대규모 클러스터의 성능을 함께 얻습니다.
다음: Scheduling 1 #
설정 주입까지 워크로드의 안쪽을 채웠습니다. 이제 그 워크로드를 어느 노드에 앉힐지를 결정하는 스케줄링으로 넘어갑니다.
#13 Scheduling 1에서는 nodeSelector로 라벨 기반 배치를 거는 기본부터, nodeAffinity로 더 유연한 노드 선택 규칙을 표현하는 법, 그리고 podAffinity/podAntiAffinity로 Pod끼리 모으거나 흩뜨리는 토폴로지 제어까지, scheduler가 어떤 순서로 노드를 추리는지 직접 매니페스트로 따라가며 정리하겠습니다.