progrhyme's tech blog

主にIT関連の技術メモ

Kubernetesのマニフェストをリポジトリ管理しつつ、リソースの削除も反映したい件

はじめに

最近、Kubenetesの運用管理に携わるようになりました。
マニフェスト*1)はYAMLで管理して、Gitリポジトリに入れています。
複数の環境を持つシステムにおいては、Kustomizeを使って、マニフェストをDRYに保つように努力しています。

そんなある日のこと、あるワークロードからExternalNameで参照しているServiceオブジェクトの定義を変えることになりました。

下のような変更です:

  apiVersion: v1
  kind: Service
  metadata:
-   name: my-api1
+   name: my-api2
    namespace: myapp
  spec:
    type: ExternalName
-   externalName: my-api1.example.com
+   externalName: my-api2.example.com

問題

……で、このYAMLkubectl apply で適用するわけですが、次の事実に気づきました:

  • 上のYAMLkubectl apply で適用すると、新しくmy-api2 Serviceが作られるが、 my-api1 Serviceは消えない

metadata.name を変えなければそうはならないのでしょうが、名前が実体を表していないのは気持ちが悪いので、なるべくなら変えたいところです。

そうでなくとも、並行稼働期間にmy-api2 Serviceを追加し、後からmy-api1 Serviceを削除したいと思った場合も、同じ問題が発生します。*2

つまり、マニフェストから削除しても、単に kubectl apply するだけでは削除が反映されない、ということです。

この場合の解決策は簡単で、 kubectl delete service my-api1 を叩けば話は終わりです。 実際、このときはそうしました。

しかし、例えばマニフェストをkustomizeで適用する自動化のパイプラインを組んだとき、差分に応じてこういうアドホックな処理を入れるのはつらそうだなと思いました。(まだそこまで自動化できてはいませんが。)

こういった問題の対処については、本などにもあまり載ってなさそうで、どうするのが良いのかと疑問に思いました。

そこで、自分でも少し調べつつ、とあるKubernetesのコミュニティで識者に聞いてみました。
本稿では、その回答も含めて、現在私が把握している方法を紹介します。

※公開コミュニティでの質疑内容ではありますが、どなたの回答かというのは本稿では伏せておきます。

方法

①labelsでバージョン管理する

次のようなやり方です。

  1. 管理対象のリソースに version というキーでラベルをつけておく
  2. リソース設定を更新する場合は、 version も更新する
  3. 古いリソースは version を指定してまとめて削除する

先の例だと、次のようにラベルを付ける形になります。

apiVersion: v1
kind: Service
metadata:
  name: my-api1
  namespace: myapp
  labels:
    version: 1
spec:
  type: ExternalName
  externalName: my-api1.example.com
apiVersion: v1
kind: Service
metadata:
  name: my-api2
  namespace: myapp
  labels:
    version: 2
spec:
  type: ExternalName
  externalName: my-api2.example.com

古いリソースを削除する際は、例えば次のようなコマンドを実行します:

kubectl delete services,... -l 'version!=2'

ラベルによって処理が統一されるので、自動化もしやすそうです。

ただし、毎回再作成していいのかなど、実運用に落とす際は考慮しなければならないこともありそうだと思いました。

②kubectl apply --pruneを使う

現時点ではalpha機能のようですが、 kubectl apply には --prune というオプションがあり、指定すると引数として渡したマニフェスト以外のリソースを削除するという挙動になります。*3

例:

# app=nginxラベルのリソースにマニフェストを適用し、マニフェスト外のリソースを削除する
kubectl apply --prune -f manifest.yaml -l app=nginx

# マニフェストに書かれていないConfigMapを全て削除
kubectl apply --prune -f manifest.yaml --all --prune-whitelist=core/v1/ConfigMap

なんとなく実行するのが怖いオプションですが、商用環境で使われている方もいるそうです。 というか、KubernetesのAddon Managerの中で使われており、Addon Managerを利用しているので、結果的に使っている……ということのようです。

③Argo CDのAutomatic Pruningを使う

Kubernetes向け継続的デリバリーツールのArgo CDの自動同期(Automated Sync)機能を使うと、Gitリポジトリマニフェストの変更をpushすると、Kubernetesクラスタに継続的に反映されるようになります。(まだ使ったことはありませんが、そういう理解です。)

この際、Automatic Pruningオプションによってマニフェストから定義が消えたオブジェクトを、自動で削除するようにできます。
このオプションはデフォルトではOFFになっているそうです。

④Terraformで管理する

各種クラウドサービスに対応した構成管理ツールのTerraformですが、KubernetesのProviderもあります。
ので、管理対象のリソースをTerraformの設定に落として、tfstateで状態を管理すれば削除も同期できそうです。

(追記)5/10 次の記事の通り、試してみました:

終わりに

Kubernetesマニフェストリポジトリ管理しつつ、リソースの削除も反映したい場合に使えそうな方法をいくつか紹介しました。

実際にKubernetesの運用管理をされている方は、なんらかの方法でこの問題を解決していると思います。

「自分はこうしているよ」など他の方々からも知見が得られれば嬉しいです。

余談

マニフェストをexportして管理する

kubectl get には --export というオプションがあり、これを付けると status などクラスタ特有の情報を取り除いた形でオブジェクトの情報を取得することができます。*4

これを利用すれば、export/importの形で変更を適用できるのではないかと思いました。
ただし、exportされたマニフェストkubectl apply で適用するだけでは、削除が同期されないという問題は解消できません。

また、 --export オプションはv1.14で非推奨とされています
v1.18ではまだ残っていますが、今後どうなるのかはわかりません。

脚注

*1:「A manifest specifies the desired state of an object that Kubernetes will maintain when you apply the manifest」 cf. https://kubernetes.io/ja/docs/reference/glossary/#term-manifest

*2:今回はどちらかといえばこれに該当していました。

*3:https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#apply

*4:https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#get