ConfigMap과 Secret — 설정과 민감 정보를 코드와 분리하는 전략
데이터베이스 비밀번호를 코드에 하드코딩하면 안 된다는 건 알겠는데, Kubernetes에서는 어떻게 분리해서 관리할까요?
설정값과 민감 정보를 코드와 분리하는 것은 12-Factor App의 핵심 원칙 중 하나입니다. Kubernetes는 이를 위해 ConfigMap과 Secret이라는 두 가지 오브젝트를 제공합니다. 비슷해 보이지만 용도와 보안 수준이 다르고, 변경 시 반영 방식도 다릅니다.
ConfigMap — 일반 설정 관리
ConfigMap은 키-값 쌍으로 설정 데이터를 저장합니다.
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
# 단순 키-값
APP_ENV: production
LOG_LEVEL: info
MAX_CONNECTIONS: "100"
# 파일 형태의 설정
application.yml: |
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://db-service:3306/mydb
# 명령어로 생성
kubectl create configmap app-config \
--from-literal=APP_ENV=production \
--from-file=application.yml
환경변수로 주입
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: my-app:1.0
env:
# 개별 키 주입
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: app-config
key: APP_ENV
envFrom:
# 전체 키를 한꺼번에 환경변수로 주입
- configMapRef:
name: app-config
볼륨으로 마운트
spec:
containers:
- name: app
image: my-app:1.0
volumeMounts:
- name: config-volume
mountPath: /etc/config
readOnly: true
volumes:
- name: config-volume
configMap:
name: app-config
items: # 특정 키만 선택 가능
- key: application.yml
path: application.yml
마운트 후 /etc/config/application.yml 파일로 접근할 수 있습니다.
Secret — 민감 정보 관리
Secret은 비밀번호, API 키, 인증서 같은 민감한 데이터를 저장합니다.
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
# base64 인코딩된 값
username: YWRtaW4= # echo -n "admin" | base64
password: cEBzc3cwcmQ= # echo -n "p@ssw0rd" | base64
stringData를 사용하면 평문으로 작성할 수 있습니다.
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData:
username: admin
password: p@ssw0rd
Kubernetes가 자동으로 base64 인코딩하여 data 필드에 저장합니다. 주의할 점은 **base64는 암호화가 아닙니다 **. 누구나 디코딩할 수 있으므로 etcd 암호화나 External Secrets 같은 추가 보안이 필요합니다.
Secret 타입
| 타입 | 용도 |
|---|---|
Opaque | 일반 비밀 데이터 (기본값) |
kubernetes.io/tls | TLS 인증서와 키 |
kubernetes.io/dockerconfigjson | 컨테이너 레지스트리 인증 |
kubernetes.io/basic-auth | 기본 인증 (username/password) |
kubernetes.io/ssh-auth | SSH 키 |
# TLS Secret 예제
apiVersion: v1
kind: Secret
metadata:
name: tls-secret
type: kubernetes.io/tls
data:
tls.crt: <base64 인코딩된 인증서>
tls.key: <base64 인코딩된 키>
# 명령어로 TLS Secret 생성
kubectl create secret tls tls-secret \
--cert=path/to/cert.pem \
--key=path/to/key.pem
Secret을 Pod에 주입
spec:
containers:
- name: app
image: my-app:1.0
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
volumeMounts:
- name: secret-volume
mountPath: /etc/secrets
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: db-credentials
환경변수 vs 볼륨 마운트
두 방식에는 중요한 차이가 있습니다.
| 특성 | 환경변수 | 볼륨 마운트 |
|---|---|---|
| 업데이트 반영 | Pod 재시작 필요 | 자동 반영 (1분 내외) |
| 접근 방식 | process.env.KEY | 파일 읽기 |
| 보안 | kubectl exec env로 노출 가능 | 파일 권한으로 제어 가능 |
| 적합한 경우 | 단순 키-값 | 설정 파일, 인증서 |
공부하다 보니 이 차이를 놓쳐서 "설정을 바꿨는데 왜 반영이 안 되지?" 하는 경우가 꽤 있더라고요. 환경변수 방식은 Pod 재시작 전까지 이전 값이 유지됩니다.
immutable ConfigMap/Secret
apiVersion: v1
kind: ConfigMap
metadata:
name: static-config
immutable: true # 수정 불가
data:
VERSION: "1.0"
immutable: true를 설정하면 두 가지 이점이 있습니다.
- ** 실수 방지 **: 운영 중인 설정이 의도치 않게 변경되는 것을 막습니다
- ** 성능 최적화 **: kubelet이 watch를 멈추므로 API Server 부하가 줄어듭니다
수정이 필요하면 삭제 후 새로 생성해야 합니다.
변경 시 재시작 전략
ConfigMap이나 Secret을 수정했을 때 Pod에 반영하는 방법입니다.
방법 1: 어노테이션 변경으로 롤링 업데이트
# Deployment의 어노테이션을 변경하면 Pod이 재생성됨
kubectl patch deployment my-app -p \
'{"spec":{"template":{"metadata":{"annotations":{"config-hash":"20260319"}}}}}'
방법 2: ConfigMap 이름에 해시 포함 (Helm 패턴)
# values.yaml이 바뀌면 ConfigMap 이름도 바뀜
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-abc123 # 내용의 해시값
---
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
volumes:
- name: config
configMap:
name: app-config-abc123 # 이름이 바뀌므로 자동 업데이트
방법 3: Reloader 사용
Stakater Reloader를 설치하면 ConfigMap/Secret 변경을 자동으로 감지하여 관련 Deployment를 롤링 업데이트합니다.
metadata:
annotations:
reloader.stakater.com/auto: "true" # 자동 재시작 활성화
실무 팁
- **Secret은 환경변수보다 볼륨 마운트를 권장합니다 **: 환경변수는 로그에 노출될 위험이 있습니다
- **ConfigMap의 크기 제한은 1MB입니다 **: 큰 설정은 분할하세요
- ** 네임스페이스 간 공유 불가 **: 같은 네임스페이스의 Pod만 사용할 수 있습니다
- **optional 플래그 **: 참조하는 ConfigMap/Secret이 없어도 Pod이 시작되게 할 수 있습니다
env:
- name: OPTIONAL_CONFIG
valueFrom:
configMapKeyRef:
name: maybe-exists
key: some-key
optional: true # 없어도 Pod 시작 가능
주의할 점
1. ConfigMap을 수정해도 이미 실행 중인 Pod에 자동 반영되지 않을 수 있다
볼륨 마운트 방식은 kubelet이 주기적으로 업데이트하지만, 환경변수로 주입한 경우에는 Pod을 재시작하지 않는 한 구 값이 계속 사용됩니다. 설정 변경 후 "반영이 안 된다"는 장애의 대부분이 이 원인입니다. 환경변수 방식을 쓴다면 Deployment를 rollout restart해야 합니다.
2. Secret을 YAML에 평문으로 커밋하면 Git 히스토리에 영원히 남는다
Secret의 data 필드는 base64 인코딩일 뿐 암호화가 아닙니다. kubectl create secret으로 만든 YAML을 그대로 Git에 올리면 누구나 디코딩할 수 있습니다. Sealed Secrets이나 External Secrets Operator를 사용하거나, 최소한 Secret YAML은 .gitignore에 추가하세요.
3. ConfigMap의 1MB 제한을 넘기면 에러 메시지가 직관적이지 않다
대용량 설정 파일이나 인증서 번들을 하나의 ConfigMap에 넣으면 etcd의 1MB 제한에 걸립니다. 에러 메시지가 "etcd request too large"로 나와서 원인 파악이 어렵습니다. 큰 설정은 여러 ConfigMap으로 분할하거나, 별도 볼륨(PVC 등)을 사용하세요.
정리
ConfigMap은 일반 설정, Secret은 민감 정보를 코드와 분리하는 Kubernetes 오브젝트입니다. 환경변수와 볼륨 마운트 두 가지 주입 방식의 차이(특히 자동 업데이트 여부)를 이해하고, immutable 옵션과 재시작 전략을 적절히 활용하면 설정 관리가 훨씬 안정적이 됩니다. Secret의 base64 인코딩은 암호화가 아니라는 점은 꼭 기억해 두세요.