CloudWatch Logsサブスクリプションフィルターを使って、Fargateコンテナのログからエラーを抽出して、メールで通知する
CloudWatch Logsの機能の1つにサブスクリプションフィルターがあります。
サブスクリプションフィルターを使用することによって、CloudWatch Logsに配信されたログの中から、特定の文字列を含むログを抽出し、アクションを実行することができます。
今回はFargateタイプのECSコンテナのログをCloudWatch Logsに配信し、その中からエラーログを抽出して、その内容をメールで通知することを目指します。
構築する環境
プライベートサブネットに、FargateタイプのECSを作成します。
CloudWatch Logsにログを配信するために、コンテナ用サブネットに、Logs用VPCエンドポイントを作成します。
CloudWatch Logsでサブスクリプションフィルターを有効化します。
「error」という文字列を検出するフィルターを作成します。
サブスクリプションフィルターに関連づけるリソースとして、Lambda関数を作成します。
Lambda関数の働きですが、SNSトピックにメッセージをパブリッシュすることです。
サブスクリプションフィルターで抽出したログの内容をパブリッシュします。
DockerHubからNginxの公式イメージを取得するために、パブリックサブネットにNATゲートウェイを配置します。
EC2インスタンスを作成します。
コンテナにアクセスするためのクライアントして使用します。
CloudFormationテンプレートファイル
上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。
https://github.com/awstut-an-r/awstut-fa/tree/main/066
テンプレートファイルのポイント解説
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関数で実行するコードをインライン表記で定義します。
詳細につきましては、以下のページをご確認ください。
コードは以下のページを参考に作成しました。
https://dev.classmethod.jp/articles/cwl-lambda-sns-publish/
ちなみに関数用の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トピックを確認します。
正常にSNSトピックが作成されていることがわかります。
加えて、サブスクライバーとして登録したメールアドレスが登録されていることもわかります。
またこのメールアドレスのStatusの値を見ると「Confirmed」とあり、認証が完了していることもわかります。
続いてECS(Fargate)を確認します。
正常にECSクラスター・サービス・タスクが作成されています。
タスクに割り当てられているプライベートアドレスが「10.0.3.61」ということもわかります。
Lambda関数を確認します。
正常に作成されています。
サブスクリプションフィルターが「error」を検出すると、この関数が実行されて、SNSトピックにログの中身が連携されます。
CloudWatch Logsロググループを確認します。
ロググループにストリームとサブスクリプションフィルターが作成されていることがわかります。
動作確認
準備が整いましたので、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)
エラーが発生しました。
すぐさま登録したアドレスにメールが届きました。
メールの本文は、先ほど発生したエラー内容となっております。
このようにサブスクリプションフィルターを使用することによって、特定の文字列を検出し、SNSと連携してメール通知することができます。
ちなみに以下がCloudWatch Logsロググループのストリーム内容です。
確かにメールの本文の元となったログが確認できます。
まとめ
ECSコンテナのログをCloudWatch Logsに配信し、その中からエラーログを抽出して、その内容をメールで通知する方法を確認しました。