- 1. 플러그인 시스템
- 2. 차트 테스트
- 3. 린팅(Linting)
- 4. 스키마 검증(Schema Validation)
- 5. OCI 레지스트리 고급 사용
- 6. 차트 서명과 검증
- 7. plugin.yaml 계약: 필수 필드와 폐기된 필드
- 8. 플러그인이 받는 환경 변수
- 9. 차트 하나를 게이트에 순서대로 통과시키기
- 10. values.schema.json을 운영에서 쓰는 법
- 11. 실패 사례와 진단 순서
- 12. 언제 쓰지 않나
- 13. 참고 자료
- 14. 정리
1. 플러그인 시스템
1.1 플러그인 구조
Helm 플러그인은 plugin.yaml 매니페스트로 정의됩니다:
# plugin.yaml
name: 'my-plugin'
version: '1.0.0'
usage: 'A custom Helm plugin'
description: 'This plugin does something useful'
command: '$HELM_PLUGIN_DIR/bin/my-plugin'
hooks:
install: '$HELM_PLUGIN_DIR/scripts/install.sh'
update: '$HELM_PLUGIN_DIR/scripts/update.sh'
delete: '$HELM_PLUGIN_DIR/scripts/cleanup.sh'
platformCommand:
- os: linux
arch: amd64
command: '$HELM_PLUGIN_DIR/bin/my-plugin-linux-amd64'
- os: darwin
arch: arm64
command: '$HELM_PLUGIN_DIR/bin/my-plugin-darwin-arm64'
1.2 플러그인 관리
# 플러그인 설치
helm plugin install https://github.com/example/helm-my-plugin
helm plugin install https://github.com/example/helm-my-plugin --version 1.0.0
# 플러그인 목록
helm plugin list
# 플러그인 업데이트
helm plugin update my-plugin
# 플러그인 삭제
helm plugin uninstall my-plugin
# 플러그인 환경 변수
# HELM_PLUGIN_DIR: 플러그인 디렉터리
# HELM_PLUGIN_NAME: 플러그인 이름
# HELM_BIN: helm 바이너리 경로
1.3 주요 플러그인
helm-diff
변경사항을 미리 확인할 수 있는 핵심 플러그인:
# 설치
helm plugin install https://github.com/databus23/helm-diff
# 업그레이드 전 변경사항 미리보기
helm diff upgrade my-release ./my-chart -f values.yaml
# 특정 리비전 간 비교
helm diff revision my-release 2 3
# 롤백 시 변경사항 미리보기
helm diff rollback my-release 2
helm-secrets
Secret 값을 안전하게 관리:
# 설치
helm plugin install https://github.com/jkroepke/helm-secrets
# SOPS로 암호화된 values 파일 사용
helm secrets install my-release ./my-chart -f secrets.yaml
# 값 암호화/복호화
helm secrets enc secrets.yaml
helm secrets dec secrets.yaml
helm secrets view secrets.yaml
helm-unittest
차트 유닛 테스트:
# 설치
helm plugin install https://github.com/helm-unittest/helm-unittest
# 테스트 실행
helm unittest ./my-chart
helm unittest ./my-chart -f 'tests/*_test.yaml'
2. 차트 테스트
2.1 helm test
Helm에 내장된 테스트 메커니즘으로, 릴리스 후 클러스터 내에서 실행됩니다:
# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: {{ include "my-chart.fullname" . }}-test-connection
labels:
{{- include "my-chart.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
restartPolicy: Never
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "my-chart.fullname" . }}:{{ .Values.service.port }}']
# 테스트 실행
helm test my-release
# 타임아웃 지정
helm test my-release --timeout 5m
# 테스트 로그 조회
helm test my-release --logs
2.2 helm-unittest
클러스터 없이 로컬에서 실행되는 유닛 테스트:
# tests/deployment_test.yaml
suite: test deployment
templates:
- deployment.yaml
tests:
- it: should create deployment with correct replicas
set:
replicaCount: 3
asserts:
- isKind:
of: Deployment
- equal:
path: spec.replicas
value: 3
- it: should set correct image
set:
image:
repository: nginx
tag: '1.25'
asserts:
- equal:
path: spec.template.spec.containers[0].image
value: 'nginx:1.25'
- it: should not create ingress when disabled
template: ingress.yaml
set:
ingress:
enabled: false
asserts:
- hasDocuments:
count: 0
- it: should have resource limits
asserts:
- isNotNull:
path: spec.template.spec.containers[0].resources.limits
- it: should match snapshot
asserts:
- matchSnapshot: {}
2.3 ct(chart-testing) 도구
CI/CD 파이프라인에서 차트 변경 감지와 테스트 자동화:
# 설치
# ct는 별도 바이너리로 설치 (Homebrew, Docker 등)
# 변경된 차트 감지 (Git diff 기반)
ct list-changed --target-branch main
# 차트 린트
ct lint --target-branch main
# 차트 설치 테스트 (Kind 클러스터에서)
ct install --target-branch main
# 린트 + 설치 테스트
ct lint-and-install --target-branch main
ct 설정 파일:
# ct.yaml
remote: origin
target-branch: main
chart-dirs:
- charts
chart-repos:
- bitnami=https://charts.bitnami.com/bitnami
helm-extra-args: --timeout 600s
validate-maintainers: false
3. 린팅(Linting)
3.1 helm lint
# 기본 린트
helm lint ./my-chart
# 엄격 모드
helm lint ./my-chart --strict
# values 파일과 함께 검증
helm lint ./my-chart -f production-values.yaml
# set 옵션과 함께
helm lint ./my-chart --set replicaCount=3
helm lint가 검사하는 항목:
- Chart.yaml 필수 필드 존재 여부
- 템플릿 렌더링 오류
- values.yaml 유효성
- 차트 이름과 버전 규칙 준수
- 레이블과 어노테이션 권장 사항
3.2 yamllint와 kubeval/kubeconform
# YAML 문법 검사
helm template my-release ./my-chart | yamllint -
# Kubernetes 스키마 검증 (kubeconform)
helm template my-release ./my-chart | kubeconform \
-strict \
-kubernetes-version 1.29.0 \
-summary
4. 스키마 검증(Schema Validation)
4.1 values.schema.json
JSON Schema로 values의 유효성을 검증합니다:
{
"$schema": "https://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["replicaCount", "image"],
"properties": {
"replicaCount": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"description": "Number of pod replicas"
},
"image": {
"type": "object",
"required": ["repository"],
"properties": {
"repository": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9._/-]*$"
},
"tag": {
"type": "string",
"default": "latest"
},
"pullPolicy": {
"type": "string",
"enum": ["Always", "IfNotPresent", "Never"]
}
}
},
"service": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["ClusterIP", "NodePort", "LoadBalancer"]
},
"port": {
"type": "integer",
"minimum": 1,
"maximum": 65535
}
}
},
"resources": {
"type": "object",
"properties": {
"limits": {
"type": "object",
"properties": {
"cpu": { "type": "string" },
"memory": { "type": "string" }
}
},
"requests": {
"type": "object",
"properties": {
"cpu": { "type": "string" },
"memory": { "type": "string" }
}
}
}
}
}
}
스키마는 helm install, helm upgrade, helm lint, helm template 실행 시 자동으로 검증됩니다.
5. OCI 레지스트리 고급 사용
5.1 OCI 아티팩트로 차트 관리
# 패키징
helm package ./my-chart
# OCI 레지스트리에 푸시
helm push my-chart-1.0.0.tgz oci://ghcr.io/myorg/charts
# OCI에서 직접 설치
helm install my-release oci://ghcr.io/myorg/charts/my-chart --version 1.0.0
# OCI에서 풀
helm pull oci://ghcr.io/myorg/charts/my-chart --version 1.0.0
# 차트 정보 조회
helm show chart oci://ghcr.io/myorg/charts/my-chart --version 1.0.0
helm show values oci://ghcr.io/myorg/charts/my-chart --version 1.0.0
5.2 CI/CD에서의 OCI 활용
# GitHub Actions 예시
# .github/workflows/helm-publish.yaml
name: Publish Helm Chart
on:
push:
tags: ['v*']
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login to GHCR
run: echo "$GITHUB_TOKEN" | helm registry login ghcr.io -u $ --password-stdin
- name: Package and Push
run: |
helm package ./charts/my-app
helm push my-app-*.tgz oci://ghcr.io/$GITHUB_REPOSITORY_OWNER/charts
6. 차트 서명과 검증
6.1 Provenance 파일
# GPG 키로 차트 서명
helm package --sign --key 'my-key' --keyring ~/.gnupg/secring.gpg ./my-chart
# 서명 검증
helm verify my-chart-1.0.0.tgz
# 설치 시 검증
helm install my-release my-chart-1.0.0.tgz --verify
6.2 .prov 파일 구조
패키징 시 my-chart-1.0.0.tgz.prov 파일이 생성되며, 차트의 무결성과 출처를 증명합니다.
7. plugin.yaml 계약: 필수 필드와 폐기된 필드
위 1.1의 예제는 지금 문서가 권장하는 형태가 아닙니다. 문서 기준으로 plugin.yaml에서 반드시 있어야 하는 필드는 name과 version 둘뿐이고, version은 SemVer 2를 따라야 합니다. 나머지는 전부 선택 필드입니다. usage는 helm이 한 줄로 출력하는 사용법 문자열이고, description은 helm help에 나오는 긴 설명입니다. ignoreFlags는 불리언으로, 켜면 Helm이 사용자가 입력한 플래그를 플러그인에 전달하지 않습니다. 인자를 직접 파싱하는 플러그인이라면 이 값을 켜서 Helm의 플래그 해석과 충돌할 여지를 없앨 수 있습니다. downloaders는 커스텀 프로토콜을 지원하는 다운로더 기능을 설정하는 필드로, 사내 아티팩트 저장소처럼 Helm이 모르는 스킴에서 차트를 가져와야 할 때 씁니다.
정작 중요한 것은 위 예제에 쓰인 command와 hooks가 문서에서 폐기(deprecated)로 표시되어 있다는 사실입니다. 각각 platformCommand와 platformHooks로 대체하라고 안내합니다. 차이는 이름 변경이 아니라 구조입니다. command는 문자열 하나이므로 지원 플랫폼이 여러 개면 그 문자열 안에서 uname 분기를 하거나 래퍼 셸 스크립트를 한 겹 더 두어야 했습니다. 그 분기는 플러그인 저자가 짠 셸 코드였고, 새 아키텍처가 나오면 조용히 틀린 바이너리를 실행했습니다. platformCommand는 os와 arch를 키로 갖는 목록이라 어느 바이너리를 실행할지 고르는 책임이 셸 스크립트에서 Helm으로 넘어갑니다. platformHooks도 같은 방식으로 install, update, delete 라이프사이클 훅에 플랫폼 조건을 붙일 수 있게 하는 필드입니다. 기존 필드를 쓰는 플러그인이 오늘 당장 깨지지는 않지만, 새로 만드는 플러그인이라면 처음부터 platform 계열로 쓰는 편이 낫습니다.
한 가지는 정직하게 밝혀 두어야 합니다. helm.sh의 플러그인 문서 페이지에는 이 문서가 아직 Helm 4에 맞게 갱신되지 않았다는 배너가 붙어 있고, 확인 시점의 사이트 버전은 4.2.4였습니다. 즉 위 필드 목록은 눈으로 확인할 수 있는 최신 문서의 내용이되 Helm 4 기준으로 검증된 것은 아닙니다. 특히 platformHooks의 정확한 YAML 구조는 문서가 필드 이름과 용도만 밝히고 예제를 보여주지 않으므로, 여기서 임의로 지어내지 않겠습니다. 정확한 필드는 사용 중인 버전의 문서에서 확인하세요.
# plugin.yaml — 문서가 권장하는 현재 형태
name: 'my-plugin'
version: '1.0.0'
usage: 'my-plugin [flags] CHART'
description: 'Renders a chart the way our CI renders it'
ignoreFlags: false
platformCommand:
- os: linux
arch: amd64
command: '$HELM_PLUGIN_DIR/bin/my-plugin-linux-amd64'
- os: darwin
arch: arm64
command: '$HELM_PLUGIN_DIR/bin/my-plugin-darwin-arm64'
8. 플러그인이 받는 환경 변수
플러그인은 독립 실행 파일이지만 진공 상태에서 도는 것이 아닙니다. Helm이 프로세스를 띄우면서 넣어 주는 환경 변수 위에서 동작하고, 문서는 다음 변수들을 보장합니다.
HELM_PLUGINS # 플러그인 디렉터리
HELM_PLUGIN_NAME # helm이 호출한 이름
HELM_PLUGIN_DIR # 이 플러그인의 디렉터리
HELM_BIN # helm 실행 파일 경로
HELM_DEBUG
HELM_NAMESPACE
HELM_KUBECONTEXT
HELM_REGISTRY_CONFIG
HELM_REPOSITORY_CACHE
HELM_REPOSITORY_CONFIG
이 목록에서 실무적으로 결정적인 것은 HELM_BIN과 HELM_KUBECONTEXT입니다. 플러그인이 클러스터에 무언가를 물어봐야 할 때 선택지는 두 가지입니다. kubeconfig를 직접 읽어서 클라이언트를 만들거나, HELM_BIN이 가리키는 helm을 다시 호출하는 것입니다. 후자를 택해야 합니다. 사용자가 helm을 특정 컨텍스트로 호출했다면 Helm은 그 결정을 HELM_KUBECONTEXT에 담아 넘겨 줍니다. 플러그인이 kubeconfig를 스스로 파싱하면 그 값을 무시하고 current-context를 읽게 되고, 사용자는 스테이징을 지목했는데 플러그인만 프로덕션을 보는 상태가 됩니다. 같은 이유로 네임스페이스도 HELM_NAMESPACE를 따라야 하며, 저장소 캐시를 뒤질 일이 있으면 HELM_REPOSITORY_CACHE와 HELM_REPOSITORY_CONFIG를, OCI 레지스트리 자격 증명이 필요하면 HELM_REGISTRY_CONFIG를 쓰는 것이 옳습니다. 이 경로들을 하드코딩하면 여러 helm 설정을 오가는 CI 러너에서 곧바로 깨집니다.
#!/usr/bin/env bash
set -euo pipefail
# 진단용: helm이 실제로 넘겨준 값이 무엇인지 먼저 찍어 본다
echo "plugin=$HELM_PLUGIN_NAME dir=$HELM_PLUGIN_DIR" >&2
echo "namespace=$HELM_NAMESPACE context=$HELM_KUBECONTEXT" >&2
# kubeconfig를 직접 읽지 말고 호출자가 준 helm으로 되돌려 호출한다
exec "$HELM_BIN" template "$@"
HELM_DEBUG도 쓸모가 있습니다. 사용자가 helm을 디버그 모드로 실행했다는 신호이므로, 플러그인의 로그 상세도를 이 값에 맞추면 별도 플래그를 만들 필요가 없습니다. 반대로 플러그인이 자체 디버그 플래그를 만들어 놓고 HELM_DEBUG를 무시하면, 사용자는 helm 쪽 디버그를 켰는데 정작 실패하는 플러그인만 조용한 상황을 만납니다.
9. 차트 하나를 게이트에 순서대로 통과시키기
앞의 도구들은 각각 소개하면 다 좋아 보이지만, 실제로는 순서가 의미를 만듭니다. 뒤 단계는 앞 단계보다 느리고 비싸므로, 앞에서 걸러낼 수 있는 것을 뒤로 미루면 피드백 루프만 길어집니다. 차트 하나를 다음 순서로 통과시키는 것이 기본형입니다.
# 1) 정적 검사 — 클러스터 없음
helm lint ./my-chart --strict --with-subcharts
# 2) 렌더 결과를 쿠버네티스 스키마에 맞춰 검증 — 클러스터 없음
helm template my-release ./my-chart \
| kubeconform -strict -kubernetes-version 1.29.0 -summary
# 3) 값 분기별 단위 검증 — 클러스터 없음
helm unittest ./my-chart
# 4) 시뮬레이션
helm install my-release ./my-chart --dry-run
# 5) 실제 배포 후 클러스터 안에서 검증
helm install my-release ./my-chart --wait
helm test my-release --logs
각 단계가 잡는 것이 다릅니다. helm lint는 차트가 well-formed인지, 즉 Chart.yaml의 필수 필드가 있는지, 템플릿이 렌더되기는 하는지를 봅니다. --strict는 경고를 실패로 승격시키고, --with-subcharts는 의존 차트까지 린트합니다. 여기서 통과했다는 것은 "문자열이 만들어졌다"는 뜻일 뿐, 그 문자열이 쿠버네티스가 받아들일 매니페스트라는 보장은 없습니다. 그 간극을 메우는 것이 2단계입니다. kubeconform은 렌더된 YAML을 지정한 쿠버네티스 버전의 OpenAPI 스키마에 대고 검사하므로 spec.replica 같은 오타나 apiVersion이 사라진 리소스를 여기서 잡습니다. lint는 이런 것을 절대 잡지 못합니다.
3단계는 앞의 둘이 구조적으로 못 하는 일을 합니다. lint와 kubeconform은 기본값 한 벌로 렌더한 결과만 보지만, 실제 사고는 대개 특정 values 조합에서 납니다. helm unittest는 값을 바꿔 가며 렌더하고 결과의 특정 경로를 단정하므로, ingress를 끄면 문서가 0개여야 한다든가 replicaCount를 3으로 주면 실제로 3이 되는지 같은 분기별 계약을 고정합니다. 다만 이것도 여전히 로컬 렌더링입니다.
4단계에서 처음으로 시뮬레이션이 등장합니다. helm install --dry-run은 문서상 none(기본값), client, server 중 하나를 받습니다. lint가 --kube-version으로 사람이 알려 준 버전을 기준으로 capabilities와 deprecation을 판단하는 것과 대비되는 지점인데, 두 값이 각각 어디까지 API 서버를 왕복하는지는 버전에 따라 달라졌습니다. 정확한 필드는 사용 중인 버전의 문서에서 확인하세요. 실무적으로 기억할 것은 하나입니다. 여기까지 통과해도 이미지가 실제로 당겨지는지, 서비스가 응답하는지는 아무도 확인하지 않았습니다.
5단계가 그 자리입니다. helm test는 릴리스가 설치된 클러스터 안에서 테스트 훅을 Pod으로 띄우고, 그 Pod이 성공하면 릴리스가 실제로 동작한다고 보는 방식입니다. 성공 출력에는 다음과 같은 블록이 붙고, 문서가 표시하는 판정 줄은 Phase: Succeeded입니다.
NAME: demo
LAST DEPLOYED: Mon Feb 14 20:03:16 2022
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: demo-test-connection
Last Started: Mon Feb 14 20:35:19 2022
Last Completed: Mon Feb 14 20:35:23 2022
Phase: Succeeded
훅 어노테이션은 값이 세 종류인데 지위가 다릅니다. 현재 표준은 "helm.sh/hook": test 하나입니다. test-success는 Helm v3까지 쓰이던 값으로 지금도 호환을 위해 받아 주고, test-failure는 폐기되었습니다. 오래된 차트를 인수했다면 이 값들이 섞여 있을 수 있으니 test로 통일하는 것이 안전합니다. 여기에 일반 훅에 쓰는 helm.sh/hook-weight와 helm.sh/hook-delete-policy도 그대로 적용됩니다. hook-weight는 문자열로 적어야 하는 숫자이고 같은 Kind 안에서 오름차순으로 정렬되므로, 데이터를 넣는 준비 Pod을 먼저 돌리고 검증 Pod을 뒤에 돌리는 식의 순서를 만들 수 있습니다. hook-delete-policy는 before-hook-creation(기본값), hook-succeeded, hook-failed 중에서 고릅니다.
# templates/tests/test-connection.yaml — 어노테이션 부분
metadata:
annotations:
'helm.sh/hook': test
'helm.sh/hook-weight': '10'
'helm.sh/hook-delete-policy': hook-succeeded
10. values.schema.json을 운영에서 쓰는 법
스키마 파일은 문서 대용이 아니라 게이트입니다. 문서는 검증이 helm install, helm upgrade, helm lint, helm template 네 명령에서 일어난다고 명시합니다. 이 목록에서 중요한 것은 lint와 template이 포함되어 있다는 점입니다. 덕분에 잘못된 values는 클러스터에 닿기 전에, 심지어 클러스터가 없는 CI 러너에서도 걸립니다. 위 9절의 1단계가 스키마 게이트를 겸한다는 뜻입니다.
두 번째로 알아야 할 규칙이 서브차트와 얽힙니다. 문서는 최종 .Values 객체가 모든 서브차트의 스키마에 대해 검사된다고 말합니다. 부모 차트는 서브차트가 건 제약을 우회할 수 없고, 그 제약을 스스로도 만족해야 합니다. 실무에서 이 규칙은 대개 이렇게 나타납니다. 부모 차트의 values.yaml이 서브차트 키를 덮어쓰는데 서브차트 스키마가 그 키에 enum이나 minimum을 걸어 두면, 부모만 보고 있던 사람에게는 갑자기 나타난 검증 실패로 보입니다. 오류 메시지가 가리키는 경로가 부모 스키마에 없다면 서브차트 스키마부터 열어 보는 것이 순서입니다.
세 번째는 탈출구입니다. 스키마가 원격 참조를 포함하면 폐쇄망에서는 검증 자체가 실패합니다. 이때 쓰라고 --skip-schema-validation이 있습니다. helm install과 helm lint 양쪽 모두 이 플래그를 문서화하고 있으며 설명은 동일하게 JSON 스키마 검증을 끈다는 것입니다. 다만 이것은 폐쇄망 회피용이지 검증이 거슬릴 때 쓰는 스위치가 아닙니다. 팀 CI에서 이 플래그가 상시로 붙어 있다면 스키마는 이미 죽은 파일입니다.
# 스키마만 따로 확인하고 싶을 때: 렌더까지 가지 않고 lint에서 끝낸다
helm lint ./my-chart -f production-values.yaml
# 폐쇄망에서 원격 참조를 포함한 스키마 때문에 막힐 때만
helm install my-release ./my-chart --skip-schema-validation
11. 실패 사례와 진단 순서
lint는 통과하는데 install이 실패하는 경우가 가장 흔합니다. 이것은 버그가 아니라 설계입니다. lint는 API 서버에 묻지 않고, 클러스터 버전조차 --kube-version으로 사람이 알려 줍니다. 그래서 RBAC 부족, 이미 존재하는 이름, admission 웹훅 거부, 존재하지 않는 CRD 같은 것은 lint의 시야 밖입니다. 진단 순서는 렌더 결과를 눈으로 보고, kubeconform으로 스키마를 확인하고, 그다음 --dry-run으로 올려 보는 것입니다. 앞의 두 단계에서 아무것도 나오지 않으면 문제는 차트가 아니라 클러스터 쪽에 있습니다.
유닛 테스트 스냅샷이 흔들리는 경우도 자주 봅니다. 증상은 코드를 건드리지 않았는데 CI만 빨간 것입니다. 원인은 대개 스냅샷이 렌더 결과 전체를 통째로 붙잡고 있는데, 그 안에 차트 버전이나 이미지 태그처럼 릴리스마다 바뀌는 값이 섞여 있는 것입니다. -u(--update-snapshot)로 갱신하면 빨간불은 사라지지만 그것은 진단이 아니라 침묵입니다. 먼저 diff를 읽고, 바뀐 줄이 의도한 변경인지 확인한 다음, 변동값이 원인이면 그 부분은 스냅샷 대신 경로 단위 단정으로 바꿔야 합니다.
플러그인이 어느 날 갑자기 잘못된 클러스터를 건드리는 사고는 8절에서 말한 그 원인입니다. 증상은 특징적입니다. helm 본체는 올바른 컨텍스트로 동작하는데 플러그인만 다른 클러스터의 결과를 돌려줍니다. 확인 순서는 플러그인 스크립트에서 kubeconfig나 KUBECONFIG를 직접 읽는 줄을 찾고, 그 자리를 HELM_BIN 재호출로 바꾸는 것입니다.
스키마 검증이 서브차트 버전을 올린 뒤에만 실패하는 경우는 10절의 규칙이 그대로 드러난 것입니다. 부모 차트는 그대로인데 실패하므로 원인이 안 보입니다. 서브차트의 새 버전이 values.schema.json을 추가했거나 기존 스키마에 제약을 더한 것인지부터 확인하세요.
마지막으로 helm test가 Pod을 남기는 문제가 있습니다. 테스트 Pod에 hook-delete-policy가 없으면 기본값인 before-hook-creation이 적용되어 다음 실행 직전까지 살아 있습니다. 네임스페이스에 Completed 상태 Pod이 쌓이는 것이 정상 동작이라는 뜻입니다. 로그를 남겨야 하는 상황이 아니라면 hook-succeeded를 붙이는 편이 낫습니다.
# 진단 순서 그대로
helm template my-release ./my-chart | less # 렌더 결과부터 눈으로
helm template my-release ./my-chart | kubeconform -strict -summary
helm install my-release ./my-chart --dry-run # 여기서 처음 시뮬레이션
kubectl get pods -l 'app.kubernetes.io/instance=my-release'
12. 언제 쓰지 않나
유닛 테스트가 템플릿을 그대로 되읊는 형태라면 쓰지 않는 편이 낫습니다. spec.replicas가 .Values.replicaCount와 같은지 단정하는 테스트는 템플릿을 한 번 더 옮겨 적은 것이라, 템플릿이 바뀌면 반드시 같이 바뀝니다. 그런 테스트는 회귀를 잡지 못하고 변경 비용만 두 배로 만듭니다. 가치가 있는 단정은 분기입니다. 특정 값 조합에서 리소스가 나오는가 나오지 않는가, 조건부 블록이 켜지는가 같은 것들입니다.
플러그인도 마찬가지입니다. values 파일 하나나 Makefile 몇 줄로 해결되는 일을 플러그인으로 만들면 배포, 버전 관리, 플랫폼별 바이너리라는 부담만 새로 생깁니다. 플러그인이 값어치를 하는 지점은 helm의 하위 명령처럼 보이면서 helm의 컨텍스트와 네임스페이스를 그대로 물려받아야 할 때입니다. 그 요구가 없다면 셸 스크립트가 낫습니다.
helm test를 실제 스모크 테스트의 대체물로 쓰는 것도 경계입니다. 테스트 훅은 릴리스가 설치된 직후 클러스터 내부에서 도는 Pod이므로, 인그레스 바깥에서의 TLS, 외부 DNS, 인증 게이트웨이, 실제 트래픽 경로는 전부 확인 범위 밖입니다. 배포 파이프라인에 별도의 엔드투엔드 검증이 있다면 helm test는 그 앞단의 값싼 확인으로 두고, 그것으로 충분하다고 선언하지는 마세요.
13. 참고 자료
- Helm — Plugins — 2026-08-16 확인 (Helm 4 미갱신 배너 있음, 사이트 버전 4.2.4)
- Helm — Chart Tests — 2026-08-16 확인
- Helm — Chart Hooks — 2026-08-16 확인
- Helm — Charts / Schema Files — 2026-08-16 확인
- helm lint — 2026-08-16 확인
- helm install — 2026-08-16 확인
- helm-unittest — 2026-08-16 확인
14. 정리
Helm의 확장성과 품질 보장:
- 플러그인 시스템: helm-diff, helm-secrets, helm-unittest 등으로 기능 확장
- 다층 테스트: helm test(통합), unittest(유닛), ct(CI/CD) 조합
- 린팅: helm lint, yamllint, kubeconform으로 다각도 검증
- 스키마 검증: values.schema.json으로 입력값 유효성 보장
- OCI 레지스트리: 컨테이너 이미지와 동일한 워크플로우로 차트 배포
- 서명/검증: 차트의 무결성과 출처 증명
다음 글에서는 Helm 차트 설계 Best Practices를 다룹니다.