본문으로 건너뛰기
버전: 3.19.0

네임드 템플릿

이제 하나의 템플릿을 넘어 여러 템플릿을 만들어 볼 차례이다. 이 섹션에서는 한 파일에서 _네임드 템플릿_을 정의하고 다른 곳에서 사용하는 방법을 알아본다. 네임드 템플릿(때때로 partial 또는 _subtemplate_이라고도 함)은 단순히 파일 내에 정의되어 이름이 부여된 템플릿이다. 템플릿을 만드는 두 가지 방법과 사용하는 여러 방법을 살펴보겠다.

흐름 제어 섹션에서 템플릿을 선언하고 관리하기 위한 세 가지 액션 define, template, block을 소개했다. 이 섹션에서는 이 세 가지 액션을 다루고, template 액션과 유사하게 작동하는 특수 목적의 include 함수도 소개한다.

템플릿 이름을 지정할 때 염두에 두어야 할 중요한 사항: 템플릿 이름은 전역적이다. 동일한 이름으로 두 개의 템플릿을 선언하면 마지막에 로드된 것이 사용된다. 서브차트의 템플릿은 최상위 템플릿과 함께 컴파일되므로 _차트별 고유 이름_으로 템플릿 이름을 지정해야 한다.

일반적인 명명 규칙은 정의된 각 템플릿 앞에 차트 이름을 접두사로 붙이는 것이다: {{ define "mychart.labels" }}. 특정 차트 이름을 접두사로 사용하면 동일한 이름의 템플릿을 구현하는 서로 다른 두 차트로 인해 발생할 수 있는 충돌을 피할 수 있다.

이 동작은 차트의 다른 버전에도 적용된다. 한 가지 방식으로 템플릿을 정의한 mychart 버전 1.0.0이 있고, 기존 네임드 템플릿을 수정하는 mychart 버전 2.0.0이 있다면 마지막에 로드된 것이 사용된다. 차트 이름에 버전을 추가하여 이 문제를 해결할 수 있다: {{ define "mychart.v1.labels" }}{{ define "mychart.v2.labels" }}.

Partial과 _ 파일

지금까지 하나의 파일을 사용했고, 그 파일에는 단일 템플릿이 포함되어 있었다. 하지만 Helm의 템플릿 언어를 사용하면 다른 곳에서 이름으로 접근할 수 있는 네임드 임베디드 템플릿을 만들 수 있다.

템플릿 작성의 핵심에 들어가기 전에, 언급할 가치가 있는 파일 명명 규칙이 있다:

  • templates/에 있는 대부분의 파일은 Kubernetes 매니페스트를 포함하는 것으로 취급된다
  • NOTES.txt는 예외이다
  • 그러나 이름이 밑줄(_)로 시작하는 파일은 내부에 매니페스트가 없는 것으로 간주된다. 이 파일들은 Kubernetes 오브젝트 정의로 렌더링되지 않지만, 다른 차트 템플릿 어디서나 사용할 수 있다.

이 파일들은 partial과 helper를 저장하는 데 사용된다. 실제로 처음 mychart를 만들 때 _helpers.tpl이라는 파일을 보았다. 이 파일은 템플릿 partial의 기본 위치이다.

definetemplate으로 템플릿 선언 및 사용하기

define 액션을 사용하면 템플릿 파일 내에 네임드 템플릿을 만들 수 있다. 문법은 다음과 같다:

{{- define "MY.NAME" }}
# body of template here
{{- end }}

예를 들어, Kubernetes 레이블 블록을 캡슐화하는 템플릿을 정의할 수 있다:

{{- 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 차트는 이러한 템플릿을 partial 파일, 보통 _helpers.tpl에 넣는다. 이 함수를 그곳으로 옮겨 보자:

{{/* Generate basic labels */}}
{{- define "mychart.labels" }}
labels:
generator: helm
date: {{ now | htmlDate }}
{{- end }}

관례상, define 함수에는 기능을 설명하는 간단한 문서화 블록 ({{/* ... */}})이 있어야 한다.

이 정의가 _helpers.tpl에 있더라도 configmap.yaml에서 여전히 접근할 수 있다:

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 }}

위에서 언급했듯이, 템플릿 이름은 전역적이다. 결과적으로 동일한 이름으로 두 개의 템플릿이 선언되면 마지막 것이 사용된다. 서브차트의 템플릿은 최상위 템플릿과 함께 컴파일되므로 _차트별 고유 이름_으로 템플릿 이름을 지정하는 것이 좋다. 일반적인 명명 규칙은 정의된 각 템플릿 앞에 차트 이름을 접두사로 붙이는 것이다: {{ define "mychart.labels" }}.

템플릿 스코프 설정하기

위에서 정의한 템플릿에서는 어떤 오브젝트도 사용하지 않았다. 함수만 사용했다. 차트 이름과 차트 버전을 포함하도록 정의된 템플릿을 수정해 보자:

{{/* Generate basic labels */}}
{{- define "mychart.labels" }}
labels:
generator: helm
date: {{ now | htmlDate }}
chart: {{ .Chart.Name }}
version: {{ .Chart.Version }}
{{- end }}

이것을 렌더링하면 다음과 같은 오류가 발생한다:

$ helm install --dry-run moldy-jaguar ./mychart
Error: unable to build kubernetes objects from release manifest: error validating "": error validating data: [unknown object type "nil" in ConfigMap.metadata.labels.chart, unknown object type "nil" in ConfigMap.metadata.labels.version]

렌더링 결과를 보려면 --disable-openapi-validation과 함께 다시 실행한다: helm install --dry-run --disable-openapi-validation moldy-jaguar ./mychart. 결과는 예상한 것과 다르다:

# 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 또는 원하는 스코프를 쉽게 전달할 수 있다. 하지만 우리가 원하는 것은 최상위 스코프이다. 네임드 템플릿의 컨텍스트에서 $는 전역 스코프가 아니라 전달한 스코프를 참조한다.

이제 helm install --dry-run --debug plinking-anaco ./mychart로 이 템플릿을 실행하면 다음을 얻는다:

# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: plinking-anaco-configmap
labels:
generator: helm
date: 2021-03-06
chart: mychart
version: 0.1.0

이제 {{ .Chart.Name }}mychart로, {{ .Chart.Version }}0.1.0으로 해석된다.

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" . }}

이것을 렌더링하면 다음과 같은 오류가 발생한다:

$ helm install --dry-run measly-whippet ./mychart
Error: unable to build kubernetes objects from release manifest: error validating "": error validating data: [ValidationError(ConfigMap): unknown field "app_name" in io.k8s.api.core.v1.ConfigMap, ValidationError(ConfigMap): unknown field "app_version" in io.k8s.api.core.v1.ConfigMap]

렌더링 결과를 보려면 --disable-openapi-validation과 함께 다시 실행한다: helm install --dry-run --disable-openapi-validation measly-whippet ./mychart. 출력은 예상한 것과 다르다:

# 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의 대안으로 템플릿의 내용을 현재 파이프라인으로 가져와서 파이프라인의 다른 함수에 전달할 수 있게 해주는 include를 제공한다.

다음은 위의 예제를 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 }}

이제 생성된 YAML은 각 섹션에 대해 올바르게 들여쓰기된다:

# 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 템플릿에서 template보다 include를 사용하는 것이 선호된다. 단순히 YAML 문서에 대한 출력 포맷팅을 더 잘 처리할 수 있기 때문이다.

때로는 컨텐츠를 템플릿이 아닌 그대로 가져오고 싶을 때가 있다. 즉, 파일을 있는 그대로 가져오고 싶은 경우이다. 다음 섹션에서 설명하는 .Files 오브젝트를 통해 파일에 접근하면 이를 달성할 수 있다.