Helmfile導入で実現できる3つのこと

# はじめに

本記事では、Helmfileのインストール方法や細かい設定値の説明は省き(よくまとまった記事は多くあるため)、Helmfileを導入することで、どんなことを実現できるのか、問題解決できるのかを伝えることを目的としています。
Kubernetes ClusterをHelmで管理している方にぜひ読んでいただき、「とりあえずHelmfileを入れてみて触ってみようか」と思ってもらえたらうれしいです。

# Helmとは
Kubernetesのアプリケーションリソースのデプロイでよく使われるのはHelmだと思います。
HelmはKubernetesのパッケージマネージャです。
* Kubernetesクラスタ上で動くアプリケーションリソースのかたまりをChartとしてパッケージ化している
* ChartパッケージがHelmリポジトリ上に公開されており、自由に取得できる (Linuxにおけるyum、macOSにおけるhomebrewのように例えられることが多いですね)
* 取得したChartの設定値を自身の環境に合わせて変え、簡単にデプロイすることができる

https://helm.sh/ja/

# Helmfileとは
HelmfileとはHelm Chartをより宣言的にデプロイするためのものです。 
ざっくり言ってしまえば、Helmの上位互換みたいなもので、Helmでのクラスタ管理をスッキリさせてくれる＆Helmで起こりうる問題点をカバーしてくれる万能ツールなのです。

https://github.com/roboll/helmfile

Helmfileを導入することで、下記3つを実現することができます。
* **アプリケーションデプロイの再現性を高められる**
* **Helm Chartで定義されていない独自のリソースをデプロイできる**
* **Chartの再利用性を高められる**

## 1. アプリケーションデプロイの再現性を高められる
Helmの場合、予めhelm addコマンドでリポジトリの登録が必要です。
また、helm install コマンドのオプションに環境設定・バージョン設定などのリリース情報を入れ込む形でデプロイをします。
このように、Helmはアプリケーションデプロイをすべてコマンドベースで行わないといけないため、ヒューマンエラーが起きやすいです。

#### Helm 設定例
* ディレクトリ構成
```
work-dir/
　└ config.yaml
```
* 予めリポジトリを登録しておく
```
# helm add jupyterhub https://jupyterhub.github.io/helm-chart/
```
* デプロイ
```
# helm install jupyterhub jupyterhub/jupyterhub \
--namespace service \
--version 2.0.0 \
--values ./config.yaml
```
---
一方Helmfileは、Helmとは異なり、リポジトリ情報やリリースに必要な設定値を専用の設定ファイル[helmfile.yaml]に定義します。
デプロイはhelmfile applyコマンドを実行するだけで、Helmのようにややこしい手順は不要です。
リリース設定は誰が見ても一目瞭然で、Helmと比較してアプリケーションデプロイの再現性が高くなります。
#### Helmfile 設定例
* ディレクトリ構成
```
work-dir/
　├ config.yaml
　└ helmfile.yaml
```
* helmfile.yaml
```yaml
# 使用するHelmレポジトリを指定する
repositories:
  - name: jupyterhub
    url: https://jupyterhub.github.io/helm-chart/

# リリースに必要な設定値を指定する
releases:
  - name: jupyterhub
    namespace: service
    chart: jupyterhub/jupyterhub
    version: 2.0.0
    values:
      - "./config.yaml"
```
* デプロイコマンド (※ helmfile.yamlが存在するwork-dir/配下で実行する)
```
# helmfile apply
```


## 2. Helm Chartで定義されていない独自のリソースをデプロイできる
Helmは配布されているChartで定義されているリソースしか作成することができません。

例えば、ExternalSecret(外部プロバイダからシークレット情報を取得するためのリソース)や、Configmap(Podに環境変数を食わせるためのリソース)などの独自リソースを、Helm Chartのリソースと一緒にデプロイしたいことがあると思います。
そのような場合、Helmでは、Helm Chartをデプロイする前に、予めkubectlで独自リソースを作成しておかないといけません。（どんなアプリケーションもこのような構成を避けられないことが多いのではと思っています）

さらに、単一のKubernetes Cluster上に複数のアプリケーションを、またはマイクロサービスを動かしている場合、アプリケーション毎でデプロイ方法が異なってしまい、管理が複雑化・煩雑化しがちです。

#### Helm 設定例
* ディレクトリ構成
```
work-dir/
　├ secrets.yaml # external-secretsのマニフェストファイル
  └ config.yaml 
```
* 予めリポジトリを登録しておく
```
# helm add jupyterhub https://jupyterhub.github.io/helm-chart/
```
* 予めexternal-secretsを作成しておく
```
# kubectl apply -f ./secrets.yaml
```
* デプロイ
```
# helm install jupyterhub jupyterhub/jupyterhub \
--namespace service \
--version 2.0.0 \
--values ./config.yaml
```
---
Helmとは異なり、HelmfileはChartの設定をhelmfile.yamlに持たせることができるため、どのChartを元にリソース作成をするかによって、デプロイ方法が変わる、ということがありません。
元になるChartは関係なく、helmfile applyコマンドを実行するだけでデプロイできます。

(Helmfileの導入のきっかけはこの部分でしたし、実際にHelmfileに移行してみると、運用をかなりスッキリまとられることを実感できると思います)

#### Helmfile 設定例
* ディレクトリ構成
```
work-dir/
　├ charts-secrets/
　|　└ secrets.yaml # external-secretsのマニフェストファイル
　├ config.yaml
　└ helmfile.yaml
```
* helmfile.yaml
```yaml
repositories:
  - name: jupyterhub
    url: https://jupyterhub.github.io/helm-chart/

releases:
  # 独自のマニフェストファイルを使用してデプロイ
  - name: jupyterhub-secrets
    namespace: service
    chart: "./charts-secrets"　# マニフェストファイルが配置してあるディレクトリを指定
  # Helm Chartを使用してデプロイ
  - name: jupyterhub
    namespace: service
    chart: jupyterhub/jupyterhub
    version: 2.0.0
    values:
      - "./config.yaml"
```
* デプロイコマンド (※ helmfile.yamlが存在するwork-dir/配下で実行する)
```
# helmfile apply
```


## 3. Chartの再利用性を高められる
### 3-1. 開発/ステージング/本番の環境毎設定ができる
HelmfileはHelmとは異なり、環境設定を定義することができます。
例えば、開発/ステージング/本番環境上でアプリケーションを動かす際、values設定の中から環境毎で異なる部分のみを抜き出し、別途、環境別のvalues設定ファイルに持たせることができます。
そうすることで、どのvalues設定が環境に依存しているのかが明白で、可読性を高めることができます。

#### Helmfile 設定例
* ディレクトリ構成
```
work-dir/
　├ config/ # 環境毎のvalues設定を定義
　|　├ dev.yaml 
　|　└ prod.yaml 
　├ config.yaml　# 全環境共通のvalues設定を定義
　└ helmfile.yaml
```

* work-dir/helmfile.yaml
```yaml
# 使用するHelmレポジトリを指定する
repositories:
  - name: jupyterhub
    url: https://jupyterhub.github.io/helm-chart/

# デプロイ環境を指定する
environments:
  dev:
  prod:

# リリースに必要な設定値を指定する
releases:
  - name: jupyterhub
    namespace: service
    chart: jupyterhub/jupyterhub
    version: 2.0.0
    values:
      - "./config.yaml"
      - "./config/{{ .Environment.Name }}.yaml"　# {{.Environment.Name}} = 上記[environments:]で指定した設定値
```
* デプロイコマンド (※ helmfile.yamlが存在するwork-dir/配下で実行する。-eオプションでデプロイ対象の環境を指定する)
```
# helmfile -e prod apply
# helmfile -e dev apply
```
---
上記のように、valuesの設定ファイルの管理がかなりわかりやすくなります。
例えば、同Cluster内で新たにステージング環境を立てたい、一時的に開発環境を立てたいという時、スムーズに対応ができる状態になります。

### 3-2. Kubernetes Cluster毎の環境設定ができる
単一のKubernetes Cluster上に複数のアプリケーションを、またはマイクロサービスを動かしている場合、Helmfileのbases設定を活用することで、Clusterの環境設定を定義することができます。
例えば、kubeContextやvalues(Cluster名, AWSアカウントID, etc)は、Cluster上の全アプリケーションに共通する設定なので、Clusterの環境設定に定義します。
これを活用することで、異なるCluster上で同様のアプリケーションを横展開することが容易にできるようになります。

#### Helmfileのbases設定とは
Helmfileはbasesに設定したファイルをhelmfile.yamlにマージして、デプロイしてくれます。
例えば、下記の設定をすると、
```
bases:
- environments.yaml
- defaults.yaml
- templates.yaml
```
全てのファイルを以下の順序でマージし、デプロイしてくれます。
この設定を活用し、Cluster毎の環境設定を入れ込みます。
```
environments.yaml <- defaults.yaml <- templates.yaml <- helmfile.yaml
```


#### Helmfile 設定例
* ディレクトリ構成
```
work-dir/
　├ environments/ # dev/prodのClusterの環境設定
　│　├ dev.yaml 
　│　└ prod.yaml
　├ environments.yaml　# Cluster全体の環境設定
　│　
　├ app1/ # APP毎の設定
　│  ├ …
　│　└ helmfile.yaml
　│
　├ app2/ # APP毎の設定
　│  ├ …
　│　└ helmfile.yaml
　│
　└ jupyterhub/ # APP毎の設定
　   ├ config.yaml.gotmpl
　 　└ helmfile.yaml
```
* Cluster全体の環境設定: work-dir/environments.yaml
```yaml
environments:
  # devのClusterの環境設定として、kubeContextとvaluesを定義する
  dev:
    kubeContext: CLUSTER-NAME-DEV
    values:
      - ./environments/dev.yaml
  # prodのClusterの環境設定として、kubeContextとvaluesを定義する
  prod:
    kubeContext: CLUSTER-NAME-PROD
    values:
      - ./environments/prod.yaml
```
* devのClusterの環境設定: work-dir/environments/dev.yaml
```yaml
# devは共通AWSアカウント[000000000000]の[ap-northeast-1]リージョンに立てたCluster[CLUSTER-NAME-DEV]で運用する
aws:
  account_id: "000000000000"
  region: "ap-northeast-1"
  cluster_name: "CLUSTER-NAME-DEV"
```
* prodのClusterの環境設定: work-dir/environments/prod.yaml
```yaml
# prodは共通AWSアカウント[000000000000]の[ap-northeast-1]リージョンに立てたCluster[CLUSTER-NAME-PROD]で運用する
aws:
  account_id: "000000000000"
  region: "us-east-1"
  cluster_name: "CLUSTER-NAME-PROD"
```

* APP毎(jupyterhub)の設定: work-dir/jupyterhub/helmfile.yaml
```yaml
# 使用するHelmレポジトリを指定する
repositories:
  - name: jupyterhub
    url: https://jupyterhub.github.io/helm-chart/

# リリースに必要な設定値を指定する
releases:
  - name: jupyterhub
    namespace: service
    chart: jupyterhub/jupyterhub
    version: 2.0.0
    values:
      - "./config.yaml.gotmpl" # Cluster毎のvalues設定を引用するため、ファイル形式を.gotmplにする

bases:
  - "../environments.yaml" # helmfile.yamlの配置先をカレントディレクトリとしたディレクトリ構成で設定する
```



* APP毎(jupyterhub)の設定: work-dir/jupyterhub/config.yaml.gotmpl
```yaml
# work-dir/environments/{{ .Environment.Name }}.yaml で定義したvalues設定を引用するため、変数設定を定義しておく
{{ $aws_account_id := .Values.aws.account_id }}
{{ $aws_region := .Values.aws.region }}

# 下記設定例 (imageをAWS ECRから取得する設定)
singleuser:
  profileList:
  - kubespawner_override:
      image: "\
        {{ $aws_account_id }}.dkr.ecr.{{ $aws_region }}.amazonaws.com\
        /XXXX/XXXX:XXXX\
        "
  ・・・(省略)・・・
```
* デプロイコマンド (※ helmfile.yamlが存在するwork-dir/jupyterhub/配下で実行する。-eオプションでデプロイ対象の環境を指定する)
```
# helmfile -e prod apply
# helmfile -e dev apply
```
---
上記のように、Cluster毎の環境設定を持たせておくと、別Clusterに同様のサービスを立てる時、★の箇所の設定だけ最適化すれば、簡単にデプロイすることができます。
Helmで同様の運用はできないので、これはHelmfile運用の醍醐味だと個人的に思っています。
```
work-dir/
　├ environments/ ★
　│　├ dev.yaml 　★
　│　└ prod.yaml　★
　├ environments.yaml ★
　│　
　├ app1/
　│  ├ …
　│　└ helmfile.yaml
　│
　├ app2/
　│  ├ …
　│　└ helmfile.yaml
　│
　└ jupyterhub/
　   ├ config.yaml.gotmpl
　 　└ helmfile.yaml
```

# おわりに
Helmfile導入時、英語のドキュメントしかなかったり、チュートリアルが少なかったりで広く使われていないのかなと思っていましたが、実際導入してみると、これかなりよいのでは！と驚き、今回記事にまとめてみました。
なんか不便だな、、と思いながらやり過ごしてることって結構あるし、その不便を解決してくれるツールも結構あるんだろうなと、気づくきっかけになりました。
「なんか不便だな」センサーを見逃さず、これからもいろんなツールを調べてみる、使ってみるを意識していきたいものです。