This page looks best with JavaScript enabled

CAMPHOR- VPSのコンテナロギングを支える技術

 ·   ·  ☕ 6 min read  ·  ✍️ さんぽし

この記事はCAMPHOR- Advent Calendar 2020の17日目の記事です。

11月の終わり頃、個人的にインターンとインターンの合間で暇していた時期にCAMPHOR-のサーバーにGrafana/Lokiを導入しました。

この記事では、Grafana/Lokiを用いて、サーバー内の容量の圧迫を防ぎつつコンテナログの永続化を行う方法とその注意点を紹介します。

背景

現状CAMPHOR-の各種サービスはdockerに載った状態で動作しています。

元々改善を入れる前は特別なログの管理ということをしていませんでした。
CAMPHOR-ではサーバーのsshを行うことができる人を制限しているので、ログを見たい時はsshのできる人がdocker logsからログを調査するという形になっていました。

この状態には以下の問題点があります

  1. コンテナ再起動に伴うログの消失
  2. サーバーへのアクセス権がある人のみしかログを閲覧できない

dockerはデフォルトの状態でコンテナの停止時にログが消えてしまいます。そのため、コンテナを再起動させた場合などに再起動前のログを追えなくなります。

コンテナログに求められる要件

今回のコンテナログに求められることは以下です

  1. ログがコンテナが停止しても消えない(must)
  2. ログを誰でも簡単に見れる
  3. ログを溜めすぎてサーバーの容量一杯になるとやばいので定期的に消したい
  4. 新しいコンテナが追加された際の対応が簡単である

初めは、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から参照する形です。

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圧縮して持ってくれるので容量を取りにくいとはいえ、塵も積もればと言いますしね)

ref: Loki Storage Retention

ちなみに注意点として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のコンテナの再起動をしようとした時に事件は起こりました。

if loki is not reachable and loki-docker-driver is activated, containers apps stops and cannot be stopped/killed

Lokiが死ぬと、loki-docker-driverがログを送ろうとしてretryを続けてdockerデーモン自体がクソ重くなってしまうという問題です。

これによって一回CAMPHOR-の全サービスが落ちました(深夜2時だったのが不幸中の幸いだった)

esa

渾身のツイートも滑っていますね

こちらは先程の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-のサービス状況を考えたときに常に大量のログが吐かれているわけではないという状況も加味した上での判断です。

今回のこの一例がどなたかのお役に立てば幸いです、ここまで読んでいただきありがとうございました。

Share on

さんぽし
WRITTEN BY
さんぽし
Web Developer /w Elixir, Go