kptでWETなKubernetesマニフェスト管理
kptでWETなKubernetesマニフェスト管理
みなさん、Kubernetesでのマニフェスト管理ってどうしてますか?
Kubernetesでは、GitOpsと呼ばれる運用方法を用いてマニフェストをGitで管理することが一般的です。
マニフェストをGitで管理することで、変更履歴の追跡、コードレビュー、環境ごとのリリース管理をおこなうことができます。
一方でWall of YAMLとも呼ばれるように、マニフェスト管理は一筋縄ではいかず、Kubernetesの利用者を悩ませる課題の1つとなっています。
本記事では、メジャーなマニフェスト管理ツールの課題やマニフェストのリリース方式の課題、WETリポジトリと呼ばれるマニフェストの管理方法を紹介した後、
kptと呼ばれるツールを利用し、既存ツールの課題やリリース方式の問題点を解決する方法について解説します。
マニフェスト管理ツール
Kubernetesのマニフェストを管理する場合、そのマニフェストをYAML形式で直書きすることはあまり多くありません。
実運用では、ステージング環境と本番環境で設定が微妙に異なったり、アップストリームから取得したマニフェストをカスタマイズして利用したり、複数のマニフェストを一括で生成する必要がでてきます。
そのような要件を満たすために、マニフェストのテンプレート化やパッケージ化、パッチの適用、プログラマブルな生成処理などが可能なマニフェスト管理ツールを利用することが一般的です。
では、Kubernetesのマニフェスト管理ツールはどれを使えばいいのでしょうか?
以下の記事では、2024年現在よく利用されているマニフェスト管理ツールが紹介されています。
KustomizeとHelmがもっともよく利用されていますが、それ以外にも非常にたくさんのマニフェスト管理ツールが登場していることがわかります。
ここでは代表的なマニフェスト管理ツールであるKustomizeとHelmに、我々がよく利用しているJsonnetを加えた3つのツールについて簡単に紹介します。(代表的なGitOpsツールであるArgo CDでも、この3つのツールが標準でサポートされています)
Kustomzieは、Kubernetes向けのマニフェスト管理ツールで、YAML形式のマニフェストファイルに対してパッチを適用して、新しいマニフェストを生成することができます。
パッチを適用することでどんなフィールドでも書き換えることができるので、非常に柔軟です。
このような特徴から、あるOSSが提供しているマニフェストをカスタマイズして利用する場合に向いています(このような利用方式をoff-the-shelf configurationと呼びます)。
また、共通のベースとなるマニフェストに対して、環境ごと(例:QA環境、開発環境、ステージング環境、本番環境など)の差分をパッチとして適用するオーバーレイ方式でのマニフェスト管理も得意としています。
一方で、テンプレートによる記述はサポートしておらず、プログラマブルな生成処理を書くこともできません。
次にHelmは、Kubernetesのマニフェストをパッケージ管理するためのツールです。
なんらかのOSSを導入するためのマニフェスト群をパッケージングして提供することができます。
多くのOSSはHelm Chartと呼ばれるパッケージを提供しており、簡単に導入することができます。
また、Helmはテンプレート形式でマニフェストを記述することができ、パラメータ化された箇所に設定を埋め込んだり、条件分岐や繰り返し処理を記述することもできます。
一方で、テンプレートのパラメータになっていない箇所を書き換えることはできません。
また、テンプレート化されたマニフェストはYAML形式ではなくなってしまうため、ツールでのサポートが受けられなかったり、可読性が低くなる場合があります。
Jsonnetは、JSONを拡張した言語で、変数や関数や条件分岐やループなどの機能を利用して、柔軟にJSONを生成することができます。
柔軟性が非常に高く、どんな複雑な生成処理でも書くことができるでしょう。
一方で、独自の言語記法を覚える必要があり、慣れるまでは可読性や書きやすさが低いと感じるかもしれません。
また、既存のYAML形式のマニフェストをカスタマイズして利用する場合には、Jsonnetで書き直す必要があるため、手間がかかるかもしれません。
このようにマニフェスト管理ツールにはそれぞれ特徴があり、チームの運用スタイルや要件に合わせて選択する必要があります。
マニフェストのリリース管理
GitOpsでマニフェストを管理する場合、利用するツールだけでなく、リリースの管理方法も重要です。
マニフェストをリリースするとき、通常は1つの環境だけではなく、複数の環境に適用することになります。
例えば、テストのためにQA環境に適用して、問題がないことが確認できたらステージング環境に適用し、しばらく問題がないことを確認してから本番環境に適用するといった流れです。
このとき、各環境ごとに適用するマニフェストはそれぞれ異なりますし、リリースするタイミングも違います。
そこで、Gitのブランチを使ってリリースするマニフェストを管理する方法がよく使われます。
- mainブランチからfeatureブランチを切ってPRを作成する。
- PRが完成したら、mainブランチにマージする。
- mainブランチをstageブランチにマージする。stageブランチの内容がステージング環境に適用される。
- stageブランチをprodブランチにマージする。prodブランチの内容が本番環境にマージされる。
しかし、ブランチ方式の場合、マージやリバートの際にコンフリクトが発生するなどの問題が発生することがあります。
ブランチ方式の問題点については、詳しくは以下の記事を参照してください。
一方、Kustomizeでは環境ごとにオーバーレイディレクトリを作成して、ベースとなるマニフェストにパッチを適用することで、環境ごとのマニフェストを生成することができます。
├── base
│ ├── deployment.yaml
│ └── kustomization.yaml
└── overlays
├── prod
│ ├── kustomization.yaml
│ └── patch.yaml
└── stage
├── kustomization.yaml
└── patch.yaml
しかしKustomizeの場合、baseのマニフェストを変更するとすべての環境のマニフェストが変更されてしまうため、リリースのタイミングを制御しにくいという問題があります。
そこで、オーバーレイディレクトリを分離した上で、リリースのタイミングはブランチで制御するという方法が用いられることがあります。
Googleのベストプラクティスにおいても、ブランチではなくフォルダを使って環境ごとのマニフェストを管理する方法が推奨されています。
DRYかWETか
プログラミングの世界ではDRY (Don't Repeat Yourself) 原則という言葉がよく使われます。
これは、同じ処理を何度も書くのではなく、共通化やモジュール化をすることで重複を避けるべきだという考え方です。
一方、この反対の考え方としてWET (Write Everything Twice) という言葉があります。
マニフェスト管理におけるWETとは、マニフェスト管理ツールによってレンダリングされたマニフェストをGitリポジトリに保存して管理する方式です。
一般的にプログラミングの世界ではWETはアンチパターンとされていますが、マニフェスト管理においてはWETな管理が有効な場合もあります。
WETリポジトリによるマニフェスト管理には以下のようなメリットがあると考えられます。
- 分かりやすい
- 各環境にどのようなマニフェストがデプロイされているのか一目で分かる。
- GitHubのPRを作成した際に、変更内容が分かりやすい。
- ディレクトリ単位でDiffを取ることで、各環境間の差分を簡単に確認できる。
- CDツールでの対応が不要
- Argo CDでは、Kustomize, Helm, Jsonnet以外のマニフェスト管理ツールを利用する場合、プラグインを作成する必要がある。
WETリポジトリ方式ではYAML形式のマニフェストがGitリポジトリにそのまま登録されているので、CDツール側での対応が不要となる。
- Argo CDでは、Kustomize, Helm, Jsonnet以外のマニフェスト管理ツールを利用する場合、プラグインを作成する必要がある。
Googleのベストプラクティスにおいても、WETリポジトリの作成が推奨されています。(DRYからWETにすることをHydrate(水分を与える)と呼んでいます。ちょっとおしゃれな言い回しですね)
また、Argo CDではmanifest hydratorという仕組みが提案されています。
この機能は、Argo CDがマニフェストをレンダリングした結果をリポジトリにPushしてくれる仕組みのようです。
kpt
ここまで解説してきたように、マニフェスト管理には様々な課題があります。
これらの課題を解決するために、kptというマニフェスト管理ツールを紹介したいと思います。
kptは以下のような特徴を備えています。
- Helmのようにパッケージ管理ができる。Gitリポジトリで管理されている生YAMLや、Helm Chartもkptのパッケージとして扱うことができる。
- Helmのようにテンプレート的な記述ができる。ただし、条件分岐や繰り返しは書けないので、可読性の低いテンプレートは生まれにくい。
- Kustomizeのように、off-the-shelf configurationの管理ができる。Upstreamのマニフェストを自由にカスタマイズできる。
- Jsonnetのようにプログラマブルな生成処理を書くことができる。(GoとTypeScriptに加え、Pythonの方言であるStarlarkで記述することが可能)
- WETリポジトリの管理に適している。(後述)
自前パッケージの管理
ここでは、kptで自前のマニフェストを管理する方法を解説します。
以下のコマンドを実行して、パッケージのテンプレートを作成します。
$ cd /path/to/your/repo
$ mkdir -p packages/nginx
$ cd packages
$ kpt pkg init nginx
作成に成功すると、以下のように3つのファイルが生成されます。
Kptfileはパッケージに関するメタデータを記述するためのファイルです。
package-context.yamlは、kptでマニフェストのレンダリングをおこなう際に利用する設定を記述するためのファイルです。
└── packages
└── nginx
├── Kptfile
├── package-context.yaml
└── README.md
作成したディレクトリに、nginxのDeploymentのマニフェストを保存します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
component: nginx
spec:
replicas: 1 # kpt-set ${replicas}
selector:
matchLabels:
component: nginx
template:
metadata:
labels:
component: nginx
spec:
containers:
- name: nginx
image: nginx:1.20
ここで、spec.replicasに# kpt-set ${replicas}
というコメントを入れておきます。
このマーカーを付けることによって、後ほどパラメーターを設定することができます。
次に、setter-config.yamlを用意します。
これはHelmのvalues.yamlのようなもので、パラメータを記述するためのファイルです。
apiVersion: v1
kind: ConfigMap
metadata:
name: setter-config
annotations:
config.kubernetes.io/local-config: "true"
data:
replicas: "3"
Kptfileを編集して、apply-setters functionを実行するようにします。
apply-setters functionは、setter-config.yamlに記述されたパラメータをマニフェストに適用するためのfunctionです。
apiVersion: kpt.dev/v1
kind: Kptfile
metadata:
name: nginx
annotations:
config.kubernetes.io/local-config: "true"
info:
description: nginx
pipeline:
mutators:
- image: gcr.io/kpt-fn/apply-setters:v0.2.0
configPath: setter-config.yaml
最後に、マニフェストのレンダリングをおこないます。
$ cd /path/to/your/repo/packages/nginx
$ kpt fn render
これにより、setter-config.yamlに記述したパラメータがマニフェストに適用されているでしょう。
作成したパッケージのディレクトリ構成は以下のようになります。
└── packages
└── nginx
├── Kptfile
├── nginx-deployment.yaml
├── package-context.yaml
├── setter-config.yaml
└── README.md
上記の変更をmainブランチにマージした後、タグを打ちます。
$ git tag packages/nginx/v1
$ git push origin packages/nginx/v1
以上でパッケージを作成することができました。
次にこのパッケージを利用して、各環境ごとにカスタマイズされたマニフェストを生成する方法を解説します。
$ cd /path/to/your/repo
$ mkdir -p manifests/production
$ cd manifests/production
$ kpt pkg get git@github.com:your-org/your-repo.git/packages/nginx@v1
manifestsディレクトリは、Hydrateされたマニフェストを保存するためのディレクトリです。
productionやstagingなど、各環境ごとにディレクトリを作成して、そこにHydrateされたマニフェストを保存します。
kpt pkg get
コマンドを使って、さきほど作成したパッケージを取り込んでいます。
ディレクトリ構成は以下のようになります。
├── manifests
│ └── production
│ └── nginx
│ ├── Kptfile
│ ├── nginx-deployment.yaml
│ ├── package-context.yaml
│ ├── setter-config.yaml
│ └── README.md
└── packages
└── nginx
├── Kptfile
├── nginx-deployment.yaml
├── package-context.yaml
├── setter-config.yaml
└── README.md
manifestsディレクトリ以下のマニフェストは自由にカスタマイズすることができます。
kptのfunctionsを使って書き換えるだけでなく、YAMLファイルを直接編集しても構いません。
これは、Kptfileでパッケージの取得元のリビジョンを記録しており、変更履歴を追跡できるようになっているためです。
次にKptfileを編集して、set-labels functionを利用してラベルを付与します。
apiVersion: kpt.dev/v1
kind: Kptfile
metadata:
name: nginx
annotations:
config.kubernetes.io/local-config: "true"
info:
description: nginx
pipeline:
mutators:
- image: gcr.io/kpt-fn/apply-setters:v0.2.0
configPath: setter-config.yaml
- image: gcr.io/kpt-fn/set-labels:v0.1.5
configMap:
env: production
exclude:
- kind: Kptfile
また、setter-config.yamlを編集して、replicasを5に変更します。
apiVersion: v1
kind: ConfigMap
metadata:
name: apply-setters-fn-config
annotations:
config.kubernetes.io/local-config: "true"
data:
replicas: "5"
マニフェストをレンダリングします。
$ cd /path/to/your/repo/manifests/production/nginx
$ kpt fn render
これで、production環境に適用するマニフェストが完成しました。
あとは、Argo CDなどのGitOpsツールを使って、このマニフェストを環境に適用するだけです。
なお、Argo CDを利用してマニフェストを適用する場合、KptfileがKubernetes環境にデプロイされてしまわないように、以下のようにargocd.argoproj.io/hook: Skip
アノテーションを付けておく必要があります。
apiVersion: kpt.dev/v1
kind: Kptfile
metadata:
name: sample
annotations:
config.kubernetes.io/local-config: "true"
argocd.argoproj.io/hook: Skip
パッケージの更新
ここでは、自作したパッケージを更新する方法について解説します。
まず、packages/nginx/
ディレクトリにあるファイルを更新し、kpt fn render
コマンドを実行してマニフェストをレンダリングします。
変更をmainブランチにPushした後、バージョンv2としてタグを打ちます。
$ git tag packages/nginx/v2
$ git push origin packages/nginx/v2
続いてパッケージの利用側で、kpt pkg update
コマンドを利用して更新作業を実施します。
$ cd /path/to/your/repo/manifets/production/
$ kpt pkg update nginx@v2
kptでは3-way merge方式を採用しており、ローカルのパッケージ(local)、アップデート前のパッケージ(origin)、アップデート後のパッケージ(upstream) の3つの状態を比較してマージをおこないます。
例えば、originには存在するがupstreamには存在しないリソースは削除されますが、originとupstreamには存在せずlocalにのみ存在するリソースはそのまま保持されます。
このような仕組みにより、ローカルのマニフェストに変更が加えられていたとしても適切にアップデートが実施されます。
ただし、変更内容によってはマージ処理がうまくいかない場合があります。
その場合は、kpt pkg update
に--strategy force-delete-replace
オプションを指定したり、kpt pkg diff
で差分をチェックして手動でマージをおこなう必要があります。
kpt pkg update
に関する詳細は以下のドキュメントを参照してください。
最後にレンダリングをおこない、変更をPushしたら更新作業の完了です。
$ cd /path/to/your/repo/manifets/production/nginx
$ kpt fn render
Helmパッケージを管理する
kptはHelmと同じようにパッケージ管理の機能を備えています。
しかし、世の中の多くのOSSはHelm Chart形式でマニフェストのパッケージを提供しています。
kptは、Gitリポジトリで管理されているYAMLファイルや、Helm Chartもパッケージとして扱うことができます。
ここではkptを使ってHelm Chartを管理する方法について、cert-managerを例に解説します。
自前パッケージを作成するときと同じようにパッケージのテンプレートを作成します。
$ cd /path/to/your/repo
$ mkdir -p packages/cert-manager
$ cd packages
$ kpt pkg init cert-manager
Kptfileを編集し、render-helm-chart functionの設定を追加します。
apiVersion: kpt.dev/v1
kind: Kptfile
metadata:
name: cert-manager
annotations:
config.kubernetes.io/local-config: "true"
info:
description: Package of cert-manager manifests
pipeline:
mutators:
- image: gcr.io/kpt-fn/render-helm-chart:v0.2.2
configPath: cert-manager-chart.yaml
続いて、Helm Chartに関する設定を記述したファイルを作成します。
apiVersion: fn.kpt.dev/v1alpha1
kind: RenderHelmChart
metadata:
name: cert-manager
annotations:
config.kubernetes.io/local-config: "true"
helmCharts:
- chartArgs:
name: cert-manager
version: v1.15.3
repo: https://charts.jetstack.io
templateOptions:
releaseName: cert-managerr
namespace: cert-manager
values:
valuesInline:
crds:
enabled: true
最後にrenderします。
このとき、render-helm-chartはネットワークアクセスするので、--allow-network
オプションを付ける必要があります。
kpt fn render --allow-network
作成したパッケージをmainブランチにマージしてタグを打ちます。
$ git tag packages/cert-manager/v1
$ git push origin packages/cert-manager/v1
後は自前パッケージと同様に、manifestsディレクトリにパッケージを取り込んでカスタマイズすることができます。
このパッケージを利用すると、Helm Chartのvaluesを利用してカスタマイズできるのはもちろんのこと、kptのapply-settersを使ってHelm Chartではパラメータ化されていない箇所を編集することもできます。
なお、以下のsource-helm-chart functionを利用すると、Helm Chartのtar-ballをRenderHelmChartに埋め込むことができ、render時にネットワークアクセスを回避することができます。
公式のfunctionではありませんが、興味があれば試してみるとよいでしょう。
Starlarkによるカスタマイズ
ここまで紹介してきたマニフェストの変更作業は、一部のフィールドを書き換えるだけの単純なものでした。
しかし、実際の運用ではもっと複雑なマニフェストの操作が必要になることがあります。
kptでは、GoやTypeScriptを使って新たなfunctionを作ることもできますし、Starlarkを利用してマニフェストを操作することもできます。
例えば、paramsで指定した名前と一致するDeploymentリソースを削除する処理をStarlarkで書いてみます。
apiVersion: fn.kpt.dev/v1alpha1
kind: StarlarkRun
metadata:
name: remove-deployments
annotations:
config.kubernetes.io/local-config: "true"
params:
targets:
- nginx
- mysql
source: |
result = []
for resource in ctx.resource_list["items"]:
remove = False
if resource["kind"] != "Deployment":
result.append(resource)
continue
for target in ctx.resource_list["functionConfig"]["params"]["targets"]:
if resource.get("metadata", {}).get("name", "") == target:
remove = True
break
if remove == False:
result.append(resource)
ctx.resource_list["items"] = result
これを利用するには、KptfileにStarlark functionの設定を追加します。
pipeline:
mutators:
- image: gcr.io/kpt-fn/starlark:v0.5.0
configPath: remove-deployments.yaml
このように、Starlarkを利用すると柔軟にマニフェストを操作することができます。
なお、似たような変更操作を何度もおこなう場合は、GoやTypeScriptでfunctionを作成し、再利用できるようにコンテナイメージ化しておくのがおすすめです。
CIによる補助
これまでに紹介してきたkptの利用方法を見てみると、マニフェストのレンダリングをしたり、パッケージの変更の度にGitのタグを打つ必要があったり、手作業が多いことがわかります。
こういった操作は作業漏れやミスが発生しやすいため、CIで自動化したり、補助ツールを作成することが望ましいです。
例えば、我々は以下のような仕組みを導入しています。
- Gitリポジトリにpushした際に、CIで
kpt fn render
の実行漏れをチェックする - パッケージをmainブランチにマージしたときに自動的にタグを打つ
- 新しいパッケージが公開された後、パッケージの利用側で更新忘れをチェックするためのスクリプトを用意
まとめ
本記事では、kptを使ったマニフェスト管理方法について解説しました。
kptはパッケージ管理機能、テンプレート機能、プログラマブルな生成処理などを備えており、他のマニフェスト管理ツールと比較しても遜色なく、柔軟にマニフェストを管理することができます。
さらにWETリポジトリ方式を導入することで、環境ごとのマニフェストの差分が分かりやすくなり、リリースのフローがシンプルになったと感じています。
本記事が読者の皆様のマニフェスト管理に少しでも参考になれば幸いです。
参考
- Kubernetes Configuration in 2024
- Kubernetes GitOps best practices with Config Sync
- Stop Using Branches for Deploying to Different GitOps Environments
- How to Model Your GitOps Environments and Promote Releases between Them
- Replacing Helm and Kustomize with KRM Functions — a New Approach to Configuration Management
- サイボウズの Kubernetes マニフェスト差分管理について
Discussion