차트 구조와 템플릿 문법
이 글의 목표:
templates/폴더에서 일어나는 일을 라인 단위로 이해한다. 템플릿 디버깅의 기초.
차트 폴더의 표준 구조
helm create mychart 로 만든 표준 차트는 이렇게 생겼다.
mychart/
├── Chart.yaml # 메타데이터
├── values.yaml # 기본 변수
├── values.schema.json # values 스키마 (선택)
├── charts/ # 의존 차트의 .tgz 보관소
├── templates/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── _helpers.tpl # ← 재사용 함수 모음 (밑줄 시작 = 직접 안 만듦)
│ ├── NOTES.txt # ← install 후 출력될 메시지
│ └── tests/ # helm test 용
└── .helmignore # 차트 패키징 시 제외할 파일
프로젝트의 helm/backend/는 더 단순하다 — _helpers.tpl도 없다.
helm/backend/
├── Chart.yaml
├── values.yaml
└── templates/
├── deployment.yaml
├── service.yaml
├── ingress.yaml
└── externalsecret.yaml
→ 작은 차트는 굳이 헬퍼 함수 안 만들어도 된다.
템플릿 렌더링 흐름 (한 명령어가 일어나는 일)
helm install backend ./helm/backend -f values-prod.yaml내부 동작:
1. ./helm/backend/Chart.yaml 읽기
2. values.yaml 읽기 (기본값)
3. -f values-prod.yaml 로 덮어쓰기
4. --set 옵션 으로 또 덮어쓰기
5. templates/*.yaml 을 Go template으로 렌더링
→ 위에서 만든 values를 .Values 로 주입
6. 결과 YAML들을 K8s API에 전송
7. 매니페스트 + 메타데이터를 Secret에 저장 (롤백용)
helm template ./helm/backend -f values-prod.yaml 을 실행하면 5단계까지만 수행되어 렌더링된 YAML을 그대로 볼 수 있다. 디버깅의 핵심 명령어다.
템플릿 안에서 쓸 수 있는 객체들
| 객체 | 어디서 옴 | 예시 |
|---|---|---|
.Values | values.yaml 등 | {{ .Values.replicaCount }} |
.Release.Name | helm install 시 인자 | {{ .Release.Name }} → "backend" |
.Release.Namespace | -n 옵션 | {{ .Release.Namespace }} |
.Release.IsInstall | install인지 upgrade인지 | {{ if .Release.IsInstall }} |
.Chart.Name | Chart.yaml의 name | {{ .Chart.Name }} |
.Chart.Version | Chart.yaml의 version | {{ .Chart.Version }} |
.Files | 차트 안의 파일 읽기 | {{ .Files.Get "config.json" }} |
.Capabilities.KubeVersion | 클러스터 버전 | {{ .Capabilities.KubeVersion.Major }} |
⚠️ 점(.)으로 시작! 점은 현재 컨텍스트(scope) 를 가리킨다.
자주 보는 문법 패턴
① {{ }} 와 {{- -}} 의 차이
{{ if .Values.x }}
foo: bar
{{ end }}↓ 렌더링 시 빈 줄이 남는다.
foo: bar
{{- if .Values.x }}
foo: bar
{{- end }}↓ 빈 줄이 깔끔하게 사라진다.
foo: bar→ YAML에서는 들여쓰기와 빈 줄이 의미 있으므로 {{- 를 거의 항상 쓴다.
② if/else
spec:
type: {{- if .Values.service.type }} {{ .Values.service.type }} {{- else }} ClusterIP {{- end }}또는 default 함수가 더 깔끔하다.
spec:
type: {{ .Values.service.type | default "ClusterIP" }}③ range (반복)
env:
{{- range $k, $v := .Values.env }}
- name: {{ $k }}
value: {{ $v | quote }}
{{- end }}values.yaml이 다음과 같으면
env:
DB_URL: "jdbc:..."
LOG_LEVEL: "INFO"→ 두 개의 - name: ... / value: ... 항목이 생성된다.
⚠️ range 안에서는 . 이 현재 순회중인 값으로 바뀐다. 바깥 .Values를 쓰려면 $.Values 로 접근한다.
④ toYaml + nindent
resources:
{{- toYaml .Values.resources | nindent 4 }}values.yaml:
resources:
requests:
cpu: 250m
memory: 512Mi→ 결과:
resources:
requests:
cpu: 250m
memory: 512Mi| 함수 | 역할 |
|---|---|
toYaml | Go 객체를 YAML 문자열로 변환 |
nindent N | 줄바꿈 후 N칸 들여쓰기 (앞에 \n 추가) |
indent N | N칸 들여쓰기만 (\n 없음) |
→ "맵을 통째로 YAML에 끼워 넣을 때" 거의 항상 toYaml | nindent 조합이다.
⑤ quote
value: {{ .Values.springProfilesActive | quote }}→ 값이 "prod" 처럼 따옴표로 감싸진다. 숫자처럼 보이는 문자열("3")을 안전하게 처리할 때 필수.
프로젝트 코드로 보는 템플릿 — helm/backend/templates/deployment.yaml
spec:
replicas: {{ .Values.replicaCount }} # ① 단순 변수
selector:
matchLabels:
app: {{ .Release.Name }} # ② Release 이름
template:
metadata:
labels:
app: {{ .Release.Name }}
spec:
containers:
- name: backend
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
env:
{{- if .Values.springProfilesActive }} # ③ 조건부
- name: SPRING_PROFILES_ACTIVE
value: {{ .Values.springProfilesActive | quote }}
{{- end }}
{{- range $k, $v := .Values.env }} # ④ map 반복
- name: {{ $k }}
value: {{ $v | quote }}
{{- end }}
{{- if .Values.externalSecret.enabled }} # ⑤ 조건부 블록
envFrom:
- secretRef:
name: {{ .Release.Name }}-secret
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }} # ⑥ 통째로 끼워넣기번호별로:
- 단순한 값 치환
- 릴리스 이름을 라벨/셀렉터로 일관되게 사용 → 이름이 곧 식별자
springProfilesActive값이 있을 때만SPRING_PROFILES_ACTIVE환경변수 추가env맵을 순회하며 환경변수 추가 —DB_URL등이 여기로- ExternalSecret이 켜져 있으면
envFrom으로 K8s Secret 통째로 마운트 resources같은 복잡한 맵은toYaml로 통째로 — 들여쓰기 칸수(12)를 정확히 맞춰야 함
_helpers.tpl — 재사용 가능한 템플릿 함수
표준 차트에는 보통 templates/_helpers.tpl 이 있다 (밑줄 시작 = 렌더링 안 됨, 함수 정의용).
{{/* Common labels */}}
{{- define "mychart.labels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version }}
{{- end }}다른 템플릿에서:
metadata:
labels:
{{- include "mychart.labels" . | nindent 4 }}→ 여러 템플릿에서 같은 라벨/이름 규칙을 쓸 때 중복 제거 용도. 우리 차트는 작아서 안 쓰지만, 차트가 커지면 거의 필수다.
NOTES.txt
templates/NOTES.txt 가 있으면 helm install/upgrade 후 출력된다.
Thank you for installing {{ .Chart.Name }}.
Your release is named {{ .Release.Name }}.
Get the application URL by running:
kubectl port-forward svc/{{ .Release.Name }} 8080:80
→ 사용자에게 이제 뭐 해야 하는지 알려주는 용도다.
.helmignore
.gitignore 처럼 차트 패키징 시 제외할 파일을 적는다.
.git/
.idea/
*.swp
.DS_Store
helm package 로 .tgz를 만들 때 위 파일들이 안 들어간다.
디버깅 명령어 모음 ⭐
| 명령어 | 용도 |
|---|---|
helm template ./mychart | 렌더링된 YAML을 화면에 출력 (가장 자주 씀) |
helm template ./mychart -f values-prod.yaml | 특정 values로 렌더링 결과 확인 |
helm install --debug --dry-run | 실제 설치 안 하고 검증 |
helm lint ./mychart | 차트 문법/구조 검사 |
helm get manifest <release> | 클러스터에 설치된 매니페스트 보기 |
helm get values <release> | 적용된 values 보기 |
→ 템플릿 에러가 나면 무조건 helm template 부터 실행해 본다.
자주 하는 실수
⚠️ 들여쓰기 한 칸 차이로 깨짐 — 특히 toYaml | nindent N 의 N
⚠️ .Values.foo가 없을 때 nil 에러 — default 함수나 if 로 가드
⚠️ range 안에서 .Release.Name 이 안 나옴 — range 안의 . 이 바뀜. $.Release.Name 사용
⚠️ 숫자처럼 보이는 문자열을 따옴표 없이 — tag: 0.0.1 이 0.001이 될 수 있음. tag: "0.0.1" 로 작성
다음 글에서는 helm/backend, helm/ingress, helm/external-secrets 를 하나씩 분해해서 읽어본다.