Zenn
🐳

Cloudwatch Logs Insights で Fargate のアクセスログを分析する

2025/03/22に公開

本記事では、AWS Fargate 上で動作する Nginx のアクセスログを CloudWatch Logs に保存し、CloudWatch Logs Insights を使ってアクセスログをクエリする方法 について紹介します🐤

▶️ 前提

本記事で扱う環境は、ALB + ECS Fargate + Nginx の構成を想定しています。リクエストは ALB を経由してコンテナ化された Nginx に送られ、ログは CloudWatch Logs に保存されます。

🐳 CloudWatch Logs へのログ出力設定

Fargate のタスク定義では、logConfiguration 設定を使用して、ログを CloudWatch Logs に出力しています。以下はTerraform での実装コードです。

logConfiguration = {
  logDriver = "awslogs",
  options = {
    awslogs-group         = aws_cloudwatch_log_group.ecs_loggroup.name,
    awslogs-region        = "ap-northeast-1",
    awslogs-stream-prefix = "web-ecs-nginx"
  }
}

設定のポイントは以下のとおりです。

設定キー 説明
logDriver = "awslogs" AWS CloudWatch Logs にログを送信
awslogs-group CloudWatch Logs のロググループ名
awslogs-region ログを送信する AWS リージョン
awslogs-stream-prefix ログストリームのプレフィックス(例: web-ecs-nginx

この設定により、Fargate の Nginx コンテナからのログは /awslogs-group/awslogs-stream-prefix/... の形式で CloudWatch Logs に保存されます。

🦎 ログのフォーマット

Nginx の設定ファイルで、以下のようにカスタムログ形式(log_format)を定義しています。

log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$http_x_forwarded_for"';

Nginx のログフォーマット設定に基づいて、CloudWatch Logs に送信されたログの一例は次のようになります。

タイムスタンプ            | メッセージ
-----------------------------------------------------------------------------
2025-03-21T10:54:01.441+09:00 | 1.1.1.1 - user123 [21/Mar/2025:01:54:01 +0000] "GET /images/logo.png HTTP/1.1" 200 1234 "https://example.com/home" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36" "2.2.2.2, 3.3.3.3"

ログの各項目の説明

項目 説明
2025-03-21T10:54:01.441+09:00 CloudWatch Logs のタイムスタンプ(タイムゾーンの指定で変動)
1.1.1.1 Nginx に送信されたリクエストの送信元 IP(ALB のプライベート IP)
user123 認証ユーザー名(未認証の場合 -
[21/Mar/2025:01:54:01 +0000] アクセス日時(Nginx のログは UTC)
"GET /images/logo.png HTTP/1.1" リクエストのメソッド、パス、プロトコル
200 HTTP ステータスコード(成功)
1234 送信されたレスポンスのバイト数
"https://example.com/home" リファラー(どのページからアクセスされたか)
"Mozilla/5.0 (Windows NT 10.0; Win64; x64)..." ユーザーエージェント(使用されたブラウザ情報)
"2.2.2.2, 3.3.3.3" X-Forwarded-For(実際のアクセス元)

🔍 アクセスログとエラーログが混ざる問題とその対策

Fargate の logConfiguration を使用すると、Nginx のアクセスログとエラーログが CloudWatch Logs に一緒に記録されます。

これは、Nginx の access_logerror_log標準出力(stdout)と標準エラー出力(stderr) を通じて同じログストリームに送信されるためです。
そのため、CloudWatch Logs Insights でクエリしたい場合は、フィルタリングを行いアクセスログのみを抽出 する必要があります。

🕸️ アクセスログのみを抽出するフィルタ

以下の CloudWatch Logs Insights のクエリ を使用すると、Nginx のアクセスログのみを取得 できます。

fields @timestamp, @message
| filter @message like /\d+\.\d+\.\d+\.\d+ - .* \[\d+\/\w+\/\d+:\d+:\d+:\d+ [\+\-]\d+\] ".*" \d+ \d+ ".*" ".*" ".*"/
| parse @message /(?<remote_addr>[^ ]*) - (?<remote_user>[^ ]*) \[(?<time_local>[^\]]*)\] "(?<request>[^"]*)" (?<status>[^ ]*) (?<body_bytes_sent>[^ ]*) "(?<http_referer>[^"]*)" "(?<http_user_agent>[^"]*)" "(?<http_x_forwarded_for>[^"]*)"/
| parse http_x_forwarded_for /(?<client_ip>[^,]*),*/
| display @timestamp, request, status, http_user_agent, client_ip
| limit 10

📌 クエリのポイント

ステップ 処理内容
filter @message like ... アクセスログの形式に一致する行のみを抽出(エラーログを除外)
parse @message ... ログのフォーマットに従って各フィールドを抽出
parse http_x_forwarded_for ... X-Forwarded-For の最初の IP を client_ip として取得(実際のクライアント IP)
display ... 必要な項目のみを表示(タイムスタンプ、リクエスト、ステータス、UA、クライアント IP)
limit 10 最新 10 件のみ取得

このフィルタを適用することで、Nginx のエラーログを除外し、アクセスログのみを対象とした分析 が可能になります🍀

🌝 調査用クエリ 一覧

アクセス数の調査、エラーログの抽出など、用途に応じたクエリを掲載します。

  1. アクセス数の検出
  • NginxAccessLog_Top10IPs (アクセス数の多いIPトップ10を表示するクエリ)
fields @timestamp, @message
| filter @message like /\d+\.\d+\.\d+\.\d+ - .* \[\d+\/\w+\/\d+:\d+:\d+:\d+ [\+\-]\d+\] ".*" \d+ \d+ ".*" ".*" ".*"/
| parse @message /(?<remote_addr>[^ ]*) - (?<remote_user>[^ ]*) \[(?<time_local>[^\]]*)\] "(?<request>[^"]*)" (?<status>[^ ]*) (?<body_bytes_sent>[^ ]*) "(?<http_referer>[^"]*)" "(?<http_user_agent>[^"]*)" "(?<http_x_forwarded_for>[^"]*)"/
| parse http_x_forwarded_for /(?<client_ip>[^,]*),*/
| stats count(*) as count by client_ip
| sort count desc
| limit 10
  • NginxAccessLog_Top10URLs (アクセス数の多いURLトップ10を表示するクエリ)
fields @timestamp, @message
| filter @message like /\d+\.\d+\.\d+\.\d+ - .* \[\d+\/\w+\/\d+:\d+:\d+:\d+ [\+\-]\d+\] ".*" \d+ \d+ ".*" ".*" ".*"/
| parse @message /(?<remote_addr>[^ ]*) - (?<remote_user>[^ ]*) \[(?<time_local>[^\]]*)\] "(?<request>[^"]*)" (?<status>[^ ]*) (?<body_bytes_sent>[^ ]*) "(?<http_referer>[^"]*)" "(?<http_user_agent>[^"]*)" "(?<http_x_forwarded_for>[^"]*)"/
| parse http_x_forwarded_for /(?<client_ip>[^,]*),*/
| stats count(*) as count by request
| sort count desc
| limit 10
  • NginxAccessLog_AccessSitesByIP (特定のIPアドレスのアクセス先調査)
fields @timestamp, @message
| filter @message like /\d+\.\d+\.\d+\.\d+ - .* \[\d+\/\w+\/\d+:\d+:\d+:\d+ [\+\-]\d+\] ".*" \d+ \d+ ".*" ".*" ".*"/
| parse @message /(?<remote_addr>[^ ]*) - (?<remote_user>[^ ]*) \[(?<time_local>[^\]]*)\] "(?<request>[^"]*)" (?<status>[^ ]*) (?<body_bytes_sent>[^ ]*) "(?<http_referer>[^"]*)" "(?<http_user_agent>[^"]*)" "(?<http_x_forwarded_for>[^"]*)"/
| parse http_x_forwarded_for /(?<client_ip>[^,]*),*/
| filter client_ip = '特定のIPアドレス'
| display @timestamp, request, http_referer
| limit 10
  1. エラーの分析
    404エラーなどのエラー発生状況を分析します。これにより、不正なアクセス試行や脆弱性スキャンなどの可能性を調査できます。
fields @timestamp, @message
| filter @message like /\d+\.\d+\.\d+\.\d+ - .* \[\d+\/\w+\/\d+:\d+:\d+:\d+ [\+\-]\d+\] ".*" \d+ \d+ ".*" ".*" ".*"/
| parse @message /(?<remote_addr>[^ ]*) - (?<remote_user>[^ ]*) \[(?<time_local>[^\]]*)\] "(?<request>[^"]*)" (?<status>[^ ]*) (?<body_bytes_sent>[^ ]*) "(?<http_referer>[^"]*)" "(?<http_user_agent>[^"]*)" "(?<http_x_forwarded_for>[^"]*)"/
| parse http_x_forwarded_for /(?<client_ip>[^,]*),*/
| filter status = '404'
| stats count(*) as count by request
| sort count desc
| limit 10

📜 参考リンク

🔗 Amazon ECS ログを CloudWatch に送信する
🔗 CloudWatch Logs Insights 言語クエリ構文
🔗 ECSにおけるログ運用の基本

Discussion

ログインするとコメントできます