logspoutでDockerコンテナのログの集約・ルーティング

progrium/logspout

logspoutは,ホスト内で動かした全てのDockerコンテナの出力を集約して,好きなところに飛ばす(ルーティングする)ためのツール.開発者はDokkのJeff Lindsay

以下の2つの特徴がある

  • コンテナとして起動(ステートレス)
  • HTTP APIによるルーティングの設定

ログを貯めて管理したり,検索するといったことはできない.コンテナのログをリアルタイムで好きなところに飛ばすだけ.

これだけだが,Dockerのログの問題をいい感じに解決してくれそう.

Dockerのログのしくみ

まず,簡単にDockerのログのしくみを説明する.

現時点(2014年5月)でDockerはコンテナ内で吐き出されたstdout/stderrを取得することができる.コンテナのプロセスがstdoutstderrにログを吐き出し,Dockerはそれをホストにjsonとして保存する.docker logコマンドを使うとそれを取得することができる.

これはシンプルだけど欠点でもある.いずれディスクが圧迫されるし,毎回docker logを叩くわけにもいかない.そのため,Dockerのログをどうするかってのはいろいろ試みられている.

Dockerのログ収集の試み

Dockerコンテナのログ収集の試みは,大きく分けて3つある.

やりようはいろいろあるが,少なくともDocker的に良いのは,

  • コンテナに複数プロセスを立てない 
  • ホストに多くを設定しない

これを満たすのは,3番目の専用のコンテナを立てる方式.ただ,現状の方法は立てるコンテナごとに--volumes-fromを駆使しなといけないなど,少しめんどくさい.

logsoutの良い点

専用のコンテナ(progrium/logspout)を立てるだけ使える.

つまり,現状動いている他のコンテナになんの設定もなしに使える.当然,ホスト側に特別な設定をする必要がない.

logsoutを使う

まず,インストール(以下でdocker runすればインストールもされるので実際は必要ない)

$ docker pull progrium/logspout

例として,”hello world”を出力し続ける単純なコンテナを立てておく.

$ docker run -d --name hello1 busybox /bin/sh -c "while true; do echo hello world; sleep 1; done"
$ docker run -d --name hello2 busybox /bin/sh -c "while true; do echo hello world; sleep 1; done"

これらに対してログを収集するには以下を実行する.

$ docker run -d -p 8000:8000 --name log -v=/var/run/docker.sock:/tmp/docker.sock progrium/logspout

HTTP APIのアクセスを可能にするため8000ポートを解放し,ホストの/var/run/docker.sockをマウントする.

ログを見てみる.

$ curl $(docker port log 8000)/logs
             log|[martini] Started GET /logs for 172.17.42.1:50859
             hello2|hello world
             hello1|hello world
             hello2|hello world
             hello1|hello world
             hello2|hello world

リアルタイムの出力が確認できる.出力は色づけもされている.

ルーティング

一番単純な方法は,rsyslogに投げること.以下のように起動コマンドにURIを指定するだけ.

$ docker run -v=/var/run/docker.sock:/tmp/docker.sock progrium/logspout syslog://logs.papertrailapp.com:55555

以下のようにPOSTを使って,ルーティングの設定をすることもできる.

$ curl $(docker port log 8000)/logs -X POST \
    -d '{"source": {"filter": "db", "types": ["stderr"]}, target": {"type": "syslog", "addr": "logs.papertrailapp.com:55555"}}'

上の例では,名前がdbであるコンテナのstderrへの出力をlogs.papertrailapp.com:55555に飛ばすように設定している.addrはDNSで名前解決さえできればいいので,Consulなどのサービスディスカバリを使えば,さらなる道を開けそう.

ルーティングは以下のようにGETでデバッグしつつ設定できる.

GET /logs
GET /logs/filter:<container-name-substring>
GET /logs/id:<container-id>
GET /logs/name:<container-name>

CoreOSの場合

Dockerの運用に関してはCoreOSに多くの知見がある.CoreOSのコンテナのログのルーティング方法について@mopemopeさんに教えていただいた(参考:“CoreOS logging to remote destinations”).

CoreOSの場合は,上の分類でいうとコンテナの外部で収集する方式になる.そもそもCoreOSでは,systemdでコンテナを起動する想定になっている.そのため,ログのルーティングにはsystemd自前のjournalが使える.logspoutと同様のことをするには,以下のようにする.

$ journalctl -o short -f | ncat remote-destination.com 12345

どの方法が一番良いかはまだまだ一概には言えないが,コンテナをsystemdで起動することが前提になっていれば,余計な設定などもなく,シンプルだなと.

まとめ

考慮する問題はいくつかある.

まだ出たばかりなので,1つ目はすぐに解決しそう.2つ目の問題に関しては, 特別なログはvolumeやマウントを駆使してなんとかするしかない.3つ目に関して,このコンテナはログをルーティングするだけで,保存はできない.つまり,コンテナが落ちている間のログは失われ,再度送信することはできない.コンテナのヘルスチェックや障害対応などまだまだ考えることは多いように思える.