KubernetesにおけるownerReferenceの誤用にご用心

はじめに

こんにちは、Necoプロジェクト兼ストレージチームのsatです。本記事はKubernetesのガベージコレクションにおけるownerReferenceというフィールドの役割、誤用したときの振る舞い、および問題を防止、検出する方法する方法について述べます。

本記事で扱う問題の実例としてRookを扱っていますが、とくにRookの知識は必要なく、Kubernetesについての基本的な知識があれば読める内容です。

KubernetesのガベージコレクションとownerReference

Kubernetesには使われていないリソースをバックグラウンドで自動的に削除するガベージコレクション機能があります。このとき、どのリソースが他のどのリソースに依存しているかを示すためにownerReferenceというフィールドを利用できます。ガベージコレクタは誰もownerがいないリソースは消してもいいと判断するというわけです。

ownerReferenceの使用にあたっては以下のことを守らなければなりません。

Note:
Cross-namespace owner references are disallowed by design.

Namespaced dependents can specify cluster-scoped or namespaced owners.
A namespaced owner must exist in the same namespace as the dependent.
If it does not, the owner reference is treated as absent, and the dependent is
subject to deletion once all owners are verified absent.

Cluster-scoped dependents can only specify cluster-scoped owners.

日本語で箇条書きすると、次のようなことが書いています。

  • namespaceをまたいだowneReferenceの設定は仕様上許されていない
  • namespacedリソースはcluster-scopedリソース、あるいはnamespacedリソースをownerにできる
  • cluster-scopedリソースはcluster-scopedリソースのみをownerにできる
  • ownerがnamespacedリソースの場合、子リソースは同じnamespaceになければいけない
  • 上記の仕様に違反したownerReferenceは無いものとみなされる。あるリソースのownerReferenceが不正なものだけの場合、当該リソースはガベージコレクションによる削除対象になる

本記事で扱うのは上記の仕様に違反した場合に発生する問題です。

ownerReferenceの仕様違反によって発生した問題

Rookにおいて昨年、上記仕様に違反したownerReferenceを設定したため、大きな問題が発生しました。

github.com

問題は、Rookが内部的に使うCephのCSIドライバ(ceph-csi)を管理するためのrook-ceph-csi-configというConfigMapが突然消えてしまうことがあるというものでした。

原因はRookではcluster resourceであるceph-csiのCSIDriverリソースのownerReferenceに、namespaced resourceであるRookのオペレータのDeploymentリソース(rook-ceph-operator)を指定していたことでした。これは上記仕様に反しています。

発生までの流れは次のようなものでした。

  1. KubernetesのガベージコレクションがCSIDriverリソースをチェックする(これは不定期に発生する)
  2. 上記オブジェクトのOwnerReferenceをチェックするが、rook-ceph-operatorという名前のDeployment "cluster" resourceは存在しない
  3. CSIDriverリソースはownerがいないので削除されてしまう
  4. CSIDriverリソースをownerとするrook-ceph-csi-configを含む他のリソースが、CSIDriver以外にownerがいなければ全て削除される

この問題は発生経緯がわかりにくいこと、およびRookではなくKubernetesの振る舞いによって引き起こされるもの(悪いのはRookですが)なこともあって、検出から修正まで一か月近くを要しました。

バグを修正するcommitは修正前、および修正後の両方を考慮して「既存のownerReferenceが不正なら修正する、そうでなければ何もしない」というものになりました。

github.com

github.com

このような変更はテストが面倒なことに加えてコードも汚くなりがちなので、できれば避けたいところです。

対策

本節では上記の問題の発生を防止あるいは検出する方法をそれぞれ紹介します。

問題の防止には、たとえばコントローラの実装にcontroller-runtimeを使っている場合はownerReferenceの設定時にSetOwnerReference、およびSetControllerReferenceという関数を使うことによってvalidationできます。

kubectl check-ownerreferencesというkubectlの拡張コマンドを使うと、動作中のKubernetesクラスタのリソースのvalidationをしてくれます。

Rookにおいては両方のvalidationを追加しました。

github.com

github.com

github.com

github.com

PRの変更を見ていただければわかるのですが、数千行にわたる大規模な修正が必要でした。このように修正が後になればなるほど変更量が増えるので、みなさんの環境でvalidationをしていない場合は、なるべく早く対策をすることをお勧めします。

Kubernetesにおいても「仕様違反をしたとしても、このような動作になるのはなるべく避けたいので、もう少しガベージコレクションの振る舞いを改善できないだろうか」という問題提起があり(上述のKubernetesのissueがそれです)、既に改善されています。

github.com

上述のownerReference仕様についても、以下のような但し書きが追加されています。

In v1.20+, if a cluster-scoped dependent specifies a namespaced kind as an owner,
it is treated as having an unresolveable owner reference, and is not able to be garbage collected.

In v1.20+, if the garbage collector detects an invalid cross-namespace ownerReference,
or a cluster-scoped dependent with an ownerReference referencing a namespaced kind,
a warning Event with a reason of OwnerRefInvalidNamespace and an involvedObject of the invalid
dependent is reported. You can check for that kind of Event by running
kubectl get events -A --field-selector=reason=OwnerRefInvalidNamespace.

おわりに

ownerReferenceにまつわる問題は一度発生してしまうと上述のようにトラブルシューティングに時間がかかりがちです。繰り返しになりますが、みなさんもvalidationを積極的に活用して問題の発生を未然に防止することをお勧めします。

最後になりますが、Necoプロジェクトおよびストレージチームでは一緒に働いてくれるかたを募集中です。ぜひ一度下記の募集要項をごらんください。

cybozu.co.jp

cybozu.co.jp

それぞれがどういうプロジェクト/チームであるかは以下リンク先のブログ/スライドをごらんください。

blog.cybozu.io

speakerdeck.com