데이터베이스 비밀번호를 코드에 하드코딩하면 안 된다는 건 알겠는데, Kubernetes에서는 어떻게 분리해서 관리할까요?

설정값과 민감 정보를 코드와 분리하는 것은 12-Factor App의 핵심 원칙 중 하나입니다. Kubernetes는 이를 위해 ConfigMap과 Secret이라는 두 가지 오브젝트를 제공합니다. 비슷해 보이지만 용도와 보안 수준이 다르고, 변경 시 반영 방식도 다릅니다.

ConfigMap — 일반 설정 관리

ConfigMap은 키-값 쌍으로 설정 데이터를 저장합니다.

YAML
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
SHELL
# 명령어로 생성
kubectl create configmap app-config \
  --from-literal=APP_ENV=production \
  --from-file=application.yml

환경변수로 주입

YAML
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

볼륨으로 마운트

YAML
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 키, 인증서 같은 민감한 데이터를 저장합니다.

YAML
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를 사용하면 평문으로 작성할 수 있습니다.

YAML
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/tlsTLS 인증서와 키
kubernetes.io/dockerconfigjson컨테이너 레지스트리 인증
kubernetes.io/basic-auth기본 인증 (username/password)
kubernetes.io/ssh-authSSH 키
YAML
# TLS Secret 예제
apiVersion: v1
kind: Secret
metadata:
  name: tls-secret
type: kubernetes.io/tls
data:
  tls.crt: <base64 인코딩된 인증서>
  tls.key: <base64 인코딩된 키>
SHELL
# 명령어로 TLS Secret 생성
kubectl create secret tls tls-secret \
  --cert=path/to/cert.pem \
  --key=path/to/key.pem

Secret을 Pod에 주입

YAML
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

YAML
apiVersion: v1
kind: ConfigMap
metadata:
  name: static-config
immutable: true  # 수정 불가
data:
  VERSION: "1.0"

immutable: true를 설정하면 두 가지 이점이 있습니다.

  1. ** 실수 방지 **: 운영 중인 설정이 의도치 않게 변경되는 것을 막습니다
  2. ** 성능 최적화 **: kubelet이 watch를 멈추므로 API Server 부하가 줄어듭니다

수정이 필요하면 삭제 후 새로 생성해야 합니다.

변경 시 재시작 전략

ConfigMap이나 Secret을 수정했을 때 Pod에 반영하는 방법입니다.

방법 1: 어노테이션 변경으로 롤링 업데이트

SHELL
# Deployment의 어노테이션을 변경하면 Pod이 재생성됨
kubectl patch deployment my-app -p \
  '{"spec":{"template":{"metadata":{"annotations":{"config-hash":"20260319"}}}}}'

방법 2: ConfigMap 이름에 해시 포함 (Helm 패턴)

YAML
# 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를 롤링 업데이트합니다.

YAML
metadata:
  annotations:
    reloader.stakater.com/auto: "true"  # 자동 재시작 활성화

실무 팁

  • **Secret은 환경변수보다 볼륨 마운트를 권장합니다 **: 환경변수는 로그에 노출될 위험이 있습니다
  • **ConfigMap의 크기 제한은 1MB입니다 **: 큰 설정은 분할하세요
  • ** 네임스페이스 간 공유 불가 **: 같은 네임스페이스의 Pod만 사용할 수 있습니다
  • **optional 플래그 **: 참조하는 ConfigMap/Secret이 없어도 Pod이 시작되게 할 수 있습니다
YAML
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 인코딩은 암호화가 아니라는 점은 꼭 기억해 두세요.

댓글 로딩 중...