Іменовані шаблони

Пора перейти від одного шаблону до створення інших. У цьому розділі ми побачимо, як визначити іменовані шаблони в одному файлі, а потім використовувати їх в інших місцях. Іменований шаблон (іноді його називають частковим або підшаблоном) — це просто шаблон, визначений у файлі, якому надали імʼя. Ми розглянемо два способи створення таких шаблонів і кілька способів їх використання.

У розділі Керування потоком ми представили три дії для оголошення та управління шаблонами: define, template і block. У цьому розділі ми розглянемо ці три дії, а також введемо спеціальну функцію include, яка працює подібно до дії template.

Важлива деталь, яку слід памʼятати при іменуванні шаблонів: імена шаблонів є глобальними. Якщо ви оголосите два шаблони з однаковим імʼям, використовуватиметься той, який був завантажений останнім. Оскільки шаблони в субчартах компілюються разом з шаблонами верхнього рівня, слід бути обережним при іменуванні шаблонів, використовуючи імена, специфічні для чарту.

Популярним способом іменування є префіксування кожного визначеного шаблону іменем чарту: {{ define "mychart.labels" }}. Використовуючи конкретне імʼя чарту як префікс, ми можемо уникнути будь-яких конфліктів, які можуть виникнути через два різних чарти, що реалізують шаблони з однаковим іменем.

Ця поведінка також стосується різних версій чарту. Якщо у вас є mychart версії 1.0.0, яка визначає шаблон одним способом, і mychart версії 2.0.0, яка змінює наявний іменований шаблон, буде використовуватися той, який був завантажений останнім. Ви можете обійти цю проблему, додавши версію в імʼя чарту: {{ define "mychart.v1.labels" }} та {{ define "mychart.v2.labels" }}.

Часткові та _ файли

До цього часу ми використовували один файл, і цей один файл містив один шаблон. Але мова шаблонів Helm дозволяє створювати іменовані вкладені шаблони, до яких можна звертатися за іменем в інших місцях.

Перед тим як перейти до деталей написання цих шаблонів, варто згадати про правила іменування файлів:

  • Більшість файлів у templates/ обробляються як файли з маніфестами Kubernetes
  • NOTES.txt є винятком
  • Але файли, імʼя яких починається з підкреслення (_), вважаються такими, що не мають маніфесту всередині. Ці файли не перетворюються на обʼєкти Kubernetes, але доступні в інших шаблонах чартів для використання.

Ці файли використовуються для зберігання часткових шаблонів і допоміжних функцій. Насправді коли ми вперше створили mychart, ми бачили файл з назвою _helpers.tpl. Цей файл є стандартним місцем для часткових шаблонів.

Оголошення та використання шаблонів з define та 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 }}

Коли рушій шаблонів читає цей файл, він зберігає посилання на mychart.labels до того часу, поки не буде викликано template "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" }}.

Встановлення області видимості шаблону

У шаблоні, який ми визначили вище, ми не використовували жодних обʼєктів. Ми просто використовували функції. Змінимо наш визначений шаблон, щоб включити назву чарту та версію чарту:

{{/* Генерація базових міток */}}
{{- 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, яка імплементує вміст шаблону в поточний конвеєр, де він може бути переданий іншим функціям у конвеєрі.

Ось приклад вище, виправлений з використанням 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"

Вважається за краще використовувати include замість template у шаблонах Helm, щоб краще керувати форматуванням виходу для YAML-документів.

Іноді ми хочемо імплементувати вміст, але не як шаблони. Тобто, ми хочемо імплементувати файли без змін. Ми можемо досягти цього, звертаючись до файлів через обʼєкт .Files, описаний у наступному розділі.