AWS EventBridge で event を検知してみた

## はじめに

「AWS 上でなにか event が発生したときに検知 -> 特定の動作を実行する」

といったことをやりたいときありますよね。<font color = 'gray'>（よく AWS の資格試験とかで問題に出てきてるやつです）</font>

そんなときに役立つのが AWS EventBridge です。

今まで、EventBridge は定期実行にしか使ってなかったというのもあり新鮮だったので記事に書き起こしました。

あと、意外に event を拾うという Eventbridge の使い方記事に転がってないので誰かの参考になればと思います。

## サマリー

- AWS RDS の autoscaling の event を EventBridge で検知する
- コストタグを付ける

## 環境

- aws-cdk : 2.107.0

## 内容

AWS Aurora では autoscaling を設定でき、任意の時間・CPU 使用率に応じてリードレプリカを増やしたりすることができますよね。

この機能とても便利なのですが、使うにあたって一つ問題が発生しました。

**「そう、tag を autoscaling 時に自動で付与する設定がない」**

ということです。

ですので、Eventbridge で auto scaling event を検知したらタグを付けるための lambda を走らせて付ける形の仕組みを作りました。

早速ですがフォルダ構成とコードはこちら ↓

classにしてるので良い感じにimportして使ってください

フォルダ構成（lambda スクリプトを cdk の asset 使ってデプロイしているためわかりやすいようにフォルダを分けている）

```フォルダ構成
lib --- lambda --- main.py
     |
     |- event.ts
     |
     |- main.ts
```

cdk スクリプト

```lambda.ts
import * as iam from 'aws-cdk-lib/aws-iam'
import * as events from "aws-cdk-lib/aws-events"
import * as lambda from 'aws-cdk-lib/aws-lambda'
import logs from 'aws-cdk-lib/aws-logs'
import * as assets from 'aws-cdk-lib/aws-s3-assets'
import type { Construct } from 'constructs'

import * as path from 'node:path'

export class Lambda {
  private region = 'ap-northeast-1'
  private accoutId: string
  private readonly rdsTag: string

  constructor(region: string, accoutId: string, rdsTag: string) {
    this.region = region
    this.accoutId = accoutId
    this.rdsTag = rdsTag
  }

  public createResource(scope: Construct): void {

    const rdsAddTagLogGroup = new logs.CfnLogGroup(scope, 'RdsAddTagLogGroup', {
      logGroupName: '/lambda/rds-add-tag',
      retentionInDays: 120
    })

    const assumeRolePolicy = {
      Statement: [
        {
          Action: 'sts:AssumeRole',
          Effect: 'Allow',
          Principal: {
            Service: 'lambda.amazonaws.com'
          }
        }
      ],
      Version: '2012-10-17'
    }

    const baseLambdaPolicy = new iam.PolicyStatement({
      sid: 'AllowBaseLambda',
      effect: iam.Effect.ALLOW,
      actions: [
        'logs:CreateLogGroup',
        'logs:CreateLogStream',
        'logs:PutLogEvents'
      ],
      resources: [rdsAddTagLogGroup.attrArn]
    })

    const cfnRdsAddTagLambdaRole = new iam.CfnRole(
      scope,
      'RdsAddTagLambdaRole',
      {
        roleName: 'rds-add-tag-lambda-role',
        assumeRolePolicyDocument: assumeRolePolicy,
        policies: [
          {
            policyDocument: new iam.PolicyDocument({
              statements: [
                baseLambdaPolicy,
                new iam.PolicyStatement({
                  sid: 'AllowRdsTag',
                  effect: iam.Effect.ALLOW,
                  actions: ['rds:AddTagsToResource', 'rds:ListTagsForResource'],
                  resources: [
                    `arn:aws:rds:${this.region}:${this.accoutId}:db:application-autoscaling-*`
                  ],
                  conditions: {
                    StringEquals: {
                      'rds:db-tag/application-autoscaling:resourceId':
                        this.rdsTag
                    }
                  }
                })
              ]
            }),
            policyName: 'rds-add-tag-lambda-policy'
          }
        ]
      }
    )

    // aurora RDSのインスタンスが作成されたときに、発火するLambda関数
    const rdsAddTagLambdaCodeDir = path.join(__dirname, './lambda/rds-tag')
    const rdsAddTagLambdaCodeConfig = new assets.Asset(
      scope,
      'RdsAddTagLambdaCodeConfig',
      {
        path: rdsAddTagLambdaCodeDir
      }
    )

    const rdsAddTagLambda = new lambda.CfnFunction(scope, 'RdsAddTagLambda', {
      functionName: 'rds-add-tag-lambda',
      code: {
        s3Bucket: rdsAddTagLambdaCodeConfig.s3BucketName,
        s3Key: rdsAddTagLambdaCodeConfig.s3ObjectKey
      },
      handler: 'main.lambda_handler',
      role: cfnRdsAddTagLambdaRole.attrArn,
      runtime: lambda.Runtime.PYTHON_3_12.toString(),
      timeout: 60,
      memorySize: 256,
      description: 'rdsがauto scalingで増えたときにtagをつけるlambda',
      loggingConfig: {
        logGroup: rdsAddTagLogGroup.logGroupName
      },
      environment: {
        variables: {
          TZ: 'Asia/Tokyo',
          LOG_LEVEL: 'INFO'
        }
      }
    }).addDependency(rdsAddTagLogGroup)

    const rdsAddTagRule = new events.CfnRule(scope , "RdsAddTagRule", {
        name: "RdsAddTagRule",
        description: "RdsAddTagRule",
        eventPattern: {
            source: ["aws.rds"],
            "detail-type": ["RDS DB Instance Event"],
            detail: {
                Message: ["DB instance created"]
            }
        },
        targets: [
        {
            arn: rdsAddTagLambda.attrArn,
            id: rdsAddTagLambda.functionName,
        }
        ]
    })

    new lambda.CfnPermission(scope, "RdsAddTagLambdaPermission", {
        action: "lambda:InvokeFunction",
        functionName: rdsAddTagLambda.attrArn,
        principal: "events.amazonaws.com",
        sourceArn: rdsAddTagRule.attrArn
      }).addDependency(rdsAddTagLambda)
  }
}


```

lambda スクリプト

動作概略：rdsインスタンスが作成されたイベントをキャッチして、autoscalingにて作成されたインスタンスのみにタグを付ける動作

```.py
import boto3
import os
import logging

logger = logging.getLogger()
logger.setLevel(os.getenv("LOG_LEVEL", logging.INFO))


def lambda_handler(event, context):
    client = boto3.client("rds")

    arn = event["detail"]["SourceArn"]

    add_key = os.getenv("KEY", "null")
    add_value = os.getenv("VALUE", "null")

    if add_key == "null" or add_value == "null":
        logger.error(f"TAG_KEY: {add_key} or TAG_VALUE: {add_value} is not set")
        exit(1)

    try:
        instance_tags = client.list_tags_for_resource(ResourceName=arn)

        # tag_listはdictの配列
        tag_list = instance_tags["TagList"]
        for tag in tag_list:
            key, value = tag["Key"], tag["Value"]
            # keyがapplication-autoscaling:resourceIdであるかつ、valueにhogeが含まれる場合
            if key == "application-autoscaling:resourceId" and "hoge" in value:
                autoscaling_key = key
                break

        # application-autoscaling:resourceIdが存在しない場合はautoscaling以外のRDSインスタンスが作成された場合なので特に処理をせず終了
        if autoscaling_key != "application-autoscaling:resourceId":
            logger.info(f"application-autoscaling:resourceId is not found : {arn}")
        else:
            response = client.add_tags_to_resource(
                ResourceName=arn, Tags=[{"Key": add_key, "Value": add_value}]
            )
            logger.info(f"Add key:{add_key}, value:{add_value} to RDS instance: {arn}")
    except Exception as e:
        logger.error(f"Error: {e}")
        raise e
```