1. Go 템플릿 내부 동작
1.1 파싱(Parsing) 단계
Helm의 템플릿 엔진은 Go 표준 라이브러리의 `text/template`을 기반으로 합니다. 템플릿 처리는 크게 **파싱**과 **실행** 두 단계로 나뉩니다.
파싱 단계에서는 템플릿 텍스트를 **AST(Abstract Syntax Tree)**로 변환합니다:
템플릿 텍스트 → 렉서(Lexer) → 토큰 스트림 → 파서(Parser) → AST
**렉서**는 템플릿에서 다음 요소를 인식합니다:
- 일반 텍스트 (그대로 출력)
- 액션 구분자: 여는 구분자와 닫는 구분자
- 파이프라인, 변수, 함수 호출
1.2 실행(Execution) 단계
실행 단계에서는 AST를 순회하면서 데이터 컨텍스트(dot `.`)를 적용합니다:
AST + 데이터 컨텍스트 → 렌더링된 텍스트 출력
**파이프라인(Pipeline)** 개념이 핵심입니다:
파이프라인: 왼쪽 출력이 오른쪽 입력으로 전달
name: { { .Values.name | lower | trunc 63 | trimSuffix "-" } }
위 파이프라인의 실행 순서:
1. .Values.name 평가 → "My-Application-Name"
2. lower 적용 → "my-application-name"
3. trunc 63 적용 → "my-application-name" (63자 이하이므로 그대로)
4. trimSuffix "-" 적용 → "my-application-name"
1.3 공백 제어(Whitespace Control)
하이픈(-)으로 공백 제거
metadata:
labels:
{{- include "my-chart.labels" . | nindent 4 }}
#^ 왼쪽 공백/줄바꿈 제거
{{ include "my-chart.labels" . | nindent 4 -}}
^ 오른쪽 공백/줄바꿈 제거
{{- include "my-chart.labels" . | nindent 4 -}}
#^ 양쪽 모두 제거
2. Sprig 라이브러리 함수
2.1 문자열 함수
기본 문자열 조작
upper: {{ "hello" | upper }} # HELLO
lower: {{ "HELLO" | lower }} # hello
title: {{ "hello world" | title }} # Hello World
untitle: {{ "Hello World" | untitle }}# hello world
trim: {{ " hello " | trim }} # hello
trimAll: {{ "**hello**" | trimAll "*" }} # hello
부분 문자열
substr: {{ substr 0 5 "hello world" }} # hello
trunc: {{ "hello" | trunc 3 }} # hel
contains: {{ contains "lo" "hello" }} # true
hasPrefix: {{ hasPrefix "he" "hello" }} # true
hasSuffix: {{ hasSuffix "lo" "hello" }} # true
치환
replace: {{ "hello world" | replace " " "-" }} # hello-world
snakecase: {{ "HelloWorld" | snakecase }} # hello_world
camelcase: {{ "hello_world" | camelcase }} # HelloWorld
kebabcase: {{ "HelloWorld" | kebabcase }} # hello-world
정규 표현식
regexMatch: {{ regexMatch "^[a-z]+$" "hello" }} # true
regexReplaceAll: {{ regexReplaceAll "[0-9]+" "abc123" "X" }} # abcX
랜덤 문자열
randAlphaNum: {{ randAlphaNum 10 }} # 10자 랜덤 영숫자
2.2 수학 함수
기본 산술
add: { { add 1 2 } } # 3
sub: { { sub 10 3 } } # 7
mul: { { mul 2 3 } } # 6
div: { { div 10 3 } } # 3
mod: { { mod 10 3 } } # 1
max: { { max 1 2 3 } } # 3
min: { { min 1 2 3 } } # 1
반올림
ceil: { { ceil 1.1 } } # 2
floor: { { floor 1.9 } } # 1
round: { { round 1.5 0 } } # 2
2.3 날짜 함수
현재 시간
now: { { now } }
date: { { now | date "2006-01-02" } }
dateInZone: { { dateInZone "2006-01-02" (now) "UTC" } }
htmlDate: { { now | htmlDate } }
날짜 계산
dateModify: { { now | dateModify "+24h" | date "2006-01-02" } }
ago: { { ago (now) } }
2.4 암호화 함수
해싱
sha256sum: {{ "hello" | sha256sum }}
sha1sum: {{ "hello" | sha1sum }}
Base64
b64enc: {{ "hello" | b64enc }} # aGVsbG8=
b64dec: {{ "aGVsbG8=" | b64dec }} # hello
비밀번호 생성
genPassword: {{ randAlphaNum 16 }}
derivePassword: {{ derivePassword 1 "long" "password" "user" "example.com" }}
UUID
uuid: {{ uuidv4 }}
2.5 리스트 함수
리스트 생성과 조작
list: { { list "a" "b" "c" } }
first: { { first (list "a" "b" "c") } } # a
last: { { last (list "a" "b" "c") } } # c
rest: { { rest (list "a" "b" "c") } } # [b c]
initial: { { initial (list "a" "b" "c") } } # [a b]
append: { { append (list "a" "b") "c" } } # [a b c]
prepend: { { prepend (list "b" "c") "a" } } # [a b c]
concat: { { concat (list "a") (list "b") } } # [a b]
has: { { has "b" (list "a" "b" "c") } } # true
without: { { without (list "a" "b" "c") "b" } } # [a c]
uniq: { { uniq (list "a" "b" "a") } } # [a b]
sortAlpha: { { sortAlpha (list "c" "a" "b") } } # [a b c]
2.6 딕셔너리 함수
딕셔너리 생성과 조작
{{- $myDict := dict "key1" "value1" "key2" "value2" }}
get: {{ get $myDict "key1" }} # value1
set: {{ set $myDict "key3" "value3" }}
hasKey: {{ hasKey $myDict "key1" }} # true
keys: {{ keys $myDict }} # [key1 key2]
values: {{ values $myDict }} # [value1 value2]
pluck: {{ pluck "key1" $myDict }} # [value1]
merge: 여러 딕셔너리 병합
{{- $merged := merge (dict "a" "1") (dict "b" "2") }}
3. 네임드 템플릿(Named Templates)
3.1 define과 template
templates/_helpers.tpl
{{- define "my-chart.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "my-chart.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
3.2 template vs include
`template`과 `include`의 핵심 차이:
template: 결과를 직접 출력 (파이프라인 사용 불가)
{{ template "my-chart.name" . }}
include: 결과를 문자열로 반환 (파이프라인 사용 가능)
{{ include "my-chart.labels" . | nindent 4 }}
**include를 사용하는 것이 권장됩니다.** 파이프라인을 통해 후처리(들여쓰기 등)가 가능하기 때문입니다.
3.3 \_helpers.tpl 관례
templates/_helpers.tpl - 표준 헬퍼 패턴
차트 이름
{{- define "my-chart.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
전체 이름
{{- define "my-chart.fullname" -}}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
차트 버전 레이블
{{- define "my-chart.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
공통 레이블
{{- define "my-chart.labels" -}}
helm.sh/chart: {{ include "my-chart.chart" . }}
{{ include "my-chart.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
셀렉터 레이블
{{- define "my-chart.selectorLabels" -}}
app.kubernetes.io/name: {{ include "my-chart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
ServiceAccount 이름
{{- define "my-chart.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "my-chart.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
4. 플로우 컨트롤
4.1 if/else
기본 조건문
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "my-chart.fullname" . }}
spec:
rules:
- host: {{ .Values.ingress.host }}
{{- end }}
if/else if/else
{{- if eq .Values.service.type "NodePort" }}
nodePort: {{ .Values.service.nodePort }}
{{- else if eq .Values.service.type "LoadBalancer" }}
loadBalancerIP: {{ .Values.service.loadBalancerIP }}
{{- else }}
ClusterIP - no extra config
{{- end }}
**Falsy 값**: false, 0, 빈 문자열 "", nil, 빈 컬렉션(list, map, tuple, dict)
4.2 with
`with`는 스코프를 변경합니다:
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 2 }}
{{- end }}
주의: with 블록 내에서 상위 스코프 접근 시 $를 사용
{{- with .Values.container }}
name: {{ $.Chart.Name }}
image: {{ .image }}
port: {{ .port }}
{{- end }}
4.3 range
리스트 반복
{{- range .Values.extraEnvVars }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
맵 반복
{{- range $key, $value := .Values.configData }}
{{ $key }}: {{ $value | quote }}
{{- end }}
인덱스와 함께 반복
{{- range $index, $item := .Values.items }}
item-{{ $index }}: {{ $item }}
{{- end }}
고정 횟수 반복
{{- range until 5 }}
- replica-{{ . }}
{{- end }}
5. 고급 템플릿 패턴
5.1 toYaml과 nindent
toYaml: Go 객체를 YAML 문자열로 변환
nindent: n칸 들여쓰기 추가 (줄바꿈 포함)
spec:
template:
spec:
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
5.2 tpl 함수
`tpl`은 문자열을 템플릿으로 렌더링합니다:
values.yaml
configTemplate: |
server.name={{ .Release.Name }}
server.namespace={{ .Release.Namespace }}
templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: { { include "my-chart.fullname" . } }
data:
config.properties: |
{{ tpl .Values.configTemplate . | nindent 4 }}
5.3 lookup 함수
실행 시점에 클러스터 리소스를 조회합니다:
기존 Secret이 있으면 재사용, 없으면 생성
{{- $secret := lookup "v1" "Secret" .Release.Namespace "my-secret" }}
{{- if $secret }}
password: {{ index $secret.data "password" }}
{{- else }}
password: {{ randAlphaNum 16 | b64enc }}
{{- end }}
주의: `helm template` 명령에서는 lookup이 항상 빈 결과를 반환합니다.
5.4 .Files 객체
파일 내용을 ConfigMap에 포함
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "my-chart.fullname" . }}-config
data:
{{- range $path, $_ := .Files.Glob "config/**" }}
{{ base $path }}: |
{{ $.Files.Get $path | nindent 4 }}
{{- end }}
바이너리 파일 (base64)
binaryData:
{{- range $path, $_ := .Files.Glob "binaries/**" }}
{{ base $path }}: {{ $.Files.Get $path | b64enc }}
{{- end }}
6. 라이브러리 차트(Library Charts)
6.1 라이브러리 차트란
라이브러리 차트는 `type: library`로 선언된 차트로, 자체 리소스를 렌더링하지 않고 네임드 템플릿만 제공합니다.
Chart.yaml
apiVersion: v2
name: my-library
type: library
version: 1.0.0
6.2 라이브러리 차트 활용
부모 차트의 Chart.yaml
dependencies:
- name: my-library
version: '1.x.x'
repository: 'https://charts.example.com'
부모 차트의 템플릿에서 라이브러리 함수 사용
metadata:
labels: { { - include "my-library.standardLabels" . | nindent 4 } }
라이브러리 차트의 장점:
- 여러 차트에서 공통 템플릿 로직을 공유
- 표준 레이블, 어노테이션, 리소스 패턴을 중앙에서 관리
- DRY(Don't Repeat Yourself) 원칙 준수
7. 디버깅
7.1 helm template
로컬에서 템플릿 렌더링 (클러스터 불필요)
helm template my-release ./my-chart
특정 values 파일 적용
helm template my-release ./my-chart -f production-values.yaml
특정 템플릿만 렌더링
helm template my-release ./my-chart -s templates/deployment.yaml
디버그 출력
helm template my-release ./my-chart --debug
7.2 helm lint
차트 문법 검사
helm lint ./my-chart
엄격 모드 (경고도 오류로 처리)
helm lint ./my-chart --strict
values 파일과 함께 검증
helm lint ./my-chart -f production-values.yaml
8. 정리
Helm 템플릿 엔진은 단순한 문자열 치환을 넘어 **강력한 프로그래밍 모델**을 제공합니다:
1. **Go 템플릿 기반**: 파싱-실행 2단계 파이프라인 아키텍처
2. **Sprig 라이브러리**: 100개 이상의 유틸리티 함수 제공
3. **네임드 템플릿**: define/include로 재사용 가능한 템플릿 모듈화
4. **플로우 컨트롤**: if/with/range로 조건부 및 반복 렌더링
5. **라이브러리 차트**: 공통 로직의 중앙 관리와 공유
다음 글에서는 Helm 릴리스의 전체 생명주기(install, upgrade, rollback)를 분석합니다.
현재 단락 (1/236)
Helm의 템플릿 엔진은 Go 표준 라이브러리의 `text/template`을 기반으로 합니다. 템플릿 처리는 크게 **파싱**과 **실행** 두 단계로 나뉩니다.