【CDK】本番環境以外のAuroraClusterを自動で起動停止させる

## はじめに
本番以外の環境ではスケジュールに応じてDBを起動停止させるなんてのはよくあるシチュエーションで、Lambdaを用いて制御するのがメジャーですが、Systems Managerでも出来るよということでCDKで実装してみました。

今回使うのはSystems Manager Automationの「AWS-StartStopAuroraCluster」というドキュメントをEventBridgeを使って指定した日時に実行させようと思います。

※Systems ManagerをターゲットとしたのEventBridgeのCDK-L2コンストラクタがまだサポートされていなかったのでL1で記述しています。

## Systems Manager Automation
Automationは、AWS のサービス(EC2、RDSなど)でのメンテナンスやデプロイ、修復に関するタスクを簡素化するための、AWS Systems Manager の機能の1つ。「AWS-StartStopAuroraCluster」以外にも「AWS-StartEC2Instance」や「AWSSupport-CollectECSInstanceLogs」など様々なドキュメントが標準で用意されています。

## CDK

### cdk.json
```cdk.json
{
  "app": "npx ts-node --prefer-ts-exts bin/src.ts",
  "watch": {
    "include": [
      "**"
    ],
    "exclude": [
      "README.md",
      "cdk*.json",
      "**/*.d.ts",
      "**/*.js",
      "tsconfig.json",
      "package*.json",
      "yarn.lock",
      "node_modules",
      "test"
    ]
  },
  "context": {
    "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true,
    "@aws-cdk/core:stackRelativeExports": true,
    "@aws-cdk/aws-rds:lowercaseDbIdentifier": true,
    "@aws-cdk/aws-lambda:recognizeVersionProps": true,
    "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true,
    "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
    "@aws-cdk/core:target-partitions": [
      "aws",
      "aws-cn"
    ],
    "prefix": "eventtest",
    "env": "stg",
    "prod": {
      "aurora": {
        "instances": 2,
        "instanceType": "r5.large"
      }
    },
    "stg": {
      "aurora": {
        "instances": 2,
        "instanceType": "t3.small"
      }
    },
    "dev": {
      "aurora": {
        "instances": 1,
        "instanceType": "t3.small"
      }
    }
  }
}

```

### app

bin/src.ts内の以下の部分でenvがprod以外の場合のみ、eventStackを走らせるようにしてます。

```ts
if (`${env}` != 'prod') {
  const eventStack = new EventStack(app, `${prefix}-${env}-event`, rdsStack)
  eventStack.addDependency(rdsStack);
}
```

```bin/src.ts
#!/usr/bin/env node
import * as cdk from "aws-cdk-lib";
import { VPCStack } from "../lib/vpc-stack";
import { RDSStack } from "../lib/aurora-stack";
import { EventStack } from "../lib/event-stack";

const app = new cdk.App();

// contest取得
const prefix = app.node.tryGetContext("prefix");
const env = app.node.tryGetContext("env");

const vpcStack = new VPCStack(app, `${prefix}-${env}-vpc`)
const rdsStack = new RDSStack(app, `${prefix}-${env}-aurora`, vpcStack)

// envがprod以外の場合のみ実行
if (`${env}` != 'prod') {
  const eventStack = new EventStack(app, `${prefix}-${env}-event`, rdsStack)
  eventStack.addDependency(rdsStack);
}

rdsStack.addDependency(vpcStack);
```

### stack
vpc、aurora、eventの3つのstackを用意
envがprodの場合は、event-stackが実行されずにvpcとaruroraの2つが展開されるようになります。

```lib/vpc-stack.ts
import * as cdk from 'aws-cdk-lib'
import * as ec2 from 'aws-cdk-lib/aws-ec2'

export interface VPCStackProps {
  readonly vpc: ec2.Vpc
}

export class VPCStack extends cdk.Stack implements VPCStackProps {
  public readonly vpc: ec2.Vpc

  // vpc
  private createVpc(name: string): ec2.Vpc {

    const vpc = new ec2.Vpc(this, `${name}`, {
      cidr: "10.0.0.0/16",
      maxAzs: 2,
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: 'Public',
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          cidrMask: 24,
          name: 'Private',
          subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
        },
      ]
    })
    return vpc;
  }

  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props)

    const prefix = this.node.tryGetContext('prefix')
    const env = this.node.tryGetContext('env')

    this.vpc = this.createVpc(`${prefix}-${env}-vpc`)
  }
}
```

```lib/aurora-stack.ts
import * as cdk from 'aws-cdk-lib'
import * as ec2 from 'aws-cdk-lib/aws-ec2'
import * as rds from 'aws-cdk-lib/aws-rds'
import type { VPCStackProps } from "./vpc-stack"

export interface RDSStackProps {
  readonly subnetgroup: rds.SubnetGroup
  readonly cluster: rds.DatabaseCluster
}

export class RDSStack extends cdk.Stack implements RDSStackProps {
  public readonly subnetgroup: rds.SubnetGroup
  public readonly cluster: rds.DatabaseCluster

  //SubnetGroup
  private creatersubnetGroup(name: string, vpc: ec2.Vpc): rds.SubnetGroup {

    const subnetgroup = new rds.SubnetGroup(this, `SubnetGroup`, {
      vpc,
      description: `${name}`,
      subnetGroupName: name,
      vpcSubnets: {subnetType: ec2.SubnetType.PRIVATE_WITH_NAT},
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    })
    return subnetgroup;
  }

  // rds
  private createrdsCluster(name: string, vpc: ec2.Vpc, subnetgroup: rds.SubnetGroup): rds.DatabaseCluster {
    const env = this.node.tryGetContext('env')
    const context = this.node.tryGetContext(env);

    const cluster = new rds.DatabaseCluster(this, `Database`, {
      engine: rds.DatabaseClusterEngine.AURORA_MYSQL,
      clusterIdentifier: `${name}-cluster`,
      instanceIdentifierBase: `${name}-instance`,
      instances: context.aurora.instances, // contextよりinstance数を取得
      subnetGroup: subnetgroup,
      instanceProps: { 
        vpc,
        instanceType: context.aurora.instanceType, // contextよりinstanceTypeを取得
        autoMinorVersionUpgrade: false,
      },
      storageEncrypted: true,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    })
    return cluster;
  }

  constructor(scope: cdk.App, id: string, VPCStack: VPCStackProps, props?: cdk.StackProps) {
    super(scope, id, props)

    const prefix = this.node.tryGetContext('prefix')
    const env = this.node.tryGetContext('env')

    this.subnetgroup = this.creatersubnetGroup(`${prefix}-${env}-subnetgroup`, VPCStack.vpc)
    this.cluster = this.createrdsCluster(`${prefix}-${env}`, VPCStack.vpc, this.subnetgroup)
  }
}
```

```lib/event-stack.ts
import * as cdk from "aws-cdk-lib";
import * as iam from 'aws-cdk-lib/aws-iam';
import * as rds from 'aws-cdk-lib/aws-rds';
import * as events from "aws-cdk-lib/aws-events";
import type { RDSStackProps } from "./aurora-stack";

export interface EventStackProps {
  readonly role: iam.Role;
}

export class EventStack extends cdk.Stack {
  public readonly role: iam.Role;

  // Role
  private createRole(name: string, aurora: rds.DatabaseCluster): iam.Role {
    const accountId = cdk.Stack.of(this).account;
    const region = cdk.Stack.of(this).region;

    const role = new iam.Role(this, `${name}-aurora-startstop`, {
      roleName: name,
      assumedBy: new iam.ServicePrincipal('events.amazonaws.com'),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonSSMAutomationRole'),
      ]
    });
    role.addToPolicy(new iam.PolicyStatement({
      sid: `AuroraStartStop`,
      effect: iam.Effect.ALLOW,
      actions: [
        "rds:DescribeDBInstances",
        "rds:DescribeDBClusters",
        "rds:StartDBCluster",
        "rds:StopDBCluster"
      ],
      resources: [
        `arn:aws:rds:${region}:${accountId}:cluster:${aurora.clusterIdentifier}`,
        `arn:aws:rds:${region}:${accountId}:db:${aurora.instanceIdentifiers[0]}`,
        `arn:aws:rds:${region}:${accountId}:db:${aurora.instanceIdentifiers[1]}`
      ]
    }))
  return role;
  }

  // EventBridge Auto Stop
  private createStopEvent(name: string, aurora: rds.DatabaseCluster, role: iam.Role): void {
    const region = cdk.Stack.of(this).region;

    const autoStopRule = new events.CfnRule(this, `${name}-AuroraAutoStopRule`, {
      name: `${name}-AuroraAutoStopRule`,
      scheduleExpression: 'cron(0 11 ? * MON-FRI *)', // 20:00(JST)に停止
      targets: [{
        arn: `arn:aws:ssm:${region}::automation-definition/AWS-StartStopAuroraCluster:$DEFAULT`,
        id: 'AuroraClusterAutoStop',
        input: `{"Action":["Stop"],"ClusterName":["${aurora.clusterIdentifier}"]}`,
        roleArn: role.roleArn
      }]
    })
  }

  // EventBridge Auto Start
  private createStartEvent(name: string, aurora: rds.DatabaseCluster, role: iam.Role): void {
    const region = cdk.Stack.of(this).region;

    const autoStartRule = new events.CfnRule(this, `${name}-AuroraAutoStartRule`, {
      name: `${name}-AuroraAutoStartRule`,
      scheduleExpression: 'cron(0 1 ? * MON-FRI *)', // 10:00(JST)に起動
      targets: [{
        arn: `arn:aws:ssm:${region}::automation-definition/AWS-StartStopAuroraCluster:$DEFAULT`,
        id: 'AuroraClusterAutoStart',
        input: `{"Action":["Start"],"ClusterName":["${aurora.clusterIdentifier}"]}`,
        roleArn: role.roleArn
      }]
    })
  }

  constructor(scope: cdk.App, id: string, auroraStack: RDSStackProps, props?: cdk.StackProps) {
    super(scope, id, props);

    const env = this.node.tryGetContext("env"); // Contextで指定したprefixを取得
    const prefix = this.node.tryGetContext("prefix"); // Contextで指定したprefixを取得

    this.role = this.createRole(`${prefix}-${env}-event-role`, auroraStack.cluster)
    this.createStopEvent(`${prefix}-${env}`, auroraStack.cluster, this.role);
    this.createStartEvent(`${prefix}-${env}`, auroraStack.cluster, this.role);
  }
}
```

## 動作確認
デプロイが完了してVPCなどのNW、AuroraCluster、EventBridge、IAMRoleがそれぞれ作成されました。

### Aurora
<img width="1353" alt="スクリーンショット 2022-03-30 3.06.03.png (73.2 kB)" src="https://img.esa.io/uploads/production/attachments/10655/2022/03/30/108350/4d227cdd-31e7-4c0f-b874-462d8f079a00.png">

### IamRole
SSMAutoAutomationRoleとAuroraへの権限を付与したポリシーがアタッチされたRoleが作成されます。
<img width="705" alt="スクリーンショット 2022-03-30 12.03.43.png (74.1 kB)" src="https://img.esa.io/uploads/production/attachments/10655/2022/03/30/108350/b2e3fdc3-bd97-4aab-a56e-0858038d850c.png">

AuroraClusterに対するStart/Stop権限を付与したポリシー
<img width="830" alt="スクリーンショット_2022-03-30_12_03_57.png (75.0 kB)" src="https://img.esa.io/uploads/production/attachments/10655/2022/03/30/108350/a6210b44-6886-48cd-9620-f0e6a72c42ff.png">


### EventBridge
AutoStartRuleとAutoStopRuleの2種類が作成
<img width="1033" alt="スクリーンショット 2022-03-30 3.09.59.png (39.5 kB)" src="https://img.esa.io/uploads/production/attachments/10655/2022/03/30/108350/2b029d11-7022-4edb-8803-e7278afcfa77.png">

Rule内では指定したcron通りのスケジュールが組まれてます。
<img width="1015" alt="スクリーンショット_2022-03-30_3_10_40.png (74.9 kB)" src="https://img.esa.io/uploads/production/attachments/10655/2022/03/30/108350/cf807260-efca-4edb-8d65-7eb7864c2d9c.png">

Eventのターゲットを見るとSystem Maneger オートメーションの「**AWS-StartStopAuroraCluster**」が指定され、入力定数の**Action**には**Start**か**Stop**、**ClusterName**に**AuroraClusterの識別子**がそれぞれ指定されています。Roleもちゃんと適用されてますね。
<img width="1317" alt="スクリーンショット 2022-03-30 3.10.59.png (40.5 kB)" src="https://img.esa.io/uploads/production/attachments/10655/2022/03/30/108350/f0ec1d92-fa47-4e1d-8af3-2d256ac37359.png">

<img width="576" alt="スクリーンショット 2022-03-30 3.11.07.png (20.6 kB)" src="https://img.esa.io/uploads/production/attachments/10655/2022/03/30/108350/76853b6c-deda-4bcd-a791-62e95b7f6b81.png">


## AuroraCluster自動停止起動確認
イベントスケジュールを適当な時刻に合わせて自動停止/起動を検証したところ、問題なく実行されました。
<img width="1405" alt="スクリーンショット_2022-03-30_12_23_35.png (60.0 kB)" src="https://img.esa.io/uploads/production/attachments/10655/2022/03/30/108350/26bea6f4-2fe2-40f6-8d30-4a41e01edba5.png">

<img width="799" alt="スクリーンショット_2022-03-30_12_23_46.png (96.0 kB)" src="https://img.esa.io/uploads/production/attachments/10655/2022/03/30/108350/39b0cf28-fbff-4827-ae13-2816f31f4721.png">


CloudTrail上でも、発信元IPアドレスが**events.amazonaws.com**による**StopDBCluster**と**StartDBCluster**がそれぞれ履歴として記録されていますね。

<img width="1032" alt="スクリーンショット 2022-03-30 12.19.37.png (57.9 kB)" src="https://img.esa.io/uploads/production/attachments/10655/2022/03/30/108350/91e60aa1-198f-4fb9-aa3c-ec0b9eb5e7b2.png">


## envをprodとdevにそれぞれ指定してdiffを確認
検証ではstgを指定してデプロイしたので、それ以外を指定した場合とで展開されるStackを比較します。
※各リソースの命名にenv区分を設けているのでdiffの結果がそのまま展開されるリソースということになります。

### devの場合
devの場合は`eventtest-dev-vpc`、`eventtest-dev-aurora`と`eventtest-dev-event`がstgの差分として出力されています。

```sh
% cdk diff -c env=dev                                                       ?[logalert]
Stack eventtest-dev-vpc
Parameters
[+] Parameter BootstrapVersion BootstrapVersion: {"Type":"AWS::SSM::Parameter::Value<String>","Default":"/cdk-bootstrap/hnb659fds/version","Description":"Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"}

Conditions
[+] Condition CDKMetadata/Condition CDKMetadataAvailable: {"Fn::Or":[{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"af-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ca-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-northwest-1"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-3"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"me-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"sa-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-2"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-2"]}]}]}

Resources
[+] AWS::EC2::VPC eventtest-dev-vpc eventtestdevvpcF1C58DDB 
[+] AWS::EC2::Subnet eventtest-dev-vpc/PublicSubnet1/Subnet eventtestdevvpcPublicSubnet1Subnet3A2F729B 
[+] AWS::EC2::RouteTable eventtest-dev-vpc/PublicSubnet1/RouteTable eventtestdevvpcPublicSubnet1RouteTable3CD01344 
[+] AWS::EC2::SubnetRouteTableAssociation eventtest-dev-vpc/PublicSubnet1/RouteTableAssociation eventtestdevvpcPublicSubnet1RouteTableAssociationF26CBE1A 
[+] AWS::EC2::Route eventtest-dev-vpc/PublicSubnet1/DefaultRoute eventtestdevvpcPublicSubnet1DefaultRoute72DD17B7 
[+] AWS::EC2::EIP eventtest-dev-vpc/PublicSubnet1/EIP eventtestdevvpcPublicSubnet1EIPB98AA85A 
[+] AWS::EC2::NatGateway eventtest-dev-vpc/PublicSubnet1/NATGateway eventtestdevvpcPublicSubnet1NATGatewayF5E1273E 
[+] AWS::EC2::Subnet eventtest-dev-vpc/PublicSubnet2/Subnet eventtestdevvpcPublicSubnet2SubnetD6AD1270 
[+] AWS::EC2::RouteTable eventtest-dev-vpc/PublicSubnet2/RouteTable eventtestdevvpcPublicSubnet2RouteTable59DEEA55 
[+] AWS::EC2::SubnetRouteTableAssociation eventtest-dev-vpc/PublicSubnet2/RouteTableAssociation eventtestdevvpcPublicSubnet2RouteTableAssociation9286CEF4 
[+] AWS::EC2::Route eventtest-dev-vpc/PublicSubnet2/DefaultRoute eventtestdevvpcPublicSubnet2DefaultRoute03E3B042 
[+] AWS::EC2::EIP eventtest-dev-vpc/PublicSubnet2/EIP eventtestdevvpcPublicSubnet2EIPEAD4B0BE 
[+] AWS::EC2::NatGateway eventtest-dev-vpc/PublicSubnet2/NATGateway eventtestdevvpcPublicSubnet2NATGateway7CAA7494 
[+] AWS::EC2::Subnet eventtest-dev-vpc/PrivateSubnet1/Subnet eventtestdevvpcPrivateSubnet1SubnetDA7C0851 
[+] AWS::EC2::RouteTable eventtest-dev-vpc/PrivateSubnet1/RouteTable eventtestdevvpcPrivateSubnet1RouteTableB25FFB72 
[+] AWS::EC2::SubnetRouteTableAssociation eventtest-dev-vpc/PrivateSubnet1/RouteTableAssociation eventtestdevvpcPrivateSubnet1RouteTableAssociationAD7C5CC1 
[+] AWS::EC2::Route eventtest-dev-vpc/PrivateSubnet1/DefaultRoute eventtestdevvpcPrivateSubnet1DefaultRoute32EA15BD 
[+] AWS::EC2::Subnet eventtest-dev-vpc/PrivateSubnet2/Subnet eventtestdevvpcPrivateSubnet2SubnetF92CED9F 
[+] AWS::EC2::RouteTable eventtest-dev-vpc/PrivateSubnet2/RouteTable eventtestdevvpcPrivateSubnet2RouteTable20C7ABC4 
[+] AWS::EC2::SubnetRouteTableAssociation eventtest-dev-vpc/PrivateSubnet2/RouteTableAssociation eventtestdevvpcPrivateSubnet2RouteTableAssociationAD13C64F 
[+] AWS::EC2::Route eventtest-dev-vpc/PrivateSubnet2/DefaultRoute eventtestdevvpcPrivateSubnet2DefaultRoute8DB548F6 
[+] AWS::EC2::InternetGateway eventtest-dev-vpc/IGW eventtestdevvpcIGW31EDB392 
[+] AWS::EC2::VPCGatewayAttachment eventtest-dev-vpc/VPCGW eventtestdevvpcVPCGWB4BD99D3 

Outputs
[+] Output Exports/Output{"Ref":"eventtestdevvpcPrivateSubnet1SubnetDA7C0851"} ExportsOutputRefeventtestdevvpcPrivateSubnet1SubnetDA7C0851A86E0B9D: {"Value":{"Ref":"eventtestdevvpcPrivateSubnet1SubnetDA7C0851"},"Export":{"Name":"eventtest-dev-vpc:ExportsOutputRefeventtestdevvpcPrivateSubnet1SubnetDA7C0851A86E0B9D"}}
[+] Output Exports/Output{"Ref":"eventtestdevvpcPrivateSubnet2SubnetF92CED9F"} ExportsOutputRefeventtestdevvpcPrivateSubnet2SubnetF92CED9FCB2379A6: {"Value":{"Ref":"eventtestdevvpcPrivateSubnet2SubnetF92CED9F"},"Export":{"Name":"eventtest-dev-vpc:ExportsOutputRefeventtestdevvpcPrivateSubnet2SubnetF92CED9FCB2379A6"}}
[+] Output Exports/Output{"Ref":"eventtestdevvpcF1C58DDB"} ExportsOutputRefeventtestdevvpcF1C58DDB38C33E3E: {"Value":{"Ref":"eventtestdevvpcF1C58DDB"},"Export":{"Name":"eventtest-dev-vpc:ExportsOutputRefeventtestdevvpcF1C58DDB38C33E3E"}}

Other Changes
[+] Unknown Rules: {"CheckBootstrapVersion":{"Assertions":[{"Assert":{"Fn::Not":[{"Fn::Contains":[["1","2","3","4","5"],{"Ref":"BootstrapVersion"}]}]},"AssertDescription":"CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."}]}}

Stack eventtest-dev-aurora
Security Group Changes
┌───┬───────────────────────────────────┬─────┬────────────┬─────────────────┐
│   │ Group                             │ Dir │ Protocol   │ Peer            │
├───┼───────────────────────────────────┼─────┼────────────┼─────────────────┤
│ + │ ${Database/SecurityGroup.GroupId} │ Out │ Everything │ Everyone (IPv4) │
└───┴───────────────────────────────────┴─────┴────────────┴─────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Parameters
[+] Parameter BootstrapVersion BootstrapVersion: {"Type":"AWS::SSM::Parameter::Value<String>","Default":"/cdk-bootstrap/hnb659fds/version","Description":"Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"}

Conditions
[+] Condition CDKMetadata/Condition CDKMetadataAvailable: {"Fn::Or":[{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"af-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ca-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-northwest-1"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-3"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"me-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"sa-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-2"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-2"]}]}]}

Resources
[+] AWS::RDS::DBSubnetGroup SubnetGroup SubnetGroup 
[+] AWS::EC2::SecurityGroup Database/SecurityGroup DatabaseSecurityGroup5C91FDCB 
[+] AWS::SecretsManager::Secret Database/Secret DatabaseSecret3B817195 
[+] AWS::SecretsManager::SecretTargetAttachment Database/Secret/Attachment DatabaseSecretAttachmentE5D1B020 
[+] AWS::RDS::DBCluster Database DatabaseB269D8BB 
[+] AWS::RDS::DBInstance Database/Instance1 DatabaseInstance1844F58FD 

Outputs
[+] Output Exports/Output{"Ref":"DatabaseB269D8BB"} ExportsOutputRefDatabaseB269D8BB88F4B1C4: {"Value":{"Ref":"DatabaseB269D8BB"},"Export":{"Name":"eventtest-dev-aurora:ExportsOutputRefDatabaseB269D8BB88F4B1C4"}}
[+] Output Exports/Output{"Ref":"DatabaseInstance1844F58FD"} ExportsOutputRefDatabaseInstance1844F58FDF162DF73: {"Value":{"Ref":"DatabaseInstance1844F58FD"},"Export":{"Name":"eventtest-dev-aurora:ExportsOutputRefDatabaseInstance1844F58FDF162DF73"}}

Other Changes
[+] Unknown Rules: {"CheckBootstrapVersion":{"Assertions":[{"Assert":{"Fn::Not":[{"Fn::Contains":[["1","2","3","4","5"],{"Ref":"BootstrapVersion"}]}]},"AssertDescription":"CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."}]}}

Stack eventtest-dev-event
IAM Statement Changes
┌───┬──────────────────────┬────────┬──────────────────────┬──────────────────────┬───────────┐
│   │ Resource             │ Effect │ Action               │ Principal            │ Condition │
├───┼──────────────────────┼────────┼──────────────────────┼──────────────────────┼───────────┤
│ + │ ${eventtest-dev-even │ Allow  │ sts:AssumeRole       │ Service:events.amazo │           │
│   │ t-role-aurora-starts │        │                      │ naws.com             │           │
│   │ top.Arn}             │        │                      │                      │           │
├───┼──────────────────────┼────────┼──────────────────────┼──────────────────────┼───────────┤
│ + │ arn:aws:rds:${AWS::R │ Allow  │ rds:DescribeDBCluste │ AWS:${eventtest-dev- │           │
│   │ egion}:${AWS::Accoun │        │ rs                   │ event-role-aurora-st │           │
│   │ tId}:cluster:{"Fn::I │        │ rds:DescribeDBInstan │ artstop}             │           │
│   │ mportValue":"eventte │        │ ces                  │                      │           │
│   │ st-dev-aurora:Export │        │ rds:StartDBCluster   │                      │           │
│   │ sOutputRefDatabaseB2 │        │ rds:StopDBCluster    │                      │           │
│   │ 69D8BB88F4B1C4"}     │        │                      │                      │           │
│   │ arn:aws:rds:${AWS::R │        │                      │                      │           │
│   │ egion}:${AWS::Accoun │        │                      │                      │           │
│   │ tId}:db:undefined    │        │                      │                      │           │
│   │ arn:aws:rds:${AWS::R │        │                      │                      │           │
│   │ egion}:${AWS::Accoun │        │                      │                      │           │
│   │ tId}:db:{"Fn::Import │        │                      │                      │           │
│   │ Value":"eventtest-de │        │                      │                      │           │
│   │ v-aurora:ExportsOutp │        │                      │                      │           │
│   │ utRefDatabaseInstanc │        │                      │                      │           │
│   │ e1844F58FDF162DF73"} │        │                      │                      │           │
└───┴──────────────────────┴────────┴──────────────────────┴──────────────────────┴───────────┘
IAM Policy Changes
┌───┬────────────────────────────────────────────┬────────────────────────────────────────────┐
│   │ Resource                                   │ Managed Policy ARN                         │
├───┼────────────────────────────────────────────┼────────────────────────────────────────────┤
│ + │ ${eventtest-dev-event-role-aurora-startsto │ arn:${AWS::Partition}:iam::aws:policy/serv │
│   │ p}                                         │ ice-role/AmazonSSMAutomationRole           │
└───┴────────────────────────────────────────────┴────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Parameters
[+] Parameter BootstrapVersion BootstrapVersion: {"Type":"AWS::SSM::Parameter::Value<String>","Default":"/cdk-bootstrap/hnb659fds/version","Description":"Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"}

Conditions
[+] Condition CDKMetadata/Condition CDKMetadataAvailable: {"Fn::Or":[{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"af-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ca-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-northwest-1"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-3"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"me-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"sa-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-2"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-2"]}]}]}

Resources
[+] AWS::IAM::Role eventtest-dev-event-role-aurora-startstop eventtestdeveventroleaurorastartstop954CBEF0 
[+] AWS::IAM::Policy eventtest-dev-event-role-aurora-startstop/DefaultPolicy eventtestdeveventroleaurorastartstopDefaultPolicy79B7D1C5 
[+] AWS::Events::Rule eventtest-dev-AuroraAutoStopRule eventtestdevAuroraAutoStopRule 
[+] AWS::Events::Rule eventtest-dev-AuroraAutoStartRule eventtestdevAuroraAutoStartRule 

Other Changes
[+] Unknown Rules: {"CheckBootstrapVersion":{"Assertions":[{"Assert":{"Fn::Not":[{"Fn::Contains":[["1","2","3","4","5"],{"Ref":"BootstrapVersion"}]}]},"AssertDescription":"CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."}]}}
```

### prodの場合
一方でprodの場合の展開されるであろうstackは`eventtest-prod-aurora`と`eventtest-prod-aurora`だけであることが分かります。event-stackが実行されないことが確認できましたね。

```sh
% cdk diff -c env=prod                                                      ?[logalert]
Stack eventtest-prod-vpc
Parameters
[+] Parameter BootstrapVersion BootstrapVersion: {"Type":"AWS::SSM::Parameter::Value<String>","Default":"/cdk-bootstrap/hnb659fds/version","Description":"Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"}

Conditions
[+] Condition CDKMetadata/Condition CDKMetadataAvailable: {"Fn::Or":[{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"af-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ca-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-northwest-1"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-3"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"me-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"sa-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-2"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-2"]}]}]}

Resources
[+] AWS::EC2::VPC eventtest-prod-vpc eventtestprodvpc60BD6525 
[+] AWS::EC2::Subnet eventtest-prod-vpc/PublicSubnet1/Subnet eventtestprodvpcPublicSubnet1Subnet933DD4F9 
[+] AWS::EC2::RouteTable eventtest-prod-vpc/PublicSubnet1/RouteTable eventtestprodvpcPublicSubnet1RouteTable3AA66A4B 
[+] AWS::EC2::SubnetRouteTableAssociation eventtest-prod-vpc/PublicSubnet1/RouteTableAssociation eventtestprodvpcPublicSubnet1RouteTableAssociation8A44C4DD 
[+] AWS::EC2::Route eventtest-prod-vpc/PublicSubnet1/DefaultRoute eventtestprodvpcPublicSubnet1DefaultRoute1D5C14B5 
[+] AWS::EC2::EIP eventtest-prod-vpc/PublicSubnet1/EIP eventtestprodvpcPublicSubnet1EIPE51ED220 
[+] AWS::EC2::NatGateway eventtest-prod-vpc/PublicSubnet1/NATGateway eventtestprodvpcPublicSubnet1NATGateway0D921433 
[+] AWS::EC2::Subnet eventtest-prod-vpc/PublicSubnet2/Subnet eventtestprodvpcPublicSubnet2Subnet4D4F7879 
[+] AWS::EC2::RouteTable eventtest-prod-vpc/PublicSubnet2/RouteTable eventtestprodvpcPublicSubnet2RouteTable8B22DBC8 
[+] AWS::EC2::SubnetRouteTableAssociation eventtest-prod-vpc/PublicSubnet2/RouteTableAssociation eventtestprodvpcPublicSubnet2RouteTableAssociation76235E9E 
[+] AWS::EC2::Route eventtest-prod-vpc/PublicSubnet2/DefaultRoute eventtestprodvpcPublicSubnet2DefaultRoute2853AEE3 
[+] AWS::EC2::EIP eventtest-prod-vpc/PublicSubnet2/EIP eventtestprodvpcPublicSubnet2EIP0C3871BE 
[+] AWS::EC2::NatGateway eventtest-prod-vpc/PublicSubnet2/NATGateway eventtestprodvpcPublicSubnet2NATGateway1B31C15E 
[+] AWS::EC2::Subnet eventtest-prod-vpc/PrivateSubnet1/Subnet eventtestprodvpcPrivateSubnet1Subnet5F692D49 
[+] AWS::EC2::RouteTable eventtest-prod-vpc/PrivateSubnet1/RouteTable eventtestprodvpcPrivateSubnet1RouteTable2A9B119A 
[+] AWS::EC2::SubnetRouteTableAssociation eventtest-prod-vpc/PrivateSubnet1/RouteTableAssociation eventtestprodvpcPrivateSubnet1RouteTableAssociation61E30962 
[+] AWS::EC2::Route eventtest-prod-vpc/PrivateSubnet1/DefaultRoute eventtestprodvpcPrivateSubnet1DefaultRouteF60E052F 
[+] AWS::EC2::Subnet eventtest-prod-vpc/PrivateSubnet2/Subnet eventtestprodvpcPrivateSubnet2Subnet53F01FAA 
[+] AWS::EC2::RouteTable eventtest-prod-vpc/PrivateSubnet2/RouteTable eventtestprodvpcPrivateSubnet2RouteTable2CD271B8 
[+] AWS::EC2::SubnetRouteTableAssociation eventtest-prod-vpc/PrivateSubnet2/RouteTableAssociation eventtestprodvpcPrivateSubnet2RouteTableAssociation90AA9E8E 
[+] AWS::EC2::Route eventtest-prod-vpc/PrivateSubnet2/DefaultRoute eventtestprodvpcPrivateSubnet2DefaultRoute1FA2B890 
[+] AWS::EC2::InternetGateway eventtest-prod-vpc/IGW eventtestprodvpcIGW8A932F50 
[+] AWS::EC2::VPCGatewayAttachment eventtest-prod-vpc/VPCGW eventtestprodvpcVPCGW6767810A 

Outputs
[+] Output Exports/Output{"Ref":"eventtestprodvpcPrivateSubnet1Subnet5F692D49"} ExportsOutputRefeventtestprodvpcPrivateSubnet1Subnet5F692D49ECB47F1A: {"Value":{"Ref":"eventtestprodvpcPrivateSubnet1Subnet5F692D49"},"Export":{"Name":"eventtest-prod-vpc:ExportsOutputRefeventtestprodvpcPrivateSubnet1Subnet5F692D49ECB47F1A"}}
[+] Output Exports/Output{"Ref":"eventtestprodvpcPrivateSubnet2Subnet53F01FAA"} ExportsOutputRefeventtestprodvpcPrivateSubnet2Subnet53F01FAA21084046: {"Value":{"Ref":"eventtestprodvpcPrivateSubnet2Subnet53F01FAA"},"Export":{"Name":"eventtest-prod-vpc:ExportsOutputRefeventtestprodvpcPrivateSubnet2Subnet53F01FAA21084046"}}
[+] Output Exports/Output{"Ref":"eventtestprodvpc60BD6525"} ExportsOutputRefeventtestprodvpc60BD65256808C09B: {"Value":{"Ref":"eventtestprodvpc60BD6525"},"Export":{"Name":"eventtest-prod-vpc:ExportsOutputRefeventtestprodvpc60BD65256808C09B"}}

Other Changes
[+] Unknown Rules: {"CheckBootstrapVersion":{"Assertions":[{"Assert":{"Fn::Not":[{"Fn::Contains":[["1","2","3","4","5"],{"Ref":"BootstrapVersion"}]}]},"AssertDescription":"CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."}]}}

Stack eventtest-prod-aurora
Security Group Changes
┌───┬───────────────────────────────────┬─────┬────────────┬─────────────────┐
│   │ Group                             │ Dir │ Protocol   │ Peer            │
├───┼───────────────────────────────────┼─────┼────────────┼─────────────────┤
│ + │ ${Database/SecurityGroup.GroupId} │ Out │ Everything │ Everyone (IPv4) │
└───┴───────────────────────────────────┴─────┴────────────┴─────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Parameters
[+] Parameter BootstrapVersion BootstrapVersion: {"Type":"AWS::SSM::Parameter::Value<String>","Default":"/cdk-bootstrap/hnb659fds/version","Description":"Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"}

Conditions
[+] Condition CDKMetadata/Condition CDKMetadataAvailable: {"Fn::Or":[{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"af-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ca-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-northwest-1"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-3"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"me-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"sa-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-2"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-2"]}]}]}

Resources
[+] AWS::RDS::DBSubnetGroup SubnetGroup SubnetGroup 
[+] AWS::EC2::SecurityGroup Database/SecurityGroup DatabaseSecurityGroup5C91FDCB 
[+] AWS::SecretsManager::Secret Database/Secret DatabaseSecret3B817195 
[+] AWS::SecretsManager::SecretTargetAttachment Database/Secret/Attachment DatabaseSecretAttachmentE5D1B020 
[+] AWS::RDS::DBCluster Database DatabaseB269D8BB 
[+] AWS::RDS::DBInstance Database/Instance1 DatabaseInstance1844F58FD 
[+] AWS::RDS::DBInstance Database/Instance2 DatabaseInstance2AA380DEE 

Other Changes
[+] Unknown Rules: {"CheckBootstrapVersion":{"Assertions":[{"Assert":{"Fn::Not":[{"Fn::Contains":[["1","2","3","4","5"],{"Ref":"BootstrapVersion"}]}]},"AssertDescription":"CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."}]}}
```

## さいごに
今回はEventBridgeとSystem Manger Automationを使ってAuroraClusterの自動停止起動を検証してみました。
コンソール上でも比較的簡単に出来るのは勿論ですが、CDKでも定義出来る事が確認出来たのでシーンによってさっと使えるようにストックしておくのはありですね。

EventBridgeは、AWS内をターゲットとしたのものだけでなくAuth0やDatadog、Shopify、Zendeskなどの外部SaaSとの統合がサポートされており可能性を秘めたサービスなので、機会があったらそちらも試してみたいですね。

## 参考
https://aws.amazon.com/jp/eventbridge/integrations/
https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-automation.html
https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_events-readme.html