K8s ConfigMap·Secret 관리 전략

ConfigMap과 Secret이란

Kubernetes에서 애플리케이션 설정과 민감 정보를 컨테이너 이미지와 분리하는 두 가지 핵심 리소스다. ConfigMap은 환경 변수, 설정 파일, 커맨드라인 인자 등 비밀이 아닌 설정 데이터를 저장하고, Secret은 비밀번호, API 키, TLS 인증서 등 민감 데이터를 Base64 인코딩하여 저장한다.

이 두 리소스를 올바르게 설계하면 하나의 컨테이너 이미지로 dev/staging/prod 환경을 모두 운영할 수 있으며, 설정 변경 시 이미지 재빌드 없이 배포가 가능하다.

ConfigMap 생성 방법

# 1. YAML 선언형
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: production
data:
  # 단순 키-값
  DATABASE_HOST: "postgres-primary.db.svc.cluster.local"
  DATABASE_PORT: "5432"
  DATABASE_NAME: "myapp"
  LOG_LEVEL: "info"
  CACHE_TTL: "300"

  # 설정 파일 통째로 저장
  nginx.conf: |
    server {
      listen 80;
      server_name _;
      location / {
        proxy_pass http://localhost:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
      }
    }

  application.yml: |
    spring:
      datasource:
        url: jdbc:postgresql://${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}
      cache:
        type: caffeine
# 2. kubectl 명령형
# 리터럴 값
kubectl create configmap app-config 
  --from-literal=DATABASE_HOST=postgres.db.svc 
  --from-literal=LOG_LEVEL=info 
  -n production

# 파일에서 생성
kubectl create configmap nginx-config 
  --from-file=nginx.conf=./config/nginx.conf 
  -n production

# 디렉토리에서 생성 (파일명 = 키, 내용 = 값)
kubectl create configmap app-configs 
  --from-file=./config/ 
  -n production

# .env 파일에서 생성
kubectl create configmap env-config 
  --from-env-file=.env.production 
  -n production

Secret 생성과 타입

타입 용도 필수 키
Opaque 범용 (기본) 없음
kubernetes.io/tls TLS 인증서 tls.crt, tls.key
kubernetes.io/dockerconfigjson Docker Registry 인증 .dockerconfigjson
kubernetes.io/basic-auth Basic 인증 username, password
kubernetes.io/ssh-auth SSH 키 ssh-privatekey
# Opaque Secret (Base64 인코딩)
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
  namespace: production
type: Opaque
data:
  DATABASE_PASSWORD: cGFzc3dvcmQxMjM=      # echo -n 'password123' | base64
  JWT_SECRET: c3VwZXItc2VjcmV0LWtleQ==
  REDIS_PASSWORD: cmVkaXNwYXNz

# stringData는 Base64 인코딩 불필요 (자동 변환)
---
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets-plain
type: Opaque
stringData:
  DATABASE_PASSWORD: "password123"
  JWT_SECRET: "super-secret-key"
# kubectl로 Secret 생성
kubectl create secret generic app-secrets 
  --from-literal=DATABASE_PASSWORD=password123 
  --from-literal=JWT_SECRET=super-secret-key 
  -n production

# TLS Secret
kubectl create secret tls api-tls 
  --cert=./certs/tls.crt 
  --key=./certs/tls.key 
  -n production

# Docker Registry Secret
kubectl create secret docker-registry ghcr-pull 
  --docker-server=ghcr.io 
  --docker-username=myuser 
  --docker-password=ghp_xxxxx 
  -n production

Pod에 주입하기: 환경 변수

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  template:
    spec:
      containers:
      - name: api
        image: myapp:latest
        env:
          # 개별 키 참조
          - name: DB_HOST
            valueFrom:
              configMapKeyRef:
                name: app-config
                key: DATABASE_HOST
          - name: DB_PASSWORD
            valueFrom:
              secretKeyRef:
                name: app-secrets
                key: DATABASE_PASSWORD

        # 전체 키를 한 번에 주입
        envFrom:
          - configMapRef:
              name: app-config
          - secretRef:
              name: app-secrets
              optional: true    # Secret 없어도 Pod 시작 가능

Pod에 주입하기: 볼륨 마운트

설정 파일을 볼륨으로 마운트하면 파일 형태로 컨테이너에 제공된다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-proxy
spec:
  template:
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        volumeMounts:
          # ConfigMap → 파일로 마운트
          - name: nginx-config
            mountPath: /etc/nginx/conf.d/default.conf
            subPath: nginx.conf       # 디렉토리가 아닌 단일 파일만 마운트
            readOnly: true

          # Secret → 파일로 마운트 (퍼미션 자동 설정)
          - name: tls-certs
            mountPath: /etc/nginx/ssl
            readOnly: true

          # 특정 키만 선택적 마운트
          - name: app-config
            mountPath: /app/config
            readOnly: true

      volumes:
        - name: nginx-config
          configMap:
            name: nginx-config

        - name: tls-certs
          secret:
            secretName: api-tls
            defaultMode: 0400       # 소유자 읽기 전용

        - name: app-config
          configMap:
            name: app-config
            items:                   # 특정 키만 선택
              - key: application.yml
                path: application.yml
              - key: LOG_LEVEL
                path: log-level.txt

설정 변경 시 자동 롤링 업데이트

ConfigMap/Secret을 변경해도 기존 Pod는 자동으로 재시작되지 않는다. 두 가지 방법으로 해결한다.

# 방법 1: checksum 어노테이션 (Helm에서 많이 사용)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  template:
    metadata:
      annotations:
        # ConfigMap/Secret 내용이 바뀌면 해시가 바뀌어 롤링 업데이트 트리거
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
        checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}

# 방법 2: kubectl rollout restart
kubectl rollout restart deployment/api-server -n production

# 방법 3: Reloader (오픈소스 컨트롤러)
# ConfigMap/Secret 변경 감지 → 자동 롤링 업데이트
# https://github.com/stakater/Reloader
metadata:
  annotations:
    reloader.stakater.com/auto: "true"

Immutable ConfigMap/Secret

Kubernetes 1.21+에서 지원하는 Immutable 리소스는 변경을 금지하여 실수로 인한 장애를 방지한다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config-v3
immutable: true          # 한번 생성 후 수정 불가
data:
  DATABASE_HOST: "postgres.db.svc"
  LOG_LEVEL: "info"

# 변경이 필요하면: 새 이름(v4)으로 생성 → Deployment 참조 변경
# 장점:
# - API 서버 부하 감소 (watch 불필요)
# - 실수로 인한 설정 변경 방지
# - 변경 이력이 리소스 이름에 남음

Secret 보안 강화

# 1. RBAC로 Secret 접근 제한
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: secret-reader
  namespace: production
rules:
- apiGroups: [""]
  resources: ["secrets"]
  resourceNames: ["app-secrets"]   # 특정 Secret만 허용
  verbs: ["get"]

# 2. etcd 암호화 (kube-apiserver 설정)
# EncryptionConfiguration으로 etcd에 저장되는 Secret을 AES로 암호화

# 3. External Secrets Operator — 외부 비밀 저장소 연동
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: app-secrets
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: app-secrets
    creationPolicy: Owner
  data:
    - secretKey: DATABASE_PASSWORD
      remoteRef:
        key: production/myapp
        property: db_password
    - secretKey: JWT_SECRET
      remoteRef:
        key: production/myapp
        property: jwt_secret

K8s RBAC 권한 설계 가이드에서 다룬 최소 권한 원칙을 Secret 접근에도 적용해야 한다. resourceNames로 필요한 Secret만 허용하고, 불필요한 ServiceAccount의 Secret 읽기 권한을 차단한다.

Sealed Secrets: GitOps 친화적 암호화

# Sealed Secrets: Secret을 암호화하여 Git에 안전하게 커밋
# 1. kubeseal CLI로 암호화
kubectl create secret generic app-secrets 
  --from-literal=DB_PASSWORD=mypassword 
  --dry-run=client -o yaml | 
  kubeseal --format yaml > sealed-secret.yaml

# 2. 암호화된 SealedSecret (Git에 커밋 가능)
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: app-secrets
  namespace: production
spec:
  encryptedData:
    DB_PASSWORD: AgBy3i4OJSWK+PiTySYZZA9rO...  # 암호화된 값
    JWT_SECRET: AgCtr8SLKJDF+werWERsdf324...

# 3. Sealed Secrets Controller가 복호화하여 일반 Secret 생성

디버깅 명령어

# ConfigMap 조회
kubectl get configmap app-config -n production -o yaml
kubectl describe configmap app-config -n production

# Secret 값 확인 (Base64 디코딩)
kubectl get secret app-secrets -n production -o jsonpath='{.data.DATABASE_PASSWORD}' | base64 -d

# Pod에 주입된 환경 변수 확인
kubectl exec -n production deploy/api-server -- env | grep DATABASE

# 마운트된 설정 파일 확인
kubectl exec -n production deploy/api-server -- cat /app/config/application.yml

# ConfigMap/Secret을 참조하는 Pod 찾기
kubectl get pods -n production -o json | 
  jq '.items[] | select(.spec.containers[].envFrom[]?.configMapRef.name == "app-config") | .metadata.name'

Helm Chart 실전 운영 가이드에서 다룬 values.yaml과 ConfigMap/Secret을 연동하면 환경별 설정을 Helm values로 일원화하여 관리할 수 있다.

정리: 설정 관리 체크리스트

  • 비밀 분리: 민감 데이터는 반드시 Secret, 일반 설정은 ConfigMap
  • envFrom 활용: 키-값이 많으면 개별 env 대신 envFrom으로 전체 주입
  • subPath 마운트: 설정 파일 하나만 마운트할 때 디렉토리 덮어쓰기 방지
  • Immutable: 운영 환경에서는 immutable: true로 실수 방지
  • 자동 롤링: Reloader 또는 checksum 어노테이션으로 설정 변경 반영
  • Git 커밋 금지: 평문 Secret을 Git에 커밋하지 않는다 → Sealed Secrets 또는 External Secrets 사용
  • RBAC 제한: Secret 접근은 resourceNames로 최소 범위 지정
위로 스크롤
WordPress Appliance - Powered by TurnKey Linux