앞 글에서 Service, Ingress, NetworkPolicy로 Pod 간 통신과 외부 노출을 다뤘습니다.
Pod를 띄우고 네트워크도 연결했는데, 아직 한 가지가 빠져 있습니다. 앱에 설정값을 어떻게 넣어주느냐는 문제입니다.
이미지에 DB 호스트 주소를 하드코딩해두면, 환경이 바뀔 때마다 이미지를 다시 빌드해야 합니다. 패스워드 같은 민감 정보를 이미지에 박아놓는 건 보안 측면에서도 위험하고요.
이번 글에서는 설정과 민감 정보를 이미지에서 분리하는 두 가지 도구, ConfigMap과 Secret을 정리합니다.
ConfigMap — 설정을 이미지에서 분리하기
왜 필요한가
DB 호스트, 로그 레벨, 기능 플래그 같은 설정값을 이미지 안에 넣으면, 값 하나 바꿀 때마다 이미지를 다시 빌드해야 합니다.
개발·스테이징·프로덕션 환경마다 설정이 다른데, 환경별로 이미지를 따로 만드는 건 비효율적이죠.
ConfigMap은 설정 데이터를 키-값 쌍으로 저장하는 오브젝트입니다. 이미지는 하나로 유지하고, 환경별로 ConfigMap만 바꿔서 적용할 수 있습니다.
생성 방법
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
DB_HOST: "db.prod.example.com"
DB_PORT: "5432"
LOG_LEVEL: "info"
# 여러 줄 설정 파일도 값으로 저장 가능
app.conf: |
server.port=8080
server.timeout=30s
cache.enabled=true
아래와 같이 명령형으로도 만들 수 있습니다.
# 리터럴 값으로
kubectl create configmap app-config \\
--from-literal=DB_HOST=db.prod.example.com \\
--from-literal=DB_PORT=5432
# 파일에서 (파일명이 키, 내용이 값)
kubectl create configmap app-config --from-file=app.conf
# 디렉터리 전체 (디렉터리 안 모든 파일이 키로)
kubectl create configmap app-config --from-file=./configs/
⚠️ 주의: ConfigMap의 데이터 크기는 최대 1MiB입니다(etcd 객체 크기 제한). 큰 설정 파일이나 바이너리 데이터를 넣으려고 하면 Request entity too large 에러가 발생합니다. 1MiB가 넘는 데이터는 PV에 두거나 외부 스토리지를 써야 합니다.
Pod에 주입하기 — 환경 변수 vs 볼륨 마운트
주입 방식은 두 가지입니다. 단순한 키-값은 환경 변수로, 설정 파일은 볼륨 마운트로 넣는 게 일반적입니다.
환경 변수로 주입
spec:
containers:
- name: app
image: myapp:v1
env:
- name: DATABASE_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: DB_HOST
# 또는 전체를 한 번에
envFrom:
- configMapRef:
name: app-config
envFrom을 쓰면 ConfigMap의 모든 키가 환경 변수가 됩니다.
다만 키 이름이 환경 변수 규칙(영문자/숫자/언더스코어)에 맞지 않으면 그 키는 건너뛰고 경고만 남기니, ConfigMap 키는 처음부터 UPPER_SNAKE_CASE로 잡는 게 안전합니다.
볼륨 마운트로 주입
spec:
containers:
- name: app
image: myapp:v1
volumeMounts:
- name: config-volume
mountPath: /etc/config
readOnly: true
volumes:
- name: config-volume
configMap:
name: app-config
이렇게 하면 /etc/config/DB_HOST, /etc/config/app.conf 같은 파일이 생기고, 파일 내용이 ConfigMap의 값입니다.
두 방식의 중요한 차이가 하나 있습니다. 볼륨 마운트는 ConfigMap을 수정하면 보통 1분 이내, 최대 약 2분 이내에 자동 갱신됩니다(kubelet sync period + 캐시 TTL의 합).
환경 변수는 Pod를 재시작해야 반영되기 때문에, 자동 갱신이 필요하면 볼륨 마운트, 간단한 값이면 환경 변수를 쓰면 됩니다.
⚠️ 주의: subPath로 개별 파일만 마운트하면 자동 갱신이 안 됩니다. 그리고 볼륨 마운트 시 해당 디렉터리의 기존 파일이 전부 사라지니, 기존 파일을 유지하려면 subPath를 써야 합니다. 자동 갱신과 기존 파일 보존은 트레이드오프인 셈입니다.
Immutable ConfigMap — 대규모 클러스터의 성능 개선
처음엔 "ConfigMap을 수정 못 하게 막는 게 무슨 의미가 있지?" 싶었는데, 알고 보니 성능 최적화 목적이 컸습니다.
kubelet은 ConfigMap을 사용하는 모든 Pod에 대해 변경을 감시(watch) 하는데, 클러스터에 수만 개의 ConfigMap이 있으면 이 watch 자체가 부담입니다.
immutable: true로 설정하면 kubelet이 해당 ConfigMap의 watch를 건너뛰어 API 서버 부하와 메모리 사용량이 줄어듭니다.
v1.21에서 GA됐고, 변경할 일이 없는 설정에 권장됩니다.
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-v1
immutable: true # 한 번 만들면 수정 불가
data:
DB_HOST: "db.prod.example.com"
값을 바꿔야 하면 새 ConfigMap을 만들고(app-config-v2) Deployment를 새 이름으로 가리키면 됩니다.
이름에 버전을 붙이는 패턴이 자연스럽게 따라옵니다.
Secret — 민감 정보 관리
ConfigMap과 뭐가 다른가
ConfigMap은 일반 설정, Secret은 패스워드·토큰·인증서 같은 민감 정보용입니다.
Kubernetes가 Secret을 좀 더 조심스럽게 다룹니다. kubectl get에서 값이 마스킹되고, 볼륨 마운트 시 tmpfs(메모리)에만 저장되며, RBAC으로 별도 접근 제어를 걸 수 있습니다.
한 가지 분명히 해둘 게 있습니다. Secret의 Base64 인코딩은 kubectl get secret -o yaml로 누구나 디코딩할 수 있기 때문에, 암호화가 아닙니다.
실질적 보호를 위해서는 etcd 암호화(EncryptionConfiguration)를 활성화하거나, HashiCorp Vault 같은 외부 도구와 연동해야 합니다.
Secret의 종류
Secret에는 여러 type이 있고, 각각 용도가 다릅니다.
| type | 용도 |
| Opaque | 임의의 키-값. 가장 일반적 |
| kubernetes.io/tls | TLS 인증서 (tls.crt, tls.key 키 필수) |
| kubernetes.io/dockerconfigjson | 프라이빗 컨테이너 레지스트리 인증 |
| kubernetes.io/service-account-token | ServiceAccount 토큰 (자동 생성) |
| kubernetes.io/ssh-auth | SSH 인증 (ssh-privatekey 키 필수) |
| kubernetes.io/basic-auth | 기본 인증 (username, password 키) |
type을 명시하면 Kubernetes가 필요한 키가 있는지 검증해줍니다.
예를 들어 kubernetes.io/tls 타입인데 tls.key가 빠지면 생성 시점에 에러가 나죠.
생성 방법
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData: # 평문으로 작성. Kubernetes가 자동으로 Base64 인코딩
username: admin
password: "p@ssw0rd"
stringData는 편의용 필드입니다. data 필드를 쓰면 직접 Base64 인코딩해야 하는데, stringData는 평문으로 적으면 자동 변환해줍니다.
# 명령형 생성
kubectl create secret generic db-credentials \\
--from-literal=username=admin \\
--from-literal=password='p@ssw0rd'
# TLS Secret
kubectl create secret tls example-tls --cert=tls.crt --key=tls.key
# Docker 레지스트리 인증
kubectl create secret docker-registry regcred \\
--docker-server=registry.example.com \\
--docker-username=user \\
--docker-password=pass
프라이빗 레지스트리에서 이미지를 pull할 때는 Pod 스펙에 imagePullSecrets로 위에서 만든 Secret을 지정합니다.
spec:
imagePullSecrets:
- name: regcred
containers:
- name: app
image: registry.example.com/private/myapp:v1
Pod에 주입하기
ConfigMap과 거의 동일하게 사용할 수 있습니다.
spec:
containers:
- name: app
image: myapp:v1
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
defaultMode: 0400 # 소유자만 읽기
⚠️ 주의: 환경 변수로 주입하면 /proc/<pid>/environ이나 env 명령으로 다른 프로세스가 읽을 수 있기 때문에, 보안이 중요한 값은 볼륨 마운트가 더 안전합니다.
⚠️ 주의: Base64는 쉽게 디코딩되기 때문에 Secret을 Git에 커밋하지 않도록 주의가 필요합니다. GitOps 워크플로에서는 Sealed Secrets, SOPS, External Secrets Operator 같은 도구로 암호화해서 관리합니다.
# Secret 값 디코딩해서 확인
kubectl get secret db-credentials -o jsonpath='{.data.password}' | base64 -d
Secret도 ConfigMap과 마찬가지로 immutable: true를 지원하고(v1.21 GA), 자주 변경하지 않는 인증서 같은 경우 immutable로 두면 성능에 도움이 됩니다.
ConfigMap vs Secret — 언제 무엇을 쓰는가
구분 ConfigMap Secret
| 구분 | ConfigMap | Secret |
| 용도 | 일반 설정값 | 패스워드, 토큰, 인증서 |
| 저장 형태 | 평문 | Base64 인코딩 (암호화 아님) |
| 볼륨 마운트 위치 | 일반 파일 시스템 | tmpfs (메모리) |
| kubectl get 출력 | 값 노출 | 값 마스킹 |
| 크기 제한 | 1MiB | 1MiB |
| Immutable 지원 | ✅ (v1.21 GA) | ✅ (v1.21 GA) |
핵심 판단 기준은 단순합니다. 노출돼도 괜찮은 값은 ConfigMap, 노출되면 안 되는 값은 Secret입니다.
트러블슈팅
ConfigMap/Secret을 쓰다가 자주 만나는 에러들입니다.
CreateContainerConfigError
kubectl describe pod <pod-name>
# Events에 "configmap "app-config" not found" 같은 메시지
ConfigMap이나 Secret이 존재하지 않을 때 발생합니다. ConfigMap은 Pod보다 먼저 만들어야 합니다.
Namespace가 다른 경우에도 같은 에러가 나니, 같은 Namespace에 있는지 확인합니다.
값이 바뀌었는데 반영 안 됨
다음 세 가지 중 하나를 의심해볼 수 있습니다.
- 환경 변수로 주입했다 — Pod 재시작 필요 (kubectl rollout restart deployment/<name>)
- subPath로 마운트했다 — 자동 갱신 안 됨. Pod 재시작 필요
- immutable: true로 설정됐다 — 아예 수정 불가. 새 ConfigMap을 만들어야 함
envFrom의 키 누락
ConfigMap에는 분명히 있는데 환경 변수로 안 들어오는 경우, 키 이름이 환경 변수 규칙을 어겼을 가능성이 큽니다.
예를 들어 1.config 같은 키는 환경 변수로 변환되지 않습니다. kubectl describe pod의 Events에서 경고 메시지를 확인할 수 있습니다.
정리
ConfigMap으로 일반 설정, Secret으로 민감 정보를 이미지에서 분리합니다.
주입 방식은 환경 변수(재시작 필요)와 볼륨 마운트(자동 갱신) 두 가지가 있고, 용도에 맞게 선택합니다.
Secret의 Base64는 암호화가 아니라는 점, 자주 변경하지 않는 값은 immutable: true로 성능을 개선할 수 있다는 점, GitOps에서는 외부 암호화 도구가 필요하다는 점을 기억해두는게 좋을것 같습니다.
다음 글에서는 Pod의 컴퓨팅 자원을 어떻게 관리하는지 - requests, limits, QoS 클래스, LimitRange, ResourceQuota를 다루겠습니다.
'DevOps > kubernetes' 카테고리의 다른 글
| 5. Service와 Ingress - 네트워킹 정리 (0) | 2026.06.06 |
|---|---|
| 4. StatefulSet 부터 CronJob 까지 (0) | 2026.05.30 |
| 3. Pod에서 Deployment 까지 (0) | 2026.05.16 |
| 2. Kubernetes 클러스터 아키텍처 (0) | 2026.05.09 |
| 1. 쿠버네티스란 무엇일까? (0) | 2026.04.25 |