Podを見張りたい
Go用のKubernetesクライアントを使ってクラスタ内のPodの様子を見張るやつを作った.
やったことのまとめ
Kubernetesクラスタ内のPodをwatchし,Podの状態変化に応じて任意の処理を行うobserver.goを作ったRetryWatcherを使う方法とInformerを使う方法を試したが,Informerを使うほうがよさそう

つかうもの
- macOS Mojave 10.14
- go version go1.14.6 darwin/amd64
- kubernetes/client-go v0.19.2
Kubernetesクラスタ v1.15.12-gke.20
やったこと
RetryWatcherでwatchする
PodをListするAPI1でwatchオプションを指定すると, Podの情報(event)がリアルタイムで流れてくる.
このeventを見ることで, Podが作成/更新/削除されたタイミングを把握することができる.
# PodのList APIにwatchオプションをつけて実行(kubectlは内部でAPIを叩いている)
$ kubectl get pods -w
# この状態で別ターミナルからJobを実行して削除
$ kubectl create job busybox --image=busybox -- /bin/sh -c 'echo hello'
$ kubectl delete job busybox
# kubectl get pods -w の出力
NAME READY STATUS RESTARTS AGE
busybox-8kzpz 0/1 Pending 0 1s # Pod作成予約
busybox-8kzpz 0/1 Pending 0 1s # Pod作成
busybox-8kzpz 0/1 ContainerCreating 0 1s # コンテナ起動
busybox-8kzpz 0/1 Completed 0 3s # Pod(コンテナ)完了
busybox-8kzpz 0/1 Terminating 0 12s # 削除直前の状態
busybox-8kzpz 0/1 Terminating 0 12s # Pod削除完了今回はGo純正のKubernetesクライアントライブラリclient-goを使い,watch用のAPIを利用してPodをひたすら見張りつづけて状態変化に応じた処理を行うようなしくみを作ってみた.
# 作業用ディレクトリとgo.modの作成
$ pwd
/path/to/workspace
$ mkdir kubernetes-pod-observer && cd kubernetes-pod-observer
$ go mod init github.com/uzimihsr/kubernetes-pod-observer
$ touch observer.goobserver.goはこんな感じで作った.Kubernetes APIクライアントの認証情報はkubeconfig(~/.kube/config)を参照して,Podをwatchし, watchしたeventの内容を元に処理を分けるような形になっている.kubeconfig読み込みからクライアント立ち上げまでの流れは公式のサンプルコード2を参考にした.
少しこだわったのはPodをwatchする際にRetryWatcher3を使用しているところ.
普通にwatch用のAPIを叩く場合一定時間が経つとAPIサーバー側が接続を切ってしまう仕様なのだが45,
これを使うとwatchの接続が切れたときにクライアント側で勝手に再接続してくれる.
また, watchを再開するとすでに観測したことのあるeventも拾ってしまうので,eventに紐づくResourceVersionを参照して既に見たことのあるeventは無視するようにした.
実際にobserver.goが動いている状態で, Podを手で作ったり, Jobから作成してみる.
# observer.goを実行
$ go run observer.go
# 以下は別のターミナルから実行
# Podを作成(1)
$ kubectl run busybox --image=busybox --restart=Never -- /bin/sh -c 'echo hello'
# Podを削除(2)
$ kubectl delete pod busybox
# Job経由でPodを作成(3)
$ kubectl create job busybox --image=busybox -- /bin/sh -c 'echo hello'
# Jobを削除(4)
$ kubectl delete job busyboxこのときのobserver.goの出力は次のようになる.
# observer.goの出力
# (1)
ResourceVersion: 60844265
the Pod < busybox > has been created.
ResourceVersion: 60844266
the Pod < busybox >'s phase is < Pending >.
ResourceVersion: 60844267
the Pod < busybox >'s phase is < Pending >.
ResourceVersion: 60844271
the Pod < busybox >'s phase is < Succeeded >.
# (2)
ResourceVersion: 60844359
the Pod < busybox >'s phase is < Succeeded >.
ResourceVersion: 60844360
the Pod < busybox > has been deleted.
# (3)
ResourceVersion: 60844936
the Pod < busybox-dx4f8 > has been created by the Job < busybox >.
ResourceVersion: 60844937
the Pod < busybox-dx4f8 >'s phase is < Pending >.
ResourceVersion: 60844940
the Pod < busybox-dx4f8 >'s phase is < Pending >.
ResourceVersion: 60844948
the Pod < busybox-dx4f8 >'s phase is < Succeeded >.
# (4)
ResourceVersion: 60845063
the Pod < busybox-dx4f8 >'s phase is < Succeeded >.
ResourceVersion: 60845064
the Pod < busybox-dx4f8 > has been deleted.
こんな感じで, watchしたPodの状態に応じた処理を行うことができた.
Informerを使ってみる
…実は同じようなことをする公式のサンプルコード6がある,
こっちはeventをwatchする部分でRetryWatcherの代わりに, Informer7というやつを使っている.Informerはwatchに関する処理をさらに抽象化したものらしくて, watchが途切れたときに再度接続を復活させたり,watchしたeventの種類で処理を分けたりとwatch対象のリソース(Pod)の状態変化に応じた処理がしやすくなっている. らしい.
(observer.goでは自分で記述していたような処理も内部でやってくれている)
サンプルコードを真似てobserver.goを書き直してみる.
ポイントはInformerの作成時にeventのType(ADDED, MODIFIED, DELETED)に応じた処理をそれぞれAddFunc, UpdateFunc, DeleteFuncで設定するところ.
こうしておくことで, 対象のリソース(Pod)の状態が変化したときにそれに応じた処理を設定することができる.
実際に動かしてみる.
# observer.goを実行
$ go run observer.go
# 以下は別のターミナルから実行
# Job経由でPodを作成(1)
$ kubectl create job busybox --image=busybox -- /bin/sh -c 'echo hello'
# Jobを削除(2)
$ kubectl delete job busybox# observer.goの出力
# (1)
Pod <busybox-m5cd6> was added by Job <busybox>.
Pod <busybox-m5cd6> is Pending phase.
Pod <busybox-m5cd6> is Pending phase.
Pod <busybox-m5cd6> is Succeeded phase. previous: Pending phase
# (2)
Pod <busybox-m5cd6> is Succeeded phase.
Pod <busybox-m5cd6> was deleted.
observer.goを起動した状態でJobを実行し, Podの作成, 状態変化, 削除のタイミングで任意の処理を行うことができた.
今回はデフォルトNamespaceのPod名を標準出力しただけだが, 頑張れば特に重要なNamespaceのPodについては変化のタイミングで特定の相手に通知を送ったりとかもできるはず.
今回はPodの状態変化を見張る部分とそれに応じた処理を1つにしてしまっているが, もちろんサンプルコード6のようにAddFuncなどでキューにデータを送り, 別のgo routineでキューからデータを取り出して処理することもできる. はず.
(ここで力尽きたのでやめた)
おわり
Kubernetesクラスタの外からPodをwatchして, その状態変化に応じた任意の処理を行うやつを作った.RetryWatcherを使う方法とInformerを使う方法の2通りを試してみたけど, Informerを使うほうがwatchを行う部分について難しいことを考える必要がなくて楽な感じがした.
どっちを使うべきか迷ったらInformerを使うほうがおすすめ?らしい8.
Informerはカスタムコントローラとかでも使われているみたいなので9, 時間があったらもう少し勉強してみたい.
おまけ

https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#list-pod-v1-core ↩︎
https://github.com/kubernetes/client-go/tree/master/examples/out-of-cluster-client-configuration ↩︎
https://pkg.go.dev/k8s.io/client-go/tools/watch#RetryWatcher ↩︎
https://kubernetes.io/docs/reference/using-api/api-concepts/#efficient-detection-of-changes ↩︎
https://github.com/kubernetes/client-go/issues/623#issuecomment-506822043 ↩︎
https://github.com/kubernetes/client-go/tree/master/examples/workqueue ↩︎ ↩︎
https://pkg.go.dev/k8s.io/client-go/tools/cache#NewInformer ↩︎
https://stackoverflow.com/questions/59544139/kubernetes-client-go-watch-interface-vs-cache-newinformer-vs-cache-newsharedi ↩︎
https://github.com/kubernetes/sample-controller/blob/master/docs/controller-client-go.md ↩︎
