1
1

エラーログ監視アラート: AWS Fargate + CloudWatch + Lambda + SNS

Last updated at Posted at 2024-07-09

はじめに

このドキュメントは、AWS Fargateを使用して実行されるWebサイトのエラーログを監視し、問題が発生した際にメールで通知するためのシステムを構築する手順を説明します。このシステムは、CloudWatch Logsを使用してログを収集し、Lambdaを使用してエラーログを検出し、SNSを使用して通知を送信します。

作成したリポジトリはこちらです。

目的

Webサイトの可用性と信頼性を確保するために、エラーログをリアルタイムで監視し、問題が発生した際に即座に対応することが重要です。このシステムは、以下の目的を達成するために設計されています。

  • リアルタイム監視: エラーログをリアルタイムで監視し、迅速な対応を可能にする。
  • 自動通知: エラーログが検出された際に、自動的に通知を送信し、担当者に即座に知らせる。
  • インフラの可視化: AWSサービスを活用して、ログ監視と通知のインフラを効率的に構築する。

インフラ構成

aws.png

  • AWS Fargate: アプリケーションコンテナを実行し、ログをCloudWatch Logsに送信します。
  • CloudWatch Logs: Fargateコンテナから送信されたログを収集・保存します。
  • Lambda: CloudWatch Logsからエラーログを検出し、SNSトピックに通知を送信します。
  • SNS (Simple Notification Service): Lambdaからの通知を受け取り、指定されたメールアドレスにエラーメールを送信します。

実装方法

以下のコードは、Express.jsを使用したシンプルなNode.jsアプリケーションです。このアプリケーションには複数のルートがあり、特定のルートにアクセスするとエラーメッセージがログに記録されます。

webserver

webserver/app/main.js
const express = require('express');
const app = express();

// 各ルートの設定
const routes = [
    { path: '/', action: 'index', message: 'ホーム画面です\n' },
    { path: '/about', action: 'about', message: 'これはAboutページです\n' },
    { path: '/faq', action: 'faq', message: 'これはFAQページです\n' },
    { path: '/portfolio', action: 'portfolio', message: 'これはPortfolioページです\n' },
    { path: '/error', action: 'error', message: 'これはerrorページです\n' },
];

routes.forEach(route => {
    app.get(route.path, (req, res) => {
        try {
            console.log(route.action);
            if (route.action === 'error') {
                throw new Error('An error page was accessed.');
            } else {
                res.send(route.message);
            }
        } catch (err) {
            console.error('Fatal error:', err.message);
            res.status(500).send('致命的なエラーが発生しました\n');
        }
    });
});

app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

iam

まず、AWSリソースに必要な権限を付与するためのIAMロールを作成します。ここでは、Fargateタスク用のecs_roleとLambda関数用のlambda_roleを設定します。

infra/modules/iam/aws_iam_role.tf
resource "aws_iam_role" "ecs_role" {
  name = "ecs_role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid : ""
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "ecs-tasks.amazonaws.com"
        }
      },
    ]
  })

  tags = {
    Name = "${var.app_name}-ecs-iam-role"
  }
}

resource "aws_iam_role" "lambda_role" {
  name = "lambda_role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Action = "sts:AssumeRole",
        Effect = "Allow",
        Principal = {
          Service = "lambda.amazonaws.com",
        },
      },
    ],
  })

  tags = {
    Name = "${var.app_name}-lambda-iam-role"
  }
}

次に、各ロールに付与するポリシーを作成します。ここでは、ECR、CloudWatch Logs、およびLambdaに必要なポリシーを定義します。

infra/modules/iam/aws_iam_policy.tf
resource "aws_iam_policy" "ecr_policy" {
  name = "${var.app_name}-ecr-policy"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "ecr:*"
        ]
        Effect   = "Allow"
        Resource = "*"
      },
    ]
  })
}

resource "aws_iam_policy" "cloudwatch_policy" {
  name = "${var.app_name}-cloudwatch-policy"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "logs:*"
        ]
        Effect   = "Allow"
        Resource = "arn:aws:logs:*:*:*"
      },
    ]
  })
}

resource "aws_iam_policy" "lambda_policy" {
  name = "lambda_policy"

  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Action = [
          "logs:CreateLogGroup",
          "logs:CreateLogStream",
          "logs:PutLogEvents",
          "sns:Publish"
        ],
        Resource = "*",
        Effect   = "Allow"
      }
    ]
  })
}

最後に、作成したIAMポリシーを各ロールにアタッチします。

infra/modules/iam/aws_iam_policy_attachment.tf
resource "aws_iam_policy_attachment" "ecr_attach" {
  name       = "${var.app_name}-ecr-attach"
  roles      = ["${aws_iam_role.ecs_role.name}"]
  policy_arn = aws_iam_policy.ecr_policy.arn
}

resource "aws_iam_policy_attachment" "cloudwatch_attach" {
  name       = "${var.app_name}-cloudwatch-attach"
  roles      = ["${aws_iam_role.ecs_role.name}"]
  policy_arn = aws_iam_policy.cloudwatch_policy.arn
}

resource "aws_iam_policy_attachment" "lambda_role_attachment" {
  name       = "${var.app_name}-lambda-attach"
  roles      = ["${aws_iam_role.lambda_role.name}"]
  policy_arn = aws_iam_policy.lambda_policy.arn
}

CloudWatch

CloudWatch Logsからエラーログを検出し、Lambda関数をトリガーするためには、ログサブスクリプションフィルタを設定する必要があります。このフィルタは、特定のロググループ内のログイベントをフィルタリングし、条件に一致するログをLambda関数に送信します。

以下のTerraformコードは、特定のロググループから"error"というパターンに一致するログイベントを検出し、Lambda関数に送信するサブスクリプションフィルタを作成します。

infra/modules/cloudwatch/aws_cloudwatch_log_subscription_filter.tf
resource "aws_cloudwatch_log_subscription_filter" "lambda_trigger" {
  name            = "ErrorLogsToLambda"
  log_group_name  = var.webserver_log_group_name
  filter_pattern  = "error"
  destination_arn = var.lambda_function_arn
}

Lambda

Lambda関数のログを保存するためのCloudWatch Log Groupを作成します。

infra/modules/lambda/aws_cloudwatch_log_group.tf
resource "aws_cloudwatch_log_group" "lambda" {
  name = "/aws/lambda/${var.lambda_function_name}"
}

Lambda関数を作成し、CloudWatch Logsからのエラーログを処理してSNS通知を送信するための設定を行います。

infra/modules/lambda/aws_lambda_function.tf
data "archive_file" "lambda" {
  type        = "zip"
  source_dir  = "./modules/lambda/src/in"
  output_path = "./modules/lambda/src/out/lambda_function_payload.zip"
}

resource "aws_lambda_function" "main" {
  filename         = "./modules/lambda/src/out/lambda_function_payload.zip"
  function_name    = var.lambda_function_name
  description      = "lambda_function"
  role             = var.lambda_iam_role
  architectures    = ["x86_64"]
  handler          = "index.lambda_handler"
  source_code_hash = data.archive_file.lambda.output_base64sha256
  timeout          = 30
  runtime          = "python3.9"
  depends_on       = [aws_cloudwatch_log_group.lambda]

  environment {
    variables = {
      SNS_TOPIC_ARN = var.sns_topic_arn
    }
  }
  tags = {
    Name = "${var.app_name}-lamdba"
  }
}

Lambda関数がSNSとCloudWatch Logsからトリガーされるように、必要な実行許可を設定します。

infra/modules/lambda/aws_lambda_permission.tf
resource "aws_lambda_permission" "allow_sns" {
  statement_id  = "AllowExecutionFromSNS"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.main.function_name
  principal     = "sns.amazonaws.com"
  source_arn    = var.sns_topic_arn
}

resource "aws_lambda_permission" "allow_cloudwatch" {
  statement_id  = "AllowExecutionFromCloudWatch"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.main.function_name
  principal     = "logs.${var.region}.amazonaws.com"
  source_arn    = "arn:aws:logs:${var.region}:${local.account_id}:log-group:${var.webserver_log_group_name}:*"
}

CloudWatch Logsからエラーログを受信し、SNSを使用して通知を送信するLambda関数のPythonコードです。

infra/modules/lambda/src/in/index.py
import boto3
import os
import json
import base64
import gzip

sns_client = boto3.client('sns')
sns_topic_arn = os.environ['SNS_TOPIC_ARN']

def lambda_handler(event, context):
    # CloudWatch LogsのデータはBase64でエンコードされています
    log_data_b64 = event['awslogs']['data']
    log_data = base64.b64decode(log_data_b64)

    log_data_decoded = gzip.decompress(log_data)
    log_data_decoded = json.loads(log_data_decoded)
    logEvents = log_data_decoded['logEvents']

    log_messages = []
    for index, event in enumerate(logEvents):
        log_messages.append(f"[error message {index + 1}]")
        log_messages.append(event['message'])

    # 文字列のリストを改行で結合します
    message = f"Error log detected:\n" + "\n".join(log_messages)
    subject = "CloudWatch Error Log Alert"

    # SNSトピックにメッセージをパブリッシュします
    sns_client.publish(
        TopicArn=sns_topic_arn,
        Message=message,
        Subject=subject
    )
    return {
        'statusCode': 200,
        'body': json.dumps('Email sent successfully!')
    }

sns

まず、エラーログ通知用のSNSトピックを作成します。このトピックにメールアドレスをサブスクライブし、エラーログが発生した際に通知を受け取るようにします。

infra/modules/sns/aws_sns_topic.tf
resource "aws_sns_topic" "sns_topic" {
  name = "email_topic"

  tags = {
    "Name" = "${var.app_name}-email_topic"
  }
}

次に、作成したSNSトピックにメールアドレスをサブスクライブします。ここでは、メールアドレスをemail.txtファイルから読み込み、SNSトピックにサブスクライブします。

infra/modules/sns/aws_sns_topic_subscription.tf
resource "aws_sns_topic_subscription" "email_subscription" {
  topic_arn = aws_sns_topic.sns_topic.arn
  protocol  = "email"
  endpoint  = trimspace(file("${path.module}/src/email.txt"))
}

結果

以下のようなメールが届きました。

email.png

まとめ

以上の手順を通じて、AWS Fargate、CloudWatch Logs、Lambda、およびSNSを使用したエラーログ監視およびメール通知システムを構築することができます。このシステムにより、Webサイトで発生したエラーをリアルタイムで検出し、指定されたメールアドレスに通知を送信することが可能になります。

これにより、システムの可用性と信頼性が向上し、問題が発生した際に迅速に対応することができます。各サービスの詳細な設定やパラメータは、AWS公式ドキュメントを参照してください。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1