Docker 入門

Docker ログとパフォーマンス監視

コンテナログとパフォーマンス監視は、アプリケーションの振る舞いを理解し、障害を診断し、Docker コンテナ (Container) が健全かつ効率的に稼働していることを保証するための必須ツールです。

前の章で学んだように、コンテナはアプリケーションに隔離 (Isolation) された環境を提供します。この隔離は多くの利点をもたらしますが、同時にホストマシン (Host Machine) のオペレーティングシステム (OS) 上でアプリケーションの出力やシステムメトリクス (Metrics) を直接確認するといった従来の方法が適用できなくなる可能性も意味します。

ログ (Logs) は、起動情報からエラーレポート、アプリケーション固有の出力まで、コンテナ化 (Containerized) アプリケーション内部で発生したすべての事象の履歴レコード (Record) を提供します。一方、パフォーマンス監視 (Performance Monitoring) は、コンテナのリソース消費に関するリアルタイムのインサイト (Insight) を提供し、パフォーマンスのボトルネック (Bottleneck) を特定し、設定を最適化 (Optimize) し、サービスの中断を防ぐのに役立ちます。どのような環境においても、コンテナ化アプリケーションを効果的に管理し、トラブルシューティング (Troubleshooting) するためには、これら2つの側面を熟知することが極めて重要です。

1. コンテナログへのアクセス

コンテナログは、コンテナ内部のアプリケーションの稼働ステータス (Status) を観察するための主要な手段です。Docker は、コンテナ内で実行されているプロセス (Process) の標準出力 (stdout) と標準エラー (stderr) ストリーム (Stream) を自動的にキャプチャ (Capture) します。

デフォルト (Default) では、これらのデータストリームは Docker のログドライバー (Log Driver、通常は json-file) によって収集され、Docker デーモン (Daemon) が実行されているホストマシンシステム上に保存されます。これにより、デバッグ (Debug)、監査 (Audit)、およびアプリケーションの稼働状況の包括的な理解のために、履歴およびリアルタイムのログを簡単に検索および表示できます。

1.1 docker logs コマンドの活用

docker logs コマンドは、実行中または停止済みのコンテナからログを検索するためのメインツールです。ニーズに合わせて出力をフィルタリング (Filter) および表示するための、いくつかの強力なオプション (Option) を提供しています。

一般的な Web サーバーである Nginx を使用してデモンストレーションしてみましょう。

基本的なログ検索:

コンテナに蓄積されたすべてのログを取得するには、そのコンテナ名または ID を使用します。まず、Nginx コンテナを実行しましょう:

docker run -d --name my-nginx -p 8080:80 nginx

このコマンドは、バックグラウンド (-d) で Nginx コンテナを起動し、名前を my-nginx とし、ホストマシンの 8080 番ポートをコンテナの 80 番ポートにマッピングします。

さて、そのログを確認してみましょう:

docker logs my-nginx

Nginx の起動ログが表示されます。これには、設定 (Configuration) の詳細や、サーバーが接続を受け入れる準備ができたことを示すメッセージが含まれます。ブラウザで http://localhost:8080 にアクセスした後、もう一度 docker logs my-nginx を実行すると、HTTP アクセスリクエストの新しいレコードが表示されます。

リアルタイムでのログの追跡 (--follow または -f):

アプリケーションをプロアクティブに監視する場合、特に開発 (Development) やデバッグ中は、ログがどのようにリアルタイムで生成されるかを確認する必要があります。--follow オプションは、新しいログエントリ (Entry) をターミナル (Terminal) にリアルタイムでプッシュ (Push) します。

docker logs -f my-nginx

このコマンドは、発生した新しいログを継続的に表示します。このコマンドを実行したまま、何度か http://localhost:8080 にアクセスしてみてください。アクセスログがすぐにポップアップ表示されるのがわかります。追跡を停止するには、Ctrl+C を押します。

直近のログの表示 (--tail):

最新のエラーを素早く確認したい場合など、膨大な履歴ログをめくることなく、最新の数行のログだけが必要な場合があります。--tail オプションを使用すると、ログの末尾から表示する行数を指定できます。

docker logs --tail 10 my-nginx

これにより、my-nginx コンテナによって生成された最後の 10 行のログが表示されます。

時間によるログのフィルタリング (--since):

特定の時間に発生した障害のトラブルシューティングを行う場合、特定のタイムスタンプ (Timestamp) 以降に生成されたログを確認したいことがあります。--since オプションを使用すると、特定のタイムスタンプまたは相対的な期間を指定できます。

# 例 1:特定時間以降のログを表示
docker logs --since "2023-01-01T10:00:00Z" my-nginx

# 例 2:過去 5 分間のログを表示
docker logs --since "5m" my-nginx

# 例 3:過去 1 時間のログを表示
docker logs --since "1h" my-nginx

これは、特定のイベントやデプロイ (Deployment) ウィンドウに関連するログを特定する際に非常に有用です。

ログへのタイムスタンプの追加 (--timestamps または -t):

デフォルトでは、一部のアプリケーションの標準出力にはタイムスタンプが含まれていない場合があります。Docker 自身は、保存するログエントリにタイムスタンプを付加することができます。-t オプションは各ログ行のタイムスタンプを表示し、Docker がそのログ行を受信した正確な時間を示します。

docker logs -t my-nginx

これは、異なるログ内のイベントを相関付けたり、単一のコンテナ内の個々のオペレーションに要した時間を理解したりするのに非常に役立ちます。

1.2 ログドライバー (Log Drivers)

Docker はコンテナのログを収集・保存するためにログドライバーを使用します。docker logs を実行するとき、実際にはデフォルトの json-file ログドライバーとインターフェース (Interface) しています。このドライバーはログをホストマシン上の JSON ファイルに書き込みます。

json-file はローカル開発や基本的なデバッグには非常に適していますが、集中型のログ管理 (Log Management) が必要な大規模な本番環境 (Production Environment) には適していません。そのようなシナリオのために、Docker は外部サービスにログを送信できる他の様々なログドライバーをサポートしています。例えば:

  • syslog: コンテナログを Syslog サーバーに送信します。
  • journald: コンテナログを systemd journal に送信します。
  • fluentd: コンテナログを Fluentd コレクターに送信します。
  • awslogs: コンテナログを Amazon CloudWatch Logs に送信します。
  • gcp-json: コンテナログを Google Cloud Logging に送信します。

これらのドライバーの設定は、通常コンテナ実行時に指定するか、Docker デーモンのデフォルトドライバーとして設定する必要があります。例えば、syslog ドライバーを使用してコンテナを実行する場合:

docker run -d --log-driver syslog --name my-app-with-syslog your-image

重要なヒント: docker logs コマンドは json-file および journald ドライバーにのみ適用されます。コンテナが他のログドライバーで設定されている場合、ログはすでに外部に送信されているため、docker logs はそのログを直接取得することはできません。この場合、選択したログサービスが提供するツール(例えば、Fluentd ダッシュボード (Dashboard)、CloudWatch コンソールなど)を使用してログを表示する必要があります。本章では、引き続き docker logs が直接インターフェースするデフォルトの json-file ドライバーに焦点を当てます。

2. コンテナパフォーマンスの監視

ログに加えて、コンテナがどれだけの CPU、メモリ (Memory)、ネットワーク (Network)、ディスク I/O を消費しているかを理解することは、パフォーマンス最適化とキャパシティプランニング (Capacity Planning) にとって極めて重要です。Docker は組み込みの docker stats コマンドを提供しており、コンテナのリソース使用状況のリアルタイムデータストリームを提供します。

2.1 docker stats の読み解き方

docker stats コマンドは、1つ以上の実行中のコンテナのリアルタイムのリソース使用統計情報のストリームを表示します。これは Linux の top コマンドや Windows のタスクマネージャーに似ていますが、Docker コンテナに特化しています。

まず、リソースの使用状況を観察できるように、CPU 負荷の高い作業(素数の計算など)を実行するシンプルなコンテナを実行しましょう。

docker run -d --name prime-calculator alpine/git:latest sh -c "i=0; while true; do echo \"Prime $(factor \$i | wc -w)\"; i=\$((i+1)); done"

このコマンドは、alpine/git コンテナ内でシンプルな Shell スクリプト (Script) を実行し、因数分解を継続的に計算することで、CPU をビジー状態に保ちます。

次に、新しいターミナルを開いて以下を実行します:

docker stats

デフォルトで毎秒更新されるテーブルが表示され、実行中のすべてのコンテナの統計情報が表示されます。私たちの prime-calculator コンテナについては、以下のような出力が観察されます:

CONTAINER IDNAMECPU %MEM USAGE / LIMITMEM %NET I/OBLOCK I/OPIDS
23b4c5d6e7f8prime-calculator98.00%10.2MiB / 2GiB0.50%726B / 0B0B / 0B1
a1b2c3d4e5f6my-nginx0.05%5.1MiB / 2GiB0.25%2.34kB / 2.34kB0B / 0B7

主要なカラムを分解してみましょう:

  • CONTAINER ID: コンテナの短い ID。
  • NAME: コンテナに割り当てた名前、または自動生成された名前。
  • CPU %: コンテナが現在使用しているホストマシンの CPU パーセンテージ。CPU パーセンテージが高い場合は、計算処理が重いことを示します。シングルコアアプリケーションの場合、100% は1つのコアを完全に利用していることを意味します。マルチコアの場合、100% を超えることがあります。
  • MEM USAGE / LIMIT: コンテナが現在使用しているメモリ量と、コンテナが使用できる総メモリ制限。制限が明示的に設定されていない場合は、ホストマシンシステム上の利用可能な総メモリが表示されます。
  • MEM %: コンテナが現在使用している割り当て制限のパーセンテージ。制限が設定されていない場合は、ホストマシンの総メモリに対するパーセンテージになります。
  • NET I/O: コンテナがネットワークインターフェースを通じて受信 (Rx) および送信 (Tx) したデータ量。ネットワークトラフィックの監視に使用されます。
  • BLOCK I/O: コンテナがブロックデバイスから読み取ったデータ量と書き込んだデータ量。これはディスクのアクティビティを反映しています。
  • PIDS: コンテナ内で現在実行されているプロセスまたはスレッド (Thread) の数。

コンテナ名または ID を指定することで、監視するコンテナを指定できます:

docker stats prime-calculator my-nginx

docker stats を停止するには、Ctrl+C を押します。

2.2 リソース制限 (Resource Limits) の設定

docker stats を使用したリソース使用状況の監視はパフォーマンスの観察に役立ちますが、Docker ではコンテナにリソース制限を設定することでパフォーマンスを管理することも可能です。これは、単一のコンテナがホストマシンのリソースを独占し、他のサービスに影響を与えるのを防ぐために不可欠です。

docker run オプションを使用して CPU とメモリの制限を設定できます:

  • --cpus <value>: コンテナが使用できる最大 CPU 量を指定します。これは浮点数 (Floating-point number) で、CPU コア数を表します。例えば、--cpus 0.5 は、コンテナがシングルコア CPU の最大 50% までしか使用できないことを意味します。
  • --memory <value>: コンテナが使用できる最大 RAM 量を指定します。bkmg などのサフィックスを使用できます(例:512m1g)。

CPU 制限を設けて prime-calculator を再起動し、変化を観察してみましょう。まず、既存のコンテナを停止して削除します:

docker stop prime-calculator
docker rm prime-calculator

ここで、0.5 コア(シングルコアの 50%)の CPU 制限付きで実行します:

docker run -d --name prime-calculator-limited --cpus 0.5 alpine/git:latest sh -c "i=0; while true; do echo \"Prime $(factor \$i | wc -w)\"; i=\$((i+1)); done"

その後、再度 docker stats で監視します:

docker stats

内部のアプリケーションがさらに多くのリソースを使用しようとしても、prime-calculator-limitedCPU % が 50% 付近で推移していることに気づくでしょう。Docker が積極的に CPU 使用率を制限しています。これは、極端にリソースを消費するアプリケーションが同一ホストマシン上の他のサービスに影響を与えるのをどのように防ぐかを示しています。

同様に、メモリ制限を設定することもできます:

docker run -d --name memory-hog --memory 128m alpine/git:latest sh -c "dd if=/dev/zero of=/dev/null bs=1M count=10000; sleep 10000"

このコンテナは、128MB をはるかに超えるメモリを割り当てようと試みます。許容されるメモリ制限を超えたため、ほどなくして Docker はこのコンテナを OOMKilled (Out Of Memory Killed) ステータスで終了させる可能性が高いです。docker ps -a を確認し、memory-hogSTATUS カラムを観察することで、これを検証できます。

適切なリソース制限の設定はコンテナ管理の重要な側面であり、同一の Docker ホストマシンを共有するアプリケーション間の安定性と公平性を確保します。docker stats コマンドは、制限が有効であるか、実際の使用パターンに基づいて調整が必要であるかを検証する上で非常に価値があります。

3. 総合実践デモンストレーション

これらの概念を、より現実的なシナリオに適用してみましょう:リクエストを記録し、ある程度のリソースを消費する可能性のあるシンプルな Web アプリケーションです。これを実現するために、Python Flask アプリケーションを使用します。

1. Python Flask アプリケーションの作成:

まず、ディレクトリを作成し、シンプルな Flask アプリケーションを作成します。

mkdir flask-app
cd flask-app

app.py を作成します:

# app.py
from flask import Flask, request, jsonify
import time
import os

app = Flask(__name__)

@app.route('/')
def hello():
    app.logger.info(f"受信元 {request.remote_addr} から / へのリクエスト")
    return "Hello, Docker! これはホームページです。"

@app.route('/slow')
def slow_endpoint():
    app.logger.info(f"受信元 {request.remote_addr} から /slow へのリクエスト - 処理をシミュレート中...")
    # CPU 負荷の高い処理をシミュレート
    result = 0
    for i in range(1, 10000000):
        result += i * i
    time.sleep(0.1) # わずかな遅延を追加してわかりやすくする
    app.logger.info(f"{request.remote_addr} からの /slow リクエストが完了しました。結果: {result}")
    return jsonify({"message": "これは遅いレスポンスです!", "result": result})

if __name__ == '__main__':
    port = int(os.environ.get('PORT', 5000))
    app.run(debug=True, host='0.0.0.0', port=port)

requirements.txt を作成します:

Flask

2. Dockerfile の作成:

# Dockerfile
FROM python:3.9-slim-buster
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]

3. Docker イメージのビルド:

docker build -t my-flask-app .

4. コンテナの実行とログへのアクセス:

さて、Flask アプリケーションコンテナを実行します。

docker run -d --name flask-web --memory 256m --cpus 0.5 -p 5000:5000 my-flask-app

実行時に、256MB のメモリ制限と 0.5 コアの CPU 制限を設定しました。

新しいターミナルを開いてログを追跡します:

docker logs -f flask-web

ブラウザまたは curl を使用して、いくつかリクエストを生成します:

# 別の独立したターミナルまたはブラウザで:
curl http://localhost:5000
curl http://localhost:5000/slow
curl http://localhost:5000

docker logs -f flask-web が実行されているターミナルでログを観察します。Flask アプリケーションからの INFO メッセージがリアルタイムで表示されるのがわかります。

5. docker stats を使用したパフォーマンスの監視:

さらに別の新しいターミナルを開き、以下を実行します:

docker stats flask-web

次に、/slow エンドポイント (Endpoint) に継続的にリクエストを送信します:

# 独立したターミナルで、遅いエンドポイントへのリクエストをループ処理する
while true; do curl http://localhost:5000/slow; sleep 0.1; done

docker stats の出力で、flask-web コンテナの CPU % を観察します。--cpus 0.5 の制限を適用しているため、アプリケーションがより多くの処理を実行しようとしても、継続して 50% 付近を推移しているのが確認できるはずです。MEM USAGE はいくつかのアクティビティを示しますが、256MiB の制限を大きく下回っているはずです。もし /slow エンドポイントが極端にメモリを消費し、256MB を超えた場合、Docker はそのコンテナを終了 (Terminate) させます。

このデモンストレーションにより、ログがアプリケーションのフローを理解しデバッグするのにどのように役立つか、そして docker stats とリソース制限によって、アプリケーションのリソース消費をどのように制御および監視できるかが明確に示されました。