infra
Platform

모듈 맵

[Kubernetes] ConfigMap과 Secret을 이용한 코드와 환경 설정 분리

0 / 29 완료

펼치기
0 / 29 완료0%

쿠버네티스 & GitOps · 08 / 29

[Kubernetes] ConfigMap과 Secret을 이용한 코드와 환경 설정 분리

환경변수·볼륨 두 가지 방식으로 ConfigMap과 Secret을 파드에 주입하고, 보안 주의사항을 실습합니다

🚨INCIDENT ALERT
HIGH

운영 DB 비밀번호를 교체했는데 애플리케이션 파드는 여전히 예전 값으로 접속하고 있습니다. ConfigMap과 Secret이 파드에 환경변수 또는 볼륨으로 주입되는 방식을 모르면 재시작 필요 여부를 놓치게 됩니다. 설정과 민감정보를 안전하게 분리하는 습관이 운영 사고를 줄입니다.

ConfigMap과 Secret — 설정과 민감 정보 분리

배포 자동화를 처음 구성할 때 가장 많이 저지르는 실수가 있습니다. 데이터베이스 비밀번호, API 키, 접속 URL을 Docker 이미지 안에 하드코딩하거나 Deployment YAML에 직접 씁니다. 이렇게 하면 개발/스테이징/운영 환경마다 다른 이미지를 빌드해야 하고, 비밀번호가 Git 이력에 남아 보안 감사에서 지적을 받습니다. Kubernetes는 이 문제를 ConfigMap과 Secret으로 해결합니다. 설정은 코드와 분리하고, 동일한 이미지를 환경별로 다른 설정으로 실행합니다. 비밀번호는 Secret으로 격리하고 RBAC으로 접근을 제어합니다. 이 패턴이 12-Factor App의 세 번째 원칙(Config)이며, 쿠버네티스 기반 운영 환경의 표준 방식입니다.


이번 챕터에서 배울 것

ConfigMap과 Secret으로 설정과 민감 정보를 이미지에서 분리하고, 두 가지 주입 방식의 차이를 실습합니다.

  • 1ConfigMap으로 일반 설정 데이터를 환경변수와 볼륨에 주입할 수 있다
  • 2Secret의 Base64 인코딩과 실제 보안 메커니즘을 설명할 수 있다
  • 3환경변수 방식과 볼륨 마운트 방식을 비교해 선택할 수 있다
  • 4Secret 변경 후 파드에 반영되는 타이밍을 파악할 수 있다
  • 5etcd 암호화와 RBAC 등 실무 보안 주의사항을 적용할 수 있다
실습 환경 준비

기본적인 kubectl 사용법(kubectl-basics)과 Deployment 개념(deployment-basics)을 익혔다면 바로 시작할 수 있습니다.

클러스터 접근 확인
kubectl cluster-info
실습 네임스페이스 생성
kubectl create namespace config-lab
현재 네임스페이스 설정 (선택)
kubectl config set-context --current --namespace=config-lab
💡개념

ConfigMap — 환경변수와 볼륨 마운트 두 방식

개발 환경과 운영 환경의 데이터베이스 접속 URL이 다를 때, 이미지를 환경마다 따로 빌드하면 배포 실수가 생기고 이미지 관리가 복잡해집니다. Docker 이미지에 설정값을 하드코딩하면 "코드는 맞는데 설정이 달라서 장애"가 생기고 어느 환경에 어떤 값이 들어갔는지 추적이 어렵습니다. ConfigMap은 이 설정 데이터를 이미지 밖으로 꺼내 Kubernetes 오브젝트로 독립 관리하는 방법입니다. 주입 방식이 환경변수냐 볼륨 마운트냐에 따라 변경 반영 시점이 달라지기 때문에, 두 방식의 차이를 알아야 설정 변경 후 재시작 없이 반영됐다고 잘못 판단하는 실수를 막을 수 있습니다.

ConfigMap 주입 방식 비교 — 환경변수 주입(envFrom/env, 파드 시작 시 고정·재시작해야 반영)과 볼륨 마운트(volumes/volumeMounts, 파일로 마운트·kubelet이 자동 갱신) 두 가지. 환경변수는 간단한 key-value에, 볼륨은 nginx.conf 같은 설정 파일에 적합. Secret 환경변수는 ps로 노출 위험확대

ConfigMap 생성

Kubernetes
# 명령어로 직접 생성
kubectl create configmap app-config \
  --from-literal=APP_ENV=production \
  --from-literal=APP_PORT=8080 \
  --from-literal=LOG_LEVEL=info \
  -n config-lab

# 파일에서 생성 (설정 파일을 통째로 ConfigMap에 저장)
kubectl create configmap nginx-config \
  --from-file=nginx.conf \
  -n config-lab

# ConfigMap 확인
kubectl get configmap app-config -n config-lab -o yaml
YAML
# configmap.yaml — YAML로 직접 정의
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: config-lab
data:
  APP_ENV: "production"
  APP_PORT: "8080"
  LOG_LEVEL: "info"
  # 여러 줄 설정 파일도 저장 가능
  app.properties: |
    server.port=8080
    spring.datasource.url=jdbc:postgresql://postgres:5432/mydb
    logging.level.root=INFO
1ConfigMap을 환경변수로 주입하고 파드 안에서 확인

ConfigMap의 값을 컨테이너 환경변수로 넣는 두 방법(envFrom 전체 주입 / configMapKeyRef 선택 주입)을 deployment에 작성하고, 적용 후 파드 안에서 env로 실제 주입됐는지 확인합니다.

YAML
# deployment-env.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: config-lab
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: app
          image: busybox
          command: ["sh", "-c", "env && sleep 3600"]
          # 방법 A: ConfigMap의 모든 key-value를 환경변수로 주입
          envFrom:
            - configMapRef:
                name: app-config

          # 방법 B: 특정 키만 선택하여 다른 이름으로 주입
          env:
            - name: PORT                      # 컨테이너 환경변수 이름
              valueFrom:
                configMapKeyRef:
                  name: app-config
                  key: APP_PORT              # ConfigMap의 key
Kubernetes
kubectl apply -f deployment-env.yaml

# 파드에서 환경변수 확인
kubectl exec -it $(kubectl get pod -n config-lab -l app=my-app -o name) \
  -n config-lab -- env | grep -E "APP_ENV|APP_PORT|LOG_LEVEL"
# APP_ENV=production
# APP_PORT=8080
# LOG_LEVEL=info
kubectl apply -f deployment-env.yaml
🔍실행 후 확인할 것
  • env 출력에 APP_ENV·APP_PORT·LOG_LEVEL이 모두 보이는지 본다 — 안 보이면 ConfigMap 이름 오타이거나 파드가 ConfigMap 생성 전에 떠서 반영 안 된 것(파드 재생성 필요)
  • envFrom은 ConfigMap의 키 이름 그대로(APP_PORT), env+configMapKeyRef는 지정한 이름(PORT)으로 들어온다 — 둘의 키 이름 차이를 확인
  • 환경변수 주입은 파드 시작 시점에 고정된다 — ConfigMap을 수정해도 기존 파드의 env는 안 바뀐다(볼륨 마운트와의 핵심 차이). 갱신하려면 롤아웃 재시작이 필요
  • 값이 비어 들어오면 ConfigMap의 해당 key가 실제로 있는지 kubectl get configmap app-config -o yaml로 교차 확인

방식 2: 볼륨으로 마운트 (자동 갱신 가능)

YAML
# deployment-volume.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-vol
  namespace: config-lab
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app-vol
  template:
    metadata:
      labels:
        app: my-app-vol
    spec:
      volumes:
        - name: config-volume
          configMap:
            name: app-config    # ConfigMap을 볼륨으로 변환
      containers:
        - name: app
          image: busybox
          command: ["sh", "-c", "ls /etc/config && cat /etc/config/APP_ENV && sleep 3600"]
          volumeMounts:
            - name: config-volume
              mountPath: /etc/config    # 이 경로에 ConfigMap key가 파일로 생성됨
Kubernetes
kubectl apply -f deployment-volume.yaml

# 마운트된 파일 확인
kubectl exec -it $(kubectl get pod -n config-lab -l app=my-app-vol -o name) \
  -n config-lab -- ls /etc/config
# APP_ENV   APP_PORT   LOG_LEVEL   app.properties
# ↑ 각 key가 파일로 생성됨

kubectl exec -it $(kubectl get pod -n config-lab -l app=my-app-vol -o name) \
  -n config-lab -- cat /etc/config/APP_ENV
# production

두 방식 비교

항목환경변수 (envFrom)볼륨 마운트
설정 변경 반영파드 재시작 필요kubelet이 자동 갱신 (~1-2분)
대용량 파일어려움가능 (nginx.conf 등)
프로세스 접근os.getenv()파일 읽기 (open())
민감도프로세스 메모리에 노출파일로 격리
적합한 용도짧은 key-value 설정설정 파일, 자주 갱신되는 설정

💡개념

Secret — Base64 인코딩과 실제 보안 메커니즘

보안 감사에서 "Git 이력에 DB 비밀번호가 평문으로 남아있습니다"라는 지적을 받는 경우, 대부분 Deployment YAML에 직접 환경변수를 넣었거나 ConfigMap에 민감값을 저장했기 때문입니다. Secret은 ConfigMap과 구조는 비슷하지만 RBAC으로 접근 제어를 분리하고, etcd 암호화 at rest로 저장 시 보호할 수 있는 전용 오브젝트입니다. Base64가 "암호화"처럼 보이지만 누구나 디코딩할 수 있다는 사실을 모르면 잘못된 보안 의식이 생깁니다. Secret의 실제 보안은 RBAC과 etcd 암호화에서 나오며, 이 두 가지가 갖춰지지 않으면 Secret이라도 안전하지 않습니다.

ConfigMap vs Secret — 둘 다 설정 주입용이나 ConfigMap은 평문(APP_ENV·nginx.conf 등 비민감), Secret은 Base64 인코딩(암호화 아님!)으로 DB 비밀번호·API 키·TLS 인증서 등 민감 데이터. Secret의 실제 보안은 RBAC 권한 분리·etcd 암호화(at rest)에서 나오며, Base64는 누구나 디코딩 가능하므로 Git 커밋 금지확대

Secret 생성 — Base64 이해

로컬 터미널
# Base64 인코딩 (암호화 아님, 인코딩임)
echo -n 'mypassword123' | base64
# bXlwYXNzd29yZDEyMw==

# Base64 디코딩 (누구나 가능)
echo 'bXlwYXNzd29yZDEyMw==' | base64 -d
# mypassword123
YAML
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
  namespace: config-lab
type: Opaque
data:
  # 값은 반드시 Base64 인코딩된 문자열
  DB_PASSWORD: bXlwYXNzd29yZDEyMw==     # 'mypassword123'
  DB_USER: YWRtaW4=                      # 'admin'
  # stringData를 쓰면 평문으로 작성 가능 (kubectl이 자동으로 인코딩)
stringData:
  DB_HOST: "postgres-service:5432"       # 평문 작성 가능
Kubernetes
# 명령어로 Secret 생성 (더 안전한 방법 — 터미널 이력에 평문이 남지 않음)
kubectl create secret generic db-secret \
  --from-literal=DB_PASSWORD=mypassword123 \
  --from-literal=DB_USER=admin \
  -n config-lab

# Secret 확인 (값은 Base64로 표시됨)
kubectl get secret db-secret -n config-lab -o yaml
# data:
#   DB_PASSWORD: bXlwYXNzd29yZDEyMw==
#   DB_USER: YWRtaW4=

# 값 디코딩 확인
kubectl get secret db-secret -n config-lab \
  -o jsonpath='{.data.DB_PASSWORD}' | base64 -d
# mypassword123

Secret을 파드에 주입

환경변수와 볼륨 방식 모두 ConfigMap과 동일합니다. 단 볼륨 마운트 시 파일 권한을 0400(소유자만 읽기)으로 제한하는 것이 보안 베스트 프랙티스입니다.

YAML
# deployment-secret.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: db-app
  namespace: config-lab
spec:
  replicas: 1
  selector:
    matchLabels:
      app: db-app
  template:
    metadata:
      labels:
        app: db-app
    spec:
      volumes:
        - name: secret-volume
          secret:
            secretName: db-secret
            defaultMode: 0400           # 소유자만 읽기 가능 (보안 강화)
      containers:
        - name: app
          image: busybox
          command: ["sh", "-c", "sleep 3600"]
          # 환경변수 방식
          envFrom:
            - secretRef:
                name: db-secret
          # 볼륨 방식 (환경변수와 동시 사용 가능)
          volumeMounts:
            - name: secret-volume
              mountPath: /etc/secrets
              readOnly: true            # 컨테이너가 Secret 파일 수정 불가
Kubernetes
kubectl apply -f deployment-secret.yaml

# Secret이 환경변수로 주입됐는지 확인
kubectl exec -it $(kubectl get pod -n config-lab -l app=db-app -o name) \
  -n config-lab -- env | grep DB
# DB_PASSWORD=mypassword123
# DB_USER=admin
# DB_HOST=postgres-service:5432

# 볼륨으로 마운트된 Secret 파일 확인
kubectl exec -it $(kubectl get pod -n config-lab -l app=db-app -o name) \
  -n config-lab -- ls -la /etc/secrets
# -r--------    1 root     root            13 May 16 05:00 DB_PASSWORD
# -r--------    1 root     root             5 May 16 05:00 DB_USER

실제 보안 주의사항

로컬 터미널
# ❌ 금지: Secret을 Git에 커밋하지 말 것
git add secret.yaml && git commit -m "add db secret"
# → 이력에 평문(또는 Base64) 값이 영원히 남음

# ✅ 권장: .gitignore에 추가
echo "*-secret.yaml" >> .gitignore
echo "*.secret.yaml" >> .gitignore

# ✅ 권장: kubectl create secret으로 터미널에서 직접 생성
# 또는 HashiCorp Vault, AWS Secrets Manager + external-secrets-operator 사용

# etcd 암호화 at rest 설정 확인 (클러스터 관리자용)
kubectl get encryptionconfigurations -A 2>/dev/null || \
  echo "EncryptionConfiguration 확인: 클러스터 설정 필요"
💡개념

ConfigMap·Secret 값이 컨테이너 안까지 들어가는 법 — 5단계

kubectl apply로 ConfigMap이나 Secret을 만들었다고 값이 바로 앱에 들어가는 것은 아닙니다. 값은 etcd에 저장됐다가, 파드가 그것을 참조하고, kubelet이 읽어와, 환경변수로 주입되거나 파일로 마운트된 다음, 마지막으로 앱이 읽어야 비로소 쓰입니다. 이 경로를 알면 "설정을 바꿨는데 왜 파드는 옛 값이지"의 답이 어느 단계에 있는지 — 재시작이 필요한 env 방식(④a)인지, 자동 갱신되는 volume 방식(④b)인지, 아니면 앱이 파일을 다시 안 읽는 것(⑤)인지 — 를 정확히 짚을 수 있습니다.

TEXT
kubectl apply -f configmap.yaml / secret.yaml
   │
   ① 리소스 생성 → etcd 저장        (ConfigMap=평문 · Secret=Base64, 암호화 아님)
   │
   ② 파드 스펙이 참조                (env/envFrom · 또는 volumes + volumeMounts)
   │
   ③ kubelet이 값 조회               (API 서버에서 해당 ConfigMap/Secret 읽음)
   │
   ├─④a env 방식: 시작 시 환경변수 주입   (프로세스에 고정 → 이후 변경 반영 안 됨)
   ├─④b volume 방식: 파일로 마운트       (..data 심링크 → 값 바뀌면 파일 자동 갱신)
   │
   ⑤ 앱이 읽음                        (os.getenv() · 또는 파일 open())
   ▼
env=재시작해야 갱신 · volume=지연 후 자동 갱신(단, 앱이 다시 읽어야 실제 적용)

각 단계에서 무슨 일이 일어나고, 막히면 어떤 증상인가:

단계하는 일여기서 막히면
① 생성·저장ConfigMap/Secret이 etcd에 저장. Secret은 Base64 인코딩일 뿐 암호화 아님잘못된 네임스페이스에 생성 → 파드가 못 찾음
② 참조파드 스펙이 configMapRef·secretRef(env) 또는 volumes(파일)로 가리킴이름 오타·미존재 → 파드가 CreateContainerConfigError로 안 뜸
③ 조회kubelet이 파드 기동 시 API 서버에서 값을 읽어 옴파드가 리소스 생성 전에 이미 떠 있었다면 옛 상태 그대로(재생성 필요)
④a env 주입시작 시 환경변수로 넣고 프로세스에 고정ConfigMap/Secret을 바꿔도 기존 파드는 옛 값 → rollout restart 필요
④b volume 마운트..data 심링크로 파일 제공, 값이 바뀌면 원자적 교체로 자동 갱신subPath로 단일 파일만 마운트하면 갱신 대상에서 빠져 옛 값 고정
⑤ 앱이 읽음앱이 os.getenv()로 환경변수를, 또는 파일을 읽어 실제 사용파일은 새 값인데 앱이 다시 안 읽으면 프로세스 안 값은 그대로(리로드 필요)

정리하면 "변경이 반영 안 됨"의 원인은 거의 항상 ④와 ⑤에 있습니다. env 방식(④a)은 원래 재시작해야 바뀌는 것이 정상이고, volume 방식인데도 안 바뀌면 ④b의 subPath를 의심하며, 파일까지 새 값인데 동작이 옛날 그대로면 ⑤(앱이 파일을 다시 읽지 않음)입니다. 반대로 파드가 아예 안 뜨면 ②(참조 이름 오타·미존재)를 먼저 봅니다.

ConfigMap vs Secret vs 환경변수 직접 주입 선택 기준
앱 설정값 (로그 레벨, 기능 플래그, 외부 API URL)처럼 비민감 구성 데이터ConfigMap평문 저장, kubectl describe로 쉽게 확인 가능, 볼륨 마운트 또는 환경변수로 주입
DB 비밀번호, API 키, TLS 인증서처럼 민감 정보SecretBase64 인코딩 저장, etcd 암호화 권장, RBAC으로 접근 제한, 평문 노출 주의
파드 YAML에 직접 env.value로 고정값을 넣어도 되는 단순 비민감 값env 직접 주입 (하드코딩)간단하지만 환경마다 YAML을 수정해야 함, 시크릿 포함 금지
설정 파일 전체를 파드 내 파일로 주입해야 하는 경우 (nginx.conf, app.yaml 등)ConfigMap 볼륨 마운트키를 파일명으로, 값을 파일 내용으로 마운트, 변경 시 자동 반영(환경변수와 달리)
민감 정보를 AWS Secrets Manager, Vault 같은 외부 비밀 관리 시스템에서 가져오는 경우External Secrets Operator 또는 CSI Secret StoreSecret 직접 생성 없이 외부 저장소와 동기화, 자동 갱신 및 감사 로그 지원

문제 상황

Kubernetes
# Secret 값을 업데이트
kubectl create secret generic db-secret \
  --from-literal=DB_PASSWORD=newpassword456 \
  -n config-lab \
  --dry-run=client -o yaml | kubectl apply -f -

# Secret 확인 — 변경됨
kubectl get secret db-secret -n config-lab \
  -o jsonpath='{.data.DB_PASSWORD}' | base64 -d
# newpassword456   ← 새 값으로 변경됨

# 파드에서 확인 — 아직 이전 값!
kubectl exec -it $(kubectl get pod -n config-lab -l app=db-app -o name) \
  -n config-lab -- env | grep DB_PASSWORD
# DB_PASSWORD=mypassword123   ← 여전히 이전 값

원인 분석

환경변수는 파드 시작 시 프로세스에 주입된 후 고정됩니다. 부모 프로세스 환경변수가 바뀌지 않듯, 실행 중인 파드의 환경변수는 Secret이 바뀌어도 자동으로 갱신되지 않습니다.

Secret 업데이트
    ↓
kubelet이 감지 (약 수십 초)
    ↓
볼륨 마운트 방식: 파일 자동 갱신 ✅
환경변수 방식:   파드 재시작 필요 ❌

해결: 파드 재시작

Kubernetes
# Deployment의 모든 파드를 롤링 재시작
kubectl rollout restart deployment/db-app -n config-lab

# 재시작 진행 상태 확인
kubectl rollout status deployment/db-app -n config-lab
# Waiting for deployment "db-app" rollout to finish: 1 old replicas are pending termination...
# deployment "db-app" successfully rolled out

# 새 파드에서 Secret 값 확인
kubectl exec -it $(kubectl get pod -n config-lab -l app=db-app -o name) \
  -n config-lab -- env | grep DB_PASSWORD
# DB_PASSWORD=newpassword456   ← 새 값 반영됨!

볼륨 마운트는 자동 갱신 확인

Kubernetes
# 볼륨 방식은 파드 재시작 없이 자동 반영 (1~2분 대기)
kubectl exec -it $(kubectl get pod -n config-lab -l app=db-app -o name) \
  -n config-lab -- cat /etc/secrets/DB_PASSWORD
# newpassword456   ← kubelet이 자동으로 갱신

# 갱신 시간 확인 (파일 수정 시간이 변경됨)
kubectl exec -it $(kubectl get pod -n config-lab -l app=db-app -o name) \
  -n config-lab -- ls -la /etc/secrets/

예방 패턴: Secret 변경 시 자동 재시작 트리거

YAML
# Deployment의 annotation에 Secret 체크섬을 추가하는 패턴
# Helm 차트에서 많이 사용
spec:
  template:
    metadata:
      annotations:
        # Secret 내용의 해시값 — Secret이 바뀌면 이 값도 바뀌어 파드 재시작됨
        checksum/secret: "{{ include (print $.Template.BasePath \"/secret.yaml\") . | sha256sum }}"

심화 — 볼륨 자동 갱신의 예외

💡개념

심화: subPath로 마운트하면 자동 갱신이 깨진다

볼륨으로 마운트하면 ConfigMap/Secret이 자동 갱신된다는 것까지는 배웠습니다. 그런데 자동 갱신을 믿고 배포했는데 파일이 옛 값 그대로인 사고가 실무에서 반복됩니다. 그 대부분은 subPath 때문입니다.

  • 자동 갱신의 원리: kubelet은 ConfigMap/Secret 볼륨을 마운트할 때 실제 데이터를 ..data 같은 타임스탬프 디렉터리에 쓰고, 마운트 경로에는 그 디렉터리를 가리키는 심볼릭 링크를 둡니다. 값이 바뀌면 새 타임스탬프 디렉터리를 만든 뒤 심링크를 원자적으로 교체해 파일이 통째로 갱신됩니다.
  • subPath의 함정: volumeMountsubPath를 지정하면 kubelet은 마운트 시점의 그 파일 하나만 컨테이너에 바인드 마운트합니다. 심링크 교체의 대상이 아니므로, ConfigMap이 바뀌어도 그 파일은 파드가 재생성될 때까지 옛 값 그대로입니다. 볼륨은 자동 갱신된다는 규칙이 subPath에서는 깨집니다.
  • 타이밍도 알아야 합니다: 자동 갱신은 즉시가 아니라 kubelet sync 주기 + 캐시 전파만큼(합쳐 최대 1분 안팎) 늦습니다. 게다가 파일이 갱신돼도 앱이 그 파일을 다시 읽지 않으면 프로세스 안의 값은 그대로입니다 — inotify로 리로드하거나 롤아웃이 필요합니다.

정리하면, 자동 갱신이 필요한 설정은 subPath 없이 디렉터리째 마운트하고, 특정 파일만 넣어야 하면 subPath 대신 볼륨의 items로 필요한 키만 파일로 노출합니다. subPath가 불가피하면 값 변경 시 롤아웃으로 파드를 새로 띄웁니다.

상황: nginx.conf를 ConfigMap 볼륨으로 마운트하고 값을 수정한 뒤 kubelet의 자동 갱신을 기대했습니다. 그런데 5분, 10분이 지나도 컨테이너 안 /etc/nginx/nginx.conf는 옛 내용 그대로입니다. 환경변수 주입 문제도 아니고 분명 볼륨 마운트인데 바뀌지 않습니다.

원인: volumeMountsubPath: nginx.conf를 써서 디렉터리가 아니라 단일 파일만 바인드 마운트했습니다. subPath로 마운트한 파일은 kubelet의 심링크 원자 교체 대상에서 빠지므로 ConfigMap이 바뀌어도 갱신되지 않습니다. /etc/nginx에 다른 파일이 이미 있어 디렉터리째 덮으면 곤란해서 subPath를 쓴 것이 원인이었습니다.

진단: 파드 spec에서 volumeMountssubPath가 설정돼 있는지 확인합니다. subPath가 있으면 자동 갱신은 기대할 수 없습니다. 컨테이너 안에서 ls -la로 마운트 경로를 보면, 자동 갱신되는 마운트는 ..data를 가리키는 심링크 구조가 보이지만 subPath는 평범한 일반 파일만 있어 바로 구분됩니다.

해결: 자동 갱신이 필요하면 subPath를 빼고 디렉터리째 마운트하거나, 특정 파일만 넣어야 하면 볼륨의 items로 필요한 키만 파일로 노출합니다(이 방식은 갱신됩니다). subPath가 불가피하면 ConfigMap을 새 이름/버전으로 만들고 Deployment를 롤아웃해 파드를 새로 뜨게 합니다(checksum annotation 패턴).


💼
실무 맥락
현업 패턴

환경별 설정 분리 패턴

실무에서 개발/스테이징/프로덕션 환경마다 ConfigMap 이름을 동일하게 유지하고 내용만 다르게 관리합니다.

Kubernetes
# 각 환경의 ConfigMap — 이름은 동일, 네임스페이스로 구분
# 개발 환경
kubectl create configmap app-config \
  --from-literal=APP_ENV=development \
  --from-literal=LOG_LEVEL=debug \
  -n development

# 프로덕션 환경
kubectl create configmap app-config \
  --from-literal=APP_ENV=production \
  --from-literal=LOG_LEVEL=warn \
  -n production

# Deployment YAML은 동일하게 사용 — namespace만 다름

Secret 관리 도구 연동 (실무 권장)

YAML
# external-secrets-operator를 사용하면 AWS Secrets Manager의 값을 
# 자동으로 Kubernetes Secret으로 동기화
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-secret
spec:
  refreshInterval: 1h                    # 1시간마다 동기화
  secretStoreRef:
    name: aws-secretsmanager
    kind: ClusterSecretStore
  target:
    name: db-secret                      # 생성될 Kubernetes Secret 이름
  data:
    - secretKey: DB_PASSWORD
      remoteRef:
        key: production/myapp/db         # AWS Secrets Manager의 경로
        property: password

불변 ConfigMap으로 의도치 않은 변경 방지

YAML
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config-v1
immutable: true    # 생성 후 수정 불가 (변경 시 새 ConfigMap 생성 필요)
data:
  APP_VERSION: "1.0.0"

대규모 클러스터에서 immutable: true를 사용하면 kubelet이 변경 감시를 중단해 API 서버 부하가 줄어드는 장점도 있습니다.

실무 체크리스트

  • Secret 값을 kubectl describe로 확인하면 Data: X bytes 형태로 마스킹됨 (기본 보안)
  • 파드의 서비스어카운트에 최소 권한 원칙 적용 — Secret 읽기 권한만 부여
  • 로그에 Secret 값이 출력되지 않도록 애플리케이션 수준에서도 주의
  • Deployment 롤링 업데이트 중에는 이전 파드와 새 파드가 동시에 다른 Secret 값을 사용할 수 있음

정리

Kubernetes
# ConfigMap 관련 명령어
kubectl create configmap <name> --from-literal=KEY=VALUE
kubectl create configmap <name> --from-file=config.yaml
kubectl get configmap -n <namespace>
kubectl describe configmap <name> -n <namespace>
kubectl edit configmap <name> -n <namespace>
kubectl delete configmap <name> -n <namespace>

# Secret 관련 명령어
kubectl create secret generic <name> --from-literal=KEY=VALUE
kubectl create secret tls <name> --cert=tls.crt --key=tls.key
kubectl get secret -n <namespace>
kubectl describe secret <name> -n <namespace>   # 값은 마스킹됨

# 적용된 값 확인
kubectl get secret <name> -o jsonpath='{.data.KEY}' | base64 -d

# Secret 변경 후 파드 재시작
kubectl rollout restart deployment/<name> -n <namespace>
kubectl rollout status deployment/<name> -n <namespace>
🔍실행 후 확인할 것
  • kubectl get configmap -o yaml 에서 data 섹션을 먼저 확인 — 키-값이 의도한 대로 있는지 검증. 키가 없으면 --from-literal 옵션이 누락된 것
  • Pod에서 환경변수로 주입된 경우: kubectl exec 후 printenv KEY_NAME 으로 실제 값 확인. 출력 없으면 envFrom/env 설정 오류 또는 ConfigMap 이름 불일치
  • Secret은 base64 인코딩: kubectl get secret -o jsonpath='{.data.KEY}' | base64 -d 로 확인 시 원본값과 다르면 인코딩 오류 — stringData 필드 사용으로 해결
  • 볼륨 마운트 방식 갱신 확인: kubectl exec 후 ls -la /etc/config 에서 파일 수정 시간을 보고 ConfigMap 수정 후 1~2분 내에 갱신됐는지 확인. 갱신 안 됐으면 kubelet sync 주기 대기
  • 환경변수 방식 미반영 확인: kubectl rollout restart 후 kubectl rollout status 에서 "successfully rolled out" 출력 → 새 파드에서 printenv 로 재확인. rollout 중이면 이전 파드와 새 파드가 동시에 다른 값 사용 중일 수 있음
  • RBAC 문제 확인: kubectl get secret 에서 Error from server (Forbidden) 이 나오면 현재 사용자 또는 ServiceAccount에 secret get 권한 없음 — kubectl auth can-i get secret 으로 확인

명령어·단축키 빠른 참조

이 모듈에서 ConfigMap·Secret을 만들고 주입·갱신할 때 쓴 kubectl 명령을 모았습니다. "변경 후 반영"과 "Secret 디코딩" 조합이 특히 자주 쓰입니다.

명령어/단축키용도자주 쓰는 예
kubectl create configmap설정값으로 ConfigMap 생성... app-config --from-literal=LOG_LEVEL=info
kubectl create configmap --from-file설정 파일 통째로 저장... nginx-config --from-file=nginx.conf
kubectl create secret generic민감값으로 Secret 생성... db-secret --from-literal=DB_PASSWORD=...
kubectl create secret tlsTLS 인증서 Secret 생성... --cert=tls.crt --key=tls.key
kubectl get configmap -o yamldata 섹션 키-값 검증kubectl get cm app-config -o yaml (cm 축약)
kubectl get secret -o jsonpathSecret 값 디코딩 확인... -o jsonpath='{.data.DB_PASSWORD}' | base64 -d
base64 / base64 -dBase64 인코딩·디코딩(암호화 아님)echo -n 'pw' | base64 / echo '...' | base64 -d
kubectl describe secret값 노출 없이 키·크기 확인Data: X bytes로 마스킹됨
kubectl exec ... -- env파드에 주입된 환경변수 확인... -- env | grep DB / printenv DB_PASSWORD
kubectl exec ... -- ls -la볼륨 마운트 파일·권한 확인... -- ls -la /etc/secrets (0400 확인)
kubectl apply --dry-run=client적용 전 매니페스트 생성... --dry-run=client -o yaml | kubectl apply -f -
kubectl rollout restart환경변수 갱신 위해 파드 재시작kubectl rollout restart deployment/db-app
kubectl rollout status롤아웃 완료 확인kubectl rollout status deployment/db-app
kubectl auth can-iSecret 접근 권한(RBAC) 확인kubectl auth can-i get secret
kubectl edit configmapConfigMap 즉시 수정kubectl edit cm app-config -n config-lab

관련 모듈로 더 깊이:

다음 챕터에서는 PersistentVolume과 PersistentVolumeClaim으로 파드 재시작에도 데이터가 유지되는 영구 스토리지를 구성합니다.

지식 확인

퀴즈 — 8문제

Q1

ConfigMap을 환경변수로 주입했을 때와 볼륨으로 마운트했을 때의 핵심 차이는?

Q2

Kubernetes Secret의 데이터가 Base64로 인코딩되는 이유는?

Q3

Secret을 변경했는데 파드에 반영되지 않는 가장 흔한 원인은?

Q4

ConfigMap과 Secret의 1MB 크기 제한을 초과하는 대용량 설정 파일은 어떻게 처리하는가?

Q5

Kubernetes Secret을 쓰면 민감정보가 '암호화'되어 안전하다고 여기는 것이 위험한 이유는?

Q6

ConfigMap을 변경했을 때, 환경변수로 주입한 경우와 볼륨으로 마운트한 경우의 반영 차이는?

Q7

[심화] ConfigMap을 볼륨으로 마운트하면 자동 갱신되지만, 같은 볼륨을 subPath로 특정 파일 하나만 마운트하면 자동 갱신이 안 되는 이유는?

Q8

[심화] nginx.conf를 ConfigMap 볼륨으로 마운트했는데 ConfigMap을 바꿔도 몇 분째 컨테이너 안 파일이 옛 값이다. 환경변수 주입이 아니라 분명 볼륨 마운트다. 원인을 가장 빨리 확정하려면?

0 / 8 답변

🧪 실습으로 확인하기

ConfigMap·Secret — K8s 환경변수·설정 관리

초급

DB URL 같은 설정값은 ConfigMap으로, API 키 같은 민감 정보는 Secret으로 분리해 Deployment에 환경변수로 주입한다. 컨테이너 이미지와 설정을 완전히 분리하는 12-Factor App 원칙을 직접 적용한다.

35📋 4단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요