TestForge | 📊 Plogger ✍️ Blog 📚 Docs
TestForge Blog
← 전체 포스트

Helm Values 계층 설계 - 환경별 오버라이드를 어떻게 나눠야 유지보수가 편한가

Helm Chart를 운영하다 보면 values.yaml이 복잡해지고 환경별 오버라이드가 뒤섞입니다. 기본값과 환경별 오버라이드를 어떤 파일 구조로 나눌지, ArgoCD와 함께 쓸 때 어떤 구조가 잘 맞는지 정리합니다.

TestForge Team ·

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프로덕션 오버라이드프로덕션에서만 다른 것만

오버라이드 파일이 짧을수록 잘 설계된 구조입니다.