infra
Platform

모듈 맵

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

0 / 29 완료

펼치기
0 / 29 완료0%

Kubernetes · 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환경변수 vs 볼륨 마운트 방식 비교
  • 4Secret 변경 후 파드 반영 타이밍
  • 5실무 보안 주의사항 — etcd 암호화와 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 — 환경변수와 볼륨 마운트 두 방식

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

방식 1: 환경변수로 주입 (envFrom / 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

방식 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이라도 안전하지 않습니다.

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

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 확인: 클러스터 설정 필요"

문제 상황

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 }}"

💼
실무 맥락
현업 패턴

환경별 설정 분리 패턴

실무에서 개발/스테이징/프로덕션 환경마다 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>

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

지식 확인

퀴즈 — 4문제

Q1

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

Q2

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

Q3

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

Q4

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

0 / 4 답변

🧪 실습으로 확인하기

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

초급

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

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

이것도 배워보세요

kubernetes중급 · 45
[Kubernetes] Namespace를 활용한 여러 개발팀 간 리소스 분할 운영
Kubernetes 트랙 계속
docker입문 · 30
[Docker] 백엔드 개발자에게 Docker와 컨테이너 가상화가 필수인 이유
Docker 트랙 시작점