Kustomizeのディレクトリ構成パターンの良し悪しを考える

# はじめに

最近、Terraformの話をよく聞くようになりました。私はIaCツールのKustomizeを使ったことがありましたが、Terraformは知らなかったため学ぶことにしました。TerraformからよりよいIaCツールの使い方を学んでKustomizeに活かしたいと思っています。
IaCツールを使うと複数のKubernetesリソースを一度に作成できるようになり、劇的に構築手順を減らせることがあります。しかし、ディレクトリ構成を考えなければ、使いづらくなってしまうことがあります。
実際にハンズオンして書いた内容になりますが、長くなるため具体的なコマンドについては別記事に書きたいと思います。

# この記事の概要

1. Kustomizeでできること
まずKustomizeでできることを簡単に記述します。
1. この記事のKubernetesの環境
ハンズオンした際のKubernetes環境について記載します。
1. Kustomizeのディレクトリ構成パターンを考える
まずTerraformで使われているディレクトリ構成パターン例を記載します。
それぞれのパターンがKustomizeに適しているかを考えます。
1. 適したKustomizeの構成を考える
上記で挙げたパターンから最も適したものを考えます。
1. Terraform（Kustomize Provider）の場合
TerraformにはKustomize用のProviderがありKubernetesリソースを作成できます。
Kustomize Providerを簡単に紹介し、ディレクトリ構成パターンについて記載します。
1. Kubernetesクラスタが複数存在する場合
今回作成した環境以外にKubernetesクラスタが複数存在する場合の構成について記載します。

# Kustomizeでできること

初めにKustomizeでできることを簡単に紹介します。
Kubernetesではyamlファイルからリソースを作成します。Kustomizeでは複数あるyamlファイルを1つにまとめたyamlを出力できます。
以下の例では、pod.yamlとnamespace.yamlに書かれた内容をまとめたyamlが出力されます。

```bash
$ tree .
.
├── kustomization.yaml
├── namespace.yaml
└── pod.yaml

0 directories, 3 files
```

```bash
$ kustomize build .
apiVersion: v1
kind: Namespace
metadata:
  labels:
    name: sample
  name: sample
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: sample
spec:
  containers:
  - image: nginx:1.14.2
    name: nginx
```

これをkubectlコマンドに渡すことで１行のコマンドですべてのリソースを作成できます。

```bash
$ kustomize build . | kubectl apply -f -
namespace/sample created
pod/nginx created
```

この時、kind毎に作成するリソースの順番は整理されます。例えばNamespace作成を前提とするリソースの作成がエラーなくできるようになります。さらに出力したyamlにパッチを当てて環境毎にパラメータを変えてリソースを作成することもできます。

https://kubectl.docs.kubernetes.io/references/kustomize/

Kustomizeではリソースに加えてパッチの内容を含めたyamlファイルを使います。Kustomizeのドキュメントでは以下のディレクトリ構成例が紹介されています。環境毎にディレクトリを分けています。

```bash
.
├── kustomization.yaml
├── base
│   ├── kustomization.yaml
│   └── pod.yaml
├── dev
│   └── kustomization.yaml
├── production
│   └── kustomization.yaml
└── staging
    └── kustomization.yaml
```

https://kubectl.docs.kubernetes.io/guides/example/multi_base/

しかし、私が実際にKustomizeを使っていて以下のような問題を感じました。

- パッチの分だけファイル数が増え管理が大変になる。
パッチのyamlファイルを作成することで簡単に環境毎に変更を加えてリソースを作成できますが、ファイル数は増えます。
ファイル数が増えると、機能の修正箇所がわかりづらくなったり、１ファイルの変更の影響が大きくなり修正が漏れたりするかもしれません。

- どのディレクトリに何のファイルがあるかわかりづらい。
作成するリソースが増えてくると、機能ごとにディレクトリを分けたくなります。しかし、数が多くなると何の目的でディレクトリを分けたか把握しきれなくなります。ディレクトリが少ない方がファイルを探す時間が減ってよいと思います。

- 既存リソースと作成予定のリソースの差分がわかりづらい。
Kustomizeはyamlを出力するだけなので、差分を保存する仕組みはありません。
kubectl diffというコマンドはありますが、yamlに書いてあるリソースの差分を取るため、削除されたリソースは追えません。

※kubectl diff実行例
```bash
$ kustomize build . | kubectl diff -f -
diff -u -N /tmp/LIVE-1531115989/v1.Pod.sample.nginx /tmp/MERGED-2422321850/v1.Pod.sample.nginx
--- /tmp/LIVE-1531115989/v1.Pod.sample.nginx    2022-11-07 12:32:49.958736519 +0000
+++ /tmp/MERGED-2422321850/v1.Pod.sample.nginx  2022-11-07 12:32:49.958736519 +0000
@@ -5,6 +5,8 @@
     kubectl.kubernetes.io/last-applied-configuration: |
       {"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"name":"nginx","namespace":"sample"},"spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx"}]}}
   creationTimestamp: "2022-11-07T12:17:59Z"
+  labels:
+    hoge: hoge
   name: nginx
   namespace: sample
   resourceVersion: "658"
```

それぞれの問題についてどのように対応するか考えます。

# この記事のKubernetesの環境

以降にディレクトリ構成パターンを幾つか書いていきますが、適しているかどうかはケースバイケースだと思います。
今回はハンズオンして学んだ関係上、その時の環境を前提とします。

1. EC2で動かすminikube環境
Kubernetesリソースの実験をするための環境を想定しました。
1. EC2上でKubernetesを動かす単一ノード環境
minikube環境で実験したリソースを動かす環境を想定しました。

※今回はEC2一台を使いましたが、クラウドサービスを使うとすると、検証系や本番系などKubernetesクラスタを複数作成して使い分けることになると思います。そのため、複数クラスタが存在する環境に適したディレクトリ環境にもできるとよいと思います。

# Kustomizeのディレクトリ構成パターンを考える

今回はディレクトリ構成をTerraformから学びました。同じIaCツールであり共通点が多くドキュメントが多いと思ったためです。
以下の記事を参考にしました。

https://qiita.com/reireias/items/253529c889cafb3fa4c7

https://qiita.com/bigwheel/items/2b420183639416b5c6bb

https://qiita.com/m22r/items/7d80d149e3fb3494c1d4

上記の記事を参考にして、いくつか構成パターンを挙げて、メリット、デメリットを整理します。

## 単一ディレクトリ構成

パッチを当てることなく１つのディレクトリで複数の環境にリソースを作成します。
以下のようなディレクトリ構成です。

```bash
$ tree .
.
├── kustomization.yaml
├── resource1.yaml
└── resource2.yaml

0 directories, 3 files
```

|比較項目|評価|理由|
|---|---|---|
|環境毎の構成の違いに対応できるか|対応できない|minikubeと単一ノードではPVの動的プロビジョニングの方法や、使えるCPU、メモリの大きさが変わってきます。yamlの修正が必要になってしまいます。|
|管理するyamlファイルの多さ|最も少ない|環境毎にファイルを分けないため。|
|yamlファイル間での内容の重複があるか|なし|環境毎にファイルを分けないため。|
|ディレクトリの多さ|最も少ない|環境毎にファイルを分けないため。|

## 環境毎にディレクトリを分ける構成

環境毎にディレクトリを分け、それぞれにyamlファイルを作成してリソースを定義します。

```bash
$ tree .
.
├── minikube
│   ├── kustomization.yaml
│   ├── resource1.yaml
│   └── resource2.yaml
└── single_node
    ├── kustomization.yaml
    ├── resource3.yaml
    └── resource4.yaml

2 directories, 6 files
```

|比較項目|評価|理由|
|---|---|---|
|環境毎の構成の違いに対応できるか|対応できる|環境毎にディレクトリを分けており、各々でリソースを定義できるため。|
|管理するyamlファイルの多さ|多い|環境分yamlファイルを複製する必要があるため。|
|yamlファイル間での内容の重複があるか|あり|環境分yamlファイルを複製する必要があるため。|
|ディレクトリの多さ|多い|環境分yamlファイルを複製する必要があるため。|

## 環境毎で分割して共通部分を切り出す構成

環境毎にディレクトリを分割し、共通する設定についてもディレクトリを分けて参照する構成です。

```bash
$ tree .
.
├── common
│   ├── resource1.yaml
│   └── resource2.yaml
├── minikube
│   ├── kustomization.yaml
│   ├── patch1_of_resource1.yaml
│   └── patch2_of_resource2.yaml
└── single_node
    ├── kustomization.yaml
    ├── patch3_of_resource1.yaml
    └── patch4_of_resource2.yaml

3 directories, 8 files
```

|比較項目|評価|理由|
|---|---|---|
|環境毎の構成の違いに対応できるか|対応できる|環境毎にディレクトリを分けており、各々でリソースを定義できるため。|
|管理するyamlファイルの多さ|最も多い|環境毎のyamlファイルに加えて、共通部分を記述したyamlファイルを作る必要があるため。|
|yamlファイル間での内容の重複があるか|少ない|共通部分は別ディレクトリにまとめているため。|
|ディレクトリの多さ|最も多い|共通部分は別ディレクトリにまとめているため。|

# 適したKustomizeの構成を考える

以下の構成がよいのではないかと思っています。

```bash
$ tree .
.
├── minikube
│   ├── kustomization.yaml
│   ├── resource1.yaml
│   └── resource2.yaml
└── single_node
    ├── kustomization.yaml
    ├── patch1_of_resource1.yaml
    └── patch2_of_resource2.yaml

2 directories, 6 files
```

理由は以下の通りです。

- 単一ディレクトリ構成は環境の違いに対応できないため、使えません。
- 環境毎にディレクトリを分けるとして、なるべくディレクトリ数、yamlファイル数が少なくしたいです。
- Kustomizeでは他環境のyamlファイルにパッチを当てることができます。minikubeのリソース定義に対してパッチを当てることで単一ノード環境を作るようにします。これによりyamlの重複を防ぎ、共通部分を切り出す必要をなくします。

# Terraform（Kustomize Provider）の場合

Terraformを使った場合のディレクトリ構成について記載します。
TerraformにはKustomize用のProviderがあり、Kubernetesリソースを作成できるようになっています。

https://registry.terraform.io/providers/kbst/kustomization/latest/docs

Kustomizeでできることは一通りカバーされていて、同じように使うことができます。
以下のようなprovider設定が紹介されています。

```terraform
terraform {
  required_providers {
    kustomization = {
      source  = "kbst/kustomization"
      version = "0.9.0"
    }
  }
}

provider "kustomization" {
  # one of kubeconfig_path, kubeconfig_raw or kubeconfig_incluster must be set

  # kubeconfig_path = "~/.kube/config"
  # can also be set using KUBECONFIG_PATH environment variable

  # kubeconfig_raw = data.template_file.kubeconfig.rendered
  # kubeconfig_raw = yamlencode(local.kubeconfig)

  # kubeconfig_incluster = true
}
```

リソースを作成する際のコマンドでは差分がわかり、リソースの作成数、変更数、削除数が確認できます。
また、確認を要求するのでミスも少なくなると思います。

https://qiita.com/Chanmoro/items/55bf0da3aaf37dc26f73

https://developer.hashicorp.com/terraform/cli/commands/apply

Terraformでのディレクトリ構成の方針はKustomizeの場合と同じです。しかし、そのままでproviderの宣言がminikubeとKustomizeで重複してしまいます。それによりエラーが起こることがあるようです。

https://stackoverflow.com/questions/72737651/how-to-fix-provider-configuration-not-present-error-in-terraform-plan

そのため、ディレクトリ構成を変えるかaliasを設定するかなど対策が必要になります。
もしディレクトリを増やす場合にはモジュールが生まれることになり、作成者以外が見た時に利用方法がわかりづらくなります。READMEなど用意した方がよいかもしれません。aliasを使う場合は、環境毎に異なるproviderを使うことになり、ファイルの修正で注意が必要になるかもしれません。

# Kubernetesクラスタが複数存在する場合

クラスタが複数存在する場合は、共通する部分が生じやすく、ディレクトリ構成の選択肢が増えると思います。
今回は単一ディレクトリは採用しませんでしたが、ステージングや本番などの似た構成であれば使える可能性が出てくると思います。
TerraformではWorkspaceがあり、同じディレクトリで使い分けやすくなっています。

https://zenn.dev/ksuga/articles/ee57f1005eb3bf

共通部分でディレクトリを分ける構成も、同じクラウドサービスを使っていれば役立つ場面が出てくると思います。クラスタが違ってもクラウドは変わらないため、PVの動的プロビジョニングの方法などが共通の設定となります。

https://aws.amazon.com/jp/premiumsupport/knowledge-center/eks-persistent-storage/

# おわりに

今回、ディレクトリ構成について学んで環境や目的によって使い分ける必要があることがわかりました。今後、長く使いやすい構成を考えていきたいです。
また、手を動かして作成したファイルについては今後紹介していけたらと思います。