2025/09/06

EventBridge

## EventBridgeの動作概要
## LambdaでEC2とRDSインスタンスをスケジュール停止/起動する例
Update: [Amazon EventBridge が IAM 実行ロールのサポートをすべてのターゲットに拡大](https://aws.amazon.com/jp/about-aws/whats-new/2025/03/amazon-eventbridge-iam-execution-role-all-targets/)

現在は、ターゲット側のリソースベースポリシーによる制御( AWS::Lambda::Permission )でなく、イベントソース側のロール指定で制御可能となっている。

```
AWSTemplateFormatVersion: 2010-09-09
Description: The template for creating EventBridge Schedule.
# -------------------------
# Metadata 
# -------------------------
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Schedule Configuration
        Parameters:
          - EC2InstanceId
          - RDSInstanceId
          - ScheduleExpressionForStop
          - ScheduleExpressionForStart

# -------------------------
# <<< Parameters 
# -------------------------
Parameters:
  EC2InstanceId:
    Type: String

  RDSInstanceId:
    Type: String

  ScheduleExpressionForStop:
    Type: String
    Default: cron(0 13 * * ? *)  # UTC 0時 → JST 22時
    
  ScheduleExpressionForStart:
    Type: String
    Default: cron(0 23 * * ? *)  # UTC 23時 → JST 8時

Resources:
  #==========================
  # IAM Role for Lambda
  #==========================
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: LambdaSchedulerRole
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        - arn:aws:iam::aws:policy/AmazonEC2FullAccess
        - arn:aws:iam::aws:policy/AmazonRDSFullAccess

  #==========================
  # Lambda Function
  #==========================
  SchedulerFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: EC2RDS-Scheduler
      Runtime: python3.9
      Handler: index.lambda_handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Timeout: 90
      Code:
        ZipFile: |
          import boto3
          import os

          def lambda_handler(event, context):
              ec2_id = os.environ['EC2_ID']
              rds_id = os.environ['RDS_ID']
              action = event.get('action', 'start') # 初期値(EventBridgeで上書き)

              ec2 = boto3.client('ec2')
              rds = boto3.client('rds')

              if action == 'start':
                  ec2.start_instances(InstanceIds=[ec2_id])
                  rds.start_db_instance(DBInstanceIdentifier=rds_id)
              elif action == 'stop':
                  ec2.stop_instances(InstanceIds=[ec2_id])
                  rds.stop_db_instance(DBInstanceIdentifier=rds_id)

      Environment:
        Variables:
          EC2_ID: !Ref EC2InstanceId
          RDS_ID: !Ref RDSInstanceId

  #==========================
  # EventBridge
  #==========================
  # for Start
  StartSchedule:
    Type: AWS::Events::Rule
    Properties:
      Name: EC2RDSStartSchedule
      ScheduleExpression: !Ref ScheduleExpressionForStart
      State: ENABLED
      Targets:
        - Arn: !GetAtt SchedulerFunction.Arn
          Id: StartTarget
          Input: '{ "action": "start" }'

  # for Stop
  StopSchedule:
    Type: AWS::Events::Rule
    Properties:
      Name: EC2RDSStopSchedule
      ScheduleExpression: !Ref ScheduleExpressionForStop
      State: ENABLED
      Targets:
        - Arn: !GetAtt SchedulerFunction.Arn
          Id: StopTarget
          Input: '{ "action": "stop" }'

  #==========================
  # Lambda Permissions
  #==========================
  # for Start
  PermissionForEventsToInvokeLambda:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref SchedulerFunction
      Action: lambda:InvokeFunction
      Principal: events.amazonaws.com
      SourceArn: !GetAtt StartSchedule.Arn

  # for Stop
  PermissionForEventsToInvokeLambdaStop:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref SchedulerFunction
      Action: lambda:InvokeFunction
      Principal: events.amazonaws.com
      SourceArn: !GetAtt StopSchedule.Arn
```

## SSM AutomationでEC2とRDSインスタンスをスケジュール停止/起動する例
```
AWSTemplateFormatVersion: 2010-09-09
Description: The template for creating EC2 scheduled start/stop resources via EventBridge Scheduler + SSM Automation.

# -------------------------
# <<< Parameters 
# -------------------------
Parameters:
  Prefix:
    Type: String

  Client:
    Type: String

  EC2StartCronExpression:
    Description: 'Enter the cron expression for EC2 auto start (Asia/Tokyo). Default: weekdays 08:00 JST.'
    Type: String
    Default: 'cron(0 8 ? * MON-FRI *)'

  EC2StopCronExpression:
    Description: 'Enter the cron expression for EC2 auto stop (Asia/Tokyo). Default: weekdays 22:00 JST.'
    Type: String
    Default: 'cron(0 22 ? * MON-FRI *)'

  SchedulerState:
    Description: 'Enable or disable the schedule.'
    Type: String
    Default: ENABLED
    AllowedValues:
      - ENABLED
      - DISABLED

Resources:
  #==========================
  # IAM Role
  #==========================
  EC2SchedulerRole:
    Type: AWS::IAM::Role
    Properties:
      Path: "/"
      RoleName: !Sub '${Prefix}-${Client}-ec2-scheduler-role'
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - scheduler.amazonaws.com
            Action:
              - sts:AssumeRole
      MaxSessionDuration: 3600
      Policies:
        - PolicyName: !Sub '${Prefix}-${Client}-ec2-scheduler-policy'
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - ssm:StartAutomationExecution
                Resource:
                  - !Sub 'arn:aws:ssm:${AWS::Region}::document/AWS-StartEC2Instance'
                  - !Sub 'arn:aws:ssm:${AWS::Region}::document/AWS-StopEC2Instance'
              - Effect: Allow
                Action:
                  - ssm:GetAutomationExecution
                  - ssm:StopAutomationExecution
                Resource: '*'
              - Effect: Allow
                Action:
                  - ec2:StartInstances
                  - ec2:StopInstances
                  - ec2:DescribeInstances
                  - ec2:DescribeInstanceStatus
                Resource:
                  - !Sub
                    - 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:instance/${TargetInstanceId}'
                    - TargetInstanceId:
                        Fn::ImportValue:
                          !Sub '${Prefix}::${Client}::InstanceId'

  #==========================
  # EventBridge Scheduler
  #==========================
  # 起動用スケジュール
  EC2StartSchedule:
    Type: AWS::Scheduler::Schedule
    Properties:
      Name: !Sub '${Prefix}-${Client}-ec2-start-schedule'
      Description: !Sub 'Start EC2 instance for ${Prefix}-${Client} via SSM Automation.'
      State: !Ref SchedulerState
      FlexibleTimeWindow:
        Mode: 'OFF'
      ScheduleExpression: !Ref EC2StartCronExpression
      ScheduleExpressionTimezone: 'Asia/Tokyo'
      Target:
        Arn: 'arn:aws:scheduler:::aws-sdk:ssm:startAutomationExecution'
        RoleArn: !GetAtt EC2SchedulerRole.Arn
        Input:
          Fn::Sub:
            - '{"DocumentName":"AWS-StartEC2Instance","Parameters":{"InstanceId":["${TargetInstanceId}"]}}'
            - TargetInstanceId:
                Fn::ImportValue:
                  !Sub '${Prefix}::${Client}::InstanceId'

  # 停止用スケジュール
  EC2StopSchedule:
    Type: AWS::Scheduler::Schedule
    Properties:
      Name: !Sub '${Prefix}-${Client}-ec2-stop-schedule'
      Description: !Sub 'Stop EC2 instance for ${Prefix}-${Client} via SSM Automation.'
      State: !Ref SchedulerState
      FlexibleTimeWindow:
        Mode: 'OFF'
      ScheduleExpression: !Ref EC2StopCronExpression
      ScheduleExpressionTimezone: 'Asia/Tokyo'
      Target:
        Arn: 'arn:aws:scheduler:::aws-sdk:ssm:startAutomationExecution'
        RoleArn: !GetAtt EC2SchedulerRole.Arn
        Input:
          Fn::Sub:
            - '{"DocumentName":"AWS-StopEC2Instance","Parameters":{"InstanceId":["${TargetInstanceId}"]}}'
            - TargetInstanceId:
                Fn::ImportValue:
                  !Sub '${Prefix}::${Client}::InstanceId'

```

## Amazon RDSで自動起動したデータベースインスタンスを停止する例

[Stopping an Automatically Started Database Instance with Amazon RDS](https://aws.amazon.com/jp/blogs/architecture/field-notes-stopping-an-automatically-started-database-instance-with-amazon-rds/)
このブログでAWSからはStep Functionsを用いた例が紹介されているが、やや大袈裟なためLambdaで対応
```
AWSTemplateFormatVersion: 2010-09-09
Description: The template for pairing RDS start/stop with EC2 instance state (handles RDS 7-day auto-restart).

# -------------------------
# <<< Parameters 
# -------------------------
Parameters:
  Prefix:
    Type: String

  Client:
    Type: String

Resources:
  #==========================
  # Lambda (EC2状態にRDSを追従させる)
  #==========================
  RDSPairingRole:
    Type: AWS::IAM::Role
    Properties:
      Path: "/"
      RoleName: !Sub '${Prefix}-${Client}-rds-pairing-role'
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      MaxSessionDuration: 3600
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: !Sub '${Prefix}-${Client}-rds-pairing-policy'
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - ec2:DescribeInstances
                Resource: '*'
              - Effect: Allow
                Action:
                  - rds:DescribeDBInstances
                  - rds:StartDBInstance
                  - rds:StopDBInstance
                Resource:
                  - !Sub
                    - 'arn:aws:rds:${AWS::Region}:${AWS::AccountId}:db:${TargetDBInstanceId}'
                    - TargetDBInstanceId:
                        Fn::ImportValue:
                          !Sub '${Prefix}::${Client}::DBInstanceIdentifier'

  RDSPairingFunction:
    Type: AWS::Lambda::Function
    DependsOn:
      - RDSPairingRole
    Properties:
      Description: 'Keep RDS start/stop state in sync with the paired EC2 instance.'
      FunctionName: !Sub '${Prefix}-${Client}-rds-pairing'
      Handler: index.handler
      Runtime: python3.12
      Role: !GetAtt RDSPairingRole.Arn
      Timeout: 900
      Environment:
        Variables:
          EC2_INSTANCE_ID:
            Fn::ImportValue:
              !Sub '${Prefix}::${Client}::InstanceId'
          RDS_INSTANCE_ID:
            Fn::ImportValue:
              !Sub '${Prefix}::${Client}::DBInstanceIdentifier'
      Code:
        ZipFile: |
          import boto3
          import os
          import time
          import logging

          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          ec2 = boto3.client('ec2')
          rds = boto3.client('rds')

          EC2_INSTANCE_ID = os.environ['EC2_INSTANCE_ID']
          RDS_INSTANCE_ID = os.environ['RDS_INSTANCE_ID']

          # RDSの状態を判定するのは安定ステータスのときだけ。
          # 'starting'/'stopping'/'rebooting'/'modifying'等
          # 遷移中ステータスをスルーして安定ステータスになるまで待つ。
          STABLE_STATUSES = {'available', 'stopped'}
          POLL_INTERVAL_SECONDS = 30
          MAX_WAIT_SECONDS = 840  # Lambda Timeout(900秒)に対して余裕を持たせる

          def get_rds_status():
              return rds.describe_db_instances(
                  DBInstanceIdentifier=RDS_INSTANCE_ID
              )['DBInstances'][0]['DBInstanceStatus']

          def wait_until_stable(status):
              waited = 0
              while status not in STABLE_STATUSES and waited < MAX_WAIT_SECONDS:
                  logger.info(f'RDS status is not stable yet ({status}). Waiting {POLL_INTERVAL_SECONDS}s...')
                  time.sleep(POLL_INTERVAL_SECONDS)
                  waited += POLL_INTERVAL_SECONDS
                  status = get_rds_status()
              return status

          def handler(event, context):
              logger.info(event)

              ec2_state = ec2.describe_instances(
                  InstanceIds=[EC2_INSTANCE_ID]
              )['Reservations'][0]['Instances'][0]['State']['Name']

              rds_status = wait_until_stable(get_rds_status())

              logger.info(f'EC2 state={ec2_state}, RDS status={rds_status}')

              if ec2_state == 'running' and rds_status == 'stopped':
                  logger.info('Starting RDS to match EC2 state.')
                  rds.start_db_instance(DBInstanceIdentifier=RDS_INSTANCE_ID)
              elif ec2_state == 'stopped' and rds_status == 'available':
                  logger.info('Stopping RDS to match EC2 state.')
                  rds.stop_db_instance(DBInstanceIdentifier=RDS_INSTANCE_ID)
              else:
                  logger.info('No action needed (already in sync, or RDS status did not stabilize within the wait limit).')

  #==========================
  # EventBridge Rule (EC2状態変化を検知)
  #==========================
  EC2StateChangeRule:
    Type: AWS::Events::Rule
    Properties:
      Name: !Sub '${Prefix}-${Client}-ec2-statechange-rule'
      Description: !Sub 'Trigger RDS pairing when the EC2 instance for ${Prefix}-${Client} starts or stops.'
      EventPattern:
        source:
          - aws.ec2
        detail-type:
          - 'EC2 Instance State-change Notification'
        detail:
          state:
            - running
            - stopped
          instance-id:
            - Fn::ImportValue:
                !Sub '${Prefix}::${Client}::InstanceId'
      State: ENABLED
      Targets:
        - Id: !Sub '${Prefix}-${Client}-rds-pairing-target'
          Arn: !GetAtt RDSPairingFunction.Arn

  EC2StateChangeRuleLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref RDSPairingFunction
      Action: lambda:InvokeFunction
      Principal: events.amazonaws.com
      SourceArn: !GetAtt EC2StateChangeRule.Arn

  #==========================
  # EventBridge Rule (RDSの7日強制自動起動イベントを検知)
  #==========================
  # RDS-EVENT-0154: 停止上限(7日)超過によりRDSが強制的に自動起動された際にのみ発火するイベント。
  # RDS-EVENT-0088(DB instance started全般)は手動起動時にも発火するため対象としない。
  RDSStateChangeRule:
    Type: AWS::Events::Rule
    Properties:
      Name: !Sub '${Prefix}-${Client}-rds-statechange-rule'
      Description: !Sub 'Trigger RDS pairing only when RDS for ${Prefix}-${Client} is force-restarted after exceeding the 7-day stopped limit.'
      EventPattern:
        source:
          - aws.rds
        detail-type:
          - 'RDS DB Instance Event'
        detail:
          SourceType:
            - DB_INSTANCE
          SourceArn:
            - Fn::ImportValue:
                !Sub '${Prefix}::${Client}::DBInstanceArn'
          EventID:
            - RDS-EVENT-0154
      State: ENABLED
      Targets:
        - Id: !Sub '${Prefix}-${Client}-rds-statechange-target'
          Arn: !GetAtt RDSPairingFunction.Arn

  RDSStateChangeRuleLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref RDSPairingFunction
      Action: lambda:InvokeFunction
      Principal: events.amazonaws.com
      SourceArn: !GetAtt RDSStateChangeRule.Arn

```
- [Amazon RDS イベントカテゴリとイベントメッセージ](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/USER_Events.Messages.html)
- [Amazon EC2 インスタンスの状態変更イベント](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/monitoring-instance-state-changes.html)

0 件のコメント:

コメントを投稿

人気の投稿