Kubernetes応用編 第09回
マニフェストのパッケージ管理 — Helm
9.1 はじめに
前回(第8回)では、HPA(Horizontal Pod Autoscaler)による自動スケーリングとProbeの詳細設計を扱いました。TaskBoardのフロントエンド(Nginx)とAPI(Payara Micro)にHPAを適用し、負荷に応じてPod数が自動で増減する仕組みを実装しています。また、Payara Microの起動時間(15〜20秒)を活かして「startupProbeがないとどうなるか」を身体で理解し、各Probeパラメータの効果を体験しました。
現在のTaskBoardの状態を確認しておきましょう。
[app Namespace]
Nginx (Deployment, replicas: 2) + Service (ClusterIP, targetPort: 8080)
★ HPA適用(CPU 70%, min:2, max:6)
SecurityContext適用済み
TaskBoard API (Deployment, replicas: 2, MySQL接続版) + Service (ClusterIP)
★ HPA適用(CPU 70%, min:2, max:4)
★ Probe詳細設定(startup / liveness / readiness)
★ カスタムHealthCheck(DB接続確認付き)
★ イメージをtaskboard-api:3.0.0に更新
SecurityContext適用済み
Gateway (taskboard-gateway) + HTTPRoute (taskboard-route)
+ ResourceQuota / LimitRange 適用済み
+ RBAC適用済み
+ NetworkPolicy適用済み
+ Pod Security Admission: warn=restricted
[db Namespace]
MySQL (StatefulSet, replicas: 1) + Headless Service + PVC
+ DB初期化Job(Completed)
+ DBバックアップCronJob(稼働中)
+ Secret(MySQL認証情報)
+ ResourceQuota / LimitRange 適用済み
+ RBAC適用済み
+ NetworkPolicy適用済み
+ Pod Security Admission: warn=baseline
[monitoring Namespace]
ログ収集DaemonSet(全Worker Nodeに配置)
+ ResourceQuota / LimitRange 適用済み
+ RBAC適用済み
[クラスタ全体]
Calico CNI
Metrics Server 稼働中
Gateway API 稼働中(/ → Nginx, /api → TaskBoard API)
マルチノード構成(CP 1 + Worker 3)
セキュリティ、ネットワーク制御、自動スケーリング、ヘルスチェック。本番運用に必要な武器はほぼ揃いました。しかし、1つだけ見て見ぬふりをしてきた課題があります。
第1回から第8回までの間に、~/k8s-applied/ディレクトリには大量のYAMLファイルが積み重なっています。Namespace定義、RBAC、Deployment、Service、StatefulSet、NetworkPolicy、HPA……。1ファイルずつkubectl applyしてきたからこそ仕組みを理解できましたが、これを日常の運用で毎回手作業で適用し続けるのは現実的でしょうか。「開発環境ではreplicas: 1、本番環境ではreplicas: 3」としたいとき、YAMLファイルをコピーして書き換えるのでしょうか。
| Before | After |
|---|---|
| 素のYAMLを直接kubectl applyしている。ファイルが増えて全体像が見えにくい | Helmチャートの構造を理解し、TaskBoardのフロントエンドマニフェストをHelm化できる |
応用編の最終回となる今回は、Kubernetesマニフェストのパッケージ管理ツール「Helm」を導入します。TaskBoardのフロントエンド(Nginx)を題材にHelm化し、values.yamlで環境差分を吸収する仕組みを体験します。なお、TaskBoard全体のHelm化は実践編第8回で本格的に行います。今回は「Helmの使い方を知る」ことに集中しましょう。
9.2 VMの世界との対比 — VMテンプレートとAnsibleのパラメータ化
9.2.1 VMの世界でのテンプレート管理
VMの世界では、似たような構成のサーバーを量産するとき「テンプレート」を使います。vSphereなら「VMテンプレート」を作成し、クローンして個別設定を変更する運用です。さらに自動化が進んだ現場では、AnsibleやTerraformで構成を「コード化」し、パラメータファイルで環境差分を吸収していたはずです。
たとえばAnsibleのPlaybookでは、共通のテンプレート(Jinja2テンプレート)に対して、group_vars/dev.ymlとgroup_vars/prod.ymlでパラメータを切り替えます。テンプレートには{{ web_server_count }}のような変数を埋め込み、環境ごとの変数ファイルで具体的な値を定義する構造です。
9.2.2 K8sではHelmがその役割を担う
Kubernetesの世界では、Helmがこの「テンプレート + パラメータファイル」の役割を担います。
| VMの世界 | K8s + Helmの世界 |
|---|---|
| VMテンプレート / Ansibleロール | Helmチャート(テンプレートの集合) |
| group_vars/dev.yml, group_vars/prod.yml | values.yaml, values-prod.yaml |
Jinja2テンプレートの変数 {{ xxx }} | Goテンプレートの変数 {{ .Values.xxx }} |
ansible-playbook -e @vars/prod.yml | helm install -f values-prod.yaml |
| Playbookの実行履歴(Tower/AWX) | Helmリリース履歴(helm history) |
| ロールバック(前回の変数でPlaybook再実行) | helm rollback |
テンプレートを使い回し、パラメータファイルで環境差分を吸収するというアプローチは、VMの世界もK8sの世界も同じです。違いは、Helmがリリース(デプロイ)の履歴管理とロールバック機能を標準で備えている点です。Ansibleの場合、Tower/AWXなどの外部ツールなしでは履歴管理が手薄でしたが、Helmではコマンド一発でリリース履歴の確認と巻き戻しができます。
9.3 Helmの全体像
9.3.1 Helmの3つの概念 — Chart / Release / values.yaml
Helmを理解するには、3つの概念を押さえるだけで十分です。
Chart(チャート)は、K8sマニフェストのテンプレートと設定値をまとめたパッケージです。ディレクトリ構造として存在し、中にはテンプレートファイル(Deployment、Serviceなど)と、デフォルトの設定値(values.yaml)が含まれます。Ansibleのロールに相当します。
Release(リリース)は、Chartをクラスタにデプロイした結果です。同じChartから複数のReleaseを作成できます。たとえば「taskboard-frontend-dev」と「taskboard-frontend-prod」のように、同じChartから開発用と本番用の2つのReleaseを作成できます。helm installするたびに1つのReleaseが作られ、helm upgradeするとそのReleaseのリビジョンが増えます。
values.yamlは、テンプレートに渡すパラメータの集合です。replicas数、イメージタグ、リソース制限など、環境ごとに変わる値をここに定義します。helm installやhelm upgrade時に-f values-prod.yamlのように別のvaluesファイルを渡すと、デフォルト値が上書きされます。
3つの関係を整理すると、Chart(テンプレート)+ values.yaml(パラメータ)= Release(クラスタ上のリソース)です。テンプレートは1つ。パラメータを切り替えるだけで、開発環境にも本番環境にもデプロイできます。
9.3.2 Helmをインストールする
Helmの公式インストールスクリプトを使います。
[Execution User: developer]
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
インストールが完了したら、バージョンを確認します。
[Execution User: developer]
helm version
version.BuildInfo{Version:"v3.17.1", GitCommit:"980d8ac1939e39138101364400756af2b3bb9707", GitTreeState:"clean", GoVersion:"go1.23.5"}
v3.17以上が表示されれば問題ありません。Helm v3はTiller(v2で必要だったクラスタ内のサーバーコンポーネント)が不要で、kubectlと同じkubeconfigを使ってクラスタに接続します。
9.3.3 helm create でチャートの雛形を確認する
helm createコマンドで、チャートの雛形を生成してみましょう。まず構造を理解するための参考として生成し、中身を確認します。
[Execution User: developer]
cd ~/k8s-applied
helm create sample-chart
Creating sample-chart
生成されたディレクトリ構造を確認します。
[Execution User: developer]
tree sample-chart/
sample-chart/
├── Chart.yaml # チャートのメタデータ(名前、バージョン等)
├── values.yaml # デフォルトのパラメータ値
├── charts/ # 依存チャートの格納先(今回は使わない)
├── templates/ # K8sマニフェストのテンプレート群
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── serviceaccount.yaml
│ ├── hpa.yaml
│ ├── ingress.yaml
│ ├── NOTES.txt # helm install後に表示されるメモ
│ ├── _helpers.tpl # 共通のヘルパーテンプレート
│ └── tests/
│ └── test-connection.yaml
└── .helmignore # パッケージング時の除外ファイル
重要なのは3つのファイルです。Chart.yamlがチャートの名刺、values.yamlがパラメータの集合、templates/ディレクトリ内がK8sマニフェストのテンプレートです。雛形には汎用的なDeployment、Service、Ingress等のテンプレートが含まれていますが、今回はTaskBoardのフロントエンド用にゼロから作り直します。この雛形は構造の参考として確認しただけなので削除しておきましょう。
[Execution User: developer]
rm -rf ~/k8s-applied/sample-chart
9.4 TaskBoardの現状を振り返る — マニフェストファイルの棚卸し
9.4.1 これまで作成したマニフェスト一覧を数える
Helm化の前に、現実を直視しましょう。第1回から第8回までの間に、~/k8s-applied/ディレクトリに作成したYAMLファイルを数えてみます。
[Execution User: developer]
ls ~/k8s-applied/*.yaml | wc -l
48
種別ごとに分類してみましょう。
[Execution User: developer]
echo "=== クラスタ基盤 ==="
ls ~/k8s-applied/kind-applied*.yaml ~/k8s-applied/namespace-*.yaml 2>/dev/null
echo ""
echo "=== ResourceQuota / LimitRange ==="
ls ~/k8s-applied/resourcequota-*.yaml ~/k8s-applied/limitrange-*.yaml 2>/dev/null
echo ""
echo "=== RBAC ==="
ls ~/k8s-applied/sa-*.yaml ~/k8s-applied/role-*.yaml ~/k8s-applied/rolebinding-*.yaml 2>/dev/null
echo ""
echo "=== フロントエンド(Nginx) ==="
ls ~/k8s-applied/nginx-*.yaml ~/k8s-applied/hpa-nginx.yaml 2>/dev/null
echo ""
echo "=== API(TaskBoard API) ==="
ls ~/k8s-applied/taskboard-api-*.yaml ~/k8s-applied/hpa-taskboard-api.yaml 2>/dev/null
echo ""
echo "=== DB(MySQL) ==="
ls ~/k8s-applied/mysql-*.yaml ~/k8s-applied/db-*.yaml 2>/dev/null
echo ""
echo "=== ネットワーク ==="
ls ~/k8s-applied/gateway.yaml ~/k8s-applied/httproute-*.yaml ~/k8s-applied/netpol-*.yaml 2>/dev/null
echo ""
echo "=== 監視 ==="
ls ~/k8s-applied/log-collector-*.yaml 2>/dev/null
=== クラスタ基盤 ===
kind-applied.yaml kind-applied-calico.yaml
namespace-app.yaml namespace-db.yaml namespace-monitoring.yaml
=== ResourceQuota / LimitRange ===
resourcequota-app.yaml resourcequota-db.yaml resourcequota-monitoring.yaml
limitrange-app.yaml limitrange-db.yaml limitrange-monitoring.yaml
=== RBAC ===
sa-developer-app.yaml sa-developer-db.yaml sa-operator-app.yaml sa-operator-db.yaml
role-developer-app.yaml role-developer-db.yaml role-operator-app.yaml role-operator-db.yaml
rolebinding-developer-app.yaml rolebinding-developer-db.yaml
rolebinding-operator-app.yaml rolebinding-operator-db.yaml
=== フロントエンド(Nginx) ===
nginx-configmap.yaml nginx-deployment.yaml nginx-deployment-v2.yaml
nginx-service.yaml nginx-service-v2.yaml hpa-nginx.yaml
=== API(TaskBoard API) ===
taskboard-api-deployment.yaml taskboard-api-deployment-v2.yaml
taskboard-api-deployment-v3.yaml taskboard-api-deployment-v4.yaml
taskboard-api-deployment-v4-no-startup.yaml taskboard-api-service.yaml
hpa-taskboard-api.yaml
=== DB(MySQL) ===
mysql-deployment-bad.yaml mysql-statefulset.yaml mysql-statefulset-v2.yaml
mysql-headless-service.yaml mysql-secret.yaml mysql-secret-app.yaml
db-init-job.yaml db-backup-cronjob.yaml db-backup-cronjob-fail.yaml
=== ネットワーク ===
gateway.yaml httproute-taskboard.yaml
netpol-app-default-deny.yaml netpol-app-allow-gateway.yaml
netpol-app-allow-api-to-db.yaml netpol-db-default-deny.yaml
netpol-db-allow-api.yaml netpol-db-allow-backup.yaml netpol-db-allow-backup-egress.yaml
=== 監視 ===
log-collector-daemonset.yaml
48ファイル。しかもこの中には、学習途中のバージョン(v1、v2、v3……)や、あえて失敗させるために作ったファイル(mysql-deployment-bad.yaml、db-backup-cronjob-fail.yaml、taskboard-api-deployment-v4-no-startup.yaml)も含まれています。「いま稼働中のTaskBoardを再現するために、どのファイルをどの順番で適用すればいいのか」を正確に答えられるでしょうか。
9.4.2 「開発環境と本番環境でreplicasとresourcesだけ変えたい」という課題
仮に、TaskBoardを開発環境と本番環境の2つで運用することになったとしましょう。変えたいのはreplicas数とresources(CPU/メモリ制限)だけです。
| 項目 | 開発環境(dev) | 本番環境(prod) |
|---|---|---|
| replicas | 1 | 3 |
| CPU requests | 50m | 100m |
| CPU limits | 200m | 400m |
| Memory requests | 64Mi | 128Mi |
| Memory limits | 128Mi | 256Mi |
素のYAMLのままだと、DeploymentファイルをコピーしてNginxのdev版とprod版を作ることになります。コピーした瞬間からメンテナンス対象が2倍に。セキュリティ設定を変更したら両方のファイルに手を入れなければなりません。ファイルが増えるほど「適用忘れ」「ファイル間の不整合」のリスクが高まります。
Helmなら、テンプレートは1つ。values.yamlの値を切り替えるだけで、環境差分を安全に管理できます。これから実際にやってみましょう。
9.5 NginxマニフェストをHelm化する
9.5.1 チャートの構造を作成する
TaskBoardのフロントエンド(Nginx)用のHelmチャートをゼロから作成します。helm createの雛形は便利ですが不要なファイルが多いため、必要なファイルだけを手作りします。
[Execution User: developer]
mkdir -p ~/k8s-applied/taskboard-frontend/templates
まず、チャートのメタデータファイル Chart.yaml を作成します。
[Execution User: developer]
cat <<'EOF' > ~/k8s-applied/taskboard-frontend/Chart.yaml
apiVersion: v2
name: taskboard-frontend
description: TaskBoard Frontend (Nginx) Helm chart
type: application
version: 0.1.0 # チャート自体のバージョン
appVersion: "1.27" # デプロイするアプリケーション(Nginx)のバージョン
EOF
apiVersion: v2はHelm v3のチャート形式です。versionはチャートのバージョン(チャートの構造やテンプレートが変わったときに上げる)、appVersionはデプロイするアプリケーションのバージョン(Nginxのバージョン)です。2つのバージョンは独立して管理します。
次に、ヘルパーテンプレート _helpers.tpl を作成します。チャート内で繰り返し使うラベルや名前の生成ロジックをここにまとめます。
[Execution User: developer]
cat <<'EOF' > ~/k8s-applied/taskboard-frontend/templates/_helpers.tpl
{{/*
チャート名を返す
*/}}
{{- define "taskboard-frontend.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
リリース名を含む完全な名前を返す
*/}}
{{- define "taskboard-frontend.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s" .Release.Name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{/*
共通ラベルを返す
*/}}
{{- define "taskboard-frontend.labels" -}}
app: taskboard
component: frontend
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
{{- end }}
{{/*
セレクタラベルを返す(matchLabelsに使う)
*/}}
{{- define "taskboard-frontend.selectorLabels" -}}
app: taskboard
component: frontend
{{- end }}
EOF
ヘルパーテンプレートのポイントを補足します。defineでテンプレートの断片に名前を付け、includeで呼び出します。セレクタラベル(app: taskboard、component: frontend)は、第1回から一貫して使ってきたラベル体系をそのまま維持しています。app.kubernetes.io/managed-byとhelm.sh/chartはHelmの標準的なラベルで、「このリソースはHelmで管理されている」ことを示します。
9.5.2 Deployment テンプレートを作成する
第7回で作成したnginx-deployment-v2.yamlをベースに、環境ごとに変わる値を{{ .Values.xxx }}で変数化します。ここで重要なのは「何をvalues.yamlに切り出し、何をテンプレートにハードコードするか」の判断です。
| 判断 | 項目 | 理由 |
|---|---|---|
| values.yamlに切り出す | replicas、image(repository / tag)、resources(requests / limits) | 環境ごとに値が変わる。dev / prodで差分が出る典型的な項目 |
| values.yamlに切り出す | SecurityContext設定(runAsNonRoot, runAsUser等) | 基本は固定だが、デバッグ時に一時的に緩めたいケースがある |
| テンプレートにハードコード | Namespace(app固定) | TaskBoardのフロントエンドは必ずapp Namespaceに配置する。環境差分ではない |
| テンプレートにハードコード | ラベル体系(app: taskboard, component: frontend) | サービスディスカバリの基盤。変更するとServiceやNetworkPolicyが壊れる |
| テンプレートにハードコード | ポート番号(8080固定) | Nginxの設定(nginx.conf)と密結合。値だけ変えても動かない |
原則として、「環境ごとに変わる値」はvalues.yamlへ、「変えるとシステムが壊れる値」はテンプレートにハードコードします。
[Execution User: developer]
cat <<'EOF' > ~/k8s-applied/taskboard-frontend/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: app
labels:
{{- include "taskboard-frontend.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "taskboard-frontend.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "taskboard-frontend.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: nginx
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- containerPort: 8080
resources:
requests:
cpu: "{{ .Values.resources.requests.cpu }}"
memory: "{{ .Values.resources.requests.memory }}"
limits:
cpu: "{{ .Values.resources.limits.cpu }}"
memory: "{{ .Values.resources.limits.memory }}"
securityContext:
runAsNonRoot: {{ .Values.securityContext.runAsNonRoot }}
runAsUser: {{ .Values.securityContext.runAsUser }}
readOnlyRootFilesystem: {{ .Values.securityContext.readOnlyRootFilesystem }}
allowPrivilegeEscalation: {{ .Values.securityContext.allowPrivilegeEscalation }}
seccompProfile:
type: {{ .Values.securityContext.seccompProfileType }}
capabilities:
drop:
- ALL
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
readOnly: true
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /var/cache/nginx
volumes:
- name: nginx-config
configMap:
name: nginx-config
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}
EOF
テンプレートの読み方を確認します。{{ .Values.replicaCount }}はvalues.yamlのreplicaCountの値に置き換わります。{{- include "taskboard-frontend.labels" . | nindent 4 }}は先ほど_helpers.tplで定義したラベルテンプレートを呼び出し、インデント4スペースで挿入します。nindentは改行 + インデントを行うHelmの組み込み関数です。
ハードコードした部分にも注目してください。namespace: app、containerPort: 8080、volumeMountsの構造(nginx-config, tmp, cache)はテンプレート内に固定しています。これらは環境によって変わるべきではない値です。ConfigMap名(nginx-config)も固定です。ConfigMap自体は今回Helm化の対象外としています(実践編第8回でTaskBoard全体をHelm化する際に含めます)。
9.5.3 Service テンプレートを作成する
[Execution User: developer]
cat <<'EOF' > ~/k8s-applied/taskboard-frontend/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: app
labels:
{{- include "taskboard-frontend.labels" . | nindent 4 }}
spec:
type: ClusterIP
selector:
{{- include "taskboard-frontend.selectorLabels" . | nindent 4 }}
ports:
- port: 80
targetPort: 8080
EOF
Serviceはシンプルです。port: 80(Service自体のポート)とtargetPort: 8080(Nginx Podへの転送先ポート)はハードコードしています。これらはHTTPRouteのbackendRefs.portやNetworkPolicyのポート指定と連動するため、values.yamlで簡単に変えられるようにすべきではありません。
9.5.4 values.yaml で環境差分を定義する
デフォルトのvalues.yaml(開発環境相当)を作成します。
[Execution User: developer]
cat <<'EOF' > ~/k8s-applied/taskboard-frontend/values.yaml
# TaskBoard Frontend (Nginx) - デフォルト値(dev環境相当)
replicaCount: 1
image:
repository: nginx
tag: "1.27"
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "200m"
memory: "128Mi"
securityContext:
runAsNonRoot: true
runAsUser: 101 # nginx:1.27 の nginx ユーザー (uid=101)
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
seccompProfileType: RuntimeDefault
EOF
各値の設計根拠を整理します。
| パラメータ | dev値 | 根拠 |
|---|---|---|
| replicaCount | 1 | 開発環境では1レプリカで十分。リソース節約 |
| image.repository | nginx | 公式Nginxイメージを使用 |
| image.tag | 1.27 | 入門編から一貫して使用しているバージョン |
| resources.requests.cpu | 50m | 静的ファイル配信のみ。設計書の目安通り |
| resources.limits.memory | 128Mi | Nginxのワーカープロセスに十分な量。設計書の目安通り |
| securityContext.runAsUser | 101 | nginx:1.27イメージのnginxユーザー(uid=101)。第7回で確認済み |
続いて、本番環境用のvaluesファイルを作成します。デフォルトのvalues.yamlとの差分だけを記載すれば、残りの値はデフォルトが使われます。
[Execution User: developer]
cat <<'EOF' > ~/k8s-applied/taskboard-frontend/values-prod.yaml
# TaskBoard Frontend (Nginx) - 本番環境用の上書き値
replicaCount: 3
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "400m"
memory: "256Mi"
EOF
values-prod.yamlには差分だけを書いている点に注目してください。imageやsecurityContextはデフォルトのvalues.yamlの値がそのまま使われます。「差分だけを管理する」というのがHelmの環境管理のポイントです。
完成したチャートの構造を確認しましょう。
[Execution User: developer]
tree ~/k8s-applied/taskboard-frontend/
taskboard-frontend/
├── Chart.yaml
├── values.yaml
├── values-prod.yaml
└── templates/
├── _helpers.tpl
├── deployment.yaml
└── service.yaml
6ファイルのシンプルなチャートです。これだけで、NginxのDeploymentとServiceを環境差分付きでデプロイできます。
9.5.5 helm install でデプロイする
Helmでデプロイする前に、まず既存のNginx DeploymentとServiceを削除します。Helmは自身が管理するリソースを追跡するため、既存のkubectl applyで作成したリソースとは別物として扱われます。既存リソースが残ったままだと名前が衝突してエラーになります。
[Execution User: developer]
# 既存のNginx DeploymentとServiceを削除(HPA、ConfigMapは残す)
kubectl delete deployment nginx -n app
kubectl delete service nginx -n app
deployment.apps "nginx" deleted
service "nginx" deleted
HPAやConfigMap、NetworkPolicyなどの関連リソースはそのまま残しています。今回Helm化するのはDeploymentとServiceだけです。HPAは既存のものがDeployment/nginxをターゲットにしているため、Helmで同名のDeploymentがデプロイされれば自動的に再接続されます。
デプロイの前に、helm templateコマンドでテンプレートのレンダリング結果を確認しましょう。実際にクラスタに何も作成せず、生成されるYAMLだけを確認できます。
[Execution User: developer]
helm template taskboard-frontend ~/k8s-applied/taskboard-frontend
---
# Source: taskboard-frontend/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: app
labels:
app: taskboard
component: frontend
app.kubernetes.io/managed-by: Helm
helm.sh/chart: taskboard-frontend-0.1.0
spec:
type: ClusterIP
selector:
app: taskboard
component: frontend
ports:
- port: 80
targetPort: 8080
---
# Source: taskboard-frontend/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: app
labels:
app: taskboard
component: frontend
app.kubernetes.io/managed-by: Helm
helm.sh/chart: taskboard-frontend-0.1.0
spec:
replicas: 1
selector:
matchLabels:
app: taskboard
component: frontend
template:
metadata:
labels:
app: taskboard
component: frontend
spec:
containers:
- name: nginx
image: "nginx:1.27"
ports:
- containerPort: 8080
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "200m"
memory: "128Mi"
securityContext:
runAsNonRoot: true
runAsUser: 101
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
seccompProfile:
type: RuntimeDefault
capabilities:
drop:
- ALL
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
readOnly: true
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /var/cache/nginx
volumes:
- name: nginx-config
configMap:
name: nginx-config
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}
第7回で作成したnginx-deployment-v2.yamlとnginx-service-v2.yamlの内容が正しく再現されていることを確認してください。replicas: 1はdev環境のデフォルト値です。SecurityContextの設定(runAsUser: 101、seccompProfile: RuntimeDefault等)も第7回の最終状態と一致しています。
では、デプロイしましょう。
[Execution User: developer]
helm install taskboard-frontend ~/k8s-applied/taskboard-frontend -n app
NAME: taskboard-frontend
LAST DEPLOYED: Mon Jan 20 10:30:00 2026
NAMESPACE: app
STATUS: deployed
REVISION: 1
helm installの構文は、helm install [リリース名] [チャートのパス] [オプション]です。-n appはHelmがリリース情報を記録するNamespaceを指定しています(テンプレート内でNamespaceをハードコードしているため、リソースの作成先には影響しません)。
デプロイされたリソースを確認します。
[Execution User: developer]
kubectl get deployment,service,pods -n app -l component=frontend
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx 1/1 1 1 30s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nginx ClusterIP 10.96.45.123 <none> 80/TCP 30s
NAME READY STATUS RESTARTS AGE
pod/nginx-6f8b9c7d4-m2k5j 1/1 Running 0 30s
dev環境のデフォルト値(replicas: 1)でNginxがデプロイされました。Gateway API経由でのアクセスも確認しておきましょう。
[Execution User: developer]
GATEWAY_IP=$(kubectl get gateway taskboard-gateway -n app -o jsonpath='{.status.addresses[0].value}')
curl -s -o /dev/null -w "%{http_code}" http://${GATEWAY_IP}/
200
HTTPステータス200が返りました。Helmでデプロイした Nginx が正常に動作し、Gateway API経由のルーティングも維持されています。Service名(nginx)とポート(80)が変わっていないため、HTTPRouteのbackendRefsには一切手を加える必要がありませんでした。
Helmのリリース一覧も確認しておきます。
[Execution User: developer]
helm list -n app
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
taskboard-frontend app 1 2026-01-20 10:30:00.000000000 +0900 JST deployed taskboard-frontend-0.1.0 1.27
REVISION 1のリリースが作成されています。ここからアップグレードやロールバックを行うと、REVISIONが増えていきます。
9.6 Helmの運用サイクルを体験する
9.6.1 values.yamlの値を変更して helm upgrade する
本番環境を想定して、values-prod.yamlの値で更新してみましょう。replicasが1から3に、resourcesが増強されます。
[Execution User: developer]
helm upgrade taskboard-frontend ~/k8s-applied/taskboard-frontend \
-f ~/k8s-applied/taskboard-frontend/values-prod.yaml \
-n app
Release "taskboard-frontend" has been upgraded. Happy Helming!
NAME: taskboard-frontend
LAST DEPLOYED: Mon Jan 20 10:35:00 2026
NAMESPACE: app
STATUS: deployed
REVISION: 2
REVISIONが1から2に上がりました。Pod数の変化を確認します。
[Execution User: developer]
kubectl get deployment nginx -n app
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 3/3 3 3 5m
replicas: 3に変わっています。resourcesも確認します。
[Execution User: developer]
kubectl get deployment nginx -n app -o jsonpath='{.spec.template.spec.containers[0].resources}' | python3 -m json.tool
{
"limits": {
"cpu": "400m",
"memory": "256Mi"
},
"requests": {
"cpu": "100m",
"memory": "128Mi"
}
}
values-prod.yamlの値が正しく反映されています。YAMLファイルをコピーして書き換える必要はありません。テンプレートは1つのまま、valuesファイルを差し替えるだけで環境差分を管理できます。
9.6.2 問題発生を想定して helm rollback する
ここで「構築 → 破壊 → 復活」の流れを体験しましょう。イメージタグの変更をシミュレートします。
[Execution User: developer]
helm upgrade taskboard-frontend ~/k8s-applied/taskboard-frontend \
-f ~/k8s-applied/taskboard-frontend/values-prod.yaml \
--set image.tag="1.99-does-not-exist" \
-n app
Release "taskboard-frontend" has been upgraded. Happy Helming!
NAME: taskboard-frontend
LAST DEPLOYED: Mon Jan 20 10:40:00 2026
NAMESPACE: app
STATUS: deployed
REVISION: 3
--setオプションを使うと、valuesファイルを変更せずにコマンドラインで値を上書きできます。ここでは存在しないイメージタグ1.99-does-not-existを指定しました。Helm自体は「マニフェストを適用した」ところまでが責務なので、STATUSはdeployedと表示されます。しかし、クラスタ上ではイメージの取得に失敗しているはずです。
[Execution User: developer]
kubectl get pods -n app -l component=frontend
NAME READY STATUS RESTARTS AGE
nginx-5d8f9a7b3-h2k4j 0/1 ImagePullBackOff 0 30s
nginx-5d8f9a7b3-m7n9p 0/1 ImagePullBackOff 0 30s
nginx-5d8f9a7b3-r3w5x 0/1 ImagePullBackOff 0 30s
nginx-6f8b9c7d4-k8m2n 1/1 Running 0 5m
nginx-6f8b9c7d4-p4r6t 1/1 Running 0 5m
nginx-6f8b9c7d4-t2v8w 1/1 Running 0 5m
ImagePullBackOffが出ています。存在しないイメージなので当然です。Deploymentのローリングアップデートにより、旧バージョンのPodはまだ稼働中ですが、新バージョンのPodが正常起動できないためデプロイが進みません。
すぐにロールバックしましょう。
[Execution User: developer]
helm rollback taskboard-frontend 2 -n app
Rollback was a success! Happy Helming!
helm rollback [リリース名] [リビジョン番号]で、指定したリビジョンの状態にロールバックできます。リビジョン2は「values-prod.yamlを適用した正常な状態」です。
[Execution User: developer]
kubectl get pods -n app -l component=frontend
NAME READY STATUS RESTARTS AGE
nginx-6f8b9c7d4-k8m2n 1/1 Running 0 7m
nginx-6f8b9c7d4-p4r6t 1/1 Running 0 7m
nginx-6f8b9c7d4-t2v8w 1/1 Running 0 7m
ImagePullBackOffのPodが消え、正常なPodだけが稼働しています。helm rollback一発で復旧できました。VMの世界でスナップショットからリストアするような感覚ですが、Helmの場合は「どのリビジョンに戻すか」を履歴から選べる点がより柔軟です。
9.6.3 helm history でリリース履歴を確認する
[Execution User: developer]
helm history taskboard-frontend -n app
REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
1 Mon Jan 20 10:30:00 2026 superseded taskboard-frontend-0.1.0 1.27 Install complete
2 Mon Jan 20 10:35:00 2026 superseded taskboard-frontend-0.1.0 1.27 Upgrade complete
3 Mon Jan 20 10:40:00 2026 superseded taskboard-frontend-0.1.0 1.27 Upgrade complete
4 Mon Jan 20 10:42:00 2026 deployed taskboard-frontend-0.1.0 1.27 Rollback to 2
全操作の履歴が残っています。REVISION 1がhelm install、REVISION 2がvalues-prod.yamlでのupgrade、REVISION 3が問題のあるイメージタグでのupgrade、REVISION 4がREVISION 2へのrollbackです。STATUSがdeployedなのは現在アクティブなリビジョン(4)だけで、残りはsuperseded(置き換え済み)です。
ロールバックは新しいリビジョンとして記録される点に注意してください。REVISION 2の内容を復元しましたが、「REVISION 2に戻した」のではなく「REVISION 4としてREVISION 2の状態を再適用した」という履歴になっています。巻き戻しの事実が履歴に残るため、あとから「何が起きたか」を追跡できます。
9.6.4 helm uninstall でクリーンアップする
最後に、helm uninstallでリリースを削除する手順も確認しておきましょう。ただし、このあとの9.7節のまとめで動作中のTaskBoardを確認するため、ここでは一旦devのデフォルト値で再デプロイしておきます。
まず、現在のリリースを削除します。
[Execution User: developer]
helm uninstall taskboard-frontend -n app
release "taskboard-frontend" uninstalled
helm uninstallはリリースに紐づくすべてのK8sリソース(Deployment、Service)を削除し、リリース履歴もクリアします。
[Execution User: developer]
kubectl get deployment,service -n app -l component=frontend
No resources found in app namespace.
きれいに削除されました。では、第8回終了時のreplicas: 2の状態でTaskBoardを復旧させましょう。--setでreplicaCountを2に指定します。
[Execution User: developer]
helm install taskboard-frontend ~/k8s-applied/taskboard-frontend \
--set replicaCount=2 \
-n app
NAME: taskboard-frontend
LAST DEPLOYED: Mon Jan 20 10:50:00 2026
NAMESPACE: app
STATUS: deployed
REVISION: 1
[Execution User: developer]
kubectl get deployment nginx -n app
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 2/2 2 2 15s
replicas: 2でNginxが稼働しています。HPAも正常に再接続されているか確認しておきましょう。
[Execution User: developer]
kubectl get hpa -n app
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
nginx-hpa Deployment/nginx 2%/70% 2 6 2 45m
taskboard-api-hpa Deployment/taskboard-api 6%/70% 2 4 2 40m
HPAのTARGETSに正常な値が表示されています。HPA自体はkubectl applyで作成したものがそのまま残っており、Helmでデプロイし直したDeployment/nginxを自動的に認識しています。Deployment名が同じ(nginx)であれば、HPAは作成手段(kubectl applyかhelm installか)を区別しません。
9.7 この回のまとめ — 応用編の総括
9.7.1 TaskBoardの完成形 — 全武器の装備マップ
応用編の全9回を通じて、TaskBoardに以下の武器が装備されました。
┌──────────────────────────────────────────────────────────────────┐
│ TaskBoard 完全体 │
│ (応用編 第9回 終了時点) │
├──────────────────────────────────────────────────────────────────┤
│ │
│ [app Namespace] │
│ Nginx (Deployment, replicas: 2) │
│ ★ Helm化(Chart: taskboard-frontend)← 今回 │
│ + Service (ClusterIP, port:80 → targetPort:8080) │
│ + HPA(CPU 70%, min:2, max:6) │
│ + SecurityContext(非root, readOnlyFS, seccomp) │
│ TaskBoard API (Deployment, replicas: 2, MySQL接続版) │
│ + Service (ClusterIP) │
│ + HPA(CPU 70%, min:2, max:4) │
│ + Probe詳細設定(startup / liveness / readiness) │
│ + カスタムHealthCheck(DB接続確認) │
│ + SecurityContext(非root, readOnlyFS, seccomp) │
│ Gateway (taskboard-gateway) + HTTPRoute (taskboard-route) │
│ / → Nginx, /api → TaskBoard API │
│ ResourceQuota / LimitRange │
│ RBAC(developer / operator ServiceAccount) │
│ NetworkPolicy(デフォルト拒否 + 必要な通信のみ許可) │
│ Pod Security Admission: warn=restricted │
│ │
│ [db Namespace] │
│ MySQL (StatefulSet, replicas: 1) │
│ + Headless Service + PVC(永続ストレージ) │
│ + DB初期化Job(Completed) │
│ + DBバックアップCronJob(稼働中) │
│ + Secret(MySQL認証情報) │
│ ResourceQuota / LimitRange │
│ RBAC │
│ NetworkPolicy(API→DBのみ許可、バックアップCronJob考慮済み) │
│ Pod Security Admission: warn=baseline │
│ │
│ [monitoring Namespace] │
│ ログ収集DaemonSet(全Worker Nodeに配置) │
│ ResourceQuota / LimitRange │
│ RBAC │
│ │
│ [クラスタ全体] │
│ Calico CNI │
│ Metrics Server │
│ Gateway API(Envoy Gateway) │
│ マルチノード構成(CP 1 + Worker 3) │
│ │
└──────────────────────────────────────────────────────────────────┘
第1回で空のクラスタにNamespaceを切ったところから始まり、9回を経てここまでたどり着きました。セキュリティ、ネットワーク制御、自動スケーリング、ヘルスチェック、パッケージ管理。本番で戦うための武器が一通り揃っています。
9.7.2 Helm設計の判断基準 — いつ使う / いつ使わない
| 判断 | ケース |
|---|---|
| Helmを使うべき | 複数環境(dev / staging / prod)で同じアプリをデプロイする。マニフェストが5ファイル以上になりパッケージとして管理したい。リリース履歴とロールバックが必要 |
| 素のYAMLで十分 | 環境が1つだけで環境差分がない。マニフェストが1〜2ファイルの小規模構成。学習やテスト目的で一時的に作成する |
| values.yamlに切り出すべき | 環境ごとに値が変わる項目(replicas、resources、image tag)。デバッグ時に一時的に変更したい設定 |
| テンプレートにハードコードすべき | 変えるとシステムが壊れる値(ポート番号、ラベル体系、Namespace)。他のリソース(NetworkPolicy、HTTPRoute等)と連動する設定 |
🔧 トラブルシュートTips
helm installで「cannot re-use a name that is still in use」エラーが出たら、同名のリリースがすでに存在しています。helm list -n appで確認し、不要ならhelm uninstallしてから再実行してください。
helm templateで生成されるYAMLが期待と異なる場合は、values.yamlの階層構造(インデント)を確認してください。YAMLではインデントがずれると別のキーとして解釈されます。
helm upgrade後にPodがImagePullBackOffになった場合は、今回体験したようにhelm rollbackで即座に復旧できます。まず復旧してから原因調査するのが鉄則です。
9.7.3 応用編で手に入れた武器の一覧
応用編全9回で学んだ武器を一覧にまとめます。
| 回 | テーマ | 手に入れた武器 | TaskBoardへの効果 |
|---|---|---|---|
| 第1回 | 環境構築とNamespace設計 | Namespace / ResourceQuota / LimitRange / Metrics Server | 環境分離とリソース制限。マルチノードクラスタ上でTaskBoardが稼働開始 |
| 第2回 | RBACアクセス制御 | ServiceAccount / Role / RoleBinding | 「誰が何をできるか」の制御。developer / operatorの権限分離 |
| 第3回 | StatefulSetでDB運用 | StatefulSet / Headless Service / PVC | MySQLの永続化とステートフル運用。APIをMySQL接続版に更新 |
| 第4回 | DaemonSet / Job / CronJob | DaemonSet / Job / CronJob | DB初期化、定期バックアップ、全ノードログ収集 |
| 第5回 | Gateway APIルーティング | Gateway / HTTPRoute | 外部からの L7 ルーティング(パスベース振り分け) |
| 第6回 | NetworkPolicy | NetworkPolicy(デフォルト拒否 + ホワイトリスト) | Pod間通信の最小権限制御。バックアップCronJobの通信も考慮 |
| 第7回 | SecurityContext / PSS | SecurityContext / Pod Security Standards | コンテナの非root化、FS読み取り専用化、権限昇格禁止 |
| 第8回 | HPA / Probe詳細 | HPA / startup / liveness / readiness Probe | 自動スケーリングとヘルスチェック。MicroProfile Health活用 |
| 第9回 | Helm | Chart / Release / values.yaml | フロントエンドのHelm化。環境差分のパラメータ管理 |
9.7.4 実践編への招待 — 「武器の使い方」から「武器の選び方」へ
応用編ではTaskBoardに1つずつ武器を追加してきました。「Namespaceはこう使う」「StatefulSetはこう動く」「NetworkPolicyはこう書く」。各回で1つの武器の使い方を覚え、TaskBoardに装備していくスタイルです。
しかし、実際のプロジェクトではこの順番で物事は進みません。まず要件があり、要件からリソースを選定し、設計書を書き、手順に沿って構築し、運用設計を整え、障害に対処する。この「ライフサイクル」を一周回す体験がなければ、武器は持っているだけで使いこなせません。
実践編では、応用編で作り上げたTaskBoardを一度すべて削除します。そして、同じTaskBoardの要件を白紙から提示します。今度は「結果的に動くもの」ではなく、「最初から全体を設計して作る」プロセスを踏みます。
構成図を描き、基本設計書を書き、マニフェストの全パラメータを根拠とともに決め、設計書通りに段階デプロイし、運用設計を整え、障害に対処する。応用編で手に入れた武器を「いつ・なぜ選ぶか」を設計判断する。それが実践編の目標です。
なお、今回はフロントエンド(Nginx)のみをHelm化しましたが、実践編第8回ではTaskBoard全体(API、DB、NetworkPolicy、RBAC等を含む)を1つのHelmチャートにまとめます。values.yamlで環境差分を管理し、helm upgradeで安全に変更を適用する日常運用のワークフローを実践します。
応用編の9回、お疲れさまでした。「本番で戦える武器」は揃いました。実践編では、その武器を使いこなすための「現場力」を身につけましょう。
AIコラム — HelmチャートのレビューをAIに依頼する
Helmチャートを作成したあと、「テンプレートの構文に誤りがないか」「values.yamlとテンプレートの変数名が一致しているか」をセルフチェックするのは骨が折れます。この確認作業はAIが得意とする領域です。
AIに「以下のHelmチャート(Chart.yaml、values.yaml、templates/deployment.yaml)をレビューしてほしい。テンプレート変数とvalues.yamlのキーの整合性、Goテンプレート構文の正確性、K8sマニフェストとしての妥当性を確認して」と依頼すると、変数名のタイポや構文エラーを短時間で指摘してくれます。
ただし、AIのレビュー結果は必ずhelm templateで実際にレンダリングして検証してください。AIが「正しい」と言ったテンプレートでも、インデントのずれや型の不一致でエラーになることはあります。helm templateは嘘をつきませんが、AIは間違えることがあります。
