この記事は株式会社ネクスト(Lifull) Advent Calendar 2016の9日目です。
業務でDockerコンテナを導入してみたいと思ったので
それらを監視するために調べた色々を書き出します。
「ログ監視」「リソース監視」の観点でまとめてみました。
環境
弊社で運営している多くのWebサービスではAWSを利用しています。
AWS上でコンテナを利用する方法はいくつかありますが
基本はElasticBeanstalk(以下、EBと略)上での複数コンテナを想定します。
(とはいえ裏側ではECSが動いているのですが)
ログ監視
Docker: logsコマンド
Dockerのコマンドで、ホストインスタンスから
コンテナ内のSTDOUTとSTDERRを取得することができます。
$ docker logs a59845048cea
[2016-12-05 17:10:05 +0000] [7] [INFO] Starting gunicorn 19.6.0
[2016-12-05 17:10:05 +0000] [7] [INFO] Listening at: http://0.0.0.0:5000 (7)
[2016-12-05 17:10:05 +0000] [7] [INFO] Using worker: sync
[2016-12-05 17:10:06 +0000] [10] [INFO] Booting worker with pid: 10
デバッグ時には便利ですが、毎回インスタンスに入るわけにもいきません。
Dockerにはロギングドライバという機能があり
logs
で得られる内容をいくつかの方法で外部に出力することができます。
AWS: CloudWatch Logsの活用
Dockerは前述のロギングドライバにおいて
公式でCloudwatch Logsへの出力をサポートしています。
EBでは--log-driver
のようにdocker run
の際に渡すパラメータを
dockerrun.aws.jsonという独自形式のjsonに定義します。
このファイルの中のコンテナ定義形式はECSと共通なので
ログの設定についてはECSのドキュメントに記載がありました。
"logConfiguration": {
"logDriver": "json-file"|"syslog"|"journald"|"gelf"|"fluentd"|"awslogs",
"options": {"string": "string"
...}
logDriver
についてはawslogs
を選択して問題なさそう。
optionsには、awslogs-region
とawslogs-group
を指定すればよさそうです。
試しに以下の定義でnginxコンテナのログを出力してみます。
公式のnginxイメージで、内容は80番ポートを開けているだけです。
{
"AWSEBDockerrunVersion": "2",
"containerDefinitions": [
{
"name": "nginx",
"image": "nginx:1.11.5-alpine",
"essential": true,
"memory": 128,
"portMappings": [
{
"hostPort": 80,
"containerPort": 80
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-region": "ap-northeast-1",
"awslogs-group": "nginx-group"
}
}
}
]
}
指定したロググループがCloudWatch側に定義されていないと
EBのデプロイごとコケるようなので注意してください。
デプロイが成功すると、CloudWatchのコンソールやcliでログが見られます。
何度か踏んだ結果、アクセスログとエラーログの両方が出ているようです。
ちなみに、STDOUTとSTDERRを表示するはずのdocker logs
で
これらが見られるのは公式のnginxイメージを使用したからです。
Dockerfileを見ると、ログの出力先がシンボリックリンクになっています。
# forward request and error logs to docker log collector
&& ln -sf /dev/stdout /var/log/nginx/access.log \
&& ln -sf /dev/stderr /var/log/nginx/error.log
docker logs
の集約についてはこの方法でなんとかなりそうです。
STDOUTとSTDERRを分けられないのは難点ですが
DockerのIssueでも少なからず議論があるので、いずれ改善されるかもしれません。
ちなみに、fluentdのロギングドライバでは分けることができるようです。
また、EBでホストインスタンスにアクセスすると
/var/log/docker-events.log
などのログが残されていることもわかります。
こちらは上記のロギングドライバで出力されない情報なので
.ebextensionsなどでホスト側にログ出力の仕組みを作る必要がありそうです。
リソース監視
Docker: statsコマンド
Dockerのコマンドで、コンテナごとのリソース使用量を表示します。
$ docker stats
CONTAINER CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
a59845048cea 1.02% 39.42 MiB / 1.951 GiB 1.97% 1.038 kB / 648 B 32.77 kB / 0 B 5
ac7470b95cef 0.19% 353.9 MiB / 1.951 GiB 17.72% 1.296 kB / 648 B 4.149 MB / 317.8 MB 27
Linuxのtop
コマンドのように使えてわかりやすいのですが
集約や保存のためにはこれでは不十分です。
AWS: CloudWatchメトリクス
EBやECSで複数コンテナを扱う場合は
ECSのコンソールでメトリクスを確認することができます。
インスタンスのクラスタ単位では以下の4つのメトリクスが見えます。
ECSで言うサービスの単位では、以下の2つのメトリクスのみでした。
ちなみにEBのコンソールでは
コンテナではない通常のインスタンス監視と特に変わりません。
見慣れたCloudWatchの形式で見られるのはありがたいのですが
コンテナごとの使用リソースなど、もう少し細かい部分が気になります。
サードパーティのサービス/ソフトウェアに頼る
cAdvisorやELKスタックなどのソフトを活用したメトリクス収集や
フルスタックな有料サービスの活用など、追加の選択肢は多く存在します。
自部署では監視領域で全般的にMackerelを利用しているため
コンテナのメトリクス可視化も試してみました。
Mackerelではagentを使用してメトリクス情報を送信しますが
コンテナのかたちで監視を行うmackerel-agentも公式で提供されています。
これで、一緒に起動しているコンテナとホストインスタンスの監視が可能です。
ドキュメント内ではdocker run
の引数として書かれている部分を
Dockerrun.aws.jsonに落とし込んでみます。
{
"AWSEBDockerrunVersion": "2",
"volumes": [
{
"name": "docker-sock",
"host": {
"sourcePath": "/var/run/docker.sock"
}
},
{
"name": "lib-mackerel-agent",
"host": {
"sourcePath": "/var/lib/mackerel-agent"
}
}
],
"containerDefinitions": [
{
"nginxコンテナの設定は": "省略"
},
{
"name": "mackerel-agent",
"image": "mackerel/mackerel-agent",
"environment": [
{ "name": "apikey", "value": "Hoge8noPEnZj9HogeMEwnHogep5aMQzKMHogee7ZHoge" },
{ "name": "enable_docker_plugin", "value": true },
{ "name": "auto_retirement", "value": true }
],
"essential": true,
"memory": 128,
"mountPoints": [
{
"sourceVolume": "docker-sock",
"containerPath": "/var/run/docker.sock"
},
{
"sourceVolume": "lib-mackerel-agent",
"containerPath": "/var/lib/mackerel-agent"
}
]
}
]
}
mackerel-agentコンテナには
サービスを利用するためにapikeyなどの環境変数を渡しています。
また、メトリクスデータのやりとりのためのボリュームマウントが必須です。
上記のDockerrun.aws.jsonからコンテナを起動した結果
Mackerelのコンソール上でメトリクスが閲覧できます。
以下のグラフはホストインスタンスの使用メモリを示します。
メモリ以外にもloadaverate、cpu、diskioなどのメトリクスが見られます。
さらに、{ "name": "enable_docker_plugin", "value": true }
の設定により
起動しているコンテナのメトリクスも取得することができます。
以下のグラフは 各コンテナ の使用メモリを示しています。
勝手に起動するecs-agentを含めて、3つのコンテナのメトリクスがあります。
これに加えてBlkIO Bytes、BlkIO IOPS、CPUの4種類が閲覧可能で
それぞれのメトリクスを起点としたアラートも飛ばせるため
かなり柔軟に監視対応ができます。
CloudWatchのメトリクスだけではかゆいところに手が届かないため
Mackerelに限らず、監視専用のソリューションは使った方が良いと思います。
まとめ
コンテナは通常のサーバ運用と比べて永続性がないため
ログやメトリクスを集約し、残していくことは特に重要だと思っています。
もっと監視が簡単になって、導入の障壁がなくなれば良いですね。