-
Helm 차트 관리 Best Practices: Umbrella Chart부터 Secret 관리까지실제 경험과 인사이트를 AI와 함께 정리한 글 2025. 10. 22. 00:03
Helm 차트 관리 Best Practices: Umbrella Chart부터 Secret 관리까지
작성일: 2025-10-21
태그: Helm, Kubernetes, DevOps, GitOps
난이도: 중급~고급들어가며
Kubernetes 애플리케이션을 운영하다 보면 여러 마이크로서비스를 관리해야 하는 복잡도가 급격히 증가합니다. imprun.dev 플랫폼은 API 서버, Web Console, MongoDB, Redis, Runtime Exporter 등 5개 이상의 컴포넌트로 구성되어 있습니다.
이 글에서는 실제 프로덕션 환경에서 검증된 Helm 차트 관리 전략을 소개합니다.
프로젝트 구조 소개
imprun.dev의 Helm 차트 구조:
k8s/imprun/ ├── Chart.yaml # Umbrella Chart 정의 ├── values.yaml # 공통 기본값 ├── values-production.yaml # 프로덕션 오버라이드 ├── values-staging.yaml # 스테이징 오버라이드 ├── values-local.yaml # 로컬 개발용 ├── templates/ │ ├── secrets.yaml # 중앙 집중식 Secret 관리 │ └── certificates.yaml # TLS 인증서 └── charts/ # 서브차트 ├── imprun-server/ ├── imprun-console/ ├── imp-runtime-exporter/ ├── mongodb/ └── redis/
Part 1: Umbrella Chart 패턴
Umbrella Chart란?
여러 독립적인 차트를 하나의 상위 차트로 묶어 관리하는 패턴입니다.
왜 Umbrella Chart를 사용하는가?
Before (개별 차트):
# 😰 5번의 배포 필요 helm install mongodb ./mongodb -n imprun-system helm install redis ./redis -n imprun-system helm install api ./imprun-server -n imprun-system helm install console ./imprun-console -n imprun-system helm install exporter ./imp-runtime-exporter -n imprun-system # 업데이트도 5번... # Secret 공유도 복잡... # 의존성 관리도 어려움...After (Umbrella Chart):
# 🎉 한 번에 전체 스택 배포! helm install imprun ./k8s/imprun -n imprun-system # 업데이트도 한 번! helm upgrade imprun ./k8s/imprun -n imprun-systemChart.yaml 구성
apiVersion: v2 name: imprun description: imprun.dev Serverless Platform - Umbrella Chart type: application version: 0.1.0 appVersion: "1.0.0" # 서브차트 의존성 정의 dependencies: # 1. 인프라 계층 (먼저 시작) - name: mongodb version: "0.1.0" repository: "file://./charts/mongodb" condition: mongodb.enabled - name: redis version: "0.1.0" repository: "file://./charts/redis" condition: redis.enabled # 2. 애플리케이션 계층 - name: imprun-server version: "0.1.0" repository: "file://./charts/imprun-server" condition: imprun-server.enabled - name: imprun-console version: "0.1.0" repository: "file://./charts/imprun-console" condition: imprun-console.enabled # 3. 모니터링/유틸리티 계층 - name: imp-runtime-exporter version: "0.1.0" repository: "file://./charts/imp-runtime-exporter" condition: imp-runtime-exporter.enabled핵심 포인트:
condition: 서브차트 활성화/비활성화 제어repository: file://: 로컬 차트 참조 (단일 저장소 관리)- 계층별 구분: 인프라 → 애플리케이션 → 유틸리티
의존성 업데이트
# 서브차트 변경 후 반드시 실행! cd k8s/imprun helm dependency update # 생성되는 파일들: # - Chart.lock: 의존성 잠금 파일 # - charts/*.tgz: 패키징된 서브차트선택적 배포
로컬 개발: 인프라만
# values-local.yaml mongodb: enabled: true redis: enabled: true imprun-server: enabled: false # 로컬에서 직접 실행 imprun-console: enabled: false # 로컬에서 직접 실행 imp-runtime-exporter: enabled: falsehelm install imprun . -n imprun-local --values values-local.yaml # → MongoDB, Redis만 k8s에 배포
Part 2: Values 파일 전략
계층적 Values 구조
values.yaml (기본값) ↓ 오버라이드 values-dev.yaml (개발 환경) values-staging.yaml (스테이징) values-production.yaml (프로덕션)1. values.yaml: 공통 기본값
원칙: 환경에 관계없이 공통된 설정만 포함
# values.yaml global: imageRegistry: docker.io imagePullSecrets: [] imprun-server: enabled: true replicaCount: 1 # 기본값 image: repository: junsik/imprun-server tag: latest pullPolicy: IfNotPresent # 기본값 resources: requests: cpu: 200m memory: 512Mi limits: cpu: 1000m memory: 2Gi env: JWT_EXPIRES_IN: "7d" # 공통 설정2. values-local.yaml: 로컬 개발
특징:
- 리소스 제한 완화
- 외부 서비스 비활성화
- NodePort 사용
# values-local.yaml global: domain: localhost # 로컬 실행하므로 비활성화 imprun-server: enabled: false imprun-console: enabled: false # 인프라만 활성화 mongodb: enabled: true replicas: 1 # 단일 인스턴스 resources: requests: cpu: 100m # 낮은 리소스 memory: 256Mi auth: rootPassword: "local_dev_password" # 약한 비밀번호 OK redis: enabled: true auth: enabled: false # 로컬에서는 인증 불필요 # NodePort로 접근 services: type: NodePort ports: api: 30000 console: 30001사용:
helm install imprun . -n imprun-local -f values-local.yaml kubectl port-forward svc/mongodb 27017:27017 kubectl port-forward svc/redis 6379:63793. values-staging.yaml: 스테이징
특징:
- 프로덕션 유사 환경
- 낮은 리소스 할당
- 테스트용 도메인
# values-staging.yaml global: domain: staging.imprun.dev imprun-server: replicaCount: 1 # 프로덕션보다 적음 image: tag: staging # 명시적 태그 pullPolicy: Always # 최신 staging 이미지 env: SERVICE_DOMAIN: "api.staging.imprun.dev" CONSOLE_URL: "https://console.staging.imprun.dev" resources: requests: cpu: 100m # 절반 리소스 memory: 256Mi limits: cpu: 500m memory: 1Gi mongodb: replicas: 1 # Single instance storage: size: 10Gi # 작은 스토리지 certificates: enabled: true issuer: letsencrypt-staging # 스테이징 인증서4. values-production.yaml: 프로덕션
특징:
- 고가용성 (HA)
- 강력한 보안
- 프로덕션 인증서
# values-production.yaml global: domain: imprun.dev imprun-server: replicaCount: 3 # HA 구성 image: tag: v1.2.3 # 명시적 버전 (시맨틱 버저닝) pullPolicy: IfNotPresent # 성능 최적화 env: SERVICE_DOMAIN: "app.imprun.dev" CONSOLE_URL: "https://portal.imprun.dev" resources: requests: cpu: 500m # 넉넉한 리소스 memory: 1Gi limits: cpu: 2000m memory: 4Gi # HPA (Horizontal Pod Autoscaler) autoscaling: enabled: true minReplicas: 3 maxReplicas: 10 targetCPUUtilizationPercentage: 70 mongodb: replicas: 3 # Replica Set storage: size: 100Gi auth: rootPassword: "" # Secret으로 주입 (아래 참조) redis: auth: enabled: true password: "" # Secret으로 주입 certificates: enabled: true issuer: letsencrypt-prod # 프로덕션 인증서 # Secret은 별도 관리 (아래 Part 3 참조)사용:
helm install imprun . -n imprun-system \ -f values.yaml \ -f values-production.yaml \ --set mongodb.auth.rootPassword=$MONGO_PASSWORD \ --set redis.auth.password=$REDIS_PASSWORDValues 오버라이드 우선순위
기본값 (values.yaml) ↓ 오버라이드 환경별 Values (-f values-production.yaml) ↓ 오버라이드 커맨드 라인 (--set key=value)예시:
# 최종값: pullPolicy=Always (--set이 최우선) helm upgrade imprun . \ -f values.yaml \ # pullPolicy: IfNotPresent -f values-production.yaml \ # pullPolicy: IfNotPresent --set imprun-server.image.pullPolicy=Always # 최종 승리!
Part 3: Secret 관리
Secret 관리의 어려움
문제점:
- 🔴 Git에 평문 저장 금지
- 🔴 환경마다 다른 비밀번호
- 🔴 여러 서비스 간 Secret 공유
- 🔴 비밀번호 로테이션
전략 1: Helm Values + 외부 주입 (기본)
values.yaml에서 비밀번호 placeholder 정의:
# values.yaml secrets: jwt: secret: "" # 비어있음 → 외부에서 주입 mongodb: rootPassword: "" github: clientId: "" clientSecret: "" google: clientId: "" clientSecret: ""배포 시 주입:
# 환경 변수에서 읽기 export JWT_SECRET=$(openssl rand -base64 32) export MONGO_PASSWORD=$(openssl rand -base64 32) export GITHUB_CLIENT_SECRET="your_github_secret" helm install imprun . -n imprun-system \ -f values-production.yaml \ --set secrets.jwt.secret=$JWT_SECRET \ --set secrets.mongodb.rootPassword=$MONGO_PASSWORD \ --set secrets.github.clientSecret=$GITHUB_CLIENT_SECRET템플릿에서 Secret 생성 (k8s/imprun/templates/secrets.yaml):
# templates/secrets.yaml apiVersion: v1 kind: Secret metadata: name: imprun-jwt-secret namespace: {{ .Release.Namespace }} type: Opaque data: JWT_SECRET: {{ .Values.secrets.jwt.secret | b64enc | quote }} --- apiVersion: v1 kind: Secret metadata: name: imprun-github-oauth namespace: {{ .Release.Namespace }} type: Opaque data: CLIENT_ID: {{ .Values.secrets.github.clientId | b64enc | quote }} CLIENT_SECRET: {{ .Values.secrets.github.clientSecret | b64enc | quote }}서브차트에서 참조:
# charts/imprun-server/templates/deployment.yaml apiVersion: apps/v1 kind: Deployment spec: template: spec: containers: - name: api-server env: - name: JWT_SECRET valueFrom: secretKeyRef: name: imprun-jwt-secret # Umbrella Chart가 생성한 Secret key: JWT_SECRET - name: GITHUB_CLIENT_SECRET valueFrom: secretKeyRef: name: imprun-github-oauth key: CLIENT_SECRET전략 2: Sealed Secrets (GitOps 친화적)
설치:
# Sealed Secrets Controller 설치 kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml # kubeseal CLI 설치 (macOS) brew install kubeseal워크플로우:
일반 Secret 생성 (로컬):
kubectl create secret generic imprun-jwt-secret \ --from-literal=JWT_SECRET=$(openssl rand -base64 32) \ --dry-run=client -o yaml > jwt-secret.yamlSealed Secret으로 암호화:
kubeseal -f jwt-secret.yaml -w jwt-sealed-secret.yaml
jwt-sealed-secret.yaml → Git에 커밋 가능! (암호화됨)
rm jwt-secret.yaml # 평문 삭제
3. **Git에 커밋**: ```yaml # k8s/imprun/templates/jwt-sealed-secret.yaml apiVersion: bitnami.com/v1alpha1 kind: SealedSecret metadata: name: imprun-jwt-secret namespace: imprun-system spec: encryptedData: JWT_SECRET: AgBq7Kx3... (암호화된 값)- Sealed Secrets Controller가 자동 복호화:
kubectl apply -f k8s/imprun/templates/jwt-sealed-secret.yaml # → Controller가 자동으로 Secret 생성
장점:
- ✅ Git에 안전하게 저장
- ✅ GitOps 워크플로우 호환
- ✅ PR 리뷰 가능
단점:
- ❌ 클러스터마다 다른 암호화 키
- ❌ Secret 로테이션 복잡
전략 3: External Secrets Operator (권장 - 엔터프라이즈)
AWS Secrets Manager, Vault, GCP Secret Manager 연동
# external-secret.yaml apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: imprun-jwt-secret spec: refreshInterval: 1h # 1시간마다 동기화 secretStoreRef: name: aws-secrets-manager kind: SecretStore target: name: imprun-jwt-secret creationPolicy: Owner data: - secretKey: JWT_SECRET remoteRef: key: imprun/production/jwt-secret # AWS Secrets Manager 경로장점:
- ✅ 중앙 집중식 Secret 관리
- ✅ 자동 로테이션
- ✅ 감사 로그
- ✅ 세밀한 접근 제어
단점:
- ❌ 외부 서비스 의존성
- ❌ 추가 비용
Secret 관리 Best Practices
1. 환경별 분리
aws-secrets-manager/ ├── imprun/ │ ├── dev/ │ │ ├── jwt-secret │ │ ├── mongodb-password │ │ └── github-oauth │ ├── staging/ │ │ └── ... │ └── production/ │ └── ...2. Secret 로테이션 전략
# 1. 새 Secret 생성 (AWS Secrets Manager) aws secretsmanager update-secret \ --secret-id imprun/production/jwt-secret \ --secret-string $(openssl rand -base64 32) # 2. External Secrets Operator가 자동 동기화 (1시간 이내) # 3. Pod 재시작 (새 Secret 적용) kubectl rollout restart deployment/imprun-imprun-server -n imprun-system3. Secret 최소 권한 원칙
# imprun-server만 JWT Secret 접근 가능 apiVersion: v1 kind: ServiceAccount metadata: name: imprun-server --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: secret-reader rules: - apiGroups: [""] resources: ["secrets"] resourceNames: ["imprun-jwt-secret"] # 특정 Secret만 verbs: ["get"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: imprun-server-secret-reader roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: secret-reader subjects: - kind: ServiceAccount name: imprun-server
Part 4: 고급 패턴
1. 동적 Secret 생성
문제: 배포 시마다 랜덤 비밀번호 생성
# templates/_helpers.tpl {{- define "imprun.jwt.secret" -}} {{- if .Values.secrets.jwt.secret -}} {{- .Values.secrets.jwt.secret -}} {{- else -}} {{- randAlphaNum 32 | b64enc -}} {{- end -}} {{- end -}} # templates/secrets.yaml apiVersion: v1 kind: Secret metadata: name: imprun-jwt-secret annotations: "helm.sh/resource-policy": keep # helm delete 시에도 유지 data: JWT_SECRET: {{ include "imprun.jwt.secret" . | quote }}주의:
helm upgrade시마다 새 값 생성 방지 위해helm.sh/resource-policy: keep필수!2. 서브차트 간 값 공유
Umbrella Chart에서 공통 값 정의:
# values.yaml global: domain: imprun.dev mongodb: host: mongodb-mongodb port: 27017 database: sys_db redis: host: redis-master port: 6379서브차트에서 참조:
# charts/imprun-server/templates/deployment.yaml env: - name: MONGODB_HOST value: {{ .Values.global.mongodb.host }} - name: REDIS_HOST value: {{ .Values.global.redis.host }}3. 조건부 리소스 생성
# templates/certificates.yaml {{- if .Values.certificates.enabled }} {{- range .Values.certificates.certs }} --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: {{ .name }} spec: secretName: {{ .secretName }} dnsNames: {{- range .dnsNames }} - {{ . }} {{- end }} issuerRef: name: {{ $.Values.certificates.issuer }} kind: ClusterIssuer {{- end }} {{- end }}사용:
# values-production.yaml certificates: enabled: true issuer: letsencrypt-prod certs: - name: app-imprun-dev secretName: app-imprun-dev-tls dnsNames: - app.imprun.dev - name: wildcard-app-imprun-dev secretName: wildcard-app-imprun-dev-tls dnsNames: - "*.app.imprun.dev"
실전 시나리오
시나리오 1: 새 환경 추가 (QA 환경)
# 1. values-qa.yaml 생성 cp values-staging.yaml values-qa.yaml # 2. QA 전용 설정 수정 vi values-qa.yaml # domain: qa.imprun.dev # replicaCount: 1 # resources: (staging과 동일) # 3. 배포 helm install imprun-qa . -n imprun-qa \ --create-namespace \ -f values-qa.yaml \ --set secrets.jwt.secret=$QA_JWT_SECRET # 4. DNS 설정 # api.qa.imprun.dev → LoadBalancer IP시나리오 2: Staging → Production 승격
# 1. Staging 이미지 태그 확인 kubectl get deployment imprun-imprun-server -n imprun-staging \ -o jsonpath='{.spec.template.spec.containers[0].image}' # 출력: junsik/imprun-server:staging-abc123 # 2. Production 태그로 재태깅 docker pull junsik/imprun-server:staging-abc123 docker tag junsik/imprun-server:staging-abc123 junsik/imprun-server:v1.3.0 docker push junsik/imprun-server:v1.3.0 # 3. Production 배포 helm upgrade imprun . -n imprun-system \ -f values-production.yaml \ --set imprun-server.image.tag=v1.3.0시나리오 3: Secret 긴급 로테이션
# 1. 새 Secret 생성 NEW_SECRET=$(openssl rand -base64 32) # 2. Secret 업데이트 kubectl create secret generic imprun-jwt-secret \ --from-literal=JWT_SECRET=$NEW_SECRET \ --dry-run=client -o yaml | kubectl apply -f - # 3. 모든 Pod 재시작 (새 Secret 로드) kubectl rollout restart deployment -n imprun-system # 4. 롤아웃 모니터링 kubectl rollout status deployment/imprun-imprun-server -n imprun-system
체크리스트
Helm 차트 설계
- Umbrella Chart로 관련 서비스 그룹화
-
condition으로 서브차트 선택적 활성화 -
global값으로 공통 설정 공유 - 서브차트 간 의존성 명확히 정의
Values 파일 관리
- 환경별 Values 파일 분리 (local/staging/prod)
- 기본값은
values.yaml에만 - 민감 정보는 절대 Git에 커밋 금지
-
--set오버라이드 최소화 (복잡도 증가)
Secret 관리
- 프로덕션은 외부 Secret 관리자 사용 (Sealed Secrets / External Secrets)
- Secret 로테이션 계획 수립
- ServiceAccount + RBAC로 최소 권한 부여
-
helm.sh/resource-policy: keep로 중요 Secret 보호
배포 자동화
- CI/CD에서 환경별 Values 파일 자동 선택
- Helm diff로 변경 사항 사전 검토
- 롤백 계획 수립 (
helm rollback)
결론
Helm 차트 관리는 단순한 패키징 이상의 의미를 가집니다:
- Umbrella Chart: 복잡한 마이크로서비스를 단일 배포 단위로 통합
- Values 전략: 환경별 차이를 명확히 분리하여 실수 방지
- Secret 관리: 보안을 최우선으로, GitOps 워크플로우와 조화
imprun.dev의 실전 경험을 바탕으로 작성된 이 가이드가 여러분의 Kubernetes 여정에 도움이 되길 바랍니다.
다음 글 예고
- Kubernetes 리소스 최적화: ARM64 환경 제약사항과 HPA 설정
- CI/CD 파이프라인 구축: GitHub Actions로 완전 자동화하기
참고 자료
- Helm 공식 문서 - Chart Best Practices
- Sealed Secrets GitHub
- External Secrets Operator
- imprun.dev Helm Charts
피드백은 언제나 환영합니다!
📧 GitHub Issues'실제 경험과 인사이트를 AI와 함께 정리한 글' 카테고리의 다른 글
CI/CD 파이프라인 구축: GitHub Actions로 완전 자동화하기 (0) 2025.10.22 Kubernetes 리소스 최적화: ARM64 환경에서 효율적으로 운영하기 (0) 2025.10.22 클러스터 전체 모니터링을 위한 VictoriaMetrics 설치 및 관리 (0) 2025.10.21 VictoriaMetrics K8s Stack 설치 가이드 (0) 2025.10.21 victoria-metrics-k8s-stack vs victoria-metrics-single (0) 2025.10.21