AWS

サブスクリプションフィルターでFargateコンテナログのエラーを抽出してメールで通知する

スポンサーリンク
サブスクリプションフィルターでFargateコンテナログのエラーを抽出してメールで通知する AWS
スポンサーリンク
スポンサーリンク

CloudWatch Logsサブスクリプションフィルターを使って、Fargateコンテナのログからエラーを抽出して、メールで通知する

CloudWatch Logsの機能の1つにサブスクリプションフィルターがあります。
サブスクリプションフィルターを使用することによって、CloudWatch Logsに配信されたログの中から、特定の文字列を含むログを抽出し、アクションを実行することができます。

今回はFargateタイプのECSコンテナのログをCloudWatch Logsに配信し、その中からエラーログを抽出して、その内容をメールで通知することを目指します。

構築する環境

Diagram of Subscription filter to extract errors in Fargate container logs and notify by email.

プライベートサブネットに、FargateタイプのECSを作成します。

CloudWatch Logsにログを配信するために、コンテナ用サブネットに、Logs用VPCエンドポイントを作成します。

CloudWatch Logsでサブスクリプションフィルターを有効化します。
「error」という文字列を検出するフィルターを作成します。
サブスクリプションフィルターに関連づけるリソースとして、Lambda関数を作成します。

Lambda関数の働きですが、SNSトピックにメッセージをパブリッシュすることです。
サブスクリプションフィルターで抽出したログの内容をパブリッシュします。

DockerHubからNginxの公式イメージを取得するために、パブリックサブネットにNATゲートウェイを配置します。

EC2インスタンスを作成します。
コンテナにアクセスするためのクライアントして使用します。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

awstut-fa/066 at main · awstut-an-r/awstut-fa
Contribute to awstut-an-r/awstut-fa development by creating an account on GitHub.

テンプレートファイルのポイント解説

ECS(Fargate)に関する基本的な事項は、以下のページをご確認ください。

プライベートサブネットにFargateを配置する方法については、以下のページをご確認ください。

SNSトピック

Resources: Topic: Type: AWS::SNS::Topic Properties: Subscription: - Endpoint: !Ref MailAddress Protocol: email TopicName: !Ref Prefix
Code language: YAML (yaml)

Subscriptionプロパティがポイントです。
サブスクライバーとしてメールアドレスを指定する場合は、Protocolプロパティに「email」を、Endpointプロパティにメールアドレスを指定します。

メールアドレスをSNSサブスクライバーに指定する方法については、以下のページをご確認ください。

CloudWatch Logsサブスクリプションフィルター

サブスクリプションフィルターで検出した内容をSNSに送るためには、4種類のリソースを作成する必要があります。

  • ロググループ
  • サブスクリプションフィルター
  • パーミッション
  • Lambda関数

ロググループ

Resources: LogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub "${Prefix}-LogGroup"
Code language: YAML (yaml)

特別な設定は不要です。
シンプルにロググループを作成します。

サブスクリプションフィルター

Resources: SubscriptionFilter: Type: AWS::Logs::SubscriptionFilter Properties: DestinationArn: !GetAtt Function.Arn FilterPattern: error LogGroupName: !Ref LogGroup
Code language: YAML (yaml)

DestinationArnプロパティでログを検出した際に連携するAWSリソースを指定します。
今回は後述のLambda関数を指定します。

FIlterPatternプロパティで抽出するログを指定します。
今回は「error」を検出するように設定します。

LogGroupNameプロパティでサブスクリプションフィルタを有効化するロググループを指定します。
先述のロググループを指定します。

パーミッション

Resources: SubscriptionFilterPermission: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !Ref Function Principal: !Sub "logs.${AWS::Region}.amazonaws.com" SourceArn: !GetAtt LogGroup.Arn
Code language: YAML (yaml)

サブスクリプションフィルタが条件を満たすログを検出した際に、ロググループが後述のLambda関数を実行するための権限を付与します。

Lambda関数

Resources: Function: Type: AWS::Lambda::Function Properties: Architectures: - !Ref Architecture Environment: Variables: REGION: !Ref AWS::Region TOPIC: !Ref TopicArn Code: ZipFile: | import base64 import boto3 import gzip import json import os topic = os.environ['TOPIC'] region = os.environ['REGION'] client = boto3.client('sns', region_name=region) subject = 'Error Detection.' def lambda_handler(event, context): subscription_data = event['awslogs']['data'] subscription_data_decoded = base64.b64decode(subscription_data) subscription_data_decompressed = gzip.decompress(subscription_data_decoded) subscription_data_loaded = json.loads(subscription_data_decompressed) response = client.publish( TopicArn=topic, Subject=subject, Message=subscription_data_loaded['logEvents'][0]['message'] ) FunctionName: !Sub "${Prefix}-function" Handler: !Ref Handler Runtime: !Ref Runtime Role: !GetAtt FunctionRole.Arn
Code language: YAML (yaml)

Environmentプロパティで関数に渡すことができる環境変数を定義できます。
先述のSNSトピックのARNや、トピックを作成したリージョン情報を渡します。

Lambda関数で実行するコードをインライン表記で定義します。
詳細につきましては、以下のページをご確認ください。

コードは以下のページを参考に作成しました。

CloudWatch Logsの特定文字を検知してログ内容を通知するLambda Function | DevelopersIO
CloudWatch Logsにサブスクリプションフィルター設定して、特定文字を含むログデータをLambda FunctionつかってChatworkに通知しました。

ちなみに関数用のIAMロールは以下の通りです。

Resources: FunctionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: sts:AssumeRole Principal: Service: - lambda.amazonaws.com ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: SNSPublishPolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - sns:Publish Resource: - !Ref TopicArn
Code language: YAML (yaml)

まずAWS管理ポリシーAWSLambdaBasicExecutionRoleを指定して、関数実行のために必要な権限を付与します。
加えて、SNSトピックにメッセージをパブリッシュする権限も付与します。

(参考)タスク定義

Resources: TaskDefinition: Type: AWS::ECS::TaskDefinition Properties: ContainerDefinitions: - Name: !Sub "${Prefix}-container" Image: nginx:latest LogConfiguration: LogDriver: awslogs Options: awslogs-group: !Ref LogGroup awslogs-region: !Ref AWS::Region awslogs-stream-prefix: !Sub "${Prefix}-container" Cpu: !Ref TaskCpu ExecutionRoleArn: !Ref FargateTaskExecutionRole Memory: !Ref TaskMemory NetworkMode: awsvpc RequiresCompatibilities: - FARGATE TaskRoleArn: !Ref TaskRole
Code language: YAML (yaml)

タスク定義のLogConfigurationプロパティがポイントです。
配信するロググループ等を設定します。

ECSのログをCloudWatch Logsに配信する方法については、以下のページもご確認ください。

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • SNSトピック:fa-066
  • ECSクラスター:fa-066-cluster
  • ECSサービス:fa-066-service
  • CloudWatch Logsロググループ:fa-066-LogGroup
  • EC2インスタンス:i-0bac8729dfcf0dc9f

メールアドレスの認証

SNSトピックのサブスクライバーとしてメールアドレスを指定した場合、そのメールアドレスを認証する必要があります。

詳細につきましては、以下のページをご確認ください。

リソース確認

AWS Management Consoleから各リソースを確認します。

SNSトピックを確認します。

Detail of SNS Topic.

正常にSNSトピックが作成されていることがわかります。

加えて、サブスクライバーとして登録したメールアドレスが登録されていることもわかります。
またこのメールアドレスのStatusの値を見ると「Confirmed」とあり、認証が完了していることもわかります。

続いてECS(Fargate)を確認します。

Detail of ECS(Fargate) 1.
Detail of ECS(Fargate) 2.

正常にECSクラスター・サービス・タスクが作成されています。
タスクに割り当てられているプライベートアドレスが「10.0.3.61」ということもわかります。

Lambda関数を確認します。

Detail of Lambda Function.

正常に作成されています。
サブスクリプションフィルターが「error」を検出すると、この関数が実行されて、SNSトピックにログの中身が連携されます。

CloudWatch Logsロググループを確認します。

Detail of CloudWatch Logs Log Group 1.
Detail of CloudWatch Logs Log Group 2.

ロググループにストリームとサブスクリプションフィルターが作成されていることがわかります。

動作確認

準備が整いましたので、EC2インスタンスにアクセスします。

% aws ssm start-session --target i-0bac8729dfcf0dc9f Starting session with SessionId: root-079cb8e3e83a3c9e9 sh-4.2$
Code language: JavaScript (javascript)

SSM Session Managerの詳細につきましては、以下のページをご確認ください。

curlコマンドを使って、タスク内のコンテナにアクセスします。

sh-4.2$ curl http://10.0.3.61 <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> html { color-scheme: light dark; } body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html>
Code language: Bash (bash)

正常にアクセスすることができました。

続いて存在しないページにアクセスし、わざとエラーを発生させます。

sh-4.2$ curl http://10.0.3.61/hogehoge.html <html> <head><title>404 Not Found</title></head> <body> <center><h1>404 Not Found</h1></center> <hr><center>nginx/1.23.1</center> </body> </html>
Code language: Bash (bash)

エラーが発生しました。

すぐさま登録したアドレスにメールが届きました。

Authentication of email addresses as notification recipients for SNS.

メールの本文は、先ほど発生したエラー内容となっております。
このようにサブスクリプションフィルターを使用することによって、特定の文字列を検出し、SNSと連携してメール通知することができます。

ちなみに以下がCloudWatch Logsロググループのストリーム内容です。

Error log in CloudWatch Logs.

確かにメールの本文の元となったログが確認できます。

まとめ

ECSコンテナのログをCloudWatch Logsに配信し、その中からエラーログを抽出して、その内容をメールで通知する方法を確認しました。

タイトルとURLをコピーしました