メインコンテンツまでスキップ

Helm Chartの作成と管理

HelmはKubernetesのパッケージマネージャーであり、複雑なKubernetesアプリケーションの定義、インストール、アップグレードを容易にします。本ドキュメントでは、Helmの基本概念からカスタムChartの作成、そして運用におけるベストプラクティスについて解説します。

Helmとは

Kubernetesのマニフェストファイル(YAML)は、アプリケーションが複雑になるにつれて管理が困難になります。Helmを使用することで、これらのマニフェストを「Chart」と呼ばれるパッケージとしてまとめ、パラメータ化して再利用可能にすることができます。

主な利点

  • 複雑性の管理: 多数のYAMLファイルを1つのパッケージとして扱えます。
  • 簡単な更新: helm upgrade コマンドでアプリケーションの更新やロールバックが可能です。
  • 共有と再利用: 公開されているChartを利用したり、自作のChartを組織内で共有したりできます。
  • 環境ごとの構成: values.yaml を切り替えることで、開発・ステージング・本番環境などで異なる設定を適用できます。

Helm Chartの基本構造

Helm Chartは特定のディレクトリ構造を持っています。

mychart/
├── Chart.yaml # Chartのメタデータ
├── values.yaml # デフォルト設定値
├── charts/ # 依存するChart
├── templates/ # Kubernetesマニフェストテンプレート
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── _helpers.tpl # テンプレートヘルパー関数
│ └── NOTES.txt # インストール後の説明
└── .helmignore # パッケージングから除外するファイル

Chart.yaml - Chartメタデータ

Chart.yamlはChartの基本情報を定義します。

apiVersion: v2
name: myapp
description: カスタムアプリケーションのHelm Chart
type: application

# Chartのバージョン(Chart自体のバージョン)
version: 1.0.0

# アプリケーションのバージョン
appVersion: "2.1.0"

# キーワード(検索用)
keywords:
- web
- api
- microservice

# メンテナー情報
maintainers:
- name: Developer Name
email: developer@example.com
url: https://example.com

# ホームページURL
home: https://github.com/example/myapp

# ソースコードリポジトリ
sources:
- https://github.com/example/myapp

# 依存するChart
dependencies:
- name: postgresql
version: "12.1.0"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
- name: redis
version: "17.3.0"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabled

# アイコンURL
icon: https://example.com/icon.png

# 非推奨の場合
# deprecated: true

# KubeVersionの制約
kubeVersion: ">=1.24.0-0"

Chart.yamlの重要フィールド

フィールド必須説明
apiVersionChart APIバージョン(v2推奨)
nameChart名(ディレクトリ名と一致)
versionChart自体のバージョン(SemVer)
appVersionデプロイされるアプリのバージョン
typeapplicationまたはlibrary
dependencies依存するChartのリスト

values.yaml - デフォルト設定値

values.yamlは、テンプレートで使用されるデフォルト値を定義します。

# レプリカ数
replicaCount: 3

# Dockerイメージ設定
image:
repository: myregistry.azurecr.io/myapp
pullPolicy: IfNotPresent
tag: "2.1.0"

# イメージPull Secret
imagePullSecrets:
- name: acr-secret

# サービスアカウント
serviceAccount:
create: true
annotations: {}
name: ""

# Pod Annotations
podAnnotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"

# Pod Security Context
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000

# Container Security Context
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true

# Service設定
service:
type: ClusterIP
port: 80
targetPort: 8080
annotations: {}

# Ingress設定
ingress:
enabled: true
className: "nginx"
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
hosts:
- host: myapp.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: myapp-tls
hosts:
- myapp.example.com

# リソース制限
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi

# オートスケーリング
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80

# ヘルスチェック
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10

readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5

# 環境変数
env:
- name: APP_ENV
value: "production"
- name: LOG_LEVEL
value: "info"

# ConfigMapからの環境変数
envFrom:
- configMapRef:
name: myapp-config

# シークレット
secrets:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: myapp-secrets
key: database-url

# ボリューム
volumes:
- name: config
configMap:
name: myapp-config
- name: cache
emptyDir: {}

volumeMounts:
- name: config
mountPath: /app/config
readOnly: true
- name: cache
mountPath: /tmp/cache

# Node Selector
nodeSelector: {}

# Tolerations
tolerations: []

# Affinity
affinity: {}

テンプレートファイル

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myapp.fullname" . }}
labels:
{{- include "myapp.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "myapp.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "myapp.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "myapp.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: {{ .Values.service.targetPort }}
protocol: TCP
{{- with .Values.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.env }}
env:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.envFrom }}
envFrom:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.volumeMounts }}
volumeMounts:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.volumes }}
volumes:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

service.yaml

apiVersion: v1
kind: Service
metadata:
name: {{ include "myapp.fullname" . }}
labels:
{{- include "myapp.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.targetPort }}
protocol: TCP
name: http
selector:
{{- include "myapp.selectorLabels" . | nindent 4 }}

ingress.yaml

{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "myapp.fullname" . }}
labels:
{{- include "myapp.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "myapp.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }}

_helpers.tpl - ヘルパーテンプレート

_helpers.tplは再利用可能なテンプレート関数を定義します。このファイルはKubernetesリソースとしてレンダリングされません。

{{/*
Chart名を展開
*/}}
{{- define "myapp.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
完全修飾アプリ名を作成
リリース名が63文字を超える場合は切り詰められます
*/}}
{{- define "myapp.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
Chart名とバージョンをチャートラベルとして使用
*/}}
{{- define "myapp.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
共通ラベル
*/}}
{{- define "myapp.labels" -}}
helm.sh/chart: {{ include "myapp.chart" . }}
{{ include "myapp.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
セレクタラベル
*/}}
{{- define "myapp.selectorLabels" -}}
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
サービスアカウント名を作成
*/}}
{{- define "myapp.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "myapp.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

{{/*
環境変数のテンプレート
*/}}
{{- define "myapp.env" -}}
- name: APP_NAME
value: {{ include "myapp.fullname" . }}
- name: APP_VERSION
value: {{ .Chart.AppVersion | quote }}
- name: RELEASE_NAME
value: {{ .Release.Name }}
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
{{- end }}

{{/*
条件付きラベル
*/}}
{{- define "myapp.podLabels" -}}
{{- if .Values.podLabels }}
{{- toYaml .Values.podLabels }}
{{- end }}
{{- end }}

ヘルパー関数の主な用途

  • 命名規則の統一: リソース名を一貫した方法で生成
  • ラベルの標準化: Kubernetesのベストプラクティスに従ったラベル
  • 条件付きロジック: 設定に基づいてテンプレートをカスタマイズ
  • コードの重複排除: 共通のテンプレート部分を再利用

ヘルパー関数の使用例

定義したヘルパー関数は、include関数を使用して他のテンプレートファイルから呼び出します。

定義 (_helpers.tpl):

{{- define "myapp.fullname" -}}
{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" -}}
{{- end -}}

使用 (deployment.yaml):

apiVersion: apps/v1
kind: Deployment
metadata:
# 定義した関数を呼び出して名前を展開
name: {{ include "myapp.fullname" . }}

include関数は出力を文字列として返すため、パイプラインで他の関数(indentnindentなど)に渡すことができます。これはYAMLのインデント構造を維持するために非常に重要です。

labels:
# 出力を4スペースでインデント
{{- include "myapp.labels" . | nindent 4 }}

テンプレート関数とパイプライン

HelmはGo TemplateSprig関数ライブラリを使用します。

基本的なテンプレート構文

# 値の参照
{{ .Values.replicaCount }}

# デフォルト値
{{ .Values.image.tag | default .Chart.AppVersion }}

# 条件分岐
{{- if .Values.ingress.enabled }}
# Ingressリソースを作成
{{- end }}

# 範囲ループ
{{- range .Values.env }}
- name: {{ .name }}
value: {{ .value }}
{{- end }}

# パイプライン(複数の関数を連鎖)
{{ .Values.name | upper | quote }}

# インデント調整
{{- toYaml .Values.resources | nindent 12 }}

# ヘルパー関数の呼び出し
{{ include "myapp.fullname" . }}

よく使う関数

関数説明
defaultデフォルト値を提供{{ .Values.tag | default "latest" }}
quote文字列をクォート{{ .Values.name | quote }}
upper/lower大文字/小文字変換{{ .Values.env | upper }}
trunc文字列を切り詰め{{ .Values.name | trunc 63 }}
trimSuffix接尾辞を削除{{ .Values.name | trimSuffix "-" }}
nindentインデントを追加{{ toYaml .Values | nindent 4 }}
toYamlYAMLに変換{{ toYaml .Values.resources }}
sha256sumSHA256ハッシュ{{ .Values.config | sha256sum }}

Chartのパッケージング - .tgzファイル

Helm Chartは.tgz(tar.gz)形式のアーカイブとしてパッケージ化されます。このファイルは、Chartリポジトリへの公開や、エアギャップ環境(インターネット接続のない環境)への転送に使用されます。

ファイル名の規則

パッケージファイル名は、Chart.yamlで定義されたnameversionに基づいて自動的に生成されます。

形式: <chart-name>-<chart-version>.tgz

例:

  • Chart名: myapp
  • バージョン: 1.0.0
  • 生成されるファイル名: myapp-1.0.0.tgz

パッケージの作成

# Chartをパッケージ化
helm package mychart/

# 出力例: myapp-1.0.0.tgz

パッケージの内容

.tgzファイルには、レンダリングされたKubernetesマニフェストではなく、Chartのソースファイルそのものが含まれています。インストール時に、Helmはこのパッケージを展開し、その場でテンプレートをレンダリングします。

# パッケージの中身を確認
tar -tzf myapp-1.0.0.tgz

# 出力:
# myapp/Chart.yaml
# myapp/values.yaml
# myapp/templates/deployment.yaml
# myapp/templates/service.yaml
# ...

.helmignore

.helmignoreでパッケージから除外するファイルを指定します。

# 開発ファイル
*.swp
*.bak
*.tmp
*.orig
*~

# Git関連
.git/
.gitignore

# CI/CD
.github/
.gitlab-ci.yml

# テスト
tests/
*.test

# ドキュメント
README.md
CONTRIBUTING.md

# 環境固有のファイル
values-dev.yaml
values-staging.yaml

Chart開発のワークフロー

1. Chartの作成

# 新しいChartを作成
helm create myapp

# ディレクトリ構造が生成されます

2. テンプレートの開発とテスト

# テンプレートのレンダリング結果を確認
helm template myapp ./myapp

# 特定の値ファイルを使用
helm template myapp ./myapp -f values-prod.yaml

# デバッグモードで詳細出力
helm template myapp ./myapp --debug

3. Chartの検証

# Chartの構文チェック
helm lint myapp/

# 依存関係の更新
helm dependency update myapp/

# ドライラン(実際にはインストールしない)
helm install myapp ./myapp --dry-run --debug

4. Chartのパッケージング

# Chartをパッケージ化
helm package myapp/

# 署名付きパッケージ(オプション)
helm package myapp/ --sign --key 'keyname' --keyring path/to/keyring

5. Chartのインストール

# ローカルChartからインストール
helm install my-release ./myapp

# パッケージからインストール
helm install my-release myapp-1.0.0.tgz

# カスタム値を指定
helm install my-release ./myapp -f custom-values.yaml

# コマンドラインで値をオーバーライド
helm install my-release ./myapp --set replicaCount=5

# 特定のnamespaceにインストール
helm install my-release ./myapp -n production --create-namespace

環境別の設定管理

異なる環境(開発、ステージング、本番)で異なる設定を使用します。

values-dev.yaml

replicaCount: 1

image:
tag: "dev"

ingress:
enabled: false

resources:
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi

autoscaling:
enabled: false

env:
- name: APP_ENV
value: "development"
- name: LOG_LEVEL
value: "debug"

values-prod.yaml

replicaCount: 5

image:
tag: "2.1.0"

ingress:
enabled: true
hosts:
- host: myapp.production.com
paths:
- path: /
pathType: Prefix

resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 500m
memory: 512Mi

autoscaling:
enabled: true
minReplicas: 5
maxReplicas: 20

env:
- name: APP_ENV
value: "production"
- name: LOG_LEVEL
value: "warn"

使用方法

# 開発環境
helm install myapp-dev ./myapp -f values-dev.yaml

# 本番環境
helm install myapp-prod ./myapp -f values-prod.yaml -n production

# 複数の値ファイルを結合(後のファイルが優先)
helm install myapp ./myapp -f values.yaml -f values-prod.yaml -f values-override.yaml

Umbrella Chartパターン

Umbrella Chart(アンブレラチャート)は、複数のマイクロサービスやアプリケーションをまとめて管理するためのHelmの設計パターンです。

概要

Umbrella Chart自体はテンプレート(templates/)をほとんど持たず、Chart.yamldependenciesセクションで他のChart(サブチャート)を定義することに特化しています。これにより、複雑なシステム全体を1つのリリースとしてデプロイ・管理できます。

主な用途

  • マイクロサービス構成: 複数のサービス(Frontend, Backend, DBなど)を一括管理
  • 依存関係の統合: アプリケーションと必要なミドルウェア(Redis, PostgreSQLなど)をセットで提供

構造例

umbrella-app/
├── Chart.yaml # 依存関係を定義
├── values.yaml # 全体の設定(サブチャートの設定をオーバーライド)
├── charts/ # 依存Chartがダウンロードされる場所
└── templates/ # 通常は空、または共通のConfigMap/Secretのみ

Chart.yamlの設定

apiVersion: v2
name: my-complex-app
version: 1.0.0
type: application

dependencies:
- name: frontend
version: 1.2.0
repository: http://my-charts.com
- name: backend
version: 2.3.0
repository: http://my-charts.com
- name: postgresql
version: 12.1.0
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled

values.yamlでの設定オーバーライド

親Chart(Umbrella Chart)のvalues.yamlから、サブチャートの設定を上書きできます。サブチャート名をキーとして使用します。

# グローバル設定(全チャートで共有)
global:
imageRegistry: myregistry.azurecr.io

# frontendサブチャートの設定
frontend:
replicaCount: 3
service:
type: LoadBalancer

# backendサブチャートの設定
backend:
env:
- name: DB_HOST
value: "my-complex-app-postgresql"

# postgresqlサブチャートの設定
postgresql:
enabled: true
auth:
database: mydb
username: myuser

コマンド

依存関係を解決してChartを準備するには、以下のコマンドを使用します。

# 依存Chartをダウンロードして charts/ ディレクトリに配置
helm dependency build ./umbrella-app

# または
helm dependency update ./umbrella-app

Chartリポジトリへの公開

ChartMuseumを使用

# ChartMuseumにアップロード
curl --data-binary "@myapp-1.0.0.tgz" http://chartmuseum.example.com/api/charts

# リポジトリの追加
helm repo add myrepo http://chartmuseum.example.com
helm repo update

# リポジトリからインストール
helm install my-release myrepo/myapp

Azure Container Registry (ACR)を使用

# ACRにログイン
az acr login --name myregistry

# ChartをOCIアーティファクトとしてプッシュ
helm push myapp-1.0.0.tgz oci://myregistry.azurecr.io/helm

# ACRからインストール
helm install my-release oci://myregistry.azurecr.io/helm/myapp --version 1.0.0

ベストプラクティス

1. 命名規則

  • Chart名: 小文字、ハイフン区切り(例: my-app
  • リソース名: {{ include "myapp.fullname" . }}を使用
  • ラベル: Kubernetesの推奨ラベルを使用

2. セキュリティ

# Security Contextを常に定義
securityContext:
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL

# Resource Limitsを設定
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi

3. ConfigとSecretの管理

# ConfigMapのハッシュをアノテーションに追加(設定変更時にPodを再起動)
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}

4. 条件付きリソース

# 機能のオン/オフを values.yaml で制御
{{- if .Values.ingress.enabled }}
# Ingressリソース
{{- end }}

5. ドキュメント

  • README.md: Chartの概要と使用方法
  • NOTES.txt: インストール後にユーザーに表示される情報
  • values.yaml: すべての設定オプションにコメントを記載

NOTES.txt の例

Thank you for installing {{ .Chart.Name }}.

Your release is named {{ .Release.Name }}.

To learn more about the release, try:

$ helm status {{ .Release.Name }}
$ helm get all {{ .Release.Name }}

To access your application:

{{- if .Values.ingress.enabled }}
Visit: https://{{ (index .Values.ingress.hosts 0).host }}
{{- else }}
Run: kubectl port-forward svc/{{ include "myapp.fullname" . }} 8080:{{ .Values.service.port }}
Then visit: http://localhost:8080
{{- end }}

トラブルシューティング

デバッグコマンド

# レンダリングされたテンプレートを確認
helm template myapp ./myapp --debug

# インストールのドライラン
helm install myapp ./myapp --dry-run --debug

# インストールされたChartの値を確認
helm get values my-release

# インストールされたマニフェストを確認
helm get manifest my-release

# リリースの履歴を確認
helm history my-release

よくある問題

  1. テンプレートのインデントエラー

    • nindent関数を使用してインデントを正確に制御
  2. 値が反映されない

    • helm get valuesで実際の値を確認
    • --set-fの優先順位を確認
  3. 依存関係の問題

    • helm dependency updateを実行
    • charts/ディレクトリを確認

まとめ

カスタムHelm Chartを作成する際の重要なポイント:

  1. Chart.yaml: メタデータとバージョン管理
  2. values.yaml: 設定可能なすべてのパラメータ
  3. templates/: Kubernetesマニフェストのテンプレート
  4. _helpers.tpl: 再利用可能なテンプレート関数
  5. .tgzファイル: パッケージ化されたChart
  6. 環境別設定: 異なる環境での柔軟なデプロイ

Helm Chartは、Kubernetesアプリケーションのデプロイを標準化し、再利用可能にする強力なツールです。適切に構造化されたChartは、チーム全体の生産性を大幅に向上させます。