Helm Values 계층 설계 - 환경별 오버라이드를 어떻게 나눠야 유지보수가 편한가
Helm Chart를 운영하다 보면 values.yaml이 복잡해지고 환경별 오버라이드가 뒤섞입니다. 기본값과 환경별 오버라이드를 어떤 파일 구조로 나눌지, ArgoCD와 함께 쓸 때 어떤 구조가 잘 맞는지 정리합니다.
values.yaml 하나로 버티다 생기는 문제
Helm Chart를 처음 만들 때는 values.yaml 하나에 다 넣습니다.
개발 환경, 스테이징, 프로덕션을 하나의 파일에서 관리하다 보면 빠르게 복잡해집니다.
흔히 만나는 상황입니다.
- 개발에서만 true여야 하는 플래그가 프로덕션에도 켜진다
- 리소스 요청값이 환경마다 달라서 주석으로 다 써두고 있다
--set플래그를 파이프라인에서 길게 늘어뜨리고 있다- 어느 환경에 어떤 값이 실제로 적용되는지 확인이 어렵다
이 상태가 되면 배포 실수가 생기기 시작합니다.
계층 설계의 기본 원칙
Helm values를 잘 나누려면 아래 두 가지를 분리해야 합니다.
기본값 (base): 환경과 관계없이 항상 적용되는 설정
오버라이드 (override): 특정 환경에서만 달라지는 설정
파일 구조로 만들면 이렇게 됩니다.
charts/my-service/
├── Chart.yaml
├── templates/
│ ├── deployment.yaml
│ ├── service.yaml
│ └── ...
├── values.yaml ← 기본값 (공통)
├── values.dev.yaml ← 개발 오버라이드
├── values.staging.yaml ← 스테이징 오버라이드
└── values.prod.yaml ← 프로덕션 오버라이드
helm install이나 helm upgrade에서 -f를 두 번 씁니다.
helm upgrade my-service ./charts/my-service \
-f values.yaml \
-f values.prod.yaml
나중에 오는 파일의 값이 앞 파일을 덮어씁니다.
기본값에 넣어야 하는 것들
values.yaml에는 “없으면 배포가 안 되는 것”을 넣습니다.
# values.yaml
replicaCount: 1
image:
repository: ""
tag: ""
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
livenessProbe:
enabled: true
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
enabled: true
initialDelaySeconds: 10
periodSeconds: 5
기본값은 최소한의 안전한 값으로 설정합니다.
실수로 오버라이드를 빠뜨려도 배포가 되긴 해야 합니다.
환경별 오버라이드에 넣어야 하는 것들
오버라이드 파일에는 “이 환경에서만 다른 것”만 씁니다.
# values.prod.yaml
replicaCount: 3
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 2000m
memory: 2Gi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
ingress:
enabled: true
host: api.example.com
tls: true
# values.dev.yaml
replicaCount: 1
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
autoscaling:
enabled: false
debug:
enabled: true
logLevel: DEBUG
오버라이드 파일을 열었을 때 “이 환경이 다른 이유”가 바로 보여야 합니다.
전체 values를 복사해서 수정하면 기본값 변경 시 오버라이드 파일도 일일이 업데이트해야 합니다.
ArgoCD와 함께 쓸 때
ArgoCD Application에서 values 파일을 지정할 수 있습니다.
# argocd/apps/my-service-prod.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-service-prod
namespace: argocd
spec:
project: production
source:
repoURL: https://github.com/org/k8s-manifests
targetRevision: main
path: charts/my-service
helm:
valueFiles:
- values.yaml
- values.prod.yaml
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
중요한 점은 valueFiles에 기본값과 오버라이드 순서를 명시해야 한다는 것입니다.
순서가 바뀌면 의도와 다른 값이 적용됩니다.
비밀값은 별도로 관리한다
DB 비밀번호, API 키 같은 민감한 값은 values 파일에 직접 넣으면 안 됩니다.
권장 패턴:
# values.yaml - 시크릿 이름만 참조
database:
secretName: "my-service-db-secret"
secretKey: "password"
# templates/deployment.yaml 에서
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.database.secretName }}
key: {{ .Values.database.secretKey }}
실제 값은 External Secrets Operator나 AWS Secrets Manager에서 주입합니다.
자주 하는 실수 두 가지
1. 오버라이드 파일에 너무 많이 쓴다
오버라이드 파일이 기본값 파일보다 길어지면 역할이 뒤바뀐 것입니다.
기본값을 적절히 조정해서 오버라이드는 진짜 차이점만 담도록 합니다.
2. 환경 이름을 값 안에 직접 쓴다
# 나쁜 패턴
env:
ENVIRONMENT: "production"
LOG_LEVEL: "INFO" # prod에서만 INFO, dev는 DEBUG
이런 경우 오버라이드가 아니라 기본값에 LOG_LEVEL: DEBUG를 두고
prod 오버라이드에서 LOG_LEVEL: INFO로 덮는 구조가 낫습니다.
정리
| 파일 | 내용 | 원칙 |
|---|---|---|
values.yaml | 공통 기본값 | 없으면 배포 실패하는 것 |
values.dev.yaml | 개발 오버라이드 | 개발에서만 다른 것만 |
values.staging.yaml | 스테이징 오버라이드 | 스테이징에서만 다른 것만 |
values.prod.yaml | 프로덕션 오버라이드 | 프로덕션에서만 다른 것만 |
오버라이드 파일이 짧을수록 잘 설계된 구조입니다.