morimorihoge です。最近格ゲー部に入部してGGXrd REV 2を始めました。高校時代にGGXをやっていたときと感覚が違いすぎてまだ全然ですが、チマチマ練習中です。
Dockerにはinitプロセスがないよ問題とsupervisor
さて、Dockerコンテナはそのままではsystemdやsysvstat経由で起動を管理するサービス(nginxとかpostgresqlとかの永続系サービス)がうまく動きません。
これはDockerの設計思想によりinitプロセスがいないことが原因で、この辺りは Docker and the PID 1 zombie reaping problem にいい感じでまとまっています。
もしsystemdが使いたければDockerではなくsystemd-nspawnを使うのが楽でしょう、という話になるのですが、Dockerでinitプロセスを扱う試みもあるので、Dockerをできるかぎり軽量VMとして透過的に利用したいということなら以下のサイトなどが参考になりそうです。
では世の中のDockerユーザはこのinitプロセスがない問題をどう解決しているかというと、 supervisor というスーパーバイザ機能のみを提供する軽量プログラムを利用するのが一般的に思えます。Ubuntu系containerであれば apt-get install supervisor
でインストールできます。
この辺りは以前社内勉強会で話したので別記事で解説しようと思います。
なお、本記事のDocker環境は普通のUbuntu 16.04LTSコンテナで、aptでパッケージインストールしたものです。
Dockerにおけるよくあるsupervisor設定のミス
supervisor は非常に簡易な設定でプロセス起動管理ができるのでありがたいのですが、注意しないとハマります。
[supervisord] セクションで nodaemon=true
にする
これをtrue
にしないとdaemonモードで起動します(こちらがデフォルト)。
daemonモードで起動してしまうと、Container立ち上げ時にすぐプロセスを手放してしまいContainerが終了します。
supervisorを設定したけどいざdocker run
したら一瞬でcontainerが止まってしまったという場合は大体これが原因です 。
[supervisord]
nodaemon = true
[program:x] セクションで autorestart
を使う場合には注意する
[program:#{PROGRAM_NAME}] セクションには command
で実行したいコマンドを書ける上に autorestart
という便利そうな設定があります。
これは当該プロセスが落ちていた場合に自動的に再起動してくれるオプションなのですが、 気軽に使うと死にます 。
もし command
に service
コマンド経由や /etc/init.d
以下のスクリプト経由でサービス起動するような設定を書いていて autorestart
を有効にした場合、 延々とプログラムが再起動し続けます 。
外部から見ると繋がったり繋がらなかったりしてチャタリングしているように見えるので、まともなサービスはできません。気をつけましょう。
とりあえず、あまり深い理解をせずに気軽に使いたければ、 autorestart
は使わず、autostart
だけ設定し、再起動が必要になったらserviceコマンドやinitスクリプトで手動立ち上げするか、またはcontainerごとrestartする(supervisordが立ち上がり直すのでautostartが起動する) ことをオススメします。詳細は後述。
supervisorの設定ファイルを弄ったら reread
update
restart
supervisorの設定は通常 /etc/supervisor
にあり、こちらの設定を弄ることになりますが、この設定はsupervisor実行中には動的に読み込まれません。
設定更新時にsupervisorプロセス自体を再起動しても良いのですが、Docker Containerの起動時にsupervisordをrunプロセスに指定していた場合、supervisorが死ぬのは困ります。
というわけで、supervisordを落とさずに設定を読み込むのは以下の手順です。
supervisorctl reread
supervisorctl update
supervisorctl restart #{新しく追加したサービス}
reread
すると設定に変更があれば標準出力に表示されますが、この状態ではまだ反映されていないので、 update
で反映します。その後、動作中のサービスであれば restart
することで新しい設定で動作するようになります。
※ぐぐった中では reread
restart
するだけで良いという記事が多々ありましたが、手元の環境(supervisor 3.2.0) では update
の必要がありました。
autorestartチャタリング問題についてもう少し詳しく
autostart
は使うな、だけだとあんまりなので、もう少し詳しく解説します。
autorestartが正しく動く条件として、supervisorが正しく監視対象のプロセスを捕捉できている必要があります。今現在supervisorが監視対象のプロセスを捕捉できているかは、 supervisorctl status
で調べることができます。
# supervisorctl status
nginx EXITED May 29 07:11 AM
postgresql RUNNING pid 13398, uptime 0:25:43
sshd RUNNING pid 8, uptime 3 days, 14:37:22
この結果を見ると、Nginxは落ちていてPostgreSQLとSSHdが起動しているように見えます。
ps
コマンドでも確認してみましょう。
# ps ax
8 ? S 0:00 /usr/sbin/sshd -D
7382 ? Ss 0:00 nginx: master process /usr/sbin/nginx
7383 ? S 0:10 nginx: worker process
13398 ? S 0:00 /usr/lib/postgresql/9.6/bin/postgres -D /var/lib/postgresql/9.6/main -c config_file=/etc
※(抜粋)
あれ? Nginxは起動しています ね。これはどういうことでしょうか?
supervisorの設定を見てみると、
[program:nginx]
command=service nginx start
となっています。 service
コマンド経由、つまり/etc/init.d以下のスクリプトを経由してサービス起動をしていますね。
この設定は正しく動きますし、初回起動させるだけであれば問題ありません。
supervisorのデフォルトでは autostart
パラメータはONになっているため、これだけ設定してあればsupervisor起動時にnginxが起動します。やったね!
supervisorctl
コマンドでNginxがEXITED
した理由
ではなぜ supervisorctl
コマンドではNginxが EXITED
ステータスになってしまったのでしょうか?
これは command
指定が service nginx start
になってしまっているためです。
supervisorの command
設定には監視対象のプログラムそのものの実行パスを書く必要があり、serviceや/etc/init.d以下の起動スクリプトはそれ自身はサービス起動後に終了してしまうため、supervisorがサービス自身を捕捉できないのだと思います。supervisorからしてみれば、 service
コマンドのプロセス自体は実行後即終了するので、 EXITED
なわけですね。
対応方法
ではどうすればいいかというと、nginxであればnginxを起動する /usr/sbin/nginx
を直接記述すれば良いわけです。
[program:nginx]
command=/usr/sbin/nginx
なお、systemdやsysvinit向けのnginx設定の場合、 これでもまだちゃんと動きません 。
nginxはデフォルトではdaemonとして起動するため、supervisorがnginxプロセスを立ち上げてもsupervisorの管理下から外れてしまいます。
この辺りについては supervisorのドキュメント にも書かれており、
supervisorで管理したいプロセスはdaemon化せず、フォアグラウンドプロセスとして起動する様に設定する必要があります (yamasita コメントありがとう :))。
# supervisorctl status
nginx EXITED Jun 01 11:00 PM
ではどうすればいいかというと、deamonモードで動かさなければ良いので、
[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
とします。
これで、nginxプロセスがsupervisorの管理下に置かれるようになり、
# supervisorctl status
nginx RUNNING pid 13816, uptime 0:04:37
とちゃんと見えるようになりました。
まとめ
supervisorは軽量スーパーバイザとして便利で、Dockerと合わせて使うテンプレートが世にたくさんあります。設定も簡単で、それほどLinuxサーバ管理に長けていなくても割とフィーリングで設定すればそれなりに動いてくれるという点でお手軽なのはありがたいツールです。
しかし、それでもちょいちょい落とし穴はあるので、軽量VMとしてDockerを使う場合にはある程度ノウハウを貯めていく必要があると思います。
ただ、僕のオススメはsupervisorで管理すると決めたら特定プロセスが止まった場合にはsshして手動でごそごそせずに、 container&supervisorごとrestartする運用 です。
最初に動かしたいプロセスをsupervisorの設定に書く手間がありますが、そもそもスーパーバイザを使うなら起動設定は全てそこに書くべきですし、 containerは起動が軽い というメリットを最大限活用するためにも気軽にrestartするのが良いと思います。
弊社でもDockerを本格運用し始めて2年くらい経ちましたが、そろそろ複数Docker Hostの管理が辛くなってきたので最近はKubernetesやAmazon EC2 Container Service辺りを勉強中です。
アプリケーション側の総portable container化についてはまだネットワークとVolumeどうするの問題及びトラシューどうしようといった問題があり、なかなか本番系では提案するノウハウが足りてないですが、この辺もおさえていきたいところです。
関連記事(Docker)
- [翻訳] Dockerについてよくある勘違い(人気記事)
- 【ゆるふわDocker部】気軽にDockerを使うのもいいじゃない(人気記事)
- 【ゆるふわDocker部】任意バージョンのPostgreSQLコマンドを実行して外部DBに接続する(人気記事)
- 今の環境がDocker Containerとして動作しているかどうかをコマンドで調べる
- docker pullしたらまずvimを入れる人用のイメージ