検索
連載

忘れたとき読んだらキュンです!――Helmチャートの作成方法とリポジトリ公開方法Cloud Nativeチートシート(4)

Kubernetesやクラウドネイティブをより便利に利用する技術やツールの概要、使い方を凝縮して紹介する連載。今回は、Helmチャートを作成して、リポジトリに公開する方法などについて。

PC用表示 関連情報
Share
Tweet
LINE
Hatena

 Kubernetesやクラウドネイティブをより便利に利用する技術やツールの概要、使い方を凝縮して紹介する本連載「Cloud Nativeチートシート」。

 前回は概要や使い方といった、ユーザー目線でKubernetes向けのパッケージマネジャー「Helm」(ヘルム)を紹介しましたが、今回はHelmチャートを作成して、リポジトリに公開する方法など提供者目線で紹介します。

チャートを作成する利点

 前回紹介したように、Helmでは「チャート」という設定ファイル群でKubernetesの全てのマニフェストを管理します。公開されているリポジトリからチャートをカスタマイズしながらインストール(デプロイ)できます。それに加えて、自身のアプリケーションをチャートとして公開することもできます。

 公開することで、自身のアプリケーションを稼働させるために必要なリソース群を、第三者のユーザーにも容易に一括でデプロイし管理してもらうことができます。また、環境によってカスタマイズしてほしい箇所のみを「values.yaml」として切り出すことができます。これらによって、より広くアプリケーションの活用を促すことができます。

チャートの要素を取得

 前回記事最後の補足で、チャートについて簡単に説明しました。

チャートの実体は「.tgz」形式で圧縮された複数ファイル群です。詳しくは次回、チャート作成を説明する際に解説しますが、tgzを解凍すると、チャートには環境共通のKubernetesリソースYAMLのテンプレートを配置する「templates」ディレクトリ、テンプレート内で変数化されているパラメーターのデフォルト値を定義するYAMLファイル(values.yaml)などが含まれます。


チャートの構成概略(前回の再掲)

 もう少し詳しくファイルの構成を見ていきましょう。「helm create」コマンドでサンプルのチャートを作成できるので確認します。

$ helm create test-chart
Creating test-chart
$ tree test-chart/
test-chart/
├── values.yaml
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── service.yaml
│   ├── serviceaccount.yaml
│   └── tests
│       └── test-connection.yaml
├── Chart.yaml
└── charts

 ディレクトリやファイルが幾つか作成されたのを確認できました。それぞれの要素を説明します。

values.yaml―−マニフェストテンプレート内で使う変数の定義

 「values.yaml」にはマニフェストテンプレート内で使う変数(後述)の定義が記載されています。

 前回説明した通り、Helmでは「パラメーター」として定義されている値を指定することで、それぞれの利用者の環境向けにカスタマイズできます。例えば、「Deployment」のレプリカ数を変更したり、デプロイされるコンポーネントをコンポーネントごとに有効/無効にしたりするなどのカスタマイズが挙げられます。

templatesディレクトリ――Kubernetesリソースのマニフェストテンプレートを配置

 「templates」ディレクトリ下には主にKubernetesリソースのマニフェストテンプレートを配置します。テンプレートとは、言葉の通り、最終的にKubernetesに送られるマニフェストの元となるファイルです。テンプレートのベースはマニフェストYAMLですが、要所を変数化しておくことで後からカスタマイズ可能にしておきます。

 helmコマンド実行時にテンプレートエンジンに送られ、values.yaml内の変数定義を展開して、最終的なマニフェストが生成され、最終的にKubernetesに送信されます。

※「NOTES.txt」とファイル名が「_」から始まるファイル(ヘルパーファイル)はテンプレートエンジンには送られません。

 templatesディレクトリには、下記のようにマニフェストテンプレートなど数種類のファイルが含まれています。

├── templates
│   ├── NOTES.txt           # helm install/upgrade時のNOTES:定義
│   ├── _helpers.tpl        # ヘルパーファイル
│   ├── deployment.yaml     # マニフェストテンプレート
│   ├── hpa.yaml            # マニフェストテンプレート
│   ├── ingress.yaml        # マニフェストテンプレート
│   ├── service.yaml        # マニフェストテンプレート
│   ├── serviceaccount.yaml # マニフェストテンプレート
│   └── tests               # helm testコマンドで実行されるtest用のリソース定義
│       └── test-connection.yaml

 ここからそれぞれのファイルについて説明します。

マニフェストテンプレートの概要

 マニフェストテンプレートは、Kubernetesリソースのマニフェストがベースとなって、変数化したい部分に「{{ }}」という構文で変数の参照や参照した値を加工します。

 試しに上記helm createコマンドで生成された「deployment.yaml」を確認してみます。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "test-chart.fullname" . }}
  labels:
    {{- include "test-chart.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "test-chart.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      {{- with .Values.podAnnotations }}
      annotations:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      labels:
        {{- include "test-chart.selectorLabels" . | nindent 8 }}
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      serviceAccountName: {{ include "test-chart.serviceAccountName" . }}
      securityContext:
        {{- toYaml .Values.podSecurityContext | nindent 8 }}
      containers:
        - name: {{ .Chart.Name }}
          securityContext:
            {{- toYaml .Values.securityContext | nindent 12 }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
          readinessProbe:
            httpGet:
              path: /
              port: http
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}

 Kubernetesの「Deployment」リソースのマニフェストがベースであることが「kind: Deployment」から分かります。Deploymentリソースの変数化しておきたい部分に{{ }}構文で変数が記載されています。例えば、Deploymentの名前「.metadata.neme」やコンテナイメージパス「.spec.template.spec.containers.image」などは各Deploymentによって違うものを指定したいので、変数化されています。

テンプレートで使える変数

 テンプレートで使える変数には大きく3種類あります。

・【1】組み込み変数

 「.Chart.Name」「Release.Name」は事前にHelm自体に定義されている変数です。values.yamlといった変数定義ファイル内の値定義も「.Values」という形で参照できます。

 組み込み変数には「Release」「Chart」「Values」「Files」「Capabilities」「Template」があります。Releaseはリリースに関する情報、Chartはチャートに関する情報、Valuesはvalues.yamlもしくはユーザーから「-f」で指定されたファイルで定義された変数を利用するために使えます。

変数名 概要
Release.Name リリース名
Release.Time リリースした時間
Release.Namespace リリースされる名前空間
Release.Service リリースするサービス。Helmでは常に「Helm」
Release.Revision リリースのリビジョン番号
Release.IsUpgrade upgradeかrollback操作の場合、trueがセットされる
Release.IsInstall install操作の場合、trueがセットされる
Values values.yamlもしくはユーザーから-fで指定されたファイルからテンプレートに渡される値。変数名は任意で定義。「Values.変数キー名」で変数の値を取得可能
Chart.ApiVersion Chart.yamlに記載されるチャートAPIのバージョン
Chart.Name Chart.yamlに記載されるチャートの名前
Chart.Version Chart.yamlに記載されるチャート自体のバージョン
Chart.KubeVersion Chart.yamlに記載される互換性のあるKubernetesバージョン
Chart.Description Chart.yamlに記載されるチャートの概要一文
Chart.Home Chart.yamlに記載されるサイトのURL
Chart.Sources Chart.yamlに記載されるソースコードのURL
Chart.Maintainers Chart.yamlに記載されるチャートのメンテナーの名前とEmail(配列)
Chart.engine Chart.yamlに記載されるテンプレートエンジン
Chart.Icon Chart.yamlに記載されるアイコンのURL
Chart.AppVersion Chart.yamlに記載されるコンテナアプリのバージョン
Chart.Deprecated Chart.yamlに記載されるチャートの推奨/非推奨(boolean値)
Capabilities.APIVersions Kubernetesリソースのバージョン
Capabilities.APIVersions.Has バージョンまたはリソースがクラスタで使用可能かどうか
Capabilities.KubeVersion コンテキストのKubernetesバージョン
Template.Name カレントパスの相対ファイルパス
Template.BasePath チャートのtemplatesディレクトリの相対パス

・【2】ローカル変数定義

 「$変数名 :=」という記法で、テンプレート内で変数を代入することができます。

あくまでテンプレート内での参照に限られるローカルな変数ですが、変数名をシンプルに扱うことができるので、頻度高く参照されるような変数や関数(後述)の結果をローカル変数として再定義するのもいいでしょう。

{{- $fullName := include "test-chart.fullname" . -}}

補足:Helmの命名規則

 Helmには下記のような命名規則があります。

チャート名

 チャート名は、小文字と数字の組み合わせで、ダッシュ(-)区切りである必要があります(※大文字やアンダースコア(_)は使用できません。これらの文字を使用している場合、テンプレートエンジンからエラーが返されるので気を付けましょう)。

変数名

 変数名はキャメルケース(例「replicaCount」)が推奨されます。


・【3】ヘルパーファイルによるグローバル変数

 「_helpers.tpl」のような「ヘルパーファイル」という特別なファイル内で定義されるグローバルな変数です。チャートのテンプレートのどこからでも使えるので、グローバルな値の定義に利用します。例えばチャート名をラベルなどで使えるように指定した文字数で切り捨てた文字列などが定義されます。

 ヘルパーファイルの中では「define」句による変数定義が主に記載されています。デフォルトで生成された_helpers.tplを確認してみましょう。

{{/*
Expand the name of the chart.
*/}}
{{- define "test-chart.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
(略)

 ここでは「test-chart.name」という変数に、「.Chart.Name」という組み込み変数をデフォルトとして(values.yamlに「nameOverride」が定義されていれば、こちらを採用し)、63文字で切り捨てて末尾の-を排除した結果がdefine(定義)されていることが分かります(※一部のリソースは63文字がnameの文字数上限なので)。

 このように_helpers.tplにはデフォルトで有用なグローバル変数が定義されているので、自身でも確認してみてください。

・グローバル変数の参照方法

 値の参照方法には下記2通りありますが、後述の理由からYAMLフォーマットに柔軟に調整可能な「include」関数を利用するのが望ましいです。

  • 「template」アクションを利用する方法
  • include関数を利用する方法

 templateアクションを使う場合、出力結果を他の関数に渡すことができない上に、変数参照時にテキストが左ぞろえになってしまうので、インデントを調整できません。

 そこで、include関数による参照結果を「nindent」関数(指定文字数分インデントする。詳細は後述)に渡すことで、指定した数字分インデントを調整できます(※Helmの関数では、Linuxコマンドと同じようにパイプ(|)を使うことで、複数の関数をつなげることができます)

 例えば下記のように、「test-chart.labels」をtemplateアクションでただ参照した場合、2行目以降のラベルのインデントがズレてしまっていることが確認できます。

metadata:
  labels:
    {{ template "test-chart.labels" . }}

metadata:
  labels:
    helm.sh/chart: test-chart-0.1.0
app.kubernetes.io/name: test-chart
app.kubernetes.io/instance: RELEASE-NAME
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm

 そこで、nindent関数によって1つ目のラベルから改行かつ2インデント深く(つまり4インデント)配置するようにします。そのためには前述した通りtemplateアクションではなくinclude関数を使用しなければいけないので、下記のようになります。

metadata:
  labels:
    {{- include "test-chart.labels" . | nindent 4 }}

metadata:
  labels:
    helm.sh/chart: test-chart-0.1.0
    app.kubernetes.io/name: test-chart
    app.kubernetes.io/instance: RELEASE-NAME
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm

 後述の「注意点1:スペースの取り扱い」で説明しますが、nindent関数を使う場合、改行文字の都合上{{ }}ではなく{{- }}によって変数部分を定義する必要があるので、そのようにしています。

テンプレートで使える関数

 includeやnindentは既に紹介しましたが、テンプレートで使用可能な関数についてあらためて説明します。関数を使うことで、参照した変数の値を編集したり、条件分岐やループ処理を実装したりすることができます。ここでは有用な関数を幾つか紹介します。

・nindent

 文字列の先頭に新しい行を追加して、指定された文字列の全ての行を指定されたインデント幅にインデントします。テンプレートエンジンの仕様で、nindent使用時には空白行が生まれるので、{{-で変数部分の左側の改行文字を取り除くようにしましょう。

metadata:
  labels:
    {{- include "test-chart.labels" . | nindent 4 }}

・if/else

 チャートで条件分岐をします。下記例では、「auto scatling」が有効になっていない場合に、「replicas」を設定するようになっています。

spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}

・with

 変数のスコープを変更できます。withの引数に渡す任意のスコープを「.」として定義できます。下記例では、{{ end }}までは.が.Values.podAnnotationsにスコープ変更されています。

template:
  metadata:
    {{- with .Values.podAnnotations }}
    annotations:
      {{- toYaml . | nindent 8 }}
    {{- end }}

・range

 リストに対するループ操作をします。「range」の引数に渡すリストの要素数分、処理がループします。ループ処理内ではカレントスコープが指定されたリストになります。下記例では、.Values.ingress.hostsリストに含まれる要素数分「{{ .host | quote }}」という処理(リスト内の「host」をクォーテーション付与で出力)を繰り返します。

rules:
  {{- range .Values.ingress.hosts }}
  - host: {{ .host | quote }}
  ...
  {{- end }}

・toYaml

 オブジェクトをYAML形式で展開します。values.yamlや_helper.tplに記載されているYAMLを直接展開したい場合に使用します。

    annotations:
      {{- toYaml . | nindent 8 }}

・quote

 引数のオブジェクトにダブルクオーテーションを付与します。参照した値を文字列としてYAMLに代入する際に有効です。

        app.kubernetes.io/managed-by: { { .Release.Service | quote } }

・htmlDate

 日付を「YYYY-MM-DD」形式にフォーマットします。ラベルに作成日付を定義したいときなどに有効です。

metadata:
  labels:
    date: {{ now | htmlDate }

 ここで紹介した以外にも多くの関数が利用可能なので、必要に応じてドキュメントを参照してください。

テンプレートを記述する際の注意点

 テンプレートを記述する際の注意点を紹介します。

・注意点1:スペースの取り扱い

 これまでの記法で{{ }}の先頭もしくは末尾に-が含まれている場合があることに気付いた方もいるかと思います。この-にも意味があります。{{-で左側の改行文字を取り除き、-}}で右側の改行文字を取り除きます。

 テンプレートエンジンがテンプレートを変換する際に{{ }}部分が処理の結果取り除かれても空白が残ってしまい、余分な改行が出力結果(マニフェスト)に残ってしまうことを防ぐために利用できます。{{- -}}のように両サイドに-を付けてしまうと2個改行文字が取り除かれてしまうので、改行文字を1つだけ取り除きたい場合は左右どちらか一方に付与するように気を付けてください。

 例えばnindent関数を使う場合、改行について特に何も処理を行わないと下記のように空白行が生まれてしまいます。

metadata:
  name: {{ include "test-chart.fullname" . }}
  labels:
    {{ include "test-chart.labels" . | nindent 4}}

  labels:
                              # 空白行
    helm.sh/chart: test-chart-0.1.0
    app.kubernetes.io/name: test-chart
    app.kubernetes.io/instance: RELEASE-NAME
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm

 {{-で改行文字を取り除くことで、下記のように正しく処理できる結果を取得できます。

metadata:
  name: {{ include "test-chart.fullname" . }}
  labels:
    {{- include "test-chart.labels" . | nindent 4}}

  labels:
    helm.sh/chart: test-chart-0.1.0
    app.kubernetes.io/name: test-chart
    app.kubernetes.io/instance: RELEASE-NAME
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm

・注意点2:変数のスコープ

 テンプレートで変数を参照する際には変数のスコープを意識する必要があります。

 例えば、defineで定義している部分における変数のスコープは、templateアクション、include関数による呼び出しの際の引数に依存して決まります。

 define内で下記のように「.Chart」「.Release」などを呼び出す場合は、スコープにルートコンテキスト「.」を通す必要があるので、「include "test-chart.name" .」というように明示的にスコープ(.)を引数で指定する必要があります。

{{- define "test-chart.selectorLabels" -}}
app.kubernetes.io/name: {{ include "test-chart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
_helper.tplにおけるdefine定義
  selector:
    matchLabels:
      {{- include "test-chart.selectorLabels" . | nindent 6 }}
deploymentテンプレートにおけるinclude関数による変数呼び出し

 他にも、range関数内でも.Chartや.Releaseなどを呼び出したい場合があります。このとき注意が必要なのがrange関数内ではカレントスコープが指定されたリストになる点です。つまり、.Chartのように通常通り参照すると、指定されたリスト内の.Chartが参照先になってしまいます。「$」を使用することで、どのスコープにおいてもルートコンテキストからの参照となるので、「$.Chart」というように記述すると、range関数のループ処理内においても.Chartや.Releaseを参照できるようになります。

NOTES.txt――アプリケーションの手引きを記載

 「NOTES.txt」はアプリケーションの手引きを記載するファイルです。「helm install」「helm upgrade」コマンドでリソースをデプロイした際にコンソールに「NOTES:」として出力されます。ドキュメント要件に記載されているように、下記の情報を記述します。

  • チャートインストール後の関連情報
  • チャートが提供するアプリケーションまたはサービスにアクセスする方法

 NOTES.txtでもテンプレート記法や関数を使えます。例えば以下のデフォルトNOTES.txtだと、アプリケーションURLへのアクセス方法を記載しています。

1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
  {{- range .paths }}
  http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
  {{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
  export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "test-chart.fullname" . }})
  export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
  echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
           You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "test-chart.fullname" . }}'
  export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "test-chart.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
  echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
  export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "test-chart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
  export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}

 helm installコマンドを実行すると、NOTES.txtに記載しているテンプレートが処理されて、「NOTES:」として出力されていることを確認できます。この例では、デプロイされたアプリケーションにアクセスするためのコマンド群が記載されています。

$ helm install test-cahrt test-chart/ -n test-chart
NAME: test-cahrt
LAST DEPLOYED: Tue Mar  9 19:35:47 2021
NAMESPACE: test-chart
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace test-chart -l "app.kubernetes.io/name=test-chart,app.kubernetes.io/instance=test-cahrt" -o jsonpath="{.items[0].metadata.name}")
  export CONTAINER_PORT=$(kubectl get pod --namespace test-chart $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl --namespace test-chart port-forward $POD_NAME 8080:$CONTAINER_PORT

 他に具体的なNotesの使い道としては、詳しいドキュメントへのリンク情報表示、初期パスワードの表示などがあります。

testsディレクトリ――動作確認テスト用のリソース定義を配置

 「tests」ディレクトリには「helm test」コマンドで実行される動作確認テスト用のリソース定義を配置します。

 リソース定義の実体はPodで「annotations」として「"helm.sh/hook": test」が付与されていることが特徴です。Podの実行コマンドとして「wget」などを定義して、その成功可否でチャートインストール後の動作確認テストと結果判定ができます。

 helm testコマンドについては「チャートのテストとインストール」で後述します。

apiVersion: v1
kind: Pod
metadata:
  name: "{{ include "test-chart.fullname" . }}-test-connection"
  labels:
    {{- include "test-chart.labels" . | nindent 4 }}
  annotations:
    "helm.sh/hook": test
spec:
  containers:
    - name: wget
      image: busybox
      command: ['wget']
      args: ['{{ include "test-chart.fullname" . }}:{{ .Values.service.port }}']
  restartPolicy: Never

Chart.yaml――チャートの情報をまとめたファイル

 Chart.yamlはチャートの情報をまとめたファイルです。テンプレート内から参照できる組み込み変数「Chart」で参照できるようなチャートのバージョン、アプリケーションのバージョン、Description、ソースのURLなどを定義します。

 以下にChart.yamlの項目説明を簡単に記載します。全てを記載する必要はありませんが、必須の「apiVersion」「name」「version」に加えて、アプリのバージョン「appVersion」、概要文「description」辺りは記載しておきましょう(※もしサブチャートを使う場合は「dependencies」の記載も必要です。詳しくは後述します)。

apiVersion: チャートAPIバージョン、Helm 3ならv2以上(必須)
name: チャートの名前(必須)
version: チャート自体のバージョン(必須)
kubeVersion: 互換性のあるKubernetesバージョン(例:>= 1.13.0 < 1.15.0)(オプション)
description: チャートの概要文(オプション)
type: チャートのタイプ(application or library)(オプション)
keywords:
  - キーワードのリスト(オプション)
home: ホームページのURL(オプション)
sources:
  - ソースコードの URL(オプション)
dependencies: # チャート依存関係のリスト(オプション)
  - name: サブチャートの名前(例:nginx)
    version: サブチャート自体のバージョン(例:"1.2.3")
    repository: リポジトリのURL(例:"https://example.com/charts")もしくは、エイリアス(例:"@repo-name")
    condition: サブチャート有効/無効に関わるboolean値のYAMLパス(例:subchart1.enabled)(オプション)
    tags: #(オプション)
      - タグを使用してチャートをグループ化し、一緒に有効/無効にすることが可能
    alias: チャートに使用されるエイリアス。同じグラフを複数回追加する必要がある場合に便利(オプション)
maintainers: #(オプション)
  - name: メンテナーの指名
    email: メンテナーの電子メール
    url: メンテナーのURL
icon: アイコンのURL(オプション).
appVersion: コンテナアプリのバージョン、ダブルクオーテーション推奨(オプション)
deprecated: 推奨/非推奨(オプション、boolean値)
annotations:
  example: アノテーションkey/value(オプション).

チャートの依存関係の定義方法

 前回の記事で多くのチャートはサブチャートを含んでいると説明しました。

 サブチャートとの依存関係の管理方法はChart.yamlの「.dependencies」に記述して動的にリンクさせるか、「charts/」ディレクトリ下に直接配置して手動で管理するかの2通りです。サブチャートがパブリックに公開されているチャートを使う場合は.dependenciesに記述する方法でいいでしょう。

※HelmのAPI Versionがv2になってから、依存関係の定義は「requirements.yaml」と「requirements.lock」からChart.yamlと「Chart.lock」に移行しました。v2を利用可能なのはHelm 3以降なのでHelm 2を使っている方は注意してください。

 下記のようにChart.yamlに記述して「helm dependency update」コマンドを実行することで、サブチャートをcharts/ディレクトリ配下に自動でダウンロードできます。

dependencies:
  - name: envoy # サブチャートの名前
    version: 1.8.1 # サブチャート自体のバージョン
    repository: https://charts.helm.sh/stable # リポジトリのURL
Chart.yamlのサンプル

 上記のような.dependenciesの記載の場合、下記のようにcharts/ディレクトリに「envoy-1.8.1.tgz」というサブチャートがダウンロードされていることを確認できました。

$ helm dependency update
$ tree
.
├── Chart.lock
├── Chart.yaml # サブチャートの依存関係を記載
├── charts
│   └── envoy-1.8.1.tgz # 自動でダウンロードされるサブチャート
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── service.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml

 ここまでチャートのそれぞれの要素について説明をしてきました。少々複雑ですが、Kubernetesのマニフェストベースのテンプレート+変数定義ファイル+ドキュメントというふうに捉えれば覚えやすいでしょう。

 一般的に公開されているチャートを「helm pull」すると、tgzファイルが手に入るので、それを解凍してみると、それぞれのチャートのテンプレートや変数定義の実装を見ることができるので面白いと思います。ぜひ皆さんも普段使っているチャートの中身を確認してみてください。

チャートを作成する手順

 ここからは、チャートを作成する手順を順を追って説明します。

Kubernetesマニフェストの作成、動作確認

 Helmテンプレートを用意する前に、まずはKubernetesリソースとして動作を確認できたマニフェスト群を用意しておくことが重要です。なぜなら、Kubernetesマニフェストとして疎通が取れていない状態でエラーが発生すると、その原因がKubernetesマニフェスト記載方法そのものにあるのか、Helmのテンプレート化部分にあるのかがはっきりせず、トラブルシュートに時間がかかるからです。

 Kubernetesマニフェストを一通り準備して、Kubernetesのマニフェストとして動作を確認できた(「kubectl apply」などでデプロイできる)段階で、テンプレート化する作業に移ることを推奨します。

マニフェストのテンプレート化

 マニフェストのテンプレート化は、まずはKubernetesのマニフェストを、templatesディレクトリ下に配置して、カスタマイズ化するとよい部分については、values.yamlに定義した上で、マニフェストの当該部分を{{ .Values.変数名 }}に置き換えます。

 テンプレート化する際は共通的に利用できる部分と、頻繁に変更できる部分を考慮します。PVの有無やSecretの有無、Ingressの有無などもテンプレート化の対象になることがあります。YAML内のブロックごとに少しずつテンプレート化({{ }}への書き換え)していきます。

 具体的には、次の手順でテンプレート化を進めます。

  1. 種チャートの用意
  2. Charts.yamlの編集
  3. リソースマニフェストのテンプレート化
  4. サブチャートのvalues.yamlのデフォルト値の設定

【1】種チャートの用意

 チャートの階層や各種デフォルトファイルを準備して、そこに手を加えるようにしたいので「種チャート」を用意します。helm createコマンドで種チャートを作成し、NOTES.txt、_helper.tpl、testsディレクトリ以外の不要なデフォルトテンプレートは削除します。

$ helm create test-chart
$ rm -rf test-chart/templates/*.yaml
$ tree
.
└── test-chart
    ├── Chart.yaml
    ├── charts
    ├── templates
    │   ├── NOTES.txt
    │   ├── _helpers.tpl
    │   └── tests
    │       └── test-connection.yaml
    └── values.yaml

【2】Chart.yamlの更新

 種チャートができたら、Chart.yamlを更新します。チャート自体のバージョン(version)、アプリのバージョン(appVersion)、概要文(description)などを適宜記載しましょう。

 サブチャートを利用する場合は、dependenciesも記載して「helm dependency build」コマンドを実行しておきます。

※Web/アプリ/DBサーバなと一般的なミドルウェアの場合、既存チャートが提供されていることが多いので、そういった再利用可能なチャートが既に提供されているかどうか、そのチャートで設定可能なパラメーター(values.yaml)を事前に確認しましょう。提供されていない、もしくは設定したいパラメーターがvalues.yamlとして定義されていない場合は、自身でテンプレート化する必要があります。

apiVersion: v2
name: test-chart
 
version: 0.1.0
description: A Test chart for Kubernetes
sources:
  - https://github.com/example
dependencies:
  - name: nginx
    version: 8.6.0
    repository: https://charts.bitnami.com/bitnami
maintainers:
  - name: helmtaro
appVersion: "1.0.0"

補足:チャートの要件

 Helm公式の「Contributing Guidelines」には技術要件とドキュメント要件が記載されています。

 個人開発といった場合は、順守することが必須ではないですが、ベストプラクティスに基づいてた要件となっているので、できるだけ参考にすることが望ましいです。

技術要件(Technical Requirements)

  • 全てのチャートの依存関係が独立している
  • リンターを通過している(helm lint)
  • デフォルト値で正常に起動する(helm install .)
    • 全てのPodが実行状態になる(または、必要な値が欠落している場合は、NOTES.txtに詳細な手順が記載されている。例:https://github.com/helm/charts/blob/master/stable/minecraft/templates/NOTES.txt#L3)
    • 全てのserviceには少なくとも1つのendpointがある
  • チャートで使用されるimageのソースはGitHubリポジトリURLを含める
  • imageに重大なセキュリティの脆弱(ぜいじゃく)性がない
  • 最新の安定したHelm/Kubernetesの機能を備えている
  • Kubernetesのベストプラクティスに従う
    • 可能な限りヘルスチェックを含める
    • resource requests、limitsを設定可能にする
  • データの永続性を保つ方法を提供する(必要な場合)
  • アプリケーションのアップデートをサポートする
  • アプリケーション設定のカスタマイズを許可する
  • 安全なデフォルト構成を提供する
  • Kubernetesのalpha機能を活用しない
  • インストール後にアプリケーションを使用する方法を説明するNOTES.txtが含まれている
  • チャートベストプラクティスに従う(特にlabelとvalue)

ドキュメント要件(Documentation Requirements)

  • 下記のような詳細をREADME.mdに含める(「helm show readme」コマンドの結果やArtifact HUBの詳細ページで表示される内容)
    • チャートが提供するアプリケーションまたはサービスの簡単な説明
    • チャートを実行する前提条件または要件
    • values.yaml内のオプションとそのデフォルト値の説明(カスタマイズ方法)
    • チャートのインストールまたは構成に関連する可能性のあるその他の情報
  • 下記のような簡易な説明をNOTES.txtに含める(helm installコマンドの結果としてコンソールに表示される内容)
    • チャートインストール後の関連情報
    • チャートが提供するアプリケーションまたはサービスにアクセスする方法

【3】リソースマニフェストのテンプレート化

 続いて、疎通済みのKubernetesリソースのマニフェスト(YAMLファイル)を/templatesディレクトリ配下に配置します。適宜テンプレート化して、変数はvalues.yamlにデフォルト値とともに定義しましょう。

 テンプレート化する対象としては「共通的に利用できる部分」や「頻繁に変更される/したい部分」です。またPVの有無やSecretの有無、Ingressの有無など、「環境によって有効/無効化したい部分」もテンプレート化の対象になることがあります。

 いきなり全てをテンプレート化するのは骨が折れるので、YAML内のブロックごとに少しずつテンプレート化({{ }}への書き換え)していくのがよいでしょう。

 ここからは、Deploymentリソースをベースにテンプレート化する箇所の例を幾つか紹介します。

metadata:
  name: {{ include "test-chart.fullname" . }}
meatadata.name

 _helper.tplで定義されているfullnameを使うと、デフォルトだとチャート名ですが、values.yamlのfullnameOverrideに定義することで上書きすることができます。

metadata:
  labels:
    app.kubernetes.io/name: {{ include "test-chart.name" . }} # Helm固有ではないもののアプリ全体を反映したアプリ名である必要がある。このため、_helper.tplで定義されているチャート名を付与する
    helm.sh/chart: {{ include "test-chart.chart" . }} # [チャート名]-[バージョン]である必要がある。_helper.tplで定義されているものを付与する 
    app.kubernetes.io/instance: {{ .Release.Name }} # リリース名にすることで、同じアプリケーションで異なるリリースを区別できるようにする  
    app.kubernetes.io/managed-by: {{ .Release.Service }} # Helmによるデプロイの場合、.Release.ServiceはHelmとなる。Helmによって管理されていることを区別するために必要
labelsブロック

 上記のラベルはHelmで使用推奨されているので、上記のようにテンプレート化して付与するようにしましょう。

spec:
  replicas: {{ .Values.replicaCount }}
replicasの数

 .spec.replicasは環境によってカスタマイズ可能にしておくケースが多いので、上記のようにテンプレート化しておきましょう。

    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
コンテナイメージ定義

 コンテナイメージの指定時は、どのイメージのバージョンがデプロイされるのかを管理できないので、latestなどを使わないことが推奨されています。そこで、[イメージ名]:[バージョン]となるように上記のようにテンプレート化しましょう。

          resources:
            {{- toYaml .Values.resources | nindent 12 }}
コンテナリソース管理

 .spec.containers[].resourcesは環境によって用意されるリソース量が異なるので、カスタマイズ可能にしておいた方がいいので、上記のようにテンプレート化しておきましょう。

 上記で参照されるvalues.yamlは下記のようになります。

replicaCount: 1
 
image:
  repository: nginx
  pullPolicy: IfNotPresent
  tag: ""
 
resources: {}
  # We usually recommend not to specify default resources and to leave this as a conscious
  # choice for the user. This also increases chances charts run on environments with little
  # resources, such as Minikube. If you do want to specify resources, uncomment the following
  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
  # limits:
  #   cpu: 100m
  #   memory: 128Mi
  # requests:
  #   cpu: 100m
	  #   memory: 128Mi
対応するvalues.yaml

【4】サブチャート変数値の再定義

 前回のサブチャートの説明で言及したように、サブチャートの変数も親チャートのvalues.yaml内で定義できます。自身で作成したリソースのテンプレート化が済んだら、サブチャートの変数について考慮しましょう。

 親チャートにとって重要なサブチャート変数やデフォルトから変更したサブチャート変数については、あらためてvalues.yamlに「.[サブチャート名]」として記述します。

grafana: #grafanaサブチャートのパラメーターを指定可能
  image:
    repository: grafana/grafana
    tag: 7.3.4
Grafanaをサブチャートとして使う場合のvalues.yamlの例

チャートのテストとインストール

 テンプレート化が一通り完了したら、チャートを自身のKubernetesクラスタ上にhelm installでデプロイします。その際に、Helmには静的解析(Lint)や、デプロイ後の動作確認(テスト)を自動化する機能があるので、それらを有効活用しながら進めていきます。

・helm lintによる静的解析

 まずはデプロイの前にhelm lintコマンドによる静的解析をしましょう。これによって、構文チェックや推奨構成を満たしているかどうかの確認が可能です。

$ helm lint test-chart/
==> Linting test-chart/
[INFO] Chart.yaml: icon is recommended
 
1 chart(s) linted, 0 chart(s) failed

 「0 chart(s) failed」と出ています。つまりfailedなチャートはないので、構文的に問題がないことが分かります。エラーが発生した場合は、エラーが発生した箇所を見直します。

・helm installによるチャートインストール

 「helm install [リリース名] [ローカルチャートディレトリのパス]」コマンドでローカルにあるチャートをクラスタにインストールします。helm listやkubectl getなども併せて活用し、デプロイが想定通り成功したかどうかを確認してください。

・テンプレートのデバッグ

 helm installがうまくいかなかった場合は、エラーをよく読んでみてください。大体はエラー箇所が記載されているので、その周辺に問題がないかどうか確認してください。

 例えば下記だと、deployment.yamlの17行目辺りに問題があるようです。

helm install --dry-run test-chart .
Error: YAML parse error on test-chart/templates/deployment.yaml: error converting YAML to JSON: yaml: line 17: mapping values are not allowed in this context

 問題箇所にアタリが付いたら、試しにコメントアウトしてhelm installが通るかどうかで問題箇所を特定できます。

 必要に応じて「helm install --dry-run --debug」によるデバッグを実施するのもいいでしょう。このコマンドを利用すると、チャートをインストールすることなくデバッグができます。

・helm testによる動作確認テスト

 helm installによるデプロイ後に、helm testコマンドを使うことで、リリースが期待通りに動いているかどうかの確認を自身の定義したテスト方法で実施できます。

 helm testは「template/test」ディレクトリ配下のPodのマニフェストを実行します。このPodにwgetやcurlなどのコマンドを定義しておくことで、テスト内容のカスタマイズが可能です。

 ちなみにhelm testコマンドで実行されるのは「.metadata.annotations」に「"helm.sh/hook": test」が定義されているからです。これは、Helmの「Chart Hooks」という仕組みです。Chart Hooksについてはテスト以外の利用方法もあるため後述します。

 例えば、以下のデフォルトの「test-connection.yaml」では、作成したServiceのポート(「test-chart.fullname」はServiceのnameとして使用されている)にwgetで疎通を確認するテストが定義されています。

apiVersion: v1
kind: Pod
metadata:
  name: "{{ include "test-chart.fullname" . }}-test-connection"
  labels:
    {{- include "test-chart.labels" . | nindent 4 }}
  annotations:
    "helm.sh/hook": test
spec:
  containers:
    - name: wget
      image: busybox
      command: ['wget']
      args: ['{{ include "test-chart.fullname" . }}:{{ .Values.service.port }}']
  restartPolicy: Never

 チャートインストール後に「helm test [リリース名]」コマンドを実行してみます。

$ helm install test-chart test-chart
$ helm test test-chart
NAME: test-chart
LAST DEPLOYED: Tue Jan 12 00:07:43 2021
NAMESPACE: helm-test
STATUS: deployed
REVISION: 1
TEST SUITE:     test-chart-test-connection
Last Started:   Tue Jan 12 00:16:21 2021
Last Completed: Tue Jan 12 00:16:23 2021
Phase:          Succeeded

 テストの結果「Succeeded」となっており、wgetによる動作確認が成功したことを確認できました。

チャートの公開

 手元で動作を確認し、テストが成功したら、いよいよチャートの公開です。

 チャートを公開するためにはドキュメントの用意やチャートのパッケージ化といった準備が必要です。

ドキュメントの用意

 まずは必要なドキュメントとして「NOTES.txt」「README.md」を用意しましょう。NOTES.txtの作成内容は前述しているので省略します。README.mdにはドキュメント要件に記載されていたように、第三者に使ってもらう上での下記の説明を記載します。

  • チャートの簡単な説明
  • 前提条件または要件
  • カスタマイズ方法(values.yaml内のオプションとそのデフォルト値の説明)

 例として「prometheus-community」のリポジトリを見てみると、リポジトリ追加方法やドキュメントへのリンクなどが記載されいます。


prometheus-communityのリポジトリ

パッケージ化

 チャートリポジトリに公開されているチャートの実体はtgzなので、チャートを公開するにはtgzにパッケージ化する必要があります。「helm package [チャートディレクトリ]」コマンドを使用します。

$ helm package test-chart/
Successfully packaged chart and saved it to: /home/kashinoki38/helm-test/test-chart/test-chart-1.0.0.tgz
$ ls *.tgz
test-chart-1.0.0.tgz

チャートリポジトリへの公開

 チャートリポジトリはHTTPまたはHTTPSでアクセスすることができて、GETリクエストに応答できるHTTPサーバである必要があります。下記のような公開先の選択肢があります。作成したリポジトリは「helm repo」コマンドでaddやsearchができるようになります。

  • 「GitHub Pages」
  • 「Google Cloud Storage」や「Amazon S3」などのクラウドストレージ
  • HTTPサーバ
  • ChartMuseum」(カスタムHelmリポジトリ機能と管理APIを提供するオープンソースソフトウェア)

 今回はGitHub Pagesを使った方法を簡単に紹介します。事前にGitHubでパブリックリポジトリを作成して、リポジトリ設定内のGitHub Pagesで設定します。GitHub Pagesを設定すると、任意のドメインかつHTTPSでリポジトリを公開できます。


GitHub Pages設定画面

 GitHub Pagesでの公開設定が完了したら、次にリポジトリのmasterブランチにindex.yaml、README.mdとチャートパッケージ(tgzファイル)をプッシュします。

index.yaml(チャートの目録)の作成

 最後にチャートの目録となるindex.yamlの作成です。これはgzファイルのリンクやメタデータ情報を記載するファイルで、チャートリポジトリを用意する際に必要です。

 index.yamlの作成は「helm repo index」コマンドを使います。「--url」オプションでチャートリポジトリを指定します。

$ helm repo index ./ --url https://kashinoki38.github.io/charts-repository/

 下記のようなindex.yamlが作成されます。

apiVersion: v1
entries:
  test-chart:
  - apiVersion: v2
    appVersion: 1.0.0
    created: "2021-03-09T20:21:05.6326649+09:00"
    description: A Test chart for Kubernetes
    digest: cc5644a1894f6dca239683dc1bf2d842d1cb80bcefe58166a1c264ef02c7333b
    name: test-chart
    type: application
    urls:
    - https://kashinoki38.github.io/charts-repository/test-chart-0.1.0.tgz
    version: 0.1.0
generated: "2021-03-09T20:21:05.6268839+09:00"

 チャートの情報やリンク、digestが列挙されていることが分かります。このindex.yamlもリポジトリにプッシュします。最終的にリポジトリには下記のようなファイルが並びます。


GitHub上のファイル階層イメージ

 この時点で、Helmリポジトリとしては完成です。

作成したリポジトリを試す

 作成したリポジトリを使ってみましょう。「helm repo add」コマンドで作成したリポジトリを登録して、「test-chart」を検索してみます。

$ helm repo add kashinoki38 https://kashinoki38.github.io/charts-repository/
"kashinoki38" has been added to your repositories
$ helm repo update
  Hang tight while we grab the latest from your chart repositories...
  ...Successfully got an update from the "kashinoki38" chart repository
  Update Complete. -Happy Helming!-
$ helm search repo test-chart 
NAME CHART VERSION APP VERSION DESCRIPTION 
kashinoki38/test-chart 1.0.0 1.0.0 A Test chart for Kubernetes

 無事test-chart情報が表示されました。自作チャートを自身のチャートリポジトリで公開できました。

チャート更新時の手順

 チャートを更新するには、下記のような手順で、適宜必要なファイルを更新してリポジトリにプッシュします。

  1. チャートの編集、静的解析、テスト
  2. ドキュメントの修正
    • NOTES.txt:アプリケーションの使用方法が変更された場合
    • README.md:インストール方法やパラメーターの変更があった場合
    • Chart.yaml
      • appVersion:アプリケーションの修正があった場合
      • version:チャート自体のバージョン変更があった場合、基本的に更新する
      • description:変更内容を明示するために更新する
  3. チャートパッケージ化とプッシュ
    • Chart.yamlのversionに合わせて、「[チャート名]-[version].tgz」のファイルが生成される
    • 古いtgzも残したまま、新規versionのtgzをリポジトリにプッシュする
  4. index.yamlの更新とプッシュ
    • helm repo indexコマンド
  5. Helmクライアントの更新
    • Helmでは明示的にクライアントのリポジトリ情報を更新する必要がある
    • helm repo updateコマンド
    • helm searchコマンドでversionが更新されていることを確認


便利な「Chart Hooks」でできること

 Helmには「Chart Hooks」機能があり、これを利用することで、リリースのライフサイクルにおいて下記例のような特定の処理ができます。

  • チャートがインストールされる前に、ConfigMapやSecretを作成する
  • チャートを新しくインストールする前に、データベースのバックアップ用のジョブを実行する
  • リリースを削除する前に、ジョブを実行して安全にサービスを終了させる

 Chart Hooksの実体は「.metadata.annotations」に特別な値が入ったKubernetesマニフェストです。このマニフェストをtemplates/ディレクトリ配下に置くことでChart Hooksとして機能します。.metadata.annotationsでは「helm.sh/hook」というannotationとしてChart Hooksの種類を定義できます。

Chart Hooksの種類
Annotation Value Description
pre-install Kubernetesリソースとしてデプロイされる前に実行される
post-install 全Kubernetesリソースがデプロイされた後に実行される
pre-delete 削除がリクエストされて、リソースが削除される前に実行される
post-delete 削除がリクエストされて、全リソースが削除された後に実行される
pre-upgrade upgradeがリクエストされて、Kubernetesリソースとしてデプロイされる前に実行される
post-upgrade upgradeがリクエストされて、全Kubernetesリソースがデプロイされた後に実行される
pre-rollback rollbackがリクエストされて、Kubernetesリソースがロールバックされる前に実行される
post-rollback rollbackがリクエストされて、Kubernetesリソースがロールバックされた後に実行される
test helm testコマンド時に実行される

 下記例では、「post-install」としてChart Hooksを定義しているので、これは全リソースがデプロイされた後に実行されることになります。この例のように「hook-weight」を付与することで実行順序を制御するための重みを定義できます。Chart Hooksはこの重みの昇順で実行されていきます。

apiVersion: batch/v1
kind: Job
metadata:
  name: "{{ .Release.Name }}"
  labels:
    app.kubernetes.io/managed-by: { { .Release.Service | quote } }
    app.kubernetes.io/instance: { { .Release.Name | quote } }
    app.kubernetes.io/version: { { .Chart.AppVersion } }
    helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
  annotations:
    # This is what defines this resource as a hook. Without this line, the
    # job is considered part of the release.
    "helm.sh/hook": post-install
    "helm.sh/hook-weight": "-5"
    "helm.sh/hook-delete-policy": hook-succeeded
(略)

 また、Chart Hooksのリソースはリリースとして管理されません。従って、一度「ready」になるとhelm uninstallコマンドによる一括削除対象に含まれません。そのため、「helm.sh/hook-delete-policy」などを活用する必要があります。helm.sh/hook-delete-policyは下記3通りから選択できます。

  • before-hook-creation:新しいフックを起動する際に直前のフックを削除する(デフォルト)
  • hook-succeeded:フック実行に成功した際に削除
  • hook-failed:フック実行に失敗した際に削除

 先ほどの例だと「hook-succeeded」としているので、実行成功時に削除されることになります。

まとめ

 今回はHelmチャートのコンポーネントの説明、作成方法、リポジトリ公開方法などを紹介しました。

 マニフェストをテンプレート化する作業は根気のいる作業です。まずはテンプレート化する箇所を一部から始めてみて少しずつ汎用(はんよう)化することをお勧めします。既にさまざまなチャートが公開されているので、ぜひそちらも参考にしてみてください。

 自身のアプリケーションをチャートとして公開することで、より広く多くのユーザーに使ってもらうことができるので、良いアプリケーションを作れたら、ぜひトライしてみてください!

Copyright © ITmedia, Inc. All Rights Reserved.

[an error occurred while processing this directive]
ページトップに戻る