チャート開発のヒントとコツ

このガイドでは、本番品質のチャートを構築する際にHelmチャート開発者が学んだヒントとコツについて説明します。

テンプレート関数を知る

Helmは、リソースファイルのテンプレート化にGoテンプレートを使用します。Goにはいくつかの組み込み関数が付属していますが、他にも多くの関数を追加しました。

まず、セキュリティ上の理由から、envexpandenvを除く、Sprigライブラリのすべての関数を追加しました。

また、includerequiredという2つの特別なテンプレート関数を追加しました。include関数を使用すると、別のテンプレートを取り込み、その結果を他のテンプレート関数に渡すことができます。

たとえば、次のテンプレートスニペットはmytplというテンプレートを含め、その結果を小文字に変換し、それを二重引用符で囲みます。

value: {{ include "mytpl" . | lower | quote }}

required関数を使用すると、特定のvaluesエントリをテンプレートレンダリングに必須として宣言できます。値が空の場合、テンプレートレンダリングはユーザーが送信したエラーメッセージで失敗します。

required関数の次の例では、.Values.whoのエントリが必須であることを宣言し、そのエントリがない場合はエラーメッセージを出力します。

value: {{ required "A valid .Values.who entry required!" .Values.who }}

文字列は引用符で囲み、整数は引用符で囲まない

文字列データを扱う場合、文字列をそのまま記述するよりも常に引用符で囲む方が安全です。

name: {{ .Values.MyName | quote }}

しかし、整数を扱う場合は、値を引用符で囲まないでください。多くの場合、Kubernetes内で解析エラーが発生する可能性があります。

port: {{ .Values.Port }}

この注意書きは、整数を表す場合でも文字列であることが期待されるenv変数の値には適用されません。

env:
  - name: HOST
    value: "http://host"
  - name: PORT
    value: "1234"

'include'関数の使用

Goには、組み込みのtemplateディレクティブを使用して、あるテンプレートを別のテンプレートに含める方法があります。ただし、組み込み関数はGoテンプレートパイプラインでは使用できません。

テンプレートを含め、そのテンプレートの出力に対して操作を実行できるようにするために、Helmには特別なinclude関数があります。

{{ include "toYaml" $value | indent 2 }}

上記は、toYamlというテンプレートを含め、$valueを渡し、そのテンプレートの出力をindent関数に渡します。

YAMLはインデントレベルと空白に意味を与えるため、これはコードスニペットを含めるのに最適な方法ですが、関連するコンテキストでインデントを処理します。

'required'関数の使用

Goには、マップに存在しないキーでマップがインデックス付けされたときの動作を制御するためにテンプレートオプションを設定する方法があります。これは通常、template.Options("missingkey=option")で設定されます。ここで、optiondefaultzero、またはerrorにすることができます。このオプションをエラーに設定すると、エラーで実行が停止しますが、これはマップ内のすべての欠落キーに適用されます。チャート開発者がvalues.yamlファイル内の一部の値に対してこの動作を強制したい状況があるかもしれません。

required関数を使用すると、開発者は値エントリをテンプレートレンダリングに必須として宣言できます。エントリがvalues.yamlで空の場合、テンプレートはレンダリングされず、開発者が提供したエラーメッセージが返されます。

{{ required "A valid foo is required!" .Values.foo }}

上記は、.Values.fooが定義されている場合はテンプレートをレンダリングしますが、.Values.fooが未定義の場合はレンダリングに失敗して終了します。

'tpl'関数の使用

tpl関数を使用すると、開発者はテンプレート内の文字列をテンプレートとして評価できます。これは、テンプレート文字列をチャートに値として渡したり、外部構成ファイルをレンダリングしたりするのに役立ちます。構文: {{ tpl TEMPLATE_STRING VALUES }}

# values
template: "{{ .Values.name }}"
name: "Tom"

# template
{{ tpl .Values.template . }}

# output
Tom

外部構成ファイルのレンダリング

# external configuration file conf/app.conf
firstName={{ .Values.firstName }}
lastName={{ .Values.lastName }}

# values
firstName: Peter
lastName: Parker

# template
{{ tpl (.Files.Get "conf/app.conf") . }}

# output
firstName=Peter
lastName=Parker

イメージプルシークレットの作成

イメージプルシークレットは、基本的にレジストリユーザー名、およびパスワードの組み合わせです。デプロイするアプリケーションでそれらが必要になる場合がありますが、それらを作成するには、base64を数回実行する必要があります。シークレットのペイロードとして使用するDocker構成ファイルを構成するヘルパーテンプレートを記述できます。次に例を示します。

まず、認証情報がvalues.yamlファイルで次のように定義されていると想定します。

imageCredentials:
  registry: quay.io
  username: someone
  password: sillyness
  email: someone@host.com

次に、ヘルパーテンプレートを次のように定義します。

{{- define "imagePullSecret" }}
{{- with .Values.imageCredentials }}
{{- printf "{\"auths\":{\"%s\":{\"username\":\"%s\",\"password\":\"%s\",\"email\":\"%s\",\"auth\":\"%s\"}}}" .registry .username .password .email (printf "%s:%s" .username .password | b64enc) | b64enc }}
{{- end }}
{{- end }}

最後に、より大きなテンプレートでヘルパーテンプレートを使用して、シークレットマニフェストを作成します。

apiVersion: v1
kind: Secret
metadata:
  name: myregistrykey
type: kubernetes.io/dockerconfigjson
data:
  .dockerconfigjson: {{ template "imagePullSecret" . }}

デプロイメントの自動ローリング

多くの場合、ConfigMapまたはSecretsはコンテナ内の構成ファイルとして挿入されるか、ポッドのローリングを必要とする他の外部依存関係の変更があります。アプリケーションによっては、後続のhelm upgradeで更新された場合に再起動が必要になる場合がありますが、デプロイメントスペック自体が変更されなかった場合、アプリケーションは古い構成で実行を続け、デプロイメントの一貫性がなくなる可能性があります。

sha256sum関数を使用すると、別のファイルが変更された場合にデプロイメントのアノテーションセクションが確実に更新されるようにすることができます。

kind: Deployment
spec:
  template:
    metadata:
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
[...]

注: ライブラリチャートにこれを追加する場合、$.Template.BasePathのファイルにアクセスすることはできません。代わりに、{{ include ("mylibchart.configmap") . | sha256sum }}を使用して定義を参照できます。

常にデプロイメントをローリングしたい場合は、上記と同様のアノテーションステップを使用できますが、代わりにランダムな文字列に置き換えて、常に変更されてデプロイメントがローリングされるようにします。

kind: Deployment
spec:
  template:
    metadata:
      annotations:
        rollme: {{ randAlphaNum 5 | quote }}
[...]

テンプレート関数の各呼び出しは、一意のランダムな文字列を生成します。つまり、複数のリソースで使用されるランダムな文字列を同期する必要がある場合は、関連するすべてのリソースが同じテンプレートファイルにある必要があります。

これらの両方の方法により、デプロイメントは組み込みの更新戦略ロジックを利用してダウンタイムを回避できます。

注: 以前は、--recreate-podsフラグを別のオプションとして使用することをお勧めしました。このフラグは、上記のより宣言的な方法を支持して、Helm 3では非推奨とマークされています。

Helmにリソースをアンインストールしないように指示する

Helmがhelm uninstallを実行したときにアンインストールしないリソースがある場合があります。チャート開発者は、リソースにアノテーションを追加して、アンインストールを防ぐことができます。

kind: Secret
metadata:
  annotations:
    helm.sh/resource-policy: keep
[...]

アノテーションhelm.sh/resource-policy: keepは、Helmに、helm操作(helm uninstallhelm upgradehelm rollbackなど)の結果、削除されるはずのリソースの削除をスキップするように指示します。ただし、このリソースは孤立します。Helmは、いかなる方法でもそれを管理しなくなります。これにより、すでにアンインストールされているが、リソースを保持しているリリースでhelm install --replaceを使用すると問題が発生する可能性があります。

"パーシャル"とテンプレートインクルードの使用

ブロックやテンプレートパーシャルなど、チャート内に再利用可能なパーツを作成したい場合があります。また、多くの場合、これらを独自のファイルに保持する方がクリーンです。

templates/ディレクトリでは、アンダースコア(_)で始まるファイルは、Kubernetesマニフェストファイルを出力するとは想定されていません。そのため、慣例として、ヘルパーテンプレートとパーシャルは_helpers.tplファイルに配置されます。

多くの依存関係を持つ複雑なチャート

CNCF Artifact Hubのチャートの多くは、より高度なアプリケーションを作成するための「構成要素」です。ただし、チャートを使用して、大規模なアプリケーションのインスタンスを作成することができます。そのような場合、単一の傘チャートには複数のサブチャートがあり、それぞれが全体の一部として機能します。

個別のパーツから複雑なアプリケーションを構成するための現在のベストプラクティスは、グローバル構成を公開するトップレベルの傘チャートを作成し、charts/サブディレクトリを使用して各コンポーネントを埋め込むことです。

YAMLはJSONのスーパーセットです

YAML仕様によると、YAMLはJSONのスーパーセットです。つまり、有効なJSON構造はYAMLでも有効である必要があります。

これには利点があります。テンプレート開発者にとって、YAMLの空白の敏感さを扱うよりも、JSONのような構文でデータ構造を表現する方が簡単な場合があります。

ベストプラクティスとして、JSON構文が書式設定の問題のリスクを大幅に軽減しない限り、テンプレートはYAMLのような構文に従う必要があります。

ランダム値の生成に注意する

Helmには、ランダムなデータや暗号鍵などを生成できる関数があります。これらを使用するのは問題ありません。しかし、アップグレード中にテンプレートが再実行されることに注意してください。テンプレートの実行によって、前回と異なるデータが生成されると、リソースの更新がトリガーされます。

1つのコマンドでリリースをインストールまたはアップグレードする

Helmは、インストールまたはアップグレードを単一のコマンドで実行する方法を提供します。--installコマンドを指定してhelm upgradeを使用します。これにより、Helmはリリースがすでにインストールされているかどうかを確認します。インストールされていない場合は、インストールが実行されます。インストール済みの場合は、既存のリリースがアップグレードされます。

$ helm upgrade --install <release name> --values <values file> <chart directory>