Kubernetes で Pod が再起動しまくった原因は、コンテナのプロセスが終了したから

Kubernetes で動かすコンテナを作ってデプロイしたところ、Docker コンテナとして単体で動かしていた時は上手く動いていたのに、Kubernetes Pod としては正常に起動しない問題に遭遇した。

$ kubectl get pod で Pod のステータスを見ると、CrashLoopBackOff とかいうステータスになっていた。

調べていくと、Kubernetes の初歩的な仕組みをド忘れした実装になっていることに気付いた、という話。

目次

どんな実装にしていたか

そのコンテナは元々、コンテナ起動時にいくつかの常駐プロセスを立ち上げ、その後に Web サーバを起動する、という作りにしていた。

Dockerfile はこんな感じで、スタートアップ用のシェルスクリプトを呼んでいた。

CMD ["sh", "/my-app/startup.sh"]

startup.sh の中ではこんな風に、常駐プロセスを立ち上げてから Web サーバを起動していた。

#!/bin/bash

# デーモンプロセスを立ち上げる…
/my-app/my-tools/start
/my-app/my-another-tools/execute my-daemon

# Web サーバを起動する
node /my-app/server.js

この常駐プロセスの起動処理には少々時間がかかるので、K8s Pod が生成された直後だと Web サーバがリクエストを受け取れない問題があった。

そこで、nohup& を使って Web サーバをバックグラウンド実行してみることにした。

#!/bin/bash

# Web サーバをバックグラウンドで起動する
nohup node /my-app/server.js &

# デーモンプロセスを立ち上げる…
/my-app/my-tools/start
/my-app/my-another-tools/execute my-daemon

ついでに、開発環境ではデーモンプロセスを立ち上げなくて良いので、環境変数で分岐してみることにした。

#!/bin/bash

# Web サーバをバックグラウンドで起動する
nohup node /my-app/server.js &

# 開発環境ならデーモンプロセスを立ち上げない
if [ -z "${PRODUCTION_ENV}" ]; then
  echo '開発環境なのでデーモンプロセスを起動しません'
  exit
fi

# デーモンプロセスを立ち上げる…
/my-app/my-tools/start
/my-app/my-another-tools/execute my-daemon

この状態で Docker イメージをビルドし、docker run してみると、意図した動きになってくれたので安心していたのだが、いざ Kubernetes にデプロイしてみると、前述のように CrashLoopBackOff なるステータス異常が発生しており、Pod が正しく起動しなかった。

CrashLoopBackOff とは何なのか

CrashLoopBackOff というステータスエラーについて調べてみると、以下の記事が見つかった。

CrashLoopBackOff

原因

コンテナ内のプロセスの終了を検知してコンテナの再起動を繰り返している。

解決

コンテナで終了するプロセスを動かしていないだろうか?コンテナが正常に稼動しているかはプロセスが落ちていないかで判断するので、プロセスが正常終了した場合であっても「あっプロセスが落ちてる」となって再起動される。そのようなコンテナは再起動したところで再びプロセスが正常終了するだろう。つまり、無限に起動し続けるということになる。

Web サーバの起動をバックグラウンドで行うようにしてしまったため、デーモンプロセスを起動する・しないに関わらず、この startup.sh を実行するプロセスは終了する作りになっていた。

プロセスが落ちると、Kubernetes が自動的にそれを検知し、Pod の状態が異常であるとみなして Pod を再起動してしまう、というワケだ。

Web サーバとして起動しっぱなしにしたいコンテナは、サーバの起動コマンド (ココでは $ node /my-app/server.js) をフォアグラウンド実行し、コレを最後に呼んでやる必要がある、ということだ。バックグラウンド実行すべきなのは、デーモンプロセスの起動処理部分だったのだ。

というワケで以下のようにスクリプトを修正。

#!/bin/bash

if [ -z "${PRODUCTION_ENV}" ]; then
  # 開発環境ならデーモンプロセスを立ち上げない
  echo '開発環境なのでデーモンプロセスを起動しません'
else
  # デーモンプロセスをバックグラウンドで起動する
  nohup /my-app/my-tools/start &
  nohup /my-app/my-another-tools/execute my-daemon &
fi

# Web サーバをフォアグラウンド実行する
node /my-app/server.js

デーモンプロセスを起動する部分の処理は、別のシェルスクリプトに切り出しておいて、それをバックグラウンド呼び出ししてやるとスッキリするかも。

#!/bin/bash

if [ -z "${PRODUCTION_ENV}" ]; then
  # 開発環境ならデーモンプロセスを立ち上げない
  echo '開発環境なのでデーモンプロセスを起動しません'
else
  # デーモンプロセスをバックグラウンドで起動する (別ファイルを呼び出す)
  nohup sh /my-app/startup_launch_daemons.sh &
fi

# Web サーバをフォアグラウンド実行する
node /my-app/server.js

こんな感じ。重要なのはこのスクリプトの最後で Web サーバを起動し、それをずっとフォアグラウンド実行させっぱなしにすること。startup.sh の実行プロセスが落ちないようにしておかないと、Kubernetes Pod が無限に再起動してしまう、ということだ。

今回はとりあえずコレで解決。ヒヤヒヤ…。