왜 렌더링 사고가 반복되는가

Helm 차트는 결국 문자열 템플릿이다. values.yaml의 작은 오타나 조건 분기 실수는 배포 시점에야 드러난다. helm install이 성공해도 실제로는 replicas가 빠지거나, 특정 환경에서 resources.limits가 렌더링되지 않는 식이다. 리뷰어는 diff에서 템플릿 변경은 보지만 "이 values 조합에서 어떤 매니페스트가 나오는가"는 머릿속으로 렌더링해야 한다. 이 검증을 사람이 아니라 CI가 하도록 만드는 것이 핵심이다.

스모크 테스트만으로는 부족하다

많은 팀이 helm lint와 helm template 성공 여부만 CI에 건다. 이건 문법 오류는 잡지만 "값이 의도대로 반영됐는지"는 검증하지 못한다. 아래 표가 각 도구의 책임 범위다.

도구검증 대상한계
helm lint차트 구조/문법렌더 결과값 미검증
helm template렌더 성공 여부내용 단언 불가
helm-unittest렌더된 값 단언클러스터 동작 미검증
kubeconform스키마 유효성비즈니스 규칙 미검증

helm-unittest로 값을 단언한다

helm-unittest 플러그인은 values 조합별로 렌더 결과를 단언한다. 테스트는 차트 안 tests/에 두고 클러스터 없이 실행된다.

helm plugin install https://github.com/helm-unittest/helm-unittest
# tests/deployment_test.yaml
suite: deployment
templates:
  - deployment.yaml
tests:
  - it: prod에서 replicas와 limits가 설정된다
    set:
      env: prod
      resources.limits.memory: 512Mi
    asserts:
      - equal:
          path: spec.replicas
          value: 3
      - equal:
          path: spec.template.spec.containers[0].resources.limits.memory
          value: 512Mi
      - notExists:
          path: spec.template.spec.containers[0].securityContext.privileged

실패 케이스도 명시한다

"이 값이 없으면 렌더가 실패해야 한다" 같은 음성 조건도 테스트한다. failedTemplate로 필수 값 누락 시 required가 제대로 막는지 검증할 수 있다.

CI 게이트로 묶기

테스트를 PR 필수 체크로 걸어 실패 시 머지를 차단한다. 스키마 검증(kubeconform)과 값 단언(unittest)을 함께 돌린다.

name: helm-ci
on: [pull_request]
jobs:
  chart-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: azure/setup-helm@v4
      - name: lint
        run: helm lint charts/*
      - name: unit test
        run: |
          helm plugin install https://github.com/helm-unittest/helm-unittest
          helm unittest charts/myapp
      - name: schema validate
        run: |
          helm template charts/myapp | \
            kubeconform -strict -summary -kubernetes-version 1.29.0

이후 저장소 설정에서 chart-test를 required status check로 지정하면, 렌더 회귀가 프로덕션이 아니라 PR에서 막힌다.

테스트 스냅샷 활용과 주의점

matchSnapshot은 전체 렌더 결과를 통째로 고정한다. 광범위한 회귀 감지에 유용하지만 남용하면 의미 없는 diff가 매번 발생해 -u로 무심코 갱신하게 된다. 스냅샷은 "구조 전체 고정"이 필요한 소수 템플릿에만 쓰고, 핵심 필드는 명시적 equal/contains로 단언하는 편이 유지보수에 낫다.

운영에서 놓치기 쉬운 부분

첫째, 유닛 테스트는 렌더 결과만 본다. RBAC 권한이 실제 클러스터에서 동작하는지, PVC가 바인딩되는지는 별도 통합 테스트(kind + helm test 훅) 영역이다. 둘째, Capabilities.KubeVersion이나 lookup에 의존하는 템플릿은 유닛 테스트에서 결과가 달라질 수 있으므로 --kube-version을 고정한다. 셋째, values 조합이 폭증하면 테스트가 느려진다. 환경별 대표 프로파일(dev/staging/prod)로 케이스를 압축하고, 나머지 경계값만 골라 단언하는 것이 현실적이다. 목표는 완전 검증이 아니라 "리뷰어가 머릿속으로 렌더링하던 일"을 CI에 넘기는 것이다.