Skip to content
Published on

Helm Chart作成とデプロイ自動化 完全ガイド

Authors
  • Name
    Twitter
Helm Chart Authoring Guide

1. Helmとは?

Helmは、Kubernetesのパッケージマネージャーです。複雑なKubernetesリソースを一つのChartとしてパッケージングし、インストール、アップグレード、ロールバックを簡単に管理できます。

なぜHelmなのか?

  • 繰り返しのYAML記述を排除
  • 環境別の設定分離(dev/staging/production)
  • バージョン管理とロールバック
  • 依存関係の管理

2. Chartディレクトリ構造

mychart/
├── Chart.yaml          # Chartメタデータ
├── Chart.lock          # 依存関係ロックファイル
├── values.yaml         # デフォルト設定値
├── values-prod.yaml    # プロダクションオーバーライド
├── templates/          # Kubernetesマニフェストテンプレート
│   ├── _helpers.tpl    # 共通ヘルパー関数
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── hpa.yaml
│   ├── configmap.yaml
│   ├── secret.yaml
│   ├── serviceaccount.yaml
│   ├── NOTES.txt       # インストール後の案内メッセージ
│   └── tests/
│       └── test-connection.yaml
├── charts/             # 依存Chart
└── .helmignore         # パッケージング除外ファイル

Chart.yamlの作成

apiVersion: v2
name: my-web-app
description: A production-ready web application
type: application
version: 1.2.0 # Chartバージョン(SemVer)
appVersion: '3.1.0' # アプリバージョン
maintainers:
  - name: youngjukim
    email: fjvbn2003@gmail.com
dependencies:
  - name: postgresql
    version: '12.x.x'
    repository: 'https://charts.bitnami.com/bitnami'
    condition: postgresql.enabled
  - name: redis
    version: '17.x.x'
    repository: 'https://charts.bitnami.com/bitnami'
    condition: redis.enabled

3. values.yaml設計パターン

# values.yaml - デフォルト設定
replicaCount: 2

image:
  repository: myregistry.io/my-web-app
  tag: '' # Chart appVersionを使用
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80
  targetPort: 8080

ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  hosts:
    - host: app.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: app-tls
      hosts:
        - app.example.com

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

autoscaling:
  enabled: false
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 80

postgresql:
  enabled: true
  auth:
    database: myapp
    username: appuser

redis:
  enabled: false

4. テンプレートの作成

_helpers.tpl - 共通ヘルパー

{{- define "mychart.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{- define "mychart.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 }}

{{- define "mychart.labels" -}}
helm.sh/chart: {{ include "mychart.chart" . }}
app.kubernetes.io/name: {{ include "mychart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{- define "mychart.selectorLabels" -}}
app.kubernetes.io/name: {{ include "mychart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "mychart.fullname" . }}
  labels:
    {{- include "mychart.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "mychart.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
      labels:
        {{- include "mychart.selectorLabels" . | nindent 8 }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: {{ .Values.service.targetPort }}
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /healthz
              port: http
            initialDelaySeconds: 15
          readinessProbe:
            httpGet:
              path: /ready
              port: http
            initialDelaySeconds: 5
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
          envFrom:
            - configMapRef:
                name: {{ include "mychart.fullname" . }}-config

5. Helm Hooks

Hookを使用すると、リリースライフサイクルの特定の時点でタスクを実行できます:

# templates/pre-install-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ include "mychart.fullname" . }}-db-migrate
  annotations:
    "helm.sh/hook": pre-install,pre-upgrade
    "helm.sh/hook-weight": "-5"
    "helm.sh/hook-delete-policy": before-hook-creation
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          command: ["python", "manage.py", "migrate"]
          envFrom:
            - secretRef:
                name: {{ include "mychart.fullname" . }}-db-secret
  backoffLimit: 3

Hookの種類

Hook実行タイミング
pre-installインストール前
post-installインストール後
pre-upgradeアップグレード前
post-upgradeアップグレード後
pre-rollbackロールバック前
pre-delete削除前
testhelm test実行時

6. CI/CDパイプライン連携

GitHub Actions + Helm

# .github/workflows/deploy.yml
name: Deploy Helm Chart
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: ap-northeast-2

      - name: Update kubeconfig
        run: aws eks update-kubeconfig --name my-cluster

      - name: Install Helm
        uses: azure/setup-helm@v3
        with:
          version: v3.14.0

      - name: Helm lint
        run: helm lint ./charts/my-web-app

      - name: Helm upgrade
        run: |
          helm upgrade --install my-app ./charts/my-web-app \
            --namespace production \
            --create-namespace \
            -f ./charts/my-web-app/values-prod.yaml \
            --set image.tag=${{ github.sha }} \
            --wait --timeout 5m

helm templateでのローカル検証

# レンダリングされたマニフェストを確認
helm template my-release ./mychart -f values-prod.yaml

# 特定の値をオーバーライド
helm template my-release ./mychart --set replicaCount=3

# kubevalで有効性を検証
helm template my-release ./mychart | kubeval --strict

7. Chartテスト

# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
  name: "{{ include "mychart.fullname" . }}-test"
  annotations:
    "helm.sh/hook": test
spec:
  containers:
    - name: curl
      image: curlimages/curl:8.5.0
      command: ['curl']
      args: ['{{ include "mychart.fullname" . }}:{{ .Values.service.port }}/healthz']
  restartPolicy: Never
# テスト実行
helm test my-release -n production

8. クイズ

Q1: Chart.yamlのversionとappVersionの違いは?
  • version: Chart自体のバージョンです。Chartの構造、テンプレート、valuesが変更されたときに上げます。SemVerに従います。
  • appVersion: Chartがデプロイするアプリケーションのバージョンです。アプリコードが変更されたときに上げます。

例:Chart version 1.2.0がappVersion 3.1.0をデプロイすることがあります。

Q2: Helm Hookのhook-weightはどのような役割を果たしますか?

hook-weightは、同じタイプのhookが複数ある場合に実行順序を決定します。低い数字が先に実行されます。

例:weight -5のDBマイグレーションがweight 0のキャッシュ初期化よりも先に実行されます。

Q3: deployment.yamlのchecksum/configアノテーションの用途は?

ConfigMapが変更されるとsha256sumが変わり、Podのアノテーションが変更されます。これにより、Deploymentがローリングアップデートをトリガーします。

ConfigMapのみ変更した場合、デフォルトではPodが再起動されない問題を解決するパターンです。