Kubernetesでエラーを返さずにPodを更新するにはwaitするのが解決策だった
Kubernetesでエラーを返さずにPodを更新するにはどうすれば良いか色々検証しました。
結論はPodが終了する直前にwaitするです。
以下、試したことを書きます。
結論だけ知りたい方はこちらからどうぞ。
今回はNode.jsで検証しましたが 、そのほか全般的に当てはまる内容も含まれると思います。
DockerでNode.jsを動かすルールを理解する
Node.jsのDocker imageのドキュメントにベストプラクティスが紹介されています。
これによると、 DockerでNode.jsを起動するとPID1で起動することになり、SIGTERMなどのシグナルを受け取れないとあります。
下記のようにnodeを起動して停止させてみると数秒経過しないと停止しません。
$ docker run --rm -it --name node-example node:latest $ docker stop node-example
これはシグナルを受け取れずに強制終了させているからです。
Dockerの場合、--init
オプションをつけることでこの問題を回避できます。
$ docker run --rm -it --name node-example --init node:latest $ docker stop node-example
こうするとすぐにnodeが停止します。
また、npm start
で起動した場合でもシグナルを受け取れないとあります。
KubernetesのPodが終了するときにはSIGTERMシグナルがコンテナに送られます。
Kubernetesで動かす場合でもシグナルを受け取れるようにする必要があります。
Kubernetesには dockerの --init
オプションのようなものはないのですが、PID1として起動しない方法はあります。
それが shareProcessNamespace
です。
これらを踏まえて、DockerとKubernetesをセットアップします。
Dockerfile例
FROM node:12-alpine WORKDIR /usr/src/app COPY package*.json ./ RUN npm install COPY . . EXPOSE 3000 CMD [ "node", "./bin/www" ]
nodeコマンドでDockerイメージを作ります。
apiVersion: apps/v1 kind: Deployment metadata: name: myapp spec: selector: matchLabels: ingress-app: myapp template: metadata: labels: ingress-app: myapp spec: shareProcessNamespace: true containers: - name: myapp image: hi1280/myapp:0.1 imagePullPolicy: IfNotPresent ports: - containerPort: 3000
shareProcessNamespaceをtrueにします。
SIGTERMシグナルを受け取り終了を試みる
今回はNode.jsでExpressを使用してアプリケーションを作成しています。
単純にSIGTERMシグナルを受け取る(失敗パターンその1)
ソース github.com
単純にshareProcessNamespaceをtrueにしただけです。
これでNode.jsがSIGTERMシグナルを受け取って終了するはずです。
fortioという負荷テストツールを使って、複数回アクセスしながらDeploymentリソースでローリングアップデートをしてみます。
下記がfortioの実行コマンドです。
$ fortio load -t 0 -qps 32 -c 8 http://localhost:31213
しばらく経った後でコマンド実行を止めると結果が表示されます。
ローリングアップデート後にいくつかのアクセスが失敗していることが分かります。
Code -1 : 10 (0.9 %) Code 200 : 1125 (99.1 %)
これはSIGTERMシグナルに反応してすぐにPodが終了してしまうからだと思われます。
アプリ内でSIGTERMシグナルを制御する(失敗パターンその2)
Node.jsでSIGTERMを受け取り、正常に終了するためのライブラリがあったので使用しました。
github.com
ソース github.com
fortioの結果は下記のようになり、見た感じ良さそうです。
Code 200 : 768 (100.0 %)
ただPodのログをみてみると、responseのstatusが不明なログがいくつかあります。
これも失敗していそうです。
myapp-657c67f595-sgj87 myapp GET / 200 503.329 ms - 23 myapp-657c67f595-sgj87 myapp GET / - - ms - - myapp-657c67f595-sgj87 myapp GET / - - ms - - myapp-657c67f595-sgj87 myapp GET / - - ms - - myapp-657c67f595-sgj87 myapp GET / 200 501.409 ms - 23 myapp-657c67f595-sgj87 myapp GET / - - ms - - myapp-657c67f595-sgj87 myapp GET / 200 500.461 ms - 23 myapp-657c67f595-sgj87 myapp GET / - - ms - -
Podが終了する直前にこのようなログが発生していることが分かりました。
ちなみにPodのログを見るのに stern
というツールが重宝しました。
Podの終了直前でwaitする
最終的にうまく行ったのがwaitするという方法です。
ソース github.com
preStopというPodが終了する直前に呼ばれる処理でsleepコマンドを実行しています。
これを実行したところ、statusが不明なログがなくなり、全てのアクセスが200のstatus codeを返す結果になりました。
なぜwaitが必要なのか、下記に詳しい解説があります。
Podが終了する処理とServiceがPodの宛先を切り替える処理は並列に実行されるようです。
このような挙動によって、終了しようとしているPodに対して終了直前までアクセスされる可能性があります。
Podの終了する処理において、wait時間を確保して終了直前のアクセスに対しても処理が完了するように制御する必要があります。
まとめ
色々試しつつ、waitするという方法に行き着きました。
SIGTERMプロセスを受け取るようにして正常終了しようと試みましたが見当違いでした。
Kubernetesの仕組み上こうするしかないのかと思いつつ、辛いやり方だなと思います。
参考資料
Docker で node.js を動かすときは PID 1 にしてはいけない - ngzmのブログ
Kubernetes: 詳解 Pods の終了 - Qiita
docker-node/BestPractices.md at master · nodejs/docker-node · GitHub