Docker コンテナ内のアプリケーションが生成するログは、アプリケーションの stdout
および stderr
出力ストリームに送信されます。コンテナのログドライバーは、これらのストリームにアクセスし、ログをファイルに送信したり、ホスト上で稼働しているログコレクターや、ログ管理サービスのエンドポイントに送信したりします。この記事では、ドライバーの違いによって、あるいはその構成方法によって、コンテナ化されたアプリケーションのパフォーマンスや、Docker ロギングの信頼性にどのような違いが出るかを説明します。また、ログドライバーを選んで使用する際のコンテナの構成方法についても解説します。最後に、パフォーマンスおよびロギングの要件を満たすために最適なドライバーの構成方法をいくつかご紹介します。
Docker ログドライバーを選ぶ
Docker には、ログドライバーが多数組み込まれています。どのドライバーを選ぶかによって、コンテナでのログのフォーマット方法と、ログの送信先が決まります。コンテナを作成する際に、これらのドライバーのいずれか 1 つを選んで適用することができます。
デフォルトでは、Docker は json-file ドライバーを使用します。このドライバーは、コンテナが稼働しているホストで、JSON 形式のログをコンテナ固有のファイルに書き込みます。以下の例は、json-file ドライバーを適用し、hello-world という Docker イメージを使用した場合に作成される JSON ログです。
{"log":"Hello from Docker!\n","stream":"stdout","time":"2020-01-09T22:51:31.549390877Z"}
{"log":"This message shows that your installation appears to be working correctly.\n","stream":"stdout","time":"2020-01-09T22:51:31.549396749Z"}
local のログドライバーも、ローカルファイルにログを書き込み、圧縮してディスク容量を節約します。
Docker には、ログをさまざまなエンドポイントに転送するためのドライバーも組み込まれています。たとえば、syslog や journald などのロギングサービス、fluentd などのログシッパー、あるいは集中型ログ管理サービスにログが転送されます。
また、Docker はログドライバープラグインをサポートするため、独自のログドライバーを作成し、Docker Hub またはプライベートコンテナレジストリを通じて配布することができます。あるいは、コミュニティで提供されるプラグインを Docker Hub から入手し、実装することも可能です。
コンテナのログをコマンドラインから確認したい場合は、docker logs <コンテナ_ID>
コマンドを使用します。このコマンドを使うと、先ほどのログが次のように表示されます。
Hello from Docker!
This message shows that your installation appears to be working correctly.
使用する Docker のバージョン、およびログドライバーによっては、docker logs
コマンドでコンテナのログのスポットチェックができない場合もあるので注意してください。たとえば Docker CE を使用して docker logs
を実行すると、json-file、local、journald のドライバーで作成されたログしか読み取れません。Docker Enterprise を使用すれば、どのログドライバーで作成されるログでも docker logs
で読み取ることができます。
選択したログドライバーでコンテナを構成する
Docker コンテナはデフォルトで json-file のログドライバーを使用します。また Datadog も、ほとんどの場合にこのドライバーを推奨しています。ユースケースに応じて別のドライバーが必要な場合は、コンテナを作成する際に、docker run
コマンドに --log-driver
オプションを追加することで、デフォルトの設定を上書きできます。たとえば、次のコマンドは Apache httpd コンテナを作成し、デフォルトのログドライバーではなく FluentD を使用するように設定を上書きします。
docker run --log-driver fluentd httpd
また、Docker の daemon.json ファイルを変更して、デフォルトのドライバーを上書きすることもできます。次のコードスニペットは、FluentD をデフォルトとして使用する場合に追加する JSON を示しています。log-opts
の項目では、FluentD ホストのアドレスをドライバーに渡しています。
daemon.json
{
"log-driver": "fluentd",
"log-opts": {
"fluentd-address": "172.31.0.100:24224"
}
}
この変更を追加して Docker を再起動すると、新しく作成されるコンテナは、FluentD ドライバーを 172.31.0.100 のアドレスを持つクラスターノードで使用するようになります。
配信モード
どのログドライバーを適用する場合でも、コンテナのロギングにブロッキングとノンブロッキングのいずれかの配信モードを指定することができます。このモードを利用して、ロギングの処理を他のタスクよりも優先するかどうかを定義できます。
ブロッキング
Docker のデフォルトであるブロッキングモードの場合、ドライバーにメッセージを配信するたびにアプリケーションが中断されます。そのため、すべてのメッセージを必ずドライバーに送信できますが、ドライバーが別の処理を行っていると、メッセージが配信されるまでコンテナが他のアプリケーションタスクを処理できないため、アプリケーションのパフォーマンスに遅延が生じる要因になります。
ブロッキングモードを指定した場合に、アプリケーションのパフォーマンスが低下するかどうかは、適用するログドライバーの種類による影響を受けます。たとえば、json-file ドライバーの場合は、ローカルのファイルシステムにログを書き込むため、処理時間が非常に短く、ブロッキングによって遅延が起こることはあまり考えられません。一方、gcplogs や awslogs のように、リモートサーバーへの接続が必要になるドライバーの場合は、ブロッキングの時間が長くなり、著しい遅延が発生する可能性が高くなります。
ノンブロッキング
ノンブロッキングを指定した場合、コンテナは最初にメモリ内のリングバッファにログを書き込み、ログドライバーがログを処理できる状態になるまで、リングバッファにログを格納します。ドライバーが別の処理を行っていても、コンテナは即座にアプリケーションからのアウトプットをリングバッファに転送して、アプリケーションの実行を再開します。そのため、処理するログの量が多くても、コンテナで稼働するアプリケーションのパフォーマンスが低下することはありません。
ノンブロッキングモードの場合、ブロッキングモードと違って、ログドライバーがすべてのイベントをロギングするとは保証されません。ドライバーで処理可能な速度以上に多くのメッセージをアプリケーションが生成すると、リングバッファの容量が足りなくなる可能性があります。その場合に、ログがバッファからログドライバーに転送される前に削除されます。リングバッファに使用する RAM の量は、max-buffer-size
を使用して設定できます。デフォルトの max-buffer-size
値は 1 MB ですが、RAM に余裕がある場合は、バッファのサイズを増やすことで、コンテナによるロギングの信頼性を高めることができます。
コンテナを作成すると、Docker はデフォルトでブロッキングモードを設定しますが、Docker の daemon.json ファイルに log-opts
の項目を追加すれば、デフォルトをノンブロッキングモードに変えることもできます。次のコードは、先ほどの例に設定を追加したものです。デフォルトドライバーは fluentd のまま、デフォルトモードを non-blocking
に変更しています。
daemon.json
{
"log-driver": "fluentd",
"log-opts": {
"fluentd-address": "172.31.0.100:24224",
"mode": "non-blocking"
}
}
また、コンテナの作成コマンドに `–log-opt オプションを追加すれば、コンテナにノンブロッキングモードを個別に設定することもできます。
docker run --log-opt mode=non-blocking httpd
要件に合わせてドライバーとモードを選ぶ
Detadog ではほとんどのケースで、json-file ログドライバーをブロッキングモードで使用することを推奨しています。このドライバーであれば、ローカルファイルにログを書き込むため高速であり、ブロッキングモードで使用しても通常は安全です。この構成なら、すべてのログを Docker で取得することができ、アプリケーションのパフォーマンスが損なわれるリスクもほとんどありません。
Agent ベースのログコレクター (Datadog Agent など) を使用すると、ローカルのログファイルを追跡し、集中型のログ管理ソリューションに転送することができます。JSON フォーマットであれば、ログ管理プラットフォームでの解析も簡単です。属性でログをフィルタリングしたり、ログのパターンや傾向に基づいて検出や通知を行って、コンテナ化されたアプリケーションのパフォーマンスを分析することができます。
また、このドライバーを使用すると、コンテナのログローテーションの動作を個別にコントロールし、コンテナ化されたアプリケーションのパフォーマンスを高めることもできます。max-size
オプション を小さい値に設定すると、ログファイルの拡大を制限できますが、一方で Docker によるログローテーションの頻度が高くなります。回数が急増した場合、Docker はより多くのリソースをログローテーションに割り当てる必要があるほか、トラフィックの増加にも対応しなくてはなりません。アプリケーションのトラフィックパターンが一定ではなく、ロギング処理でバーストが発生する場合は、パフォーマンスを維持するために、max-size
の値を増やすことを検討してください。
最後に、json-log
ドライバーであれば、Docker Enterprise と Docker CE のどちらでも、docker logs
コマンドを使ってログを表示させることができます。
このように、json-file ドライバーとブロッキングモードは、アプリケーションのパフォーマンスとロギングの信頼性を高めるための最適な組み合わせです。しかし、要件によっては別のログドライバーや配信モードを選ぶことが必要になるケースもあります。
大量のログを生成するアプリケーションでロギングの信頼性を高める
アプリケーションが大量のログを生成し、I/O 処理の負荷が高い場合は、json-file ログドライバーをノンブロッキングモードで使用するべきか検討してください。この構成なら、コンテナのアプリケーションのパフォーマンスを維持しながら、ロギングの信頼性を高めることができます。ローカルのストレージにログを書き込むため、リングバッファが一杯になることがほとんどありません。ロギング処理の急激な増加への対応が必要なアプリケーションの場合は、リングバッファが一杯になる可能性もありますが、そうでない場合はこの構成ですべてのログを取得でき、アプリケーションの処理の中断もありません。
大量の RAM を消費し、外部のロギングサービスを使用するアプリケーション
ログをローカルに作成できない場合は、gcplogs や awslogs などのデフォルト以外のドライバーを使用して、ログをリモートの場所に送信する必要があります。アプリケーションが非常に多くのメモリを消費し、コンテナに割り当てられる RAM のほとんどを必要とする場合は、ブロッキングモードを使用することが通常は必要になります。その理由は、ネットワーク障害などによってドライバーがログをエンドポイントに配信できなくなった場合に、リングバッファが十分な量のメモリを利用できないからです。
ブロッキングモードを使用すると、すべてのログを確実に取得できますが、デフォルト以外のドライバーをブロッキングモードで使用すると、ロギングが中断された場合にアプリケーションのパフォーマンスが低下する可能性があるため、Datadog はこれを推奨していません。ミッションクリティカルなアプリケーションで、ログをローカルのファイルシステムに生成することが許されず、しかもパフォーマンスがログの信頼性よりも優先される場合は、十分な量の RAM をプロビジョニングし、リングバッファの信頼性を高めたうえで、ノンブロッキングモードを使用してください。この構成であれば、ロギングが中断してもアプリケーションのパフォーマンスが損なわれず、またログデータの大半を取得するための余裕も与えられます。
ロギングの信頼性とアプリケーションのパフォーマンスを最適化する
Docker のログドライバーとログ配信モードに何を選ぶかによって、コンテナ化されたアプリケーションのパフォーマンスに大きな違いが現れます。ロギングの信頼性を高め、パフォーマンスを安定させ、Datadog などの集中型ロギングプラットフォームによる可視性を向上させるために、Datadog では json-file ドライバーの使用を推奨しています。Datadog では、すべてのアプリケーションから送られるログをフィルタリングし、分析や通知を行うことができます。まだ Datadog をご利用でない場合は、14 日間の完全版無料トライアルをお試しください。