はじめに
CloudWatch Logs へ保存したログを、サブスクリプションフィルタを使って特定の文字列を検知し、その内容を SNS を使ってEメール通知するということを行いました。
また、今回の設定は全て aws-cli を利用して行っています。(確認として管理コンソールの画像も載せてます)
構成
まず、クライアントから CloudWatch Logs へログを送ります。
すると サブスクリプションフィルタ の検知をトリガーに Lambda が実行されます。
実行された Lambda が SNS を通してクライアントへ Eメール を送信します。
設定
今回は aws-cli を利用して作成します。設定は構成図の番号の流れで行っていきます。
① SNSの設定
検知した際の通知先設定を行います。
まず、トピックの作成を行います。
$ aws sns create-topic --name subscription-topic
{
"TopicArn": "arn:aws:sns:ap-northeast-1:123456789012:subscription-topic"
}
作成したら次は通知先となるサブスクリプションの登録です。
$ aws sns subscribe --topic-arn arn:aws:sns:ap-northeast-1:123456789012:subscription-topic --protocol email --notification-endpoint test@hogehoge.jp
{
"SubscriptionArn": "pending confirmation"
}
登録先がEmailの場合、通知先となるメールアドレスに以下のような確認メールが届きます。
このままだとステータスが 「PendingConfirmation」 となっていて送れる状態になっていません。
$ aws sns list-subscriptions-by-topic --topic-arn arn:aws:sns:ap-northeast-1:123456789012:subscription-topic
{
"Subscriptions": [
{
"SubscriptionArn": "PendingConfirmation",
"Owner": "123456789012",
"Protocol": "email",
"Endpoint": "test@hogehoge.jp",
"TopicArn": "arn:aws:sns:ap-northeast-1:123456789012:subscription-topic"
}
]
}
届いたメールの 「Confirm subscription」 のリンクをクリックして承認します。
すると、先ほど 「PendingConfirmation」 となっていた SubscriptionArn がサブスクリプションのARNに置き換わっているのが確認できます。
$ aws sns list-subscriptions-by-topic --topic-arn arn:aws:sns:ap-northeast-1:123456789012:subscription-topic
{
"Subscriptions": [
{
"SubscriptionArn": "arn:aws:sns:ap-northeast-1:123456789012:subscription-topic:62a143c8-2b3f-4a43-9fc2-7486f7eaf862",
"Owner": "123456789012",
"Protocol": "email",
"Endpoint": "test@hogehoge.jp",
"TopicArn": "arn:aws:sns:ap-northeast-1:123456789012:subscription-topic"
}
]
}
これで送信可能な状態となりました。
② IAM の設定
ここでは Lambda が SNS を利用するためのIAMロールを作成します。
まず、Lambdaがログを出力するためのポリシーをjsonファイルに記載します。
$ vim example-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
}
]
}
定義したjsonファイルを利用してポリシー作成を行います。
$ aws iam create-policy --policy-name subscription-policy --policy-document file://example-policy.json
{
"Policy": {
"PolicyName": "subscription-policy",
"PolicyId": "ANPA2ZM2YJELWMNIB6FR5",
"Arn": "arn:aws:iam::123456789012:policy/subscription-policy",
"Path": "/",
"DefaultVersionId": "v1",
"AttachmentCount": 0,
"PermissionsBoundaryUsageCount": 0,
"IsAttachable": true,
"CreateDate": "2021-01-27T13:04:30+00:00",
"UpdateDate": "2021-01-27T13:04:30+00:00"
}
}
次は、作成するロールが Lambda で利用できるように信頼ポリシーを作成します。
$ vim example-role-trust-policy.json
jsonファイルに以下の定義をします。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
それではロールの作成を行います。
$ aws iam create-role --role-name subscription-role --assume-role-policy-document file://example-role-trust-policy.json
{
"Role": {
"Path": "/",
"RoleName": "subscription-role",
"RoleId": "AROA2ZM2YJEL2EULM52BI",
"Arn": "arn:aws:iam::123456789012:role/subscription-role",
"CreateDate": "2021-01-27T13:05:27+00:00",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
}
}
以下のようにロールが作成されます。
次に先ほど作成したポリシーをロールヘアタッチします。
$ aws iam attach-role-policy --role-name subscription-role --policy-arn "arn:aws:iam::123456789012:policy/subscription-policy"
次にSNSへのアクセス権限を付与します。今回は既存のAWS管理ポリシーを利用します。
% aws iam attach-role-policy --role-name subscription-role --policy-arn "arn:aws:iam::aws:policy/AmazonSNSFullAccess"
以下の2つのポリシーがアタッチされていることが確認できます。
③ Lambda 関数の作成
サブスクリプションフィルタで利用するLambdaの作成を行います。
$ vim subscription.py
以下のコードを用意します。
import base64
import json
import zlib
import datetime
import os
import boto3
from botocore.exceptions import ClientError
print('Loading function')
def lambda_handler(event, context):
data = zlib.decompress(base64.b64decode(event['awslogs']['data']), 16+zlib.MAX_WBITS)
data_json = json.loads(data)
log_json = json.loads(json.dumps(data_json["logEvents"][0], ensure_ascii=False))
print(log_json)
try:
sns = boto3.client('sns')
#SNS Publish
publishResponse = sns.publish(
TopicArn = os.environ['SNS_TOPIC_ARN'],
Message = log_json['message'],
Subject = os.environ['ALARM_SUBJECT']
)
except Exception as e:
print(e)
zipファイル化します。
$ zip subscription.zip subscription.py
それではLambda関数を作成します。
$ aws lambda create-function --function-name subtest --role arn:aws:iam::123456789012:role/subscription-role --runtime python3.7 --handler subscription.lambda_handler --zip-file fileb://subscription.zip
{
"FunctionName": "subtest",
"FunctionArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:subtest",
"Runtime": "python3.7",
"Role": "arn:aws:iam::123456789012:role/subscription-role",
"Handler": "subscription.lambda_handler",
"CodeSize": 581,
"Description": "",
"Timeout": 3,
"MemorySize": 128,
"LastModified": "2021-01-29T13:47:14.979+0000",
"CodeSha256": "tSwPi+21KmRkigZ7LupkcTlpla4eVmnIMR9FwI5TMAI=",
"Version": "$LATEST",
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "fb42511e-0e72-458b-b725-22ebbf32c375",
"State": "Active",
"LastUpdateStatus": "Successful"
}
作成したら環境変数の設定を行います。
$ aws lambda update-function-configuration --function-name subtest --environment Variables='{SNS_TOPIC_ARN="arn:aws:sns:ap-northeast-1:123456789012:subscription-topic",ALARM_SUBJECT="TEST"}'
{
"FunctionName": "subtest",
"FunctionArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:subtest",
"Runtime": "python3.7",
"Role": "arn:aws:iam::123456789012:role/subscription-role",
"Handler": "subscription.lambda_handler",
"CodeSize": 525,
"Description": "",
"Timeout": 3,
"MemorySize": 128,
"LastModified": "2021-01-31T13:52:09.511+0000",
"CodeSha256": "yHLp4MvKbGfdizOKxRKOlKKwj+zCY1FJpBQWZMizuCg=",
"Version": "$LATEST",
"Environment": {
"Variables": {
"SNS_TOPIC_ARN": "arn:aws:sns:ap-northeast-1:123456789012:subscription-topic",
"ALARM_SUBJECT": "TEST"
}
},
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "19d21114-bf2c-415c-9d6e-996acbe6895a",
"State": "Active",
"LastUpdateStatus": "Successful"
}
④ CloudWatch Logs の設定
ログの保存先となるロググループを作成します。
$ aws logs create-log-group --log-group-name test-subscriptionfilter
次にログストリームを作成します。
$ aws logs create-log-stream --log-group-name test-subscriptionfilter --log-stream-name test-stream
ログ送信を送るのに creationTime の情報が必要になるので確認します。
$ aws logs describe-log-streams --log-group-name test-subscriptionfilter
{
"logStreams": [
{
"logStreamName": "test-stream",
"creationTime": 1611494667587,
"arn": "arn:aws:logs:ap-northeast-1:123456789012:log-group:test-subscriptionfilter:log-stream:test-stream",
"storedBytes": 0
}
]
}
ログの送信テストをします。
その際、 timestamp は確認した値を入れます。
$ aws logs put-log-events --log-group-name test-subscriptionfilter --log-stream-name test-stream --log-events timestamp=1611494667587,message="This is Test"
{
"nextSequenceToken": "49614471546104629625117997422005678002374743753152879794"
}
msessage で指定したThis is Test
が保存されているのが確認できました。
これでログ保存先の用意は完了です。
2回目以降のログ送信はコマンド実行した際の nextSequenceToken の値が必要になります。
後でも確認できますが面倒でなければメモしておくとスムーズかもしれないです。
それではいよいよサブスクリプションフィルタの作成を行います。
$ aws logs put-subscription-filter --log-group-name test-subscriptionfilter --filter-name subscription-filter --filter-pattern test --destination-arn arn:aws:lambda:ap-northeast-1:123456789012:function:subtest
An error occurred (InvalidParameterException) when calling the PutSubscriptionFilter operation: Could not execute the lambda function. Make sure you have given CloudWatch Logs permission to execute your function.
エラーが発生してしまいました。。。どうやら CloudWatch Logs
が Lambda を実行できないそうです。
コンソールからサブスクリプションフィルタを作成する場合は気にする必要ないのですが、cliで設定する場合は事前にLambda側でパーミッションの追加をしておく必要があります。
$ aws lambda add-permission --function-name subtest --statement-id "lambdapermission" --principal "logs.ap-northeast-1.amazonaws.com" --action "lambda:InvokeFunction" --source-arn "arn:aws:logs:ap-northeast-1:123456789012:log-group:*:*" --source-account 123456789012
{
"Statement": "{\"Sid\":\"lambdapermission\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"logs.ap-northeast-1.amazonaws.com\"},\"Action\":\"lambda:InvokeFunction\",\"Resource\":\"arn:aws:lambda:ap-northeast-1:123456789012:function:subtest\",\"Condition\":{\"StringEquals\":{\"AWS:SourceAccount\":\"123456789012\"},\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:logs:ap-northeast-1:123456789012:log-group:*:*\"}}}"
}
ということで、改めてサブスクリプションフィルタの作成を行います。
$ aws logs put-subscription-filter --log-group-name test-subscriptionfilter --filter-name subscription-filter --filter-pattern Test --destination-arn arn:aws:lambda:ap-northeast-1:123456789012:function:subtest
無事作成できました。
テスト
それでは実際にサブスクリプションフィルタの情報がメールで通知されるか試してみます。
$ aws logs put-log-events --log-group-name test-subscriptionfilter --log-stream-name test-stream --log-events timestamp=1611453737087,message="This is Test" --sequence-token 49614471546104629625117997422005678002374743753152879794
{
"nextSequenceToken": "49614471546104629625118392286855075160199013121852005554"
}
もし put-log-events で以下のようなエラーが発生した場合、 --sequence-token の値をエラー内の記載されている値(ここだと49614471546104629625118452176870929254188121824069508274
)に置き換えて改めて送信してみてください。
An error occurred (InvalidSequenceTokenException) when calling the PutLogEvents operation: The given sequenceToken is invalid. The next expected sequenceToken is: 49614471546104629625118452176870929254188121824069508274
メールが届けば成功です。
先ほど Lambda のところで update-function-configuration で設定した ALARM_SUBJECT
の値が件名で表示され、本文には put-log-events で送信した message
の内容が表示されます。
おわりに
メトリクスフィルターの方が簡単に設定できますが、文字列を検知したことの通知しかできません。
ログ本文を通知内容に含めたい場合はサブスクリプションフィルターを利用することで解決できます。
参考URL
- AWS CLI Command Reference - SNS
create-topic
subscribe
list-subscriptions-by-topic - AWS CLI Command Reference - IAM
create-policy
create-role
attach-role-policy - AWS CLI Command Reference - Lambda
create-function
update-function-configuration
add-permission
・AWS CLI Command Reference - CloudWatchLogs
create-log-group
create-log-stream
put-log-events - AWS公式
AWS CLI での Amazon SNS の使用
AWS CLI を使用して IAM ロールを引き受ける方法を教えてください。 - Developers.IO
CloudWatch Logs を文字列検知してログ内容をメールを送信してみた サブスクリプションフィルター版