Kubernetes の Watch API とタイムアウト

Kubernetes の Watch API を下記クライアントライブラリを使って試してみました。

ソースは http://github.com/fits/try_samples/tree/master/blog/20180409/

はじめに

下記のコマンドを実行して Javascript Kubernetes Client をインストールしておきます。

Javascript Kubernetes Client のインストール
> npm install @kubernetes/client-node

Watch API による Pod の監視

Watch APIdefault Namespace の Pod に関するイベントを監視して、イベントのタイプと Pod 名を標準出力する処理を実装してみます。

watch の第一引数に Watch API のエンドポイント URL、第三引数でイベントハンドラを指定します。(第二引数はクエリパラメータ)

今回は Pod を監視していますが、default Namespace の Deployment を監視する場合は endpoint/apis/apps/v1/namespaces/default/deployments とします。

なお、$HOME/.kube/config もしくは %USERPROFILE%\.kube\config ファイルから Kubernetes への接続情報を取得するようにしています。

sample_watch_pod.js
const k8s = require('@kubernetes/client-node')
// default Namespace の Pod
const endpoint = '/api/v1/namespaces/default/pods'

// Windows 環境用の設定
if (!process.env.HOME) {
    process.env.HOME = process.env.USERPROFILE
}

const conf = new k8s.KubeConfig()
conf.loadFromFile(`${process.env.HOME}/.kube/config`)

const w = new k8s.Watch(conf)

w.watch(
    endpoint,
    {}, 
    (type, obj) => {
        console.log(`${type} : ${obj.metadata.name}`)
    },
    err => {
        if (err) {
            console.error(err)
        }
        else {
            console.log('done')
        }
    }
)

動作確認

今回、Kubernetes の環境を minikube で用意します。

minikube コマンドを使って start を実行するとローカル用の Kubernetes 環境が立ち上がります。

その際に、%USERPROFILE%\.kube\config ファイル等が作られます。

minikube 開始
> minikube start

・・・
Starting local Kubernetes v1.9.0 cluster...
Starting VM...
Getting VM IP address...
Moving files into cluster...
Setting up certs...
Connecting to cluster...
Setting up kubeconfig...
Starting cluster components...
Kubectl is now configured to use the cluster.
Loading cached images from config file.

Watchスクリプトを実行します。

sample_watch_pod.js の実行
> node sample_watch_pod.js

下記 YAML ファイルを使って、Kubernetes 環境へ nginx 実行用の Deployment と Service を作成してみます。

nginx.yaml (nginx 用の Deployment と Service 定義)
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  labels:
    app: nginx
spec:
  ports:
  - name: http
    port: 80
    nodePort: 30001
  selector:
    app: nginx
  type: NodePort

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80

kubectl を使って Deployment と Service を作成します。

Deployment と Service 作成
> kubectl create -f nginx.yaml

service "nginx-service" created
deployment "nginx-deploy" created

Watch の結果は以下のようになりました。

sample_watch_pod.js の結果1
> node sample_watch_pod.js

ADDED : nginx-deploy-679dc9c764-r9ds5
MODIFIED : nginx-deploy-679dc9c764-r9ds5
ADDED : nginx-deploy-679dc9c764-54d5d
MODIFIED : nginx-deploy-679dc9c764-r9ds5
MODIFIED : nginx-deploy-679dc9c764-54d5d
MODIFIED : nginx-deploy-679dc9c764-54d5d
MODIFIED : nginx-deploy-679dc9c764-r9ds5
MODIFIED : nginx-deploy-679dc9c764-54d5d

ここで、いつまでも接続が続くわけでは無く、minikube の環境では 40分程度(ただし、毎回異なる)で接続が切れ以下のようになりました。

sample_watch_pod.js の結果2 (一定時間経過後)
> node sample_watch_pod.js

・・・
done

タイムアウト時間の確認

Watch API の接続が切れる原因を探ってみます。

Kubernetes と minikube のソースから、タイムアウトに関係していると思われる箇所 timeout = time.Duration(float64(minRequestTimeout) * (rand.Float64() + 1.0)) を見つけました。※

 ※ minikube では localkube 内で Kubernetes の API Server を実行しているようです

これだと、タイムアウトは 30 ~ 60分でランダムに決まる事になりそうなので、接続の切れる時間が毎回異なるという現象に合致します。

ソース kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/get.go
func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch bool, minRequestTimeout time.Duration) http.HandlerFunc {
    return func(w http.ResponseWriter, req *http.Request) {
        ・・・
        if opts.Watch || forceWatch {
            ・・・
            timeout := time.Duration(0)
            if opts.TimeoutSeconds != nil {
                timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
            }
            if timeout == 0 && minRequestTimeout > 0 {
                timeout = time.Duration(float64(minRequestTimeout) * (rand.Float64() + 1.0))
            }
            glog.V(2).Infof("Starting watch for %s, rv=%s labels=%s fields=%s timeout=%s", req.URL.Path, opts.ResourceVersion, opts.LabelSelector, opts.FieldSelector, timeout)

            ・・・
            return
        }
        ・・・
    }
}
ソース minikube/pkg/localkube/apiserver.go
// defaults from apiserver command
config.GenericServerRunOptions.MinRequestTimeout = 1800

get.go の処理ではログレベル 2 でタイムアウトの値をログ出力しているので(glog.V(2).Infof(・・・) の箇所)ログから確認できそうです。

ただし、普通に minikube start で実行してもログレベル 2 のログは見れないようなので、minikube を -v <ログレベル> オプションを使って起動しなおします。

ログレベル 2 で miinkube 開始
> minikube start -v 2

Watchスクリプトを実行します。

sample_watch_pod.js の実行
> node sample_watch_pod.js

・・・

minikube logs でログ内容を確認してみると、get.go が出力しているタイムアウトの値を確認できました。

ログ確認
> minikube logs

・・・
Apr 08 01:00:30 minikube localkube[2995]: I0408 01:00:30.533448    2995 get.go:238] Starting watch for /api/v1/namespaces/default/pods, rv= labels= fields= timeout=58m38.2420124s
・・・