1クール続けるブログ

とりあえず1クール続けるソフトウェアエンジニアの備忘録

kustomizeでCRDのpatchesStrategicMergeを動かしてみる

記事一覧はこちら

背景・モチベーション

以前に記事を書いて、kustomizeでbuildする時にCRDでpatchesStrategicMergeが利用できない悲しみを文字に起こしました。
(紛らわしいので当該記事は非公開にしてあります)

ところが、v4.1.0のリリースで可能になったようです!うれしいですね…!ということで早速試してみた記事です。

参考文献

どのようにCRDでpatchesStrategicMergeを実現するのか

CRDでのpatchesStrategicMergeはkustomizeでどのように実現されるのか確認していきます

Kubernetes本体ではv1.16からCRDのapplyがJMPからSMPに変わっている

kubernetesのv1.15までは、CRDをapplyしたときの挙動がkubernetes/kustomizeで足並み揃っていて、下記のような挙動になっていました。

  • kubernetesのnativeオブジェクトはstrategicMergePatch(SMP)を使う
  • CRDはjsonMergePatch(JMP)を使う

例えば、PodTemplateのコンテナのリストをマージする時などはその違いが顕著です。SMPであれば、各要素のnameをキーとして利用して、同一のキーが存在すれば更新し、無ければ配列に追加するという挙動を取ります。対して、JMPの場合には配列ごと置き換えます。

この状況は、v1.16のServer-Side Applyのリリースによって変わります。
詳しくは、Merge Strategyを参照いただければと思うのですが、API開発者がList/Map/structのマージ方法をOpenAPIスキーマで定義できるmarker(extension)が追加されました。
ちなみにstrategic merge patch で以前から利用されているx-kubernetes-patch-strategy: mergex-kubernetes-patch-merge-keyは、それぞれx-kubernetes-list-type: mapx-kubernetes-list-map-keysとして解釈されます。

そして、Server-Side Applyを利用したCRDで定義されたリソース更新では、markerが存在すれば、その定義に基づいてフィールドの更新を行うようになりました。

しかしながら、kustomizeは従来どおりのJsonMergePatchをCRDのマージで利用する挙動になっていたため、kubernetes v1.16のリリース時からkubernetesとkustomizeの間で挙動の違いが生まれることになってしまいました。

kustomizeでCRDのSMPを行う方法

KubernetesネイティブオブジェクトのSMPに関しては、kustomizeのバイナリにOpenAPIの定義が含まれるので、Merge Strategyはその定義を参照することで確定することができます。対してCRDの定義はバンドルしていません。kustomize自体がどうにかしてCRDの定義を知る必要があります。

そこで、kustomizeはkustomization.yamlファイルにopenapiフィールドを追加して、そのフィールドを読み込んでスキーマを取得するように機能追加を行いました。openapiフィールドが指定されていれば指定されたスキーマを読み込み、そうでなければビルトインの定義を利用するような動きをするようになっています。
この機能追加により、CRDを含む全体のスキーマファイルが手元にあれば、より柔軟なマージ戦略を行うことができるようになりました。

ではスキーマの取得が必要になってきます。これはKuberneteのAPIサーバに対して、/openapi/v2のパスでリクエストすることで可能です。kustomizeはopenapi fetchというサブコマンドの内部でそれを実行することでスキーマの取得をサポートしています。

流れとしては下記のような流れとなります。

  • (CRDのファイルをapplyする)
  • kubectl openapi fetchスキーマのファイルを取得する
  • kustomizationにopenapiのフィールドを追加してファイルを指定する

スキーマのfetchの問題点

kubectl openapi fetch の際の内部処理で/openapi/v2にリクエストしていると先ほど言及させていただきました(参考)。これはOpenAPIのv2での公開です。
OpenAPI v3で定義されたCRDはv2でサポートされていないフィールドを含んだり、表現できないnullableを利用したりします(参考)。それらはkubectl v1.13より前のバージョンでは解釈できないため、互換性を守るために公開時に削除されます。
もしかしたら、この挙動がkustomize buildで影響を及ぼす可能性があります。

実際にCRDでpatchesStrategicMergeできるか確かめてみる

自前のGKEクラスタで確認していきます。バージョンは1.18.16です。

CRDのマニフェストのmergeStrategyを確認する

事前の準備としてCRDのマニフェストのmergeStrategyを確認しておく必要があります。今回はArgoCDのRolloutリソースで確認していきますが、2つのキーの組み合わせで一意に解釈させたい.spec.template.spec.containers.portsなどは既にmarkerが利用されていますが、.spec.template.spec.containersのarrayはそうなっていません。
今回、自分の方でmarkerを追加して試してみたいと思います。.spec.template.spec.containersのarrayのみにmarkerを追加いたしました。ちなみにkustomizeの実装を見ていると、x-kubernetes-list-typeを確認せずにx-kubernetes-patch-strategyのmarkerを確認しに行きます。そのmarkerが見つからないとx-kubernetes-list-map-keysを確認してくれないみたいなので注意してください(該当コード)。
…本家のServer-Side Applyでもこうなっているのかな…?後で確認してみようと思います。

CRDのリファレンスを確認すると、x-kubernetes-patch-strategyが許容するフィールドに存在しないため、schemaをfetchした後に追加してあげる必要があります。辛い…。

確認手順

利用するファイル群はこちらにまとめています。

github.com

自前のGKEクラスタで試してみます。

# CRDの登録
$ cd /tmp
$ git clone https://github.com/44smkn/kustomize-crd-smp-sample.git
$ cd kustomize-crd-smp-sample
$ gcloud container clusters get-credentials my-gke-cluster
$ kubectl apply -f crds/rollout.yaml --validate=false

# kustomizeのインストール
$ curl -s "https://raw.githubusercontent.com/\
kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"  | bash
$ ./kustomize version   # v4.1.0以上であることを確認

# スキーマのfetchとキーの挿入
$ ./kustomize openapi fetch | sed -e '1d' > tmp.json
$ cat tmp.json | jq  '.definitions["io.argoproj.v1alpha1.Rollout"].properties.spec.properties.template.properties.spec.properties.containers |= .+ {"x-kubernetes-patch-strategy": "merge"}' > schema.json


# 確認
$ ./kustomize build > actual.yaml
$ diff -h actual.yaml expected.yaml

上手く動くところまで確認できましたが、仕様の問題なのか自分のドキュメントの読み込みが甘いのかわかりませんが、まだ実用に持っていくのは難しいような要素を感じました。
細かいところを確認して必要そうならIssue挙げてみようと思います。以上!