docker logsとPID 1問題

1サービス1コンテナという言葉がある通り、Dockerコンテナでは1つのアプリケーションをパッケージングすることが推奨されています。
これを遵守していれば、docker logsの仕組みについてあまり理解せずともサービスのログの出力先を標準出力、標準エラー出力に向ければdocker logsでログを見ることができました。

docker docsでは例としてnginxとhttpdのコンテナでの設定を紹介しています。

nginx

The official nginx image creates a symbolic link from /var/log/nginx/access.log to /dev/stdout, and creates another symbolic link from /var/log/nginx/error.log to /dev/stderr, overwriting the log files and causing logs to be sent to the relevant special device instead.

View logs for a container or service | Docker Documentation

ログの出力先をシンボリックリンクにしておき標準出力、標準エラー出力にログを流しています。

ここで登場する「/dev/stdout」「/dev/stderr」に関してはそれぞれ下記のようにシンボリックリンクが設定されています。

  • /dev/stdout -> /proc/self/fd/1
  • /dev/stderr -> /proc/self/fd/2

httpd

The official httpd driver changes the httpd application’s configuration to write its normal output directly to /proc/self/fd/1 (which is STDOUT) and its errors to /proc/self/fd/2 (which is STDERR).

View logs for a container or service | Docker Documentation

「/proc/self/fd/1」「/proc/self/fd/2」については上述のnginxの項で出ましたね。
つまり最終的にはここにログを流せば良さそうということになります。

self?

selfというのは自分自身を表す言葉ですが、ここでself示してるのはPIDです。
例に上がっていたnginx,httpの場合selfは何になるのか?ということになりますが
DockerfileのCMDで起動したプロセスのPIDは1になります。つまりselfは1です。

となるとそれぞれ下記にログを転送すればdocker logsにログが出てくれることになります。

  • /proc/1/fd/1
  • /proc/1/fd/2

1というのがPIDであることがわかったので、/proc配下をみてみるとPID毎にディレクトリが切られていることがわかります。
つまりPID毎に標準出力、標準エラー出力を管理していることがわかります。
考えてみれば当然ですね。同じサーバーに複数のクライアントが接続していたとして全てのクライアントに同じ標準出力がでるわけではないので。

複数サービスのログを出したい

ここで最初に触れた1サービス1コンテナの話に戻ります。
1サービス1コンテナがいいことは重々承知ですが、どうしても2サービス動かしたい場合がありました。
その時docker logsに2サービスのログを流すにはどうすればいいかがわかりませんでした。

上述のPID1用の標準出力、標準エラー出力にログを流してあげれば実現できます。

  • /proc/1/fd/1
  • /proc/1/fd/2

PID1問題

ログの問題が解決できたので、複数サービスを起動するシェルスクリプトを作成してDockerコンテナを起動するようにしました。
すると、Dockerコンテナを正常に終了することができなくなりました。

下記事象が原因で発生していると推測できます。

参考

ざっくり理解したのは下記を認識していれば良さそうです。

  • PID1のプロセスがSIGTERMをハンドリングしていなければ正常終了することができない
  • Dockerの場合はTiniを使用すれば回避できる