✉️

Kubernetes Advent Calendar 2021 20日目 notifications-engine で作る通知機能

2021/12/19に公開

こちらは Kubernetes Advent Calendar 2021 の 20 日目の記事となります。

今回は KubeCon 2021 NA でも発表された notifications-engine というものを紹介しようと思います。

notifications-engine とは

notifications-engine は Argo Project の一つで、それぞれの Argo Family のプロジェクトで常々カスタマイズ可能な通知機能が欲しいと言われていましたが、コアメンバ的にはそれぞれのプロジェクトの軽量性や責務を損なってしまうということで頭を悩ませていたようです。
最終的には通知のためのコードを共通ライブラリに切り出し、各プロジェクトで固有の Custom Resource に対して通知機能を実装するようになりました。

notifcations-engine は共通ライブラリとなっていてやれることとしては下記のようになります

https://github.com/argoproj/notifications-engine

  • 独自のリソースに対する通知のフックを Go のコードで informer ベースで簡単に実装できる
  • リソースに対する通知のフック条件やメッセージを ConfigMap やリソースの Annotation で柔軟に指定できる

できないことに関しても書いておきます

  • 1つの informer で1つのリソースしかハンドリングできないので、複数リソースの対する通知を行うには複数の informer を実装しなければならない
  • 通知先の provider はライブラリに組み込まれたものしか使用できない
    • これに関しては webhook が使用できるのでサポートされていない通知先に対しても webhook を介して別のサービスにさらに中継するという方式をとれば実現可能

ちなみにサポートされている通知先に関しては8個ぐらい

notifications-engine の設定

cert-manager の Certificate リソースに対する通知が公式の example にあります。

https://github.com/argoproj/notifications-engine/tree/master/examples/certmanager

Certificate リソースに下記のような Annotation を書くだけで通知を有効化できます

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  annotations:
    notifications.argoproj.io/subscribe.on-cert-ready.slack: <CHANNEL>

今回は slack に対する通知を設定しています

  • Annotation の notifications.argoproj.io/subscribe この部分は Prefix として固定されています。
  • <prefix>.on-cert-ready は下記の ConfigMap で指定している trigger のどれかを指定します。
  • <prefix>.<trigger>.slack は trigger で発火した後、メッセージを送信する Service を指定しています。
  • ConfigMap の data.template.cert-ready では送信するメッセージのテンプレートを設定しています。 {{.cert.metadata.name}} では Certificate リソースのパスを指定していします。
    • cert というキーは informer の中で設定しています
  • service.slack では通知先への秘密情報を参照しています。
    • $slack-token は informer に引数として渡した Secret リソースの .data.slack-token というパスを参照します
apiVersion: v1
kind: ConfigMap
metadata:
  name: cert-manager-notifications-cm
data:
  trigger.on-cert-ready: |
    - when: any(cert.status.conditions, {.reason == 'Ready' && .status == 'True'})
      send: [cert-ready]
  template.cert-ready: |
    message: |
      Certificate {{.cert.metadata.name}} is ready!
  service.slack: |
    token: $slack-token

informer の初期設定として下記のように行うことで、上記の ConfigMap と Slack の秘密情報を書いた Secret の名前を渡しています

informer のコード

informersFactory := informers.NewSharedInformerFactoryWithOptions(
	kubernetes.NewForConfigOrDie(restConfig),
	time.Minute,
	informers.WithNamespace(namespace))
secrets := informersFactory.Core().V1().Secrets().Informer()
configMaps := informersFactory.Core().V1().ConfigMaps().Informer()
notificationsFactory := api.NewFactory(api.Settings{
	ConfigMapName: "cert-manager-notifications-cm",
	SecretName:    "cert-manager-notifications-secret",
	InitGetVars: func(cfg *api.Config, configMap *v1.ConfigMap, secret *v1.Secret) (api.GetVars, error) {
		return func(obj map[string]interface{}, dest services.Destination) map[string]interface{} {
			return map[string]interface{}{"cert": obj}
		}, nil
	},
}, namespace, secrets, configMaps)

これを見ると、informer が動作する namespace と Secret や ConfigMap の namespace は同一でないとダメなようですね。

次に実際に Certificate リソースを監視するコードを書きます。
他のリソースを監視する場合も example をベースに API Version と Group などを書き換えるだけで十分でしょう。

certClient := dynamic.NewForConfigOrDie(restConfig).Resource(schema.GroupVersionResource{
	Group: "cert-manager.io", Version: "v1", Resource: "certificates",
})
certsInformer := cache.NewSharedIndexInformer(&cache.ListWatch{
	ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
		return certClient.List(context.Background(), options)
	},
	WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
		return certClient.Watch(context.Background(), metav1.ListOptions{})
	},
}, &unstructured.Unstructured{}, time.Minute, cache.Indexers{})
ctrl := controller.NewController(certClient, certsInformer, notificationsFactory)

2022/03 追記

Twitter で go module の依存がこけるのは go-check というライブラリを replace すれば解決できるのを教えていただいたので自分で作ったカスタムリソースの状態変化を通知してみました
https://twitter.com/sshota0809/status/1472748704482627584

コードはここにあります
https://github.com/Bo0km4n/notifications-engine-sample

Foo リソースの status が Ready になったら slack の test チャンネルに通知しています。
通知は実際には画像のようにできました。

ちなみに、configMap に定義する Trigger の条件式は expr という外部ライブラリを利用していますので、詳しい文法などはこちらのリンクを参照してください。
https://github.com/antonmedv/expr/blob/master/docs/Language-Definition.md

まとめ

  • notifications-engine で任意の Kubernetes リソースに対して、柔軟な通知設定を行うことができる
  • Go の informer と ConfigMap, Secret を設定する必要がある

Argo Family への組み込みは Argo CD, Argo Rollouts にはすでに組み込まれています。
Argo Workflows の対応はまだのようです。

参考、引用

Discussion