この記事はCAMPHOR- Advent Calendar 2020の17日目の記事です。
11月の終わり頃、個人的にインターンとインターンの合間で暇していた時期にCAMPHOR-のサーバーにGrafana/Lokiを導入しました。
この記事では、Grafana/Lokiを用いて、サーバー内の容量の圧迫を防ぎつつコンテナログの永続化を行う方法とその注意点を紹介します。
背景
現状CAMPHOR-の各種サービスはdockerに載った状態で動作しています。
元々改善を入れる前は特別なログの管理ということをしていませんでした。
CAMPHOR-ではサーバーのsshを行うことができる人を制限しているので、ログを見たい時はsshのできる人がdocker logs
からログを調査するという形になっていました。
この状態には以下の問題点があります
- コンテナ再起動に伴うログの消失
- サーバーへのアクセス権がある人のみしかログを閲覧できない
dockerはデフォルトの状態でコンテナの停止時にログが消えてしまいます。そのため、コンテナを再起動させた場合などに再起動前のログを追えなくなります。
コンテナログに求められる要件
今回のコンテナログに求められることは以下です
- ログがコンテナが停止しても消えない(must)
- ログを誰でも簡単に見れる
- ログを溜めすぎてサーバーの容量一杯になるとやばいので定期的に消したい
- 新しいコンテナが追加された際の対応が簡単である
初めは、dockerの吐くログファイルを直接永続化してlogrotateをかけるなどのシンプルな方法を考えていましたが、要件2を満たすために、WebUIからログを閲覧できる必要があると考え、今回はGrafana/Lokiを使用することにしました。
要件を満たす方法は他にも「EFKスタック」と呼ばれる ElasticSearch / Fluentd / Kibana などをはじめとして色々あるかと思います。
loki-docker-driverを用いたコンテナロギングの構築
Lokiにログを転送するためのdocker-pluginとしてDocker Driver Client(grafana/loki-docker-driver)が存在します。これをdockerのlogging driverとして使用することでコンテナの吐くログをうまくコンテナごとにlabelを貼りつつlokiに送ってくれます。
このpluginを入れればあとはconfigurationにのっとって設定をしていくだけになります。難しい部分が本当にひとつもなくサクッと導入をすることができました。
LokiとGrafanaは公式のdocker-compose.yamlをほとんどそのまま使用して立ち上げました。
と言うことで、図に表すとこんな感じになります
(図をわかりやすくするために、LokiとGrafanaをdockerの外に出しています)
loki-docker-driverがlokiに送ったログをGrafanaから参照する形です。
このような形で閲覧できます。
ログの最大容量の制限
ここから少し細かい設定に関してみていきます
{
"log-driver": "loki",
"log-opts": {
"mode": "non-blocking",
"loki-url": "http://localhost:3100/loki/api/v1/push",
"loki-batch-size": "400",
"loki-retries": "2",
"loki-timeout": "1s",
"keep-file": "true",
"max-size": "5g"
}
}
上記が実際に使用しているdockerデーモンの設定ファイル(daemon.json
)です。
keep-fileと言う設定が入っています。
This indicates the driver to keep json log files once the container is stopped. By default files are removed, this means you won’t be able to use docker logs once the container is stopped.
https://grafana.com/docs/loki/latest/clients/docker-driver/configuration/
説明にもありますが、これによってdocker logs
コマンドが使用可能になります。docker logs
コマンドはホストに残されるjson fileのデータをもとに実行されるので、logging driverをloki-docker-driverに変更してしまうとデフォルトだとjson fileが吐かれなくなりdocker logs
コマンドも使用不可能になります。
Grafanaがいるとはいえ、docker logs
コマンドがないと不便なのでkeep-file
を有効にしています。
「いやいや、要件3の「ログ溜めすぎると容量がやばい」はどうなんねん」と思われるかと思いますが、その一つ下のmax-size
によってjson-fileの最大容量を制限しています。
次はLoki自体の設定を見ていきます。
chunk_store_config:
max_look_back_period: 2184h
table_manager:
retention_deletes_enabled: true
retention_period: 2184h
上記はデフォルトから変更を入れたlokiの設定です。ここではLokiに残されるログデータの容量を制限するために、約3ヶ月より前のログに関してはdeleteするように指定しています。
これによって、Lokiに送られたデータが容量を圧迫すると言う心配もありません。(Lokiはログをgzip圧縮して持ってくれるので容量を取りにくいとはいえ、塵も積もればと言いますしね)
ちなみに注意点としてperiodに指定する値は168hの倍数である必要があります。
Hey, Loki requires that retention (720h in your case) is divisible by the duration of each index table (168h by default).
https://github.com/grafana/loki/issues/2151
Lokiが死ぬとdocker全体が死ぬ問題
順調に見えたLokiの導入ですが、Lokiのコンテナの再起動をしようとした時に事件は起こりました。
Lokiが死ぬと、loki-docker-driverがログを送ろうとしてretryを続けてdockerデーモン自体がクソ重くなってしまうという問題です。
これによって一回CAMPHOR-の全サービスが落ちました(深夜2時だったのが不幸中の幸いだった)
渾身のツイートも滑っていますね
このissueのせいで一瞬dockerデーモンごとドッカーンしました(真顔)https://t.co/I9o2hrq2hV
— さんぽし (@sanpo_shiho) November 27, 2020
こちらは先程のdaemon.json
の再掲です
{
"log-driver": "loki",
"log-opts": {
"mode": "non-blocking",
"loki-url": "http://localhost:3100/loki/api/v1/push",
"loki-batch-size": "400",
"loki-retries": "2",
"loki-timeout": "1s",
"keep-file": "true",
"max-size": "5g"
}
}
この問題の対処のために複数の設定を入れています
- mode: non-blockingにすることでコンテナの処理が中断されない
- loki-retries: リトライの回数を減らす(デフォルトは10)
- loki-timeout: timeoutまでの時間を短くする(デフォルトは10s)
これで上記の問題は抑えることができました。
しかし、ログの信頼性が代わりに落ちるので、この辺りはバランスを考えて設定すべきですね。
終わりに
今回はloki-docker-driverを使用した、コンテナロギングの構築の一例を紹介しました。
どの程度のアクセスがくるサービスのログなのか、ログを吐くプロセスがいくつ走っているのか等、状況によってログに求められるものが大きく変わってきます。
今回の構成では最後のissueに対処するためにログの信頼性が少し下がってしまう設定を入れています。しかしこれは現状のCAMPHOR-のサービス状況を考えたときに常に大量のログが吐かれているわけではないという状況も加味した上での判断です。
今回のこの一例がどなたかのお役に立てば幸いです、ここまで読んでいただきありがとうございました。