RDSイベントやECSイベントをCloudWatch Logsに転送する仕組みをCloudFormationで作成する

概要

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のロググループに、 リソースポリシーがないからです。


リソースポリシーとは、以下の記事に説明などを記載してあります。

go-to-k.hatenablog.com


そのため、転送先のCloudWatch Logsのロググループに、リソースポリシーをアタッチする必要があります。(厳密にはロググループにポリシーを直接アタッチするわけではないがイメージつきやすいのでこう書いています・・・)


コード

ここで、RDSイベント・ECSイベントをそれぞれCloudWatch Logsに転送するCloudFormationテンプレートを記載します。

※RDSイベントもECSイベントも、「RDS(rds) <-> ECS(ecs)」という文字列の変換をしているだけで、他は全部同じコードです!


GitHubにもあります。

github.com


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に色々記載することになります。


また、ResourcePolicyForEventsToLogsRDSEventsRuleRDSEventsLogGroup依存(Arnを参照)しているのでまずロググループが先に作られますが、ResourcePolicyForEventsToLogsRDSEventsRuleの間には依存関係がなく、どちらが先にリソースが作られるかはわかりません


しかし、イベントルールはあくまでログにリソースポリシーがあってこそなので、DependsOn: ResourcePolicyForEventsToLogsをつけることで、リソースポリシーが作られてからイベントルールが作られるように依存関係を成り立たせています。

(なくても成功したのですが一応、、、)

  RDSEventsRule:
    Type: AWS::Events::Rule
    DependsOn: ResourcePolicyForEventsToLogs
    Properties:
       ...
       ...


AWS::Logs::ResourcePolicy

こちらが一番の肝である、「リソースポリシー」です。

上記リンクで記載したのですが、今まではCloudFormationに対応していなくて、CLIであったりCloudFormationカスタムリソースによって作成するしかありませんでした。

これがなんとCloudFormationに対応したため、こんなに簡単に作成ができるようになったのです。


内容としてはシンプルに、PolicyNamePolicyDocumentだけの構成なのですが、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ルールを作成できるため、それならば良いと思います。

  • Resource例
    • arn:aws:logs${AWS::Region}:${AWS::AccountId}:log-group:/aws/events/*


ただ、イベントは基本的にログとして残しておいた方が後々良かったりするので、個人的にはとりあえず全イベント転送しちゃってもいいのかなと思います。(料金などとの兼ね合いにもなります。)


最後に

リソースポリシーのCloudFormation対応のおかげで、ずいぶん簡単にRDSのイベントやECSのイベントをログとして保存できるようになりました。