概要
RDSイベントやECSイベント、最初からCloudWatch Logsに流してくれると楽なんだけどなあと思いつつ、そのままではCloudWatch Logsに流れてくれません。
そのための仕組みをCloudFormationで作成する方法を書いてみました。
やること
RDSイベントとECSイベントの全イベントをそれぞれ1つのロググループに転送する仕組みを、CloudFormationで構築します。
ポイント
わざわざログを流すだけのことをこのように記事にするのは、一度つまずいたからです。
EventBridge
まずRDSイベント・ECSイベントなどをCloudWatch Logsに流すためには、EventBridge(元CloudWatch Events)というサービスを使用する必要があります。
EventBridgeで「ルール」を作成し、それに「イベントパターン」と「ターゲット」を指定してあげることで転送が可能になります。
RDSやECSのイベントを転送するためのルールには、イベントパターンに"source": ["aws.rds"]
や"source": ["aws.ecs"]
などのようにサービスの種類をソースとして指定し、ターゲットには転送したいCloudWatch Logsのロググループ(のARN)を指定する必要があります。
転送するイベントを絞る場合は、イベントパターンに"detail-type"
であったり絞りたい種類のパターンを指定することで可能です。
このルールによって、各ソースで発生したイベントをCloudWatch Logsに流せるようになるのです。
CloudFormationで作成すると、、、
そして、上記のEventBridge(とCloudWatch Logs)をCloudFormationで構築します。
ところが、先に結論を言うと、これだとログ転送が始まりません。
リソースポリシー
なぜ転送が始まらないのかというと、CloudWatch Logsのロググループに、 リソースポリシーがないからです。
リソースポリシーとは、以下の記事に説明などを記載してあります。
そのため、転送先のCloudWatch Logsのロググループに、リソースポリシーをアタッチする必要があります。(厳密にはロググループにポリシーを直接アタッチするわけではないがイメージつきやすいのでこう書いています・・・)
コード
ここで、RDSイベント・ECSイベントをそれぞれCloudWatch Logsに転送するCloudFormationテンプレートを記載します。
※RDSイベントもECSイベントも、「RDS(rds) <-> ECS(ecs)」という文字列の変換をしているだけで、他は全部同じコードです!
※GitHubにもあります。
RDSイベントのCloudWatch Logs転送
AWSTemplateFormatVersion: "2010-09-09" Resources: ResourcePolicyForEventsToLogs: Type: AWS::Logs::ResourcePolicy Properties: PolicyName: "RDSEvents-ToLogs-ResourcePolicy" PolicyDocument: !Sub - | { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "events.amazonaws.com" }, "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "${CloudWatchLogsLogGroupArn}" } ] } - CloudWatchLogsLogGroupArn: !GetAtt RDSEventsLogGroup.Arn RDSEventsLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: /aws/events/RDSEvents RDSEventsRule: Type: AWS::Events::Rule DependsOn: ResourcePolicyForEventsToLogs Properties: Name: RDSEventsToLogs Description: "Transfer RDSEvents to CloudWatch Logs" State: ENABLED EventPattern: { "source": ["aws.rds"] } Targets: - Arn: !GetAtt RDSEventsLogGroup.Arn Id: !Sub "RDSEventsLogGroup"
ECSイベントのCloudWatch Logs転送
AWSTemplateFormatVersion: "2010-09-09" Resources: ResourcePolicyForEventsToLogs: Type: AWS::Logs::ResourcePolicy Properties: PolicyName: "ECSEvents-ToLogs-ResourcePolicy" PolicyDocument: !Sub - | { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "events.amazonaws.com" }, "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "${CloudWatchLogsLogGroupArn}" } ] } - CloudWatchLogsLogGroupArn: !GetAtt ECSEventsLogGroup.Arn ECSEventsLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: /aws/events/ECSEvents ECSEventsRule: Type: AWS::Events::Rule DependsOn: ResourcePolicyForEventsToLogs Properties: Name: ECSEventsToLogs Description: "Transfer ECSEvents to CloudWatch Logs" State: ENABLED EventPattern: { "source": ["aws.ecs"] } Targets: - Arn: !GetAtt ECSEventsLogGroup.Arn Id: !Sub "ECSEventsLogGroup"
解説
AWS::Logs::LogGroup
まず、転送先ロググループを作成します。ここは特に特別なことはありません。
AWS::Events::Rule
上記のログにイベントを転送するためのEventBridgeの「ルール」を作成します。
EventPattern: { "source": ["aws.rds"] }
、EventPattern: { "source": ["aws.ecs"] }
とすることで、RDS・ECSというソースで発生したイベントを全てCloudWatch Logsへ転送します。
細かい転送ルールを組みたい場合は、このEventPattern
に色々記載することになります。
また、ResourcePolicyForEventsToLogs
もRDSEventsRule
もRDSEventsLogGroup
に依存(Arnを参照)しているのでまずロググループが先に作られますが、ResourcePolicyForEventsToLogs
とRDSEventsRule
の間には依存関係がなく、どちらが先にリソースが作られるかはわかりません。
しかし、イベントルールはあくまでログにリソースポリシーがあってこそなので、DependsOn: ResourcePolicyForEventsToLogs
をつけることで、リソースポリシーが作られてからイベントルールが作られるように依存関係を成り立たせています。
(なくても成功したのですが一応、、、)
RDSEventsRule: Type: AWS::Events::Rule DependsOn: ResourcePolicyForEventsToLogs Properties: ... ...
AWS::Logs::ResourcePolicy
こちらが一番の肝である、「リソースポリシー」です。
上記リンクで記載したのですが、今まではCloudFormationに対応していなくて、CLIであったりCloudFormationカスタムリソースによって作成するしかありませんでした。
これがなんとCloudFormationに対応したため、こんなに簡単に作成ができるようになったのです。
内容としてはシンプルに、PolicyName
とPolicyDocument
だけの構成なのですが、PolicyDocument
が大事な要素になります。
PolicyDocument
は基本的にはIAMポリシーなどと同じ形式・要素になり、JSON形式で記述します。
PolicyDocument: !Sub - | { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "events.amazonaws.com" }, "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "${CloudWatchLogsLogGroupArn}" } ] } - CloudWatchLogsLogGroupArn: !GetAtt RDSEventsLogGroup.Arn
まず、EventBridgeが操作を許可できるように、PrincipalのServiceに"events.amazonaws.com"
と指定します。
"Principal": { "Service": "events.amazonaws.com" },
次に、許可する操作を指定します。
ここではCloudWatch Logsへ転送するために必要なアクションであるlogs:CreateLogStream
, logs:PutLogEvents
を指定します。
"Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ],
そして、Resourceには転送先のロググループのARNを指定します。(ここでは変数を指定していますが、以下の余談で後述します。)
ここではワイルドカードなども可能ですが、最小限の対象に絞ることがポリシー設計の基本です。
"Resource": "${CloudWatchLogsLogGroupArn}"
また余談なのですが、yamlテンプレートでJSONをうまく記述する場合、「|」を使用すると、JSONフォーマットがそのまま書けるようになります。
PolicyDocument: !Sub - | { "Version": "2012-10-17", ... ... "Resource": "${CloudWatchLogsLogGroupArn}" ... ... } - CloudWatchLogsLogGroupArn: !GetAtt RDSEventsLogGroup.Arn
このように、JSONを1行の文字列として書く必要がないため、非常に便利な機能です。
また、組み込み関数「Sub」のList形式の記法を使うことで、他のリソース情報を参照するような変数を使用できるようになります。
注意点
上記ではイベントパターンには特にdetail-typeなどの条件を指定せず、全イベントを転送しました。
もちろん、ここでイベントパターンを指定して転送するのも可能です。
しかし注意点なのですが、上記スタックを複製して、例えばイベントパターンの種類ごとにスタックを作成する場合、ある地点からスタック作成に失敗するようになります。
それは、CloudWatch Logsのリソースポリシーは、AWSアカウントの各リージョンにつき10個までしか作成できないからです。
そのため、個人的にはフィルタリングせず全イベントをCloudWatch Logsに保存しておいて、ログからメトリクスフィルターなどでフィルタリングしてアラームなどに使う方がリソースポリシーを節約できてオススメです。
ただし、リソースポリシーのみを別スタックで作成しておいて、そのリソースポリシーで指定するCloudWatch LogsのARNにはうまく命名規則に基づいたワイルドカードなどを駆使すれば、1つのリソースポリシーで色々なEventBridgeルールを作成できるため、それならば良いと思います。
ただ、イベントは基本的にログとして残しておいた方が後々良かったりするので、個人的にはとりあえず全イベント転送しちゃってもいいのかなと思います。(料金などとの兼ね合いにもなります。)
最後に
リソースポリシーのCloudFormation対応のおかげで、ずいぶん簡単にRDSのイベントやECSのイベントをログとして保存できるようになりました。