-
[Helm] 가이드 - 템플릿공부/쿠버네티스&헬름 2025. 3. 15. 21:31
named template(때로는 partial 또는 subtemplate이라고도 함)은 단순히 파일 내부에 정의되고 이름이 부여된 템플릿입니다. 이를 생성하는 두 가지 방법과 사용하는 몇 가지 다른 방법을 살펴볼 것입니다.
템플릿 이름을 지정할 때 중요한 세부 정보는 템플릿 이름은 전역적이라는 것입니다. 동일한 이름으로 두 개의 템플릿을 선언하면 마지막에 로드된 템플릿이 사용됩니다. 서브차트의 템플릿은 최상위 레벨 템플릿과 함께 컴파일되므로, 템플릿 이름을 차트별 고유한 이름으로 지정하는 데 주의해야 합니다.
일반적인 명명 규칙 중 하나는 정의된 각 템플릿에 차트 이름을 접두사로 붙이는 것입니다. 예를 들어
{{ define "mychart.labels" }}
와 같이 사용합니다. 특정 차트 이름을 접두사로 사용하면 서로 다른 두 차트가 동일한 이름의 템플릿을 구현하여 발생할 수 있는 충돌을 피할 수 있습니다.이 동작은 차트의 다른 버전에도 적용됩니다.
mychart
버전1.0.0
이 특정 방식으로 템플릿을 정의하고,mychart
버전2.0.0
이 기존 명명된 템플릿을 수정하는 경우, 마지막에 로드된 템플릿이 사용됩니다. 차트 이름에 버전을 추가하여 이 문제를 해결할 수 있습니다. 예를 들어{{ define "mychart.v1.labels" }}
와{{ define "mychart.v2.labels" }}
와 같이 사용할 수 있습니다.define과 template
define
액션을 사용하면 템플릿 파일 내부에 명명된 템플릿을 만들 수 있습니다. 문법은 다음과 같습니다.{{- define "MY.NAME" }} # body of template here {{- end }}
예를 들어, Kubernetes label 블록을 캡슐화하는 템플릿을 정의할 수 있습니다.
{{- define "mychart.labels" }} labels: generator: helm date: {{ now | htmlDate }} {{- end }}
이제 이 템플릿을 기존 ConfigMap 내부에 포함하고
template
액션을 사용하여 삽입할 수 있습니다.{{- define "mychart.labels" }} labels: generator: helm date: {{ now | htmlDate }} {{- end }} apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap {{- template "mychart.labels" }} data: myvalue: "Hello World" {{- range $key, $val := .Values.favorite }} {{ $key }}: {{ $val | quote }} {{- end }}
템플릿 엔진이 이 파일을 읽으면
template "mychart.labels"
가 호출될 때까지mychart.labels
에 대한 참조를 저장해 둡니다. 그런 다음 해당 템플릿을 현재 위치에 렌더링합니다. 따라서 결과는 다음과 같습니다.# Source: mychart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: running-panda-configmap labels: generator: helm date: 2016-11-02 data: myvalue: "Hello World" drink: "coffee" food: "pizza"
참고:
define
는 이 예시에서처럼template
으로 호출되지 않는 한 출력을 생성하지 않습니다.관례적으로 Helm 차트는 이러한 템플릿들을 파셜(partials) 파일인
_helpers.tpl
안에 넣습니다. 이 함수를 그곳으로 옮겨 보겠습니다. _helpers.tpl에 관련된 내용은 https://brownbears.tistory.com/699 에서 자세히 확인할 수 있습니다.템플릿 범위 설정
위에서 정의한 템플릿에서는 어떤 객체도 사용하지 않고 함수만 사용했습니다. 차트 이름과 차트 버전을 포함하도록 정의된 템플릿을 수정해 보겠습니다.
{{/* Generate basic labels */}} {{- define "mychart.labels" }} labels: generator: helm date: {{ now | htmlDate }} chart: {{ .Chart.Name }} version: {{ .Chart.Version }} {{- end }} ------ # Source: mychart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: moldy-jaguar-configmap labels: generator: helm date: 2021-03-06 chart: version:
이름과 버전은 정의된 템플릿의 스코프에 없었습니다.
define
으로 생성된 명명된 템플릿이 렌더링될 때,template
호출에 의해 전달된 스코프를 받게 됩니다. 우리 예시에서는 다음과 같이 템플릿을 포함했습니다.{{- template "mychart.labels" }}
스코프가 전달되지 않았으므로 템플릿 내에서
.
을 통해 아무것도 접근할 수 없습니다. 하지만 이 문제는 쉽게 해결할 수 있습니다. 간단히 템플릿에 스코프를 전달하면 됩니다.apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap {{- template "mychart.labels" . }}
template
호출의 끝에.
을 전달한 것을 주목하세요..Values
나.Values.favorite
또는 원하는 어떤 스코프든 마찬가지로 쉽게 전달할 수 있습니다. 하지만 우리가 원하는 것은 최상위 스코프입니다. 명명된 템플릿의 컨텍스트에서$
는 어떤 전역 스코프가 아니라 여러분이 전달한 스코프를 참조하게 됩니다.include
{{- define "mychart.app" -}} app_name: {{ .Chart.Name }} app_version: "{{ .Chart.Version }}" {{- end -}}
위 템플릿에
labels:
섹션과data:
섹션 모두에 삽입하고 싶다고 가정해 봅시다.apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap labels: {{ template "mychart.app" . }} data: myvalue: "Hello World" {{- range $key, $val := .Values.favorite }} {{ $key }}: {{ $val | quote }} {{- end }} {{ template "mychart.app" . }} --------- # Source: mychart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: measly-whippet-configmap labels: app_name: mychart app_version: "0.1.0" data: myvalue: "Hello World" drink: "coffee" food: "pizza" app_name: mychart app_version: "0.1.0"
app_version
의 들여쓰기가 두 곳 모두 잘못된 것을 확인하세요. 삽입된 템플릿의 텍스트가 왼쪽으로 정렬되어 있기 때문입니다.template
은 액션이지 함수가 아니므로template
호출의 출력을 다른 함수로 전달할 방법이 없습니다. 데이터는 단순히 현재 위치에 삽입됩니다.이러한 경우를 해결하기 위해 Helm은
template
의 대안으로 템플릿의 내용을 현재 파이프라인으로 가져와 파이프라인의 다른 함수로 전달할 수 있는 기능을 제공합니다.위의 예시를
indent
함수를 사용하여mychart.app
템플릿을 올바르게 들여쓰도록 수정했습니다.apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap labels: {{ include "mychart.app" . | indent 4 }} data: myvalue: "Hello World" {{- range $key, $val := .Values.favorite }} {{ $key }}: {{ $val | quote }} {{- end }} {{ include "mychart.app" . | indent 2 }} ----- # Source: mychart/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: edgy-mole-configmap labels: app_name: mychart app_version: "0.1.0" data: myvalue: "Hello World" drink: "coffee" food: "pizza" app_name: mychart app_version: "0.1.0"
Helm 템플릿에서는 YAML 문서의 출력 형식을 더 잘 처리할 수 있도록 단순히
template
대신include
를 사용하는 것이 더 바람직하게 여겨집니다.템플릿 안에서 파일 접근하기
템플릿이 아닌 파일을 가져와서 해당 내용을 템플릿 렌더러를 거치지 않고 직접 삽입을 해야 될 때가 있습니다. Helm은
.Files
객체를 통해 파일에 접근하는 기능을 제공합니다. 하지만 템플릿 예제를 시작하기 전에 이 기능이 어떻게 작동하는지에 대한 몇 가지 주의 사항이 있습니다.- Helm 차트에 추가 파일을 포함하는 것은 괜찮습니다. 이러한 파일은 번들로 묶입니다. 하지만 Kubernetes 객체의 저장 용량 제한으로 인해 차트 크기는 1MB 미만이어야 합니다.
- 일반적으로 보안상의 이유로
.Files
객체를 통해 접근할 수 없는 파일들이 있습니다.templates/
디렉토리의 파일은 접근할 수 없습니다..helmignore
를 사용하여 제외된 파일은 접근할 수 없습니다.- 부모 차트를 포함하여 Helm 애플리케이션 서브차트 외부의 파일은 접근할 수 없습니다.
- 차트는 UNIX 모드 정보를 보존하지 않으므로
.Files
객체를 사용할 때 파일 수준의 권한은 파일 접근 가능성에 영향을 미치지 않습니다.
기본 예시
이러한 주의 사항을 염두에 두고, 세 개의 파일을 읽어 ConfigMap에 넣는 템플릿을 작성해 보겠습니다. 시작하려면 차트에 세 개의 파일을 추가하고 이 세 파일을 모두
mychart/
디렉토리 바로 아래에 넣을 것입니다.-- config1.toml message = Hello from config 1 -- config2.toml message = This is config 2 -- config3.toml message = Goodbye from config 3
이 파일들은 모두 간단한 TOML 파일이고 파일 이름을 알고 있으므로
range
함수를 사용하여 이 파일들을 반복 처리하고 그 내용을 ConfigMap에 삽입할 수 있습니다.apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-configmap data: {{- $files := .Files }} {{- range tuple "config1.toml" "config2.toml" "config3.toml" }} {{ . }}: |- {{ $files.Get . }} {{- end }}
path helper
파일 작업을 할 때 파일 경로 자체에 대한 몇 가지 표준 작업을 수행하는 것이 매우 유용할 수 있습니다. 이를 돕기 위해 Helm은 Go의
path
패키지에 있는 많은 함수를 가져와서 사용할 수 있도록 제공합니다. 이러한 함수들은 Go 패키지에서와 동일한 이름으로 접근할 수 있지만, 첫 글자는 소문자로 변경됩니다. 예를 들어,Base
는base
가 됩니다.가져온 함수는 다음과 같습니다.
- base
- dir
- ext
- isAbs
- clean
glob 패턴
차트가 커짐에 따라 파일을 더 많이 정리해야 할 필요성이 커질 수 있으며, glob 패턴의 모든 유연성을 활용하여 특정 파일을 추출하는 데 도움이 되는
.Files.Glob(pattern string)
메서드를 제공합니다..Glob
은Files
타입을 반환하므로, 반환된 객체에서Files
타입의 모든 메서드를 호출할 수 있습니다.아래와 같은 디렉토리 구조가 있다고 가정합니다.
foo/ foo.txt foo.yaml bar/ bar.go bar.conf baz.yaml
다양한 glob 패턴을 사용할 수 있습니다.
{{ $currentScope := .}} {{ range $path, $_ := .Files.Glob "**.yaml" }} {{- with $currentScope}} {{ .Files.Get $path }} {{- end }} {{ end }} --- 또는 {{ range $path, $_ := .Files.Glob "**.yaml" }} {{ $.Files.Get $path }} {{ end }}
configmap과 secret 유틸리티 함수
(Helm 2.0.2 이상에서 사용 가능)
파일 내용을 ConfigMap과 Secret 모두에 넣어 런타임 시 파드에 마운트하는 것은 매우 일반적인 요구 사항입니다. 이를 돕기 위해
Files
타입에 몇 가지 유틸리티 메서드를 제공합니다.더욱 체계적인 관리를 위해 이러한 메서드를
Glob
메서드와 함께 사용하는 것이 특히 유용합니다.위의
Glob
예제의 디렉토리 구조가 주어졌을 때 다음과 같이 표현이 가능합니다.--- apiVersion: v1 kind: ConfigMap metadata: name: conf data: {{ (.Files.Glob "foo/*").AsConfig | indent 2 }} --- apiVersion: v1 kind: Secret metadata: name: very-secret type: Opaque data: {{ (.Files.Glob "bar/*").AsSecrets | indent 2 }}
인코딩
파일을 가져와서 템플릿이 base64로 인코딩하여 성공적인 전송을 보장하도록 할 수 있습니다.
apiVersion: v1 kind: Secret metadata: name: {{ .Release.Name }}-secret type: Opaque data: token: |- {{ .Files.Get "config1.toml" | b64enc }}
lines
템플릿에서 파일의 각 줄에 접근하는 것이 바람직할 때가 있습니다. 이를 위해 편리한
Lines
메서드를 제공합니다.range
함수를 사용하여Lines
를 반복 처리할 수 있습니다.data: some-file.txt: {{ range .Files.Lines "foo/bar.txt" }} {{ . }}{{ end }}
규칙
templates/ 구조
templates/
디렉토리는 다음과 같이 구성되어야 합니다.- 템플릿 파일이 YAML 출력을 생성하는 경우 확장자는
.yaml
이어야 합니다. 형식화된 콘텐츠를 생성하지 않는 템플릿 파일에는.tpl
확장자를 사용할 수 있습니다. - 템플릿 파일 이름은 카멜 케이스가 아닌 대시 표기법(my-example-configmap.yaml)을 사용해야 합니다.
- 각 리소스 정의는 자체 템플릿 파일에 있어야 합니다.
- 템플릿 파일 이름은 이름에 리소스 종류를 반영해야 합니다. 예:
foo-pod.yaml
,bar-svc.yaml
defined 템플릿 이름
{{ define }}
지시문 내에서 생성된 정의된 템플릿은 전역적으로 접근 가능합니다. 이는 하나의 차트와 그 모든 서브차트가{{ define }}
으로 생성된 모든 템플릿에 접근할 수 있다는 의미입니다.이러한 이유로, 모든 정의된 템플릿 이름은 네임스페이스되어야 합니다.
# 좋은 방식 {{- define "nginx.fullname" }} {{/* ... */}} {{ end -}} # 잘못된 방식 {{- define "fullname" -}} {{/* ... */}} {{ end -}}
템플릿 포맷팅
템플릿은 두 칸 공백을 사용하여 들여쓰기해야 합니다 (탭은 사용 X). 템플릿 지시문은 여는 중괄호 뒤와 닫는 중괄호 앞에 공백이 있어야 합니다.
# 옳은 방식 {{ .foo }} {{ print "foo" }} {{- print "bar" -}} # 잘못된 방식 {{.foo}} {{print "foo"}} {{-print "bar"-}}
가능한 경우 템플릿은 공백을 제거해야 합니다.
foo: {{- range .Values.items }} {{ . }} {{ end -}}
블록 (제어 구조 등)은 템플릿 코드의 흐름을 나타내기 위해 들여쓰기될 수 있습니다.
{{ if $foo -}} {{- with .Bar }}Hello{{ end -}} {{- end -}}
하지만 YAML은 공백 기반 언어이므로, 코드 들여쓰기가 항상 그 규칙을 따르는 것은 종종 불가능합니다.
템플릿 공백
생성된 템플릿의 공백 양을 최소한으로 유지하는 것이 바람직합니다. 특히, 여러 개의 빈 줄이 서로 인접하여 나타나서는 안 됩니다. 하지만 가끔씩 빈 줄(특히 논리적인 섹션 사이)이 있는 것은 괜찮습니다.
# 최고 apiVersion: batch/v1 kind: Job metadata: name: example labels: first: first second: second # 이정도는 ok apiVersion: batch/v1 kind: Job metadata: name: example labels: first: first second: second # 안좋음 apiVersion: batch/v1 kind: Job metadata: name: example labels: first: first second: second
주석 (yaml 주석 VS 템플릿 주석)
-- yaml 주석 # This is a comment type: sprocket -- 템플릿 주석 {{- /* This is a comment. */}} type: frobnitz
템플릿 주석은 정의된 템플릿을 설명하는 것과 같이 템플릿의 기능을 문서화할 때 사용해야 합니다.
{{- /* mychart.shortname provides a 6 char truncated version of the release name. */}} {{ define "mychart.shortname" -}} {{ .Release.Name | trunc 6 }} {{- end -}}
템플릿 내부에서 YAML 주석은 Helm 사용자가 디버깅 중에 주석을 보는 것이 유용할 때 사용할 수 있습니다.
# This may cause problems if the value is more than 100Gi memory: {{ .Values.maxMem | quote }}
위의 주석은 사용자가
helm install --debug
를 실행할 때 표시되지만,{{- /* */}}
섹션에 지정된 주석은 표시되지 않습니다. 특정 템플릿 함수에서 필요로 할 수 있는 Helm 값을 포함하는 템플릿 섹션에#
YAML 주석을 추가하는 것을 주의해야 합니다. 예를 들어, 위의 예시에required
함수가 도입되고maxMem
이 설정되지 않은 경우,#
YAML 주석은 렌더링 오류를 발생시킬 것입니다.-- 옳은 방법 (helm template은 아래 블록을 렌더링 하지 않음) {{- /* # This may cause problems if the value is more than 100Gi memory: {{ required "maxMem must be set" .Values.maxMem | quote }} */ -}} -- 잘못된 방법 (helm template은 오류를 발생시킴) # This may cause problems if the value is more than 100Gi # memory: {{ required .Values.maxMem "maxMem must be set" | quote }}
템플릿, 템플릿 출력에서 json 사용
YAML은 JSON의 상위 집합입니다. 어떤 경우에는 JSON 구문을 사용하는 것이 다른 YAML 표현보다 더 읽기 쉬울 수 있습니다.
예를 들어, 다음 YAML은 일반적인 YAML의 리스트 표현 방식과 더 유사합니다.
arguments: - "--dirname" - "/foo" -- 아래 json으로도 표현 가능 arguments: ["--dirname", "/foo"]
가독성 향상을 위해 JSON을 사용하는 것은 좋습니다. 하지만 더 복잡한 구조를 표현하는 데 JSON 구문을 사용해서는 안 됩니다. YAML 내부에 순수 JSON이 포함된 경우 (예: init 컨테이너 구성), JSON 형식을 사용하는 것이 물론 적절합니다.
레퍼런스
https://helm.sh/docs/chart_best_practices/templates/