[Kubernetes] ConfigMap + SealedSecrets로 설정 관리하기

ConfigMap + Sealed Secrets 설정 관리

문제

모든 서비스의 시크릿(DB 비밀번호, API 키 등)이 Spring Boot application.yml평문으로 하드코딩되어 있었다.

목표

  • 비민감 설정 → ConfigMap (Git에 평문 OK)
  • 민감 설정 → SealedSecret (Git에 암호화 저장, 클러스터 내 복호화)
  • 개발자(앱 설정)와 DevOps(인프라 설정) 완전 분리 → 별도 Git 레포

아키텍처

[gitops 레포] (DevOps 영역)              [configs 레포] (설정 영역)
├── charts/<service>/                     ├── <service>/
│   ├── values.yaml (배포 설정)           │   ├── config.yaml (비민감 설정)
│   └── templates/                        │   └── secrets.yaml (암호화 시크릿)
│       ├── rollout.yaml                  ├── manage-env.sh   (자동화)
│       ├── configmap.yaml                └── seal-all.sh     (일괄 암호화)
│       └── sealedsecret.yaml
├── argo-apps/
│   └── clusters/prod/
│       └── <service>.yaml
│           (ArgoCD Multiple Sources)

데이터 흐름

configs 레포의 secrets.yaml (암호화 값)
    ↓ ArgoCD Multiple Sources로 values 병합
gitops 레포의 sealedsecret.yaml 템플릿 → SealedSecret 리소스 생성
    ↓ Sealed Secrets 컨트롤러가 복호화
일반 Secret 자동 생성
    ↓ rollout.yaml의 envFrom → secretRef
Pod 시작 시 환경변수로 주입 (SPRING_DATASOURCE_URL=jdbc:mysql://...)

핵심 설계

  • envFrom 방식: 템플릿 수정 없이 키-값 추가만으로 환경변수 확장
  • checksum 어노테이션: 설정 변경 시 Pod 자동 롤아웃
  • 레포 분리: 인프라(gitops)와 설정(configs)의 관심사 분리
  • Sealed Secrets: Git에 암호화 저장, 클러스터 내에서만 복호화

로컬 개발 vs 배포 환경 차이

[로컬 개발]
application-local.yml에 DB 주소, 비밀번호 직접 작성
→ IDE에서 바로 실행

[클라우드 배포]
application-prod.yml에서 환경변수 참조 (${SPRING_DATASOURCE_URL} 등)
→ K8s가 ConfigMap/Secret에서 환경변수 주입 → Pod 시작

설정 변경 시나리오별 가이드

1. 비민감 설정 변경

예시: Backend의 HikariCP max pool size를 5 → 10으로 변경

# configs-repo/<service>/config.yaml
config:
  SPRING_PROFILES_ACTIVE: "prod"
  TZ: "Asia/Seoul"
  MY_HIKARI_MAXIMUMPOOLSIZE: "10"   # 5 → 10 변경
git add <service>/config.yaml
git commit -m "feat: backend HikariCP max pool size 10으로 증가"
git push
# ArgoCD가 자동 감지 → ConfigMap 업데이트 → Pod 재시작

끝! 로컬 코드 변경 없음.

2. 민감 설정 변경 (DB 비밀번호 등)

# Bastion에서 새 비밀번호를 kubeseal로 암호화
echo -n 'NewPassword123!' | kubeseal --raw \
  --from-file=/dev/stdin \
  --namespace <namespace> \
  --name <service>-secret \
  --cert sealed-secrets-pub-cert.pem

# 출력: AgCEW/aoOTzri8aC... (암호화된 값)
# configs-repo/<service>/secrets.yaml
sealedSecrets:
  SPRING_DATASOURCE_PASSWORD: AgCEW/aoOTzri8aC...  # 새 암호화 값으로 교체
git commit -m "fix: backend DB 비밀번호 변경"
git push
# ArgoCD → SealedSecret 업데이트 → Secret 재생성 → Pod 재시작

3. 새 환경변수 추가

Step 1: Spring Boot에서 환경변수 참조하도록 수정

# application-prod.yml (앱 레포)
my-service:
  api-key: ${MY_SERVICE_API_KEY}

Step 2: 민감한 값이면 → secrets.yaml에 추가

# Bastion에서 암호화
echo -n 'actual-api-key-value' | kubeseal --raw \
  --from-file=/dev/stdin \
  --namespace <namespace> \
  --name <service>-secret \
  --cert sealed-secrets-pub-cert.pem
# configs-repo/<service>/secrets.yaml
sealedSecrets:
  MY_SERVICE_API_KEY: AgBx...  # 암호화 값 추가

Step 2 (대안): 민감하지 않은 값이면 → config.yaml에 추가

# configs-repo/<service>/config.yaml
config:
  MY_SERVICE_URL: "https://api.example.com"  # 평문으로 추가

자동화 스크립트 (manage-env.sh)

Bastion에서 실행하는 파이프라인 스크립트:

# 일반 설정 추가/수정 (ConfigMap)
./manage-env.sh config <service> <key> <value>

# 민감 정보 추가/수정 (SealedSecret — 자동 암호화)
./manage-env.sh secret <service> <key> <value>

# 환경변수 삭제
./manage-env.sh delete <service> <key>

동작: 값 암호화(secret만) → YAML 자동 업데이트 → diff 확인 → git commit & push → ArgoCD sync

환경변수 이름 규칙

Spring Boot의 환경변수 바인딩 규칙:

application.yml 경로 환경변수 이름
spring.datasource.url SPRING_DATASOURCE_URL
spring.datasource.username SPRING_DATASOURCE_USERNAME
jwt.token.access-secret JWT_TOKEN_ACCESSSECRET
my.tenant.routes.COMPANY.jdbc-url MY_TENANT_ROUTES_COMPANY_JDBCURL

규칙: ._, - 제거, 전부 대문자


서비스별 설정 현황

서비스 ConfigMap 키 SealedSecret 키
Backend API 프로필, TZ, HikariCP, Prometheus URL DB 3종, JWT 2종, 암호화 키, 멀티테넌트 DB 6종
Data Ingestion 프로필, TZ, Kafka, Processor URL DB 3종
Message Processor 프로필, TZ, Kafka, OpenSearch 호스트 DB 3종, OpenSearch 2종
Admin Backend 프로필, TZ, API URL, Prometheus URL DB 3종, JWT 2종
Frontend BUILD_MODE, TZ -
Admin Frontend BUILD_MODE, TZ -
AI Service 환경, TZ, LLM, LangChain, CORS API 키, DB URL 등 6종

배포 순서 (점진적)

  1. Phase 0: Sealed Secrets 컨트롤러 배포
  2. Phase 1: kubeseal 암호화 + secrets.yaml 완성
  3. Phase 2-a: Admin Frontend (ConfigMap만, 시크릿 없음, 가장 안전)
  4. Phase 2-b: Frontend (ConfigMap만)
  5. Phase 2-c: Admin Backend (ConfigMap + SealedSecret, 낮은 트래픽)
  6. Phase 2-d: Backend API (핵심 서비스, 신중하게)
  7. Phase 2-e: Ingestion + Processor
  8. Phase 2-f: AI Service

검증 (각 서비스 배포 후)

# ConfigMap 생성 확인
kubectl get configmap <service>-config -n <namespace> -o yaml

# SealedSecret → Secret 복호화 확인
kubectl get secret <service>-secret -n <namespace>

# Pod 환경변수 주입 확인
kubectl exec -it <pod> -n <namespace> -- env | grep SPRING_DATASOURCE

# 서비스 정상 동작 확인
curl http://<endpoint>/actuator/health

주의사항

  1. seal-all.sh는 절대 원격에 push하지 말 것 — 평문 비밀번호가 들어있음
  2. sealed-secrets-pub-cert.pem — 공개키이므로 push해도 무방하지만, 클러스터가 바뀌면 새로 발급 필요
  3. 암호화 값은 클러스터+네임스페이스에 종속 — 다른 클러스터에서는 복호화 불가
  4. config.yaml 변경 시 Pod 자동 재시작 — rollout.yaml에 checksum 설정 있음
  5. secrets.yaml 변경 시에는 수동 재시작 필요할 수 있음kubectl rollout restart