Helmを「完全に理解した」

# はじめに

[TRIAL＆RetailAI Advent Calendar 2024](https://qiita.com/advent-calendar/2024/retail-ai)  の 20日目の記事です。

https://qiita.com/advent-calendar/2024/retail-ai

昨日の記事は [@kyojinnaapyon](https://qiita.com/kyojinnaapyon) さんの『[Google Vision AIでレシートスキャンに挑戦！Kotlin × Quarkusでアプリを作ってみた](https://qiita.com/kyojinnaapyon/items/e9435fe1178670b4605d)』という記事でした。

Google Cloudにレシート情報を取り込んだ後は、自分がどの商品をどのくらいの間隔で買うのか、必要なもの不要なものはどれかなどをAIで分析できるようにしても面白そうですね🧐

# 自己紹介

はじめまして。新卒2年目の伊藤と申します。

インフラ・クラウド関係の部署に配属され、約9ヶ月。
SREという重い肩書きを背負いながら、日々学習に明け暮れています。

最近は12/23のGoogle Cloud Architectの試験に向けて、怯えながら過去問を解いている毎日です。

# 本題

業務でTerraformとHelmに触れる機会があり、「コンテナ？Kubernetes？Helm？IaC？…何だそれ？」と概念から理解することのできなかった日から、脳が焼き切れるくらい考え、Helmを「完全に理解」することができたので、この機会にHelmをちょっとだけ紹介(アウトプット)したいと思います。

ちなみに、Kubernetesは本当にまだ理解できていません。

※こちらの記事は、個人的な見解であり、所属する会社、組織とは全く関係ありません。

# Helmとは

> Helm is the best way to find, share, and use software built for Kubernetes.
(Helm は、 Kubernetes 用に構築されたソフトウェアを検索、共有、使用するための最良の方法です。)
> 

公式(https://helm.sh/)より

Kubernetesを作成した方なら経験があると思いますが、yamlファイルを地獄のように書き続けなければならないです。
Helmはその手間を省き、`helm install` というコマンド一発で環境を構築することができます。

Helmを理解する近道は、実際に使ってみることです！

▼ 私が以前Kubernets作成で参考にさせていただいた手順書（鬼ムズ）

https://github.com/kelseyhightower/kubernetes-the-hard-way

▼ Helmが利用できるか確認するサイトもあります

https://artifacthub.io/

# やってみる
コードとかダラダラ書いてるので読み飛ばしていいです。

## 作業内容

GKE (Google Kubernetes Engine) のクラスタ内に、Helmでリソースのデプロイします。
アプリケーションだと、Deployment、Service、Ingressが最小構成になると思います。
今回は外部公開をしないので、ロードバランサーの役割を担う、Ingressは作成しません。

#### イメージ図

![Helm.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618316/6ead4ba2-8423-5517-c245-b645e724e877.png)

## Helm 構成

`helm create <chart-name>`  というコマンドを実行すると、以下のようなファイルが自動生成されます。

```yaml
<chart-name>/
├── Chart.yaml                # チャートのメタ情報
├── values.yaml               # ユーザーが設定を上書きするためのデフォルト値ファイル
├── charts/                   # 他のチャートの依存関係を格納するディレクトリ（helm dependencyで管理）
├── templates/                # Kubernetesマニフェストのテンプレートファイルを格納
│   ├── _helpers.tpl          # テンプレート内で使用されるヘルパーファイル（繰り返し利用する関数）
│   ├── deployment.yaml       # Deploymentリソースのテンプレート
│   ├── service.yaml          # Serviceリソースのテンプレート
│   ├── ingress.yaml          # Ingressリソースのテンプレート
│   ├── hpa.yaml              # Horizontal Pod Autoscalerのテンプレート
│   ├── NOTES.txt             # インストール時に表示されるメッセージ
│   └── tests/                # テスト用のマニフェストを格納（helm testで使用）
│       └── test-connection.yaml  # Pod間接続テスト用テンプレート
└── .helmignore               # 無視するファイルやディレクトリを定義 (.gitignoreに類似)

```

上記のものがHelmの雛形になりますが、少し分かりにくいので、不要なものを削除します。

```yaml
service/
├── Chart.yaml
├── values.yaml
├── charts/
├── templates/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── ingress.yaml
└── .helmignore
```
あら、綺麗。

#### 簡単な説明

***chart.yaml***

- apiのversionや名前を保存する

***values.yaml***

- values-dev.yamlや、values-stg.yaml、values.yamlなどで、環境ごとにデフォルト値を設定できる
- イメージ情報やレプリカ数などを保存する

***charts/***

- 別のチャートを追加できる
- 無くても動くため、特に気にする必要はない

***templates/***

- Kubernetesマニュフェスト(deployment.yamlやconfigmap.yamlなど)のテンプレを保存する
- この中身から判断して、KubernetesのYAMLファイルを作成する

## 作業
### 1. クラスタを準備

GKEでクラスタを準備します。（コンソールから簡単に作成できます）
本題では無いので省略。

![test-cluster.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618316/9a3be3d8-e17b-4366-979a-e4b1c711a5fc.png)

### 2. コードを書く

values.yamlに値を、templateフォルダ内に変数を入れます。

**deployment**

```yaml
# service/values.yaml
deployment:
  enabled: true
  replicas: 1
  labels:
    app: my-test-app

  containers:
    - name: app-container
      image:
        repository: nginx
        tag: latest
        pullPolicy: IfNotPresent
      ports:
        containerPort: 80
      resources:
        limits:
          cpu: "500m"
          memory: "512Mi"
        requests:
          cpu: "250m"
          memory: "256Mi"
      livenessProbe:
        enabled: true
        httpGet:
          path: /
          port: 80
        initialDelaySeconds: 5
        periodSeconds: 10
      readinessProbe:
        enabled: true
        httpGet:
          path: /
          port: 80
        initialDelaySeconds: 5
        periodSeconds: 10
```

```yaml
# service/template/deployment.yaml
{{- if .Values.deployment.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-deployment
  labels:
    app: {{ .Values.deployment.labels.app }}
spec:
  replicas: {{ .Values.deployment.replicas }}
  selector:
    matchLabels:
      app: {{ .Values.deployment.labels.app }}
  template:
    metadata:
      labels:
        app: {{ .Values.deployment.labels.app }}
    spec:
      containers:
        {{- range .Values.deployment.containers }}
        - name: {{ .name }}
          image: "{{ .image.repository }}:{{ .image.tag }}"
          imagePullPolicy: {{ .image.pullPolicy }}
          ports:
            - containerPort: {{ .ports.containerPort }}
          resources:
            limits:
              cpu: {{ .resources.limits.cpu }}
              memory: {{ .resources.limits.memory }}
            requests:
              cpu: {{ .resources.requests.cpu }}
              memory: {{ .resources.requests.memory }}
          {{- if .livenessProbe.enabled }}
          livenessProbe:
            httpGet:
              path: {{ .livenessProbe.httpGet.path }}
              port: {{ .livenessProbe.httpGet.port }}
            initialDelaySeconds: {{ .livenessProbe.initialDelaySeconds }}
            periodSeconds: {{ .livenessProbe.periodSeconds }}
          {{- end }}
          {{- if .readinessProbe.enabled }}
          readinessProbe:
            httpGet:
              path: {{ .readinessProbe.httpGet.path }}
              port: {{ .readinessProbe.httpGet.port }}
            initialDelaySeconds: {{ .readinessProbe.initialDelaySeconds }}
            periodSeconds: {{ .readinessProbe.periodSeconds }}
          {{- end }}
        {{- end }}
{{- end }}
```

**service**

```yaml
# service/values.yaml
service:
  type: ClusterIP
  port: 80
  labels:
    app: my-test-app
```

```yaml
# service/template/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ .Release.Name }}-service
  labels:
    app: {{ .Values.deployment.labels.app }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: 80
      protocol: TCP
  selector:
    app: {{ .Values.deployment.labels.app }}
```

**ingress**

```yaml
# service/values.yaml
ingress:
  enabled: false     # 無効
  className: "nginx"
  host: example.com
  path: /
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
```

```yaml
# service/template/ingress.yaml
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
# ~ 省略 ~
{{- end }}
```

▼ 全容はこちら

https://github.com/Ito-Ryu/helm-test

#### 簡単な説明

***`{{ .Values.xxx.xxxxx }}`***

values.yamlを参照。
valuesのxxxのxxxxxの値のように、細かく分けることができる。

例）
```yaml
# chart/template/deployment.yaml
labels:
  app: {{ .Values.deployment.labels.app }}
```

```yaml
# chart/values.yaml
deployment:
  labels:
    app: my-test-app
```

***`{{- if .Values.xxx.xxxxx }}`***

true or false の条件式。
enabledは有効にするか無効にするか。

例）
```yaml
# chart/template/deployment.yaml
{{ if .Values.deployment.enabled }}

{{ end }}
```

```yaml
# chart/values.yaml
deployment:
  enabled: true
```

***`{{- range .Values.xxx.xxxxx }}`***
リストやマップを繰り返し処理する。
.Valuesを省略できるため、使い勝手がいい。

例）
```yaml
# chart/template/deployment.yaml
{{- range .Values.deployment.containers }}
  - name: {{ .name }}
    image: "{{ .image.repository }}:{{ .image.tag }}"
    imagePullPolicy: {{ .image.pullPolicy }}
{{ end }}
```

```yaml
# chart/values.yaml
deployment:
  containers:
    - name: app-container-1
      image:
        repository: nginx
        tag: latest
        pullPolicy: IfNotPresent
    - name: app-container-2
      image:
        repository: redis
        tag: latest
        pullPolicy: IfNotPresent
```

### 3. 認証

①ローカルPCにgoogle cloudの認証情報をセット
`gcloud auth login`
`gcloud config set project <PROJECT_ID>`
`gcloud config set account <ACCOUNT_ADDRESS>`

`gcloud config list` コマンドでPCにどの認証情報があるか確認します。

![gcloud_config_list_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618316/18c2fdcb-155f-9895-1099-ad95bd833d5c.png)


②プロジェクトで、どのクラスタがあるのか確認する

`gcloud container clusters list`
接続しているプロジェクト内に、作成したtest-clusterがリストにあることが分かります。

![gcloud_container_cluster_list.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618316/a72e4dd8-d9ba-1061-175c-0dfdfa62c55a.png)

③リソースをデプロイしたいクラスタとの認証する
`gcloud container clusters get-credentials <CLUSTER_NAME>` 

![gcloud_credentials.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618316/7246ca77-5c70-5f43-7211-1303ebef8396.png)

#### イメージ図
![gke_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618316/4827bb22-95c1-f9c1-43b4-7f0e4b37848d.png)

### 4. Helm install

**helm install**
`kube create ns <NAMESPACE>`
`helm install -n <NAMESPACE> <RELEASE_NAME> -f <VALUE_FILE> <CHART_NAME>`

テストコマンド
`helm install -n <NAMESPACE> <RELEASE_NAME> -f <VALUE_FILE> <CHART_NAME> --dry-run --debug`

![kube_create_ns.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618316/65c7a756-1c42-34e1-f5f4-4b7359611edd.png)

![helm_install_service1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618316/89edad47-8b33-bff8-4622-b461a078b891.png)

コマンド成功後、GKEにも反映されました！
![gke.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618316/853f3295-7324-b058-5b42-a527bafcb790.png)

**helm uninstall**
不要になったとき実行
`helm uninstall <RELEASE_NAME> -n <NAMESPACE>`

### 5. Kubectl で操作する

**kubectl**
`kubectl get xxx -n <NAMESPACE>`
kubernetesのコマンドで、K8s内の情報を取得

![kube_get_services.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618316/dada4a44-5228-821e-920d-a45fe7f7ef22.png)

![kube_get_deployment.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618316/45255d26-6da4-1d45-df65-6539b4d15364.png)

![kube_get_pods.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618316/566867f9-bdc4-7b47-83a3-0b8285b2841d.png)

他に`kubectl exec`コマンドでpodの中に入ったり、`kubectl logs`でログを見たりできる。

#### イメージ図
![gke_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618316/bbb15c5d-37f1-bd41-32f0-3811b55f6e59.png)

# 完全に理解した

ちなみに「完全に理解した」と言ってくる人の信用度は低いです。
おそらく全てを学んではいないでしょう。

![ダニング・クルーガー効果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618316/06a27a48-43b2-ddec-b809-f1bc3e5eb77b.png)


Linuxの生みの親は、Linuxを「チョットデキル」。

![チョットデキル.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618316/c77d8ff3-ee9e-3ccb-5892-d7c47dd5bad0.jpeg)

# 何もわからない

「完全に理解した」と大きく書いていますが、実際はHelmで一部のKubenetesのオブジェクトをつくれるようなるチュートリアルが終わっただけであって、本当に理解しているという意味ではありません...

Kubernetsには、ReplicaSetやDaemonSet、StatefulSetなどのControllerや、SecretやConfigMapなどなどたくさんのオブジェクトがあり、PrometheusやElasticsearch、Grafanaなどのソフトウェアやサービスのさまざまなチャートで、どの構成がベストプラクティスなのかも考える必要があると思います。

Kubernetes含む、Helmを全てを理解するには、まだ道のりが長そうです…
「Kubernetes？Helm？IaC？ チョットデキルよ。」と言えるように、学習を続けていきます。

**長々と書いていますが、この記事で「Helmというものがあるんだなぁ」程度に覚えていただければ、全てOKです！**

# さいごに

明日のTRIAL＆RetailAI Advent Calendar 2024 の 21日目の記事は、
[@k-yoshigai](https://qiita.com/k-yoshigai) さんの『インテグレーションテストをもっと楽に！Testcontainers はじめました』という記事です。

一時期テスターしてたので、テスト作業の辛さが分かりますが、テストが楽になるのはテスターにとって最高のことですね。
次回もお楽しみに！

TRIALとRetailAIではエンジニアを募集しています。
興味ある方は以下のURLからチェックしてください。
個人的には、ともにクラウドを極める、[クラウドエンジニア](https://hrmos.co/pages/trialgp/jobs/1878319533791674717) が来てくれると嬉しいです！

https://hrmos.co/pages/trialgp/jobs?category=2020003768196907008

https://hrmos.co/pages/retail-ai/jobs?category=2060931051759239170

# 参考資料
https://developers.gnavi.co.jp/entry/sre_deployment_base/

https://developers.freee.co.jp/entry/understand-of-perfect-understanding

https://gihyo.jp/news/report/2014/05/2201