Helmの_helpers.tplを使える人になりたい

# はじめに

Helmチャートの構成で出てくる``_helpers.tpl``の使い所がいまいちわかっていなかったので、自分が理解する目的も含めて本記事で書いてみます。

``helm create [チャート名]``でHelmチャートのひな形を作ると以下の構成ができると思いますが、``templates``ディレクトリにできる``_helpers.tpl``というファイルが今回記事の主題です。
```
.
├── Chart.yaml
├── charts
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl    # 一体どう使うの？？
│   ├── deployment.yaml
│   ...
└── values.yaml
```

# values.yamlの使い方の確認

``_helpers.tpl``の前に、まずは「``values.yaml``がどんな風な使われ方をするのか」を考えてみましょう。
``values.yaml``は、Helmチャートで使用されるデフォルトのパラメータが記載されたものです。以下は簡単な例ですが、Deploymentリソースのパラメータが定義されています。
```yaml:values.yaml
replicas: "3"
name: nginx
image: docker.io/nginx:1.25.1
```

templatesディレクトリには``deployment.yaml``があり、次のように``values.yaml``の値を参照しています。
```yaml:templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: {{ .Values.name }}
  name: {{ .Values.name }}-deployment
spec:
  replicas: {{ .Values.replicas }}
  selector:
    matchLabels:
      app: {{ .Values.name }}
  template:
    metadata:
      labels:
        app: {{ .Values.name }}
    spec:
      containers:
      - image: {{ .Values.image }}
        name: {{ .Values.name }}
```

デフォルトの値でそのままDeploymentリソースをデプロイする場合は、``helm install``コマンドを次のように実行します。
```
helm install nginx .
```

デフォルト以外の値にしたい場合は、例えば``--set``オプションを使って変更可能です。
```
helm install nginx . --set replicas=2
```

こうして改めて見てみると、Helmチャートによるデプロイを「プログラムの実行」と見なせば、**``values.yaml``はプログラムを実行する際のconfigファイルやコマンドライン引数に相当するもの**と思ってもよさそうです。

# もう一つDeploymentリソースが加わる

ここでDeploymentにnginxだけではなくて、httpdも使う必要が出てきました。httpdのDeploymentを追加で作ってみたところ、「マニフェストの構造が全く同じで値だけが違う」という結果になってしまいました...
現在のディレクトリ構造は以下のような感じです。``deployment-httpd.yaml``と``deployment-nginx.yaml``がだいたい同じような内容になっている状態で悩ましいのです。
```
.
├── Chart.yaml
├── charts
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl          # どう使うの？
│   ├── deployment-httpd.yaml # ↓ほぼ同じ内容...
│   └── deployment-nginx.yaml # ↑ほぼ同じ内容...
└── values.yaml
```

プログラムを書いた経験のある方だと「全く同じ内容が出てきたら関数にすればいいんじゃない！？」と思われるかもしれません。Helmチャートでも関数みたいな書き方が出来ると今の冗長な書き方を回避できるのですが、そんな事ができるのでしょうか？

# _helpers.tplで関数みたいに書いてみる

結論から言うと **``_helpers.tpl``で関数のような書き方をすることができ**、上の課題を解決できます。
細かい説明は置いといて、先に出てきた``templates/deployment.yaml``を少し変えて、Deploymentリソースのテンプレートを``_helpers.tpl``に書いてみましょう。

```yaml:templates/_helpers.tpl
{{- define "deployment" -}}
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: {{ .name }}
  name: {{ .name }}-deployment
spec:
  replicas: {{ .replicas }}
  selector:
    matchLabels:
      app: {{ .name }}
  template:
    metadata:
      labels:
        app: {{ .name }}
    spec:
      containers:
      - image: {{ .image }}
        name: {{ .name }}
{{- end -}}
```

``templates/deployment.yaml``との違いは以下の2点です。
* 変数を参照する``{{ .Values.xxxx }}``の記述を``{{ .xxxx }}``に変更する
* 最初に``{{- define "deployment" -}}``、最後に``{{- end -}}``をつける

若干ネタバレになりますが、関数に例えると上記の内容は今後以下のように使われていきます。
* ``{{- define "deployment" -}}``の``deployment``が関数の名前
* その中で使われている``{{ .xxxx }}``が関数の引数を通して渡された変数
* 戻り値は``{{- define "deployment" -}}``から``{{- end -}}``までの部分に変数を代入してレンダリングされたテキスト（具体的にはDeploymentのマニフェスト）

# 関数を呼ぶ部分を作る

続いて``_helpers.tpl``にdefineした``deployment``を呼ぶ部分を作ります。プログラムの関数だと``deployment(引数)``のような書き方になると思いますが、Helmの場合は

**``{{ include "deployment" 引数 }}``**

のような書き方で実現できます。

:::note info
``{{ include ... }}`` は ``{{ template ... }}``とも書けるようですが、ざっと調べた感じ基本的には``include``を使っていてよさそうです。
:::

この書き方ができれば、各DeploymentのYAMLファイルは次のようにたったの1行で表現できます。

```yaml:deployment-nginx.yaml
{{ include "deployment" nginx用の引数 }}
```
```yaml:deployment-httpd.yaml
{{ include "deployment" httpd用の引数 }}
```

# 関数の引数として使う変数を設定する

上記各YAMLファイルで「nginx用の引数」「httpd用の引数」と曖昧に書きましたが、具体的にどのように設定すればいいのでしょうか？
この「引数」に相当する部分は、``value.yaml``に定義した値を設定することができます。今回はnginx用とhttpd用とで値を設定したいので、``value.yaml``に次のように書いてみます。

```yaml:values.yaml
nginx:
  replicas: "1"
  name: nginx
  image: docker.io/nginx:1.25.1
httpd:
  replicas: "3"
  name: httpd
  image: docker.io/httpd:2.4.57
```

このように書くと、includeの引数は次のように指定してvalues.yamlの値を取得できます。

```yaml:deployment-nginx.yaml
{{ include "deployment" .Values.nginx }}
```
```yaml:deployment-httpd.yaml
{{ include "deployment" .Values.httpd }}
```

:::note info
**少し解説**
``.Values.nginx``と``.Values.httpd``は、それぞれ「``values.yaml``の``nginx``および``httpd``の配下を引数として渡している」感じになります。``nginx``と``httpd``の下には``replicas``、``name``、``image``のKeyValueがありますが、これら3つが``deployment``の ``.replicas``、``.name``、``.image``に対応しています。
:::

# デプロイの確認

次のように問題なくデプロイできるのを確認できます。

```
helm install nginx-and-httpd .
```

```
$ kubectl get deploy
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
httpd-deployment   3/3     3            3           54s
nginx-deployment   1/1     1            1           54s
```

```
$ kubectl get po
NAME                                READY   STATUS    RESTARTS   AGE
httpd-deployment-77d86486fb-k4d5l   1/1     Running   0          73s
httpd-deployment-77d86486fb-l2wm7   1/1     Running   0          73s
httpd-deployment-77d86486fb-r2nf4   1/1     Running   0          73s
nginx-deployment-5c5c5dfff9-w25hz   1/1     Running   0          73s
```

# おわりに

実際の例をもとに``_helpers.tpl``を使い、**``define``がプログラムの関数のような使い方ができる**ことを確認できました。``define``は今回ご紹介した内容以外でも便利な使い方ができるはずなので、私自身も今後勉強していきたいと思います。