Docker on EC2のログ監視、CloudWatchで始める最小構成【初心者向けガイド】

記事タイトルとURLをコピーする

概要

このブログでは、EC2インスタンス内で動作するDockerアプリケーションのログをCloudWatchに出力する方法を説明します。

最終的に以下の構成図のような構成を目指します。

構成図

前提条件

  • VPCとEC2インスタンスがセットアップされていること
  • EC2インスタンスにDockerがインストールされていること
  • EC2インスタンスにアクセスできること
    • 今回はAWS Systems Manager Session Manager(以下、Session Manager)を使用してアクセスします
    • SessionManagerを使用するにはIAMロールにAmazonSSMManagedInstanceCoreポリシーが必要です
  • 東京リージョン(ap-northeast-1)を使用します。別のリージョンを使用する場合は、コマンド内のリージョン指定を適宜変更してください。

想定ユースケース

  • EC2インスタンス上でDockerコンテナを使用してアプリケーションを実行し、そのログを監視したい場合
  • EC2上にファイル出力を行うアプリケーションをAWS上で監視する場合

ログを出力するPythonアプリケーションの作成と実行

Dockerコンテナ上で動作するPythonスクリプトを作成します。

$ cd
$ mkdir container_log
$ cd container_log

$ vi app.py

下記Pythonスクリプトを貼り付けます。

import time
import sys

count = 1

# カウントアップしながらログを出力し続ける
while True:
    # 標準出力にログを出力します
    print(f"Log {count}")

    # 1分ごとにエラー出力を発生させる
    if count % 6 == 0:
        print(f"Error {count}", file=sys.stderr)

    time.sleep(10)
    count += 1

次にベースとなるDockerイメージを作成します。

$ vi Dockerfile

下記内容を貼り付けます。

【ポイント】 pythonの標準出力を即時更新(フラッシュ)するために、print関数にflush=Trueを追加するか、Pythonスクリプトの実行時に-uオプションを使用します。今回はDockerfileで-uオプションを使用しています。

# ベースイメージとしてAWS公式の Amazon Linux イメージを使用
FROM public.ecr.aws/amazonlinux/amazonlinux:latest

# 作業ディレクトリを設定
WORKDIR /app

# Pythonスクリプトをコンテナにコピー
COPY app.py .

# スクリプトを実行
CMD ["python3", "-u", "app.py"]

Dockerイメージをビルドし、コンテナを実行してみます。

$ docker build -t my-app .

# コンテナをフォアグラウンドで起動
$ docker run --name my-app my-app:latest
Log 1
Log 2
...
Log 6
Error 6
...

(Ctrl + C で停止)

このアプリケーションでは、10秒ごとに標準出力にログを出力し、60秒ごと(6回目ごと)に標準エラー出力へエラーログも出力します。

このままではコンテナがフォアグラウンドで実行されているため、出力を確認できますが、他の操作ができません。 実際の運用では、複数のコンテナをバックグラウンドで動作させることになります。

コンテナをバックグラウンドで実行する

次に、コンテナをバックグラウンドで実行し、ログを確認する方法を説明します。

一度、コンテナを停止し、削除します。

# コンテナを停止(Ctrl + Cで停止していない場合)
$ docker stop my-app

# コンテナを削除
$ docker rm my-app

バックグラウンドでコンテナを起動します。

$ docker run -d --name my-app my-app:latest

先ほどは10秒ごとにログが出ていましたが、このままではログを確認できません。 これは、コンテナがバックグラウンドで実行されているためです。

ログの出力方法(標準機能)

Docker上で動くアプリケーションのログの標準機能としてdocker logsコマンドがあります。 docker logsコマンドを使用することで、コンテナの標準出力と標準エラー出力に送られたログメッセージを表示できます。

実際にログを確認してみます。

$ docker logs -f my-app
Log 1
Log 2
Log 3
Log 4
...

(Ctrl + C で停止)

フォアグラウンドで実行している場合と同様に、10秒ごとにログが出力されていることが確認できたと思います。

このままでは先ほどと同様に、ログを確認している間は他の操作ができません。 そこで、次のようにしてログをファイルに出力することで、他の操作を行えるようにします。

【重要:セキュリティと権限の設定】 /var/log/配下にディレクトリを作成する際は、適切な権限設定が必須です。 実運用では専用ユーザーを作成し、最小権限の原則に従って権限を付与することを強く推奨します。 今回はSession Manager経由でログインしているため、ssm-userを使用していますが、本番環境では専用のアプリケーションユーザーを作成してください。

# 1. ディレクトリ作成
$ sudo mkdir -p /var/log/docker/my-app

# 2. 現在のユーザーに権限を付与(SSMでログインしているためssm-user)
$ sudo chown ssm-user:ssm-user /var/log/docker/my-app
$ sudo chmod 755 /var/log/docker/my-app


# 3. 権限確認
$ ls -al /var/log/docker/
drwxr-xr-x. 2 ssm-user ssm-user  6 Oct  8 11:26 my-app

# 4. コンテナの実行
$ docker run -d --name my-app my-app:latest

# 5. ログ出力
$ docker logs -f my-app > /var/log/docker/my-app/app.log 2>&1 &

# 6. ログ確認
$ tail -f /var/log/docker/my-app/app.log
Log 1
Log 2
Log 3
Log 4

【注意事項】 この方法ではログファイルが際限なく大きくなります。実運用ではlogrotate等を使用してログローテーションの設定が必要です。また、バックグラウンドで実行しているログ出力プロセスの管理には、systemdサービス化などの適切なプロセス管理手法を検討してください。

これで、EC2上で動くアプリケーションのログをファイルに出力できるようになりました。

多くのAWSサービスではCloudWatch Logsにそのログが自動的に集約されます。 CloudWatch Logs、CloudWatch メトリクス、CloudWatch アラームを組み合わせることで、ログファイルの監視やアラート通知も可能です。 それでは、EC2上のファイルをCloudWatch Logsに送信するにはどうすればよいでしょうか?

次のセクションに進む前に、不要なコンテナを停止・削除しておきます。

# コンテナを停止
$ docker stop my-app

# コンテナを削除
$ docker rm my-app

CloudWatch エージェントを使用したログの送信・監視

EC2上のファイルをCloudWatch Logsに送信する答えは「CloudWatch エージェント」を使用することです。

CloudWatch エージェントとは

CloudWatch エージェントは、EC2インスタンス上で動作し、システムレベルの情報をCloudWatchに送信するためのツールです。 CPU使用率、メモリ使用率、ディスク使用率などのメトリクスを収集することができます。 今回はログファイルをCloudWatch Logsに送信するために使用します。

公式ドキュメント

CloudWatch エージェントの前提条件

CloudWatch エージェントを使用してログを送信するには、以下の前提条件を満たす必要があります。

  • EC2インスタンスに適切なIAMポリシー(CloudWatchAgentServerPolicy)がアタッチされていること
    • EC2コンソール画面で対象インスタンスを選択
    • 「アクション」→「セキュリティ」→「IAMロールを変更」を選択
    • 既存のIAMロールにCloudWatchAgentServerPolicyマネージドポリシーをアタッチ
    • または、新しいIAMロールを作成してCloudWatchAgentServerPolicyをアタッチし、インスタンスに関連付け
  • CloudWatch エージェントがインストールされていること

CloudWatch エージェントのインストール

Amazon Linux 2023ではデフォルトでインストールされていませんでした。 CloudWatch エージェントをインストールするには、以下の手順を実行します。

$ sudo yum install -y amazon-cloudwatch-agent

AWS公式ドキュメント:パッケージマネージャーを使用して Amazon Linux にインストールする

CloudWatch エージェントの設定ファイルを作成する

先ほど紹介したように、CloudWatch エージェントでは様々な情報をCloudWatchに送信できます。 そのために、何の情報を送信するのかを定義した設定ファイルが必要です。

ウィザードを使用することで簡単に設定ファイルを作成できますが、今回は手動で設定ファイルを作成します。

【ポイント!】 以下のコマンドでウィザードを起動できます。

$ sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-config-wizard
================================================================
= Welcome to the Amazon CloudWatch Agent Configuration Manager =
=                                                              =
= CloudWatch Agent allows you to collect metrics and logs from =
= your host and send them to CloudWatch. Additional CloudWatch =
= charges may apply.                                           =
================================================================
On which OS are you planning to use the agent?
1. linux
2. windows
3. darwin
default choice: [1]:

(以下、質問に回答)

AWS公式ドキュメント:CloudWatch エージェントの設定ファイルを作成する

今回はログファイルのみをCloudWatch Logsに送信する設定を行うため、以下の手順で設定ファイルを作成します。

【重要ポイント!】 ファイル名は amazon-cloudwatch-agent.json である必要があり、 配置先は /opt/aws/amazon-cloudwatch-agent/etc/ である必要があります。

# 設定ファイルを作成
$ sudo touch /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json

# 設定内容を編集
$ sudo vi /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json

設定ファイルには、以下の内容を貼り付けます。

{
  "logs": {
    "logs_collected": {
      "files": {
        "collect_list": [
          {
            "file_path": "/var/log/docker/my-app/app.log",
            "log_group_name": "/aws/ec2/docker/applications/my-app",
            "log_stream_name": "{instance_id}-{hostname}",
            "timezone": "Local"
          }
        ]
      }
    }
  }
}

【ポイント!】 パラメータストアやSecrets Managerに保存しておき、CloudWatch エージェントの起動時に参照することも可能です。 一元管理が可能になり、Auto Scaling等でスケーリングさせた際も同じ設定で起動できるため、運用の効率化が図れます。

【注意】 設定ファイルを変更した場合は、CloudWatch エージェントの再起動が必要です:

$ sudo systemctl restart amazon-cloudwatch-agent

CloudWatch エージェントの起動と確認

設定ファイルを作成後、以下の手順でエージェントを起動します:

# エージェントを起動
$ sudo systemctl start amazon-cloudwatch-agent

# ステータス確認
$ sudo systemctl status amazon-cloudwatch-agent

# 自動起動を有効化
$ sudo systemctl enable amazon-cloudwatch-agent

# ログで動作確認
$ sudo journalctl -u amazon-cloudwatch-agent -f

以上でCloudWatch エージェントの設定と起動が完了しました。 続いて、実際にログがCloudWatch Logsに送信されているか確認してみましょう。

CloudWatch Logsでの確認

まず、アプリケーションを起動してログを出力させます。

# コンテナをバックグラウンドで起動
$ docker run -d --name my-app my-app:latest

# ログをファイルに出力
$ docker logs -f my-app > /var/log/docker/my-app/app.log 2>&1 &

数分待ってから、CloudWatch コンソールでログを確認します。

CloudWatch コンソールに移動し、左側のメニューから「ロググループ」を選択します。 先ほどの設定ファイルで指定したロググループ名 /aws/ec2/docker/applications/my-app を検索します。

ロググループ

ログストリーム名として{instance_id}-{hostname} と設定しているため、EC2インスタンスのIDとホスト名が含まれたログストリームが表示されています。

ログストリーム

ログストリームをクリックすると、EC2インスタンス上のアプリケーションが出力したログが表示されています。

ログイベント

以上の設定で、EC2インスタンス上のDockerコンテナから出力されたログがCloudWatch Logsに送信され、確認できることがわかりました。 10秒ごとにログが出力され、60秒ごと(6回ごと)にエラーが出力されていることも確認できました。

アプリケーションのエラーを確認する

EC2上のアプリケーションが出力したログをCloudWatch Logsに送信できるようになりました。 本ブログのテーマである「監視」を実現するためには、アプリケーションのエラーを検出する必要があります。

それでは、CloudWatch Logs Insightsでエラーを検索してみます。

CloudWatch コンソールの左側のメニューから「ログのインサイト」を選択します。

「選択基準」から先ほどのロググループ /aws/ec2/docker/applications/my-app を選択します。 クエリエディタに以下のクエリを貼り付け、実行します。

fields @timestamp, @message, @logStream, @log
| filter @message like /Error/
| sort @timestamp desc
| limit 10

ログのインサイト画面

「クエリの実行」ボタンをクリックすると、クエリ結果が表示されます。

【ポイント!】 特定の期間のログを取得したい場合は右上で期間を設定することができます。 クエリエディタ下部のQuery generator(英語のみ)やコンソール右上のAmazon Q(日本語も可)を利用することで対話的にクエリ文を生成できます。

クエリ結果

CloudWatchで監視設定を行う

ログの監視を行うためには、

  • 検出文字列のメトリクスフィルターを作成し、CloudWatch メトリクスに変換
  • CloudWatch アラームを作成し、メトリクスに基づいてアラートを発行

の2つのステップが必要です。

このステップでは、AWS マネジメントコンソール上でシェルコマンドが実行できるAWS CloudShellから、 AWS CLIコマンドを利用してメトリクスフィルターとアラームを作成します。

メトリクスフィルターの作成

マネジメントコンソール画面左下に「CloudShell」ボタンをクリックし、CloudShellを起動します。 CloudShellを開き、以下のコマンドを実行してメトリクスフィルターを作成します。

$ aws logs put-metric-filter \
 --log-group-name /aws/ec2/docker/applications/my-app \
 --filter-name ErrorFilter \
 --filter-pattern "Error" \
 --metric-transformations metricName=ErrorCount, metricNamespace=MyApp, metricValue=1
オプション 意味 / 用途
--log-group-name 対象となるCloudWatch Logsのロググループ名
--filter-name 作成するメトリクスフィルターの名前
--filter-pattern ログ本文とマッチさせるパターン
--metric-transformations マッチ時に発行するメトリクス設定複合オプション
metricName= 生成するメトリクス名
metricNamespace= メトリクス名前空間
metricValue= 1行マッチごとに送る値

このコマンドは、ロググループ /aws/ec2/docker/applications/my-app に対して、"Error" という文字列を含むログエントリを検出するメトリクスフィルター ErrorFilter を作成します。

Errorという文字列が含まれるログエントリが検出されると、メトリクス MyApp/ErrorCount1増加させます。

先ほど確認したロググループの「メトリクスフィルター」タブを確認すると、作成したフィルターが表示されています。

メトリクスフィルター画面

そして、このフィルターに基づいてCloudWatch メトリクス MyApp/ErrorCount が作成されます。

左ペインの「メトリクス」から「すべてのメトリクス」を選択し、MyApp 名前空間 → ディメンションなしのメトリクスをたどり、ErrorCountメトリクスをチェックします。

メトリクス画面

「グラフ化したメトリクス」にて統計期間を調整し、1分毎のカウント数を確認すると、想定通り1分間に1つのエラーが発生していることがわかります。

SNSトピックの作成

今回はアラーム通知をSNSを経由したメールで受け取ります。 SNSトピックを作成し、事前にメールアドレスをサブスクライブしておきます。

CloudShellを使用して、以下のコマンドを実行し、SNSトピックを作成します。

# SNSトピックの作成
$ aws sns create-topic --name MyAppAlerts

# 出力されたTopicArnをメモしておきます
# 出力例:
# {
#     "TopicArn": "arn:aws:sns:ap-northeast-1:<12桁のAWSアカウントID>:MyAppAlerts"
# }

# メールアドレスをサブスクライブ
# 注意:<12桁のAWSアカウントID>の部分は、上記で出力されたTopicArnから取得するか、
#       AWSコンソール右上のアカウント番号(12桁の数字)に置き換えてください
#       <your-email@example.com>は実際のメールアドレスに置き換えてください
$ aws sns subscribe \
  --topic-arn arn:aws:sns:ap-northeast-1:<12桁のAWSアカウントID>:MyAppAlerts \
  --protocol email \
  --notification-endpoint <your-email@example.com>

指定したメールアドレスに確認メールが届くので、メール内の「Confirm subscription」リンクをクリックしてサブスクリプションを完了させます。

アラームの作成

メトリクスフィルターと同様に、CloudShellを使用して、以下のコマンドを実行し、アラームを作成します。

# 注意:--alarm-actionsの<12桁のAWSアカウントID>を実際のアカウントIDに置き換えてください
$ aws cloudwatch put-metric-alarm \
 --alarm-name MyAppErrorAlarm \
 --metric-name ErrorCount \
 --namespace MyApp \
 --statistic Sum \
 --period 60 \
 --threshold 1 \
 --comparison-operator GreaterThanThreshold \
 --evaluation-periods 1 \
 --alarm-actions arn:aws:sns:ap-northeast-1:<12桁のAWSアカウントID>:MyAppAlerts \
 --treat-missing-data notBreaching
オプション 意味 / 用途
--alarm-name アラームの一意な名前
--metric-name 監視対象メトリクス名
--namespace メトリクス名前空間
--statistic 集計方法
--period 1データポイントの評価期間(秒)
--threshold アラームのしきい値
--comparison-operator 比較演算子
--evaluation-periods しきい値を超えたと見なす連続した評価期間の数
--alarm-actions アラーム状態になったときに実行するアクションのARN
今回はSNSトピックを指定する例
--treat-missing-data データが欠落している場合の扱い方

このCLIコマンドは、メトリクス ErrorCount に基づいてアラーム MyAppErrorAlarm を作成します。 Errorカウントを表すメトリクスが1分間に1回以上発生した場合にアラーム状態になります。

アラームが作成されたら、CloudWatch コンソールの左ペインから「アラーム」→「アラーム」を選択し、作成したアラーム MyAppErrorAlarm が表示されていることを確認します。

しばらく待っていると、アラームが「アラーム状態」に遷移することが確認できます。

アラーム詳細画面

SNSトピックにサブスクライブしているメールアドレスに通知が届いていることも確認できます。

件名:ALARM: "MyAppErrorAlarm" in Asia Pacific (Tokyo)

以上で、CloudWatch Logsのログからエラーを検出して、アラームを発行する監視設定が完了しました。

まとめ

本ブログでは、EC2上のDockerコンテナからCloudWatch Logsへのログ出力と、監視のためのエラー検出手法について説明しました。

「EC2上のログファイル → CloudWatch Logs → メトリクス → アラーム」という最短ラインを押さえることで、監視の土台が一気に固まります。

今回は単純にログファイルに出力された文字列をCloudWatch Logsに送信し、単純な文字列マッチでエラーを検出しましたが、CloudWatch エージェントの設定ファイルやメトリクスフィルターのパターンを工夫することで、より高度な監視が可能になります。

まずはこの最小構成を構築し、そこから改善・自動化・メトリクス拡張で"運用して楽な仕組み"に育てていきましょう!

圡井一磨(執筆記事の一覧)

23年度新卒入社しました。最近は自炊にはまっています。アパートのキッチンが狭くて困ってます。