helm と kustomizeの比較

# はじめに

KubernetesのYaml管理関連でよく出てくるHelmとKustomize。 やってることは似て非なるもの。比較してみた結果をまとめておく。 

- Helm:https://helm.sh/ 
- Kustomize:https://kustomize.io/

# 比較

比較はしてみたものの実は結構違うものなので、比較対象にそもそもならないかもという気持ちを置いて比較してみる。

## Helm (Package manager)

- メリット
    - 配布しやすい
    - テンプレートのみでなく、Hook、Rollback、Packagingなどがある
    - 拡張性がある
        - if文やloopが可能
        - helpersに自分で定義をしたものをテンプレート内で使用できる
    - すでに公開されているChartsが豊富にある
    - Values.yamlによって更新できる部分を限定できる
    - lintやテスト機能がある
- デメリット
    - テンプレートの可読性が低い
    - シンプルなデプロイにはオーバーヘッドとなる
    - 抽象レイヤーが追加されることで学習コストが増える
    - 既存のChartで変更がサポートされてない場合は、Chartの更新が必要になる

- 複数環境へのデプロイ方法
    - value fileを環境ごとに準備して環境固有の値を与える

## Kustomize

- メリット
    - kubectlに入っている [kubernetes-1-14-release-announcement](https://kubernetes.io/blog/2019/03/25/kubernetes-1-14-release-announcement/)
    - すべてがプレーンyamlなためvalidationが楽
    - シンプルにYamlパッチをしてくれる
    - Overlaysでの変更は比較的自由
- デメリット
    - kubectlに入っているが、1.21前だと最新系が使えない (v2.0.3) ([CHANGELOG-1.21.md#kustomize-updates-in-kubectl](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.21.md#kustomize-updates-in-kubectl))
    - BaseやOverlaysの変更が実際に最終的にどんな変更になるのかが変更ファイルからのみだと分かりづらい
    - DRY原則に則っていない
    - Package Managerではないため、ある時点でのVersionを見るにはGitなどのVCSで特定のVersionに戻るしかない
- 複数環境へのデプロイ方法
    - `overlays/<環境>`に環境ごとに変更する部分を書いてbaseを上書きする

# Helmとkustomize作成の比較

## Helm作成のステップ

1. `helm create <name>` で初期化 例: `helm create helm-example` (以下は `helm-example`を前提に進める)
1. `helm-example` というディレクトリの中に生成された`templates`や`Values.yaml`を自分のアプリケーションのデプロイしたいものに調整する。
    1. helpersを利用した例

        ```yaml:templates/_helpers.tpl
        {{/*
        Create a default fully qualified app name.
        We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
        If release name contains chart name it will be used as a full name.
        */}}
        {{- define "helm-example.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 }}
        ```

        ```yaml:templates/deployment.yaml
        metadata:
          name: {{  include "helm-example.fullname" . }}
        ```
    1. values.yamlを利用した例

        ```yaml:values.yaml
        nginx:
          image:
            repository: nginx
            pullPolicy: IfNotPresent
            tag: ""
        ```
        ```yaml:templates/deployment.yaml
        ...
        spec:
          template:
            spec:
              containers:
                - name: nginx
                  image: "{{ .Values.nginx.image.repository }}:{{ .Values.nginx.image.tag | default "latest" }}"
        ...
        ```
1. 調整してから実際にKubernetesにDeployする前に `dryrun` 機能をつかって準備したYamlファイルや変数などが正しいかどうかチェックする

    ```
    helm install helm-example --debug --dry-run ./helm-example
    ```
1. Lintのチェック

    ```
    helm lint helm-example
    ```
1. Kubernetesクラスタへインストール (namespaceを指定したい場合は、 `-n <namespace>`をつける)

    ```
    helm install helm-example --debug ./helm-example
    ```

1. インストールされたhelmのチェック

    ```
    helm ls
    ```

1. インストールされたHelmアプリケーションに対してテスト (`test`以下に何をテストするか自分で書く必要がある。test用のPodが立ち上がって`test`以下に書かれたテストが実行される。)

    ```
    helm test helm-example
    ```

1. パッケージ (開発が終わったらパッケージ化する)

    ```
    helm package helm-example
    ```

    `helm-example-0.1.0.tgz` が作成される。

1. Chartを公開する (今回はGithub RepoをChart Repoとして使用 例: https://github.com/nakamasato/helm-charts-repo)

    ```
    helm repo index ./ --url https://nakamasato.github.io/helm-charts-repo
    ```

    これで、`index.yaml`が作成される。上のステップで作成された `helm-example-0.1.0.tgz`と合わせてchart repoにPushする

1. 作成したRepoを追加

    ```
    helm repo add nakamasato https://nakamasato.github.io/helm-charts-repo
    helm repo update # update the repository info
    ```

    自分のチャートを探して見つかることを確認

    ```
    helm search repo naka
    ```

1. Chartをインストール

    ```
    helm install example-from-my-repo nakamasato/helm-example
    ```

1. Chart をEnvごとに変えてインストール

    ```yaml:values-prod.yaml
    replicaCount: 2

    nginx:
      image:
        repository: nginx
        pullPolicy: IfNotPresent
        tag: "1.15.2"
    ```

    ```
    helm upgrade -f values-prod.yaml helm-example nakamasato/helm-example -n helm-prod
    ```

## Kustomize作成のステップ

1. ディレクトリの作成 (今回はディレクトリを `kustomize-example`とする)

    ```
    mkdir -p kustomize-example/{base,overlays/dev,overlays/prod} && cd kustomize-example
    ```

    生成されたディレクトリを確認: 

    ```
    tree
    .
    ├── base
    └── overlays
        ├── dev
        └── prod

    4 directories, 0 files
    ```

1. `base` に必要なリソースのYamlファイルを作成し、 `kustomization.yaml`でそれらを指定する。

    1. `commonLabels`はkustomizeで展開されるすべてのリソースにつけるラベル
    1. `resources` には、必要なyamlをリストで渡す
    1. `configMapGenerator`や`secretGenerator`を必要に応じて使う (詳細参照: [Secrets and ConfigMaps
](https://kubectl.docs.kubernetes.io/guides/config_management/secrets_configmaps/))
        Fileからconfigmapを作成してconfigmap名の最後にhashをつけてくれるので、configmapの変更でも依存しているDeploymentなどをRolloutすることができる。

    例: 

    ```yaml:kustomization.yaml
    commonLabels:
      app: kustomize-example
    resources:
      - deployment.yaml
      - configmap.yaml
      - service.yaml
      - secret.yaml
    configMapGenerator:
      - name: kustomize-example-nginx
        files:
        - nginx.conf
    ```

2. overlaysを環境ごとに作り (今回の例では`dev`と`prod`)、baseを書き換える部分を書く

    1. 例: イメージを環境ごとに書き換える

        ```yaml:overlays/dev/kustomization.yaml
        namespace: kustomize-dev
        bases:
          - ../../base
        images:
          - name: nginx
            newName: nginx
            newTag: v1.15.2
        ```
    1. 例: replicaの数を書き換える. `deployment.yaml`をoverlaysに追加して、kustomization.yamlで`patches`に追加

        ```yaml:overlays/prod/kustomization.yaml
        ...
        patches:
          - deployment.yaml
        ```
        ```yaml:overlays/prod/deployment.yaml
        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: kustomize-example
        spec:
          replicas: 2
        ```

1. アプリケーションのデプロイ
    1. dev:

        ```
        kubectl apply -k overlays/dev
        ```

    1. prod:

        ```
        kubectl apply -k overlays/prod
        ```

# CD

ArgoCDはhelmもkustomizeも対応しているので試してみる

## Helmの場合のApplication設定

サンプルコード: https://github.com/nakamasato/kubernetes-training/tree/master/helm-vs-kustomize/argocd/helm

- `spec.source.repoURL`と`spec.source.path`と`targetRevision`に作成したHelm Chartのコードレポ, Dir, Revisionをそれぞれ指定する。直接Chart repositoryから持ってくるわけではない。
- `spec.source.helm.valueFiles`にこの環境で使いたいvalue fileを指定する。今回は、`value-dev.yaml` (上で指定したDirにある必要がある。コマンドラインで渡すようにChart Repo + value fileで指定できたらいいなと思った)
- その他destinationとsyncPolicyはHelmの場合とkustomizeの場合で関係ないので割愛

```yaml:helm-dev.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: helm-dev
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: helm

  source:
    repoURL: https://github.com/nakamasato/kubernetes-training.git
    targetRevision: master
    path: helm-vs-kustomize/helm-example/helm-example
    helm:
      releaseName: helm-dev
      valueFiles:
        - values-dev.yaml

  destination:
    server: https://kubernetes.default.svc
    namespace: helm-dev

  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

```

## KustomizeのApplication設定

サンプルコード: https://github.com/nakamasato/kubernetes-training/tree/master/helm-vs-kustomize/argocd/kustomize

- `spec.source.repoURL`, `spec.source.targetRevision`, `spec.source.path` にそれぞれkustomizationのRepo、Regision, Pathを書く。
- その他destinationとsyncPolicyはHelmの場合とkustomizeの場合で関係ないので割愛


```yaml:kustomize-dev.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: kustomize-dev
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: kustomize

  source:
    repoURL: https://github.com/nakamasato/kubernetes-training.git
    targetRevision: master
    path: helm-vs-kustomize/kustomize-example/overlays/dev

  destination:
    server: https://kubernetes.default.svc
    namespace: kustomize-dev

  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
```

# Example: 同じApplicationを複数環境 (devとprod)にhelmとkustomizeを用いて、ArgoCDからDeployする。

デプロイする内容は以下のような単純なApplication (今回はHelmとKustomizeの比較がメインなのでPasswordは丸見え状態＋ちょっとめんどくさがってMulti-containersにした..)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7059/e136ac70-1bd8-ea6d-be5d-8c544dcf9d04.png)


1. ArgoCDのインストール (v2.0.3)

    ```
    kubectl create namespace argocd                   
    kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v2.0.3/manifests/install.yaml
    ```

1. Prepare application dependency (Deployment and Service for MySQL)

    ```
    kubectl create ns database; kubectl -n database apply -f https://raw.githubusercontent.com/nakamasato/kubernetes-training/master/helm-vs-kustomize/dependencies/mysql/mysql.yaml
    ```

1. HelmでConfigurationしたArgoCD Applicationのアプライ

    argocd/helmの中身は、`helm` というなのArgoProjectと `helm-dev`と`helm-prod`というApplication. (サンプルコード: https://github.com/nakamasato/kubernetes-training/tree/master/helm-vs-kustomize/argocd/helm)

    ```
    kubectl apply -f argocd/helm
    ```

1. KustomizeでConfigurationしたArgoCD Applicationのアプライ

    argocd/kustomizeの中身は、`kustomize` というなのArgoProjectと `kustomize-dev`と`kustomize-prod`というApplication. (サンプルコード: https://github.com/nakamasato/kubernetes-training/tree/master/helm-vs-kustomize/argocd/kustomize)

    ```
    kubectl apply -f argocd/kustomize
    ```

1. 確認

    1. ArgocdのDefaultのSecretを取得

        ```
        kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath='{.data.password}' | base64 --decode
        ```

    1. ArgoCDをPortForwardして確認

        ```
        kubectl port-forward svc/argocd-server 8080:80  -n argocd
        ```

    ![Screen Shot 2021-06-02 at 9.55.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7059/5c759145-08f8-fd8e-227d-54cda5d9015a.png)

# 感想

一度Chartを作ってしまえば、Helmを使って開発するのが良さそうな気がした。

良さそうなところ:

1. 開発しているアプリケーションのHelm Chartを作成することで配布が楽
1. ArgoCDのようにHelm対応しているCDもあるため、Kustomizeとリリース自体はほぼ変わらない
1. パッケージマネージャなので、アプリケーションのYamlを含めたVersion参照ができる
1. 大規模なYaml変更が必要な場合はChartのVersion更新が必要になるが、その場合のみReviewをすればよくなる
1. 環境ごとの存在するYamlファイルが減らせる (values.yamlだけでいい)

検討が必要なところ:

1. Helmでやる場合、Secretをどう管理するかはよくわかってない。 kustomizeの場合はsealed-secretsを使ってGithub Repoに入れたりできるが、Chartのコード中に入れるのは微妙な感じ？ 

    その他:
    1. [kubernetes-external-secrets](https://github.com/external-secrets/kubernetes-external-secrets)
    1. [helm-secrets](https://github.com/zendesk/helm-secrets)
    1. [aws-secret-operator](https://github.com/mumoshu/aws-secret-operator)
    1. [Hashcorp Vault](https://www.vaultproject.io/)
1. kustomizeのConfigMapGeneratorやSecretGeneratorが便利なのでそれが使えなくなるのはちょっと痛手。