👀

Custom Controller外のリソースのイベントを監視する

2023/06/28に公開

はじめに

Custom Controllerは通常、以下のリソースに起こるイベント(Create, Update, Delete)を元にCustom Resourceをworkqueueに入れ、順番に処理(Reconcile)を実行します。

  • 自分自身
  • 自分をOwnerとする子リソース

以下のようなコードです。

func (r *ReplicatorReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		// 自分自身
		For(&replicatev1.Replicator{}).
		// 自分をOwnerとする子リソース達
		Owns(&corev1.ConfigMap{}).
		Owns(&appsv1.Deployment{}).
		Owns(&corev1.Service{}).
		Owns(&networkv1.Ingress{}).
		Complete(r)
}

その際に上記リソース以外に監視対象を追加し、任意のタイミングでReconcileを実行したい場合があります。これを実現する方法について調査をしました。

実現コード

結論から言うと、以下のようなコードで実現可能です。
Custom Controller管理外のSecretリソース(namespace: default, name config)を監視し、作成、更新された際にCustom ControllerのReconcileを実行します。

func (r *ClusterDetectorReconciler) SetupWithManager(mgr ctrl.Manager) error {
	mapFn := handler.EnqueueRequestsFromMapFunc(
		func(ctx context.Context, obj client.Object) []ctrl.Request {
			return []ctrl.Request{
				{NamespacedName: client.ObjectKey{
					Name:      "config",
					Namespace: "default",
				}},
			}
		})

	p := predicate.Funcs{
		UpdateFunc: func(e event.UpdateEvent) bool {
			old := e.ObjectOld.(*corev1.Secret)
			new := e.ObjectNew.(*corev1.Secret)
			return old.ResourceVersion != new.ResourceVersion
		},
		CreateFunc: func(e event.CreateEvent) bool {
			return true
		},
	}

	return ctrl.NewControllerManagedBy(mgr).
		For(&plumberv1.ClusterDetector{}).
		Watches(
			&corev1.Secret{},
			mapFn,
			builder.WithPredicates(p),
		).
		Complete(r)
}

解説

handler実装

以下コードにて、handlerを実装します。
ここでは、後続で実装するイベントを検知し、Custom Controllerに渡すRequestに変換します。
この時、NamespacedName構造体にリソースのnamespaceとnameを渡してあげることで、監視対象を限定できます。

mapFn := handler.EnqueueRequestsFromMapFunc(	
	func(ctx context.Context, obj client.Object) []ctrl.Request {
		return []ctrl.Request{
			{NamespacedName: client.ObjectKey{
				Name:      "config",
				Namespace: "default",
			}},
		}
	})

イベント実装

今回は、更新と作成のみを監視するように実装しています。
更新の場合、新旧のResourceVersionを比較し、異なる場合に更新されたと見なします。
作成の場合は...そのままですね。

p := predicate.Funcs{
	UpdateFunc: func(e event.UpdateEvent) bool {
		old := e.ObjectOld.(*corev1.Secret)
		new := e.ObjectNew.(*corev1.Secret)
		return old.ResourceVersion != new.ResourceVersion
	},
	CreateFunc: func(e event.CreateEvent) bool {
		return true
	},
}

predicate.Funcsの実装は以下の通りとなっており、更新、削除、作成、それ以外のイベントを捕まえることが可能となっています。
https://github.com/kubernetes-sigs/controller-runtime/blob/116a1b831fffe7ccc3c8145306c3e1a3b1b14ffa/pkg/predicate/predicate.go#L54-L67

監視開始

最後にhandlerとイベントをWatches関数に渡してあげることで監視を開始できます。

return ctrl.NewControllerManagedBy(mgr).
	For(&plumberv1.ClusterDetector{}).
	Watches(
		&corev1.Secret{},
		mapFn,
		builder.WithPredicates(p),
	).
	Complete(r)

他の実装

じつはイベントの検知だけでなく、goroutineを利用した監視や、特定時間に処理を実施する方法なども存在します。機会があれば活用したいと思います。
https://book-v1.book.kubebuilder.io/beyond_basics/controller_watches.html
https://yash-kukreja-98.medium.com/develop-on-kubernetes-series-demystifying-the-for-vs-owns-vs-watches-controller-builders-in-c11ab32a046e

さいごに

今回は、任意のリソースやタイミングでReconcileを回す方法をご紹介しました。
是非ご活用いただけると嬉しいです!

株式会社エーピーコミュニケーションズ

Discussion