일반 템플릿

일반 템플릿 (Named Templates)

이 문서는 다른 곳에서 재사용할 수 있는 일반 템플릿(named templates)을 정의하고 사용하는 방법을 다뤄요. define, template, block 액션과 include 함수, 템플릿 범위 설정을 설명해요.

출처: 문서

본문

하나의 템플릿을 넘어 다른 템플릿을 만들기 시작할 때예요. 이 섹션에서는 한 파일에서 일반 템플릿을 정의하고 다른 곳에서 사용하는 방법을 볼 거예요. 일반 템플릿(때로 부분(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" }}) 이 문제를 해결할 수 있어요.

부분과 _ 파일 (Partials and _ files)

지금까지 하나의 파일을 사용했고 그 파일은 단일 템플릿을 담고 있었어요. 하지만 Helm의 템플릿 언어는 다른 곳에서 이름으로 접근할 수 있는 이름이 있는 임베디드 템플릿을 만들 수 있게 해줘요.

그 템플릿을 작성하는 요점에 들어가기 전에 언급할 가치가 있는 파일 명명 규칙이 있어요:

  • templates/의 대부분의 파일은 Kubernetes 매니페스트를 포함하는 것으로 취급돼요.

  • NOTES.txt는 한 가지 예외예요.

  • 하지만 밑줄(_)로 시작하는 이름의 파일은 매니페스트를 가지고 있지 않은 것으로 가정돼요. 이 파일들은 Kubernetes 객체 정의로 렌더링되지 않지만, 다른 차트 템플릿 안 어디에서나 사용할 수 있어요.

이 파일들은 부분과 헬퍼를 저장하는 데 사용돼요. 실제로 mychart를 처음 만들었을 때 _helpers.tpl이라는 파일을 봤어요. 그 파일은 템플릿 부분의 기본 위치예요.

definetemplate으로 템플릿 선언 및 사용 (Declaring and using templates with define and template)

define 액션은 템플릿 파일 안에서 일반 템플릿을 만들 수 있게 해줘요. 그 구문은 다음과 같아요:

{{- define "MY.NAME" }}
  # 여기에 템플릿 본문
{{- 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은 이 예시처럼 템플릿으로 호출되지 않으면 출력을 생성하지 않아요.

관례적으로 Helm 차트는 이러한 템플릿을 부분 파일(보통 _helpers.tpl)에 넣어요. 이 함수를 그곳으로 옮겨 봐요:

{{/* 기본 레이블 생성 */}}
{{- 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" }}.

템플릿의 범위 설정 (Setting the scope of a template)

위에서 정의한 템플릿에서는 어떤 객체도 사용하지 않았어요. 함수만 사용했어요. 정의된 템플릿에 차트 이름과 차트 버전을 포함하도록 수정해 봐요:

{{/* 기본 레이블 생성 */}}
{{- 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 함수 (The include function)

이렇게 생긴 간단한 템플릿을 정의했다고 합시다:

{{- 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의 대안을 제공해요.

다음은 nindent를 사용해 mychart.app 템플릿을 올바르게 들여쓰도록 수정한 위 예시예요:

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
  labels:
    {{- include "mychart.app" . | nindent 4 }}
data:
  myvalue: "Hello World"
  {{- range $key, $val := .Values.favorite }}
  {{ $key }}: {{ $val | quote }}
  {{- end }}
  {{- include "mychart.app" . | nindent 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 템플릿에서 YAML 문서의 출력 포맷을 더 잘 처리할 수 있도록 단순히 template보다 include를 사용하는 것이 더 선호되는 것으로 간주돼요.

때로는 내용을 가져오되 템플릿으로는 가져오지 않으려 할 수 있어요. 즉, 파일을 그대로(verbatim) 가져오고 싶을 수 있어요. 다음 섹션에서 설명하는 .Files 객체를 통해 파일에 접근하면 이를 달성할 수 있어요.

더 알아보기 (Learn more)