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.go
observer.go
はこんな感じで作った.Kubernetes API
クライアントの認証情報はkubeconfig
(~/.kube/config)を参照して,Pod
をwatch
し, watch
したevent
の内容を元に処理を分けるような形になっている.kubeconfig
読み込みからクライアント立ち上げまでの流れは公式のサンプルコード2を参考にした.
少しこだわったのはPod
をwatch
する際にRetryWatcher
3を使用しているところ.
普通に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
の代わりに, Informer
7というやつを使っている.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 ↩︎