📌

EC2、RDS、Aurora を CloudFormation で自動定期起動/停止してみた

に公開

はじめに

検証環境など限られた予算で実行しなければいけない場合、EC2 や RDS、Aurora の停止を忘れると致命的・・・
そこで、特定時間内のみで実行/停止できないかと思い調べてみました。

仕様

指定した EC2 or RDS or Aurora について、指定時間(曜日)になったら起動/停止
例えば、月から金曜日の 9:00 から 17:00 の間だけ起動など

EC2 の自動定期起動/停止

YAMLテンプレート

ec2-start-stop.yaml(折り畳んでます)
AWSTemplateFormatVersion: '2010-09-09'
Description: EC2 instance start/stop schedule

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Resource Configuration
        Parameters:
          - InstanceId
          - StartDayOfWeek
          - StartHours
          - StartMinutes
          - StopDayOfWeek
          - StopHours
          - StopMinutes

Parameters:
  InstanceId:
    Type: String
    Description: Instance ID to start and stop
    ConstraintDescription: Please enter Instance ID
# ---Start Parameter--- #
  StartMinutes:
    Type: Number
    Description: Specified minutes starting instances
    ConstraintDescription: Please enter a number of 0-59
    MinValue: 0
    MaxValue: 59
  StartHours:
    Description: Specified hours starting instances
    ConstraintDescription: Please enter a number of 0-23
    Type: Number
    MinValue: 0
    MaxValue: 23
  StartDayOfWeek:
    Type: String
    Description: The day of the week starting instances
    ConstraintDescription: Please enter the day of the week
    AllowedPattern: (\*|SUN|MON|TUE|WED|THU|FRI|SAT)?((,|-)(SUN|MON|TUE|WED|THU|FRI|SAT))?
# ---Stop Parameter--- #
  StopMinutes:
    Type: Number
    Description: Specified minutes starting instances
    ConstraintDescription: Please enter a number of 0-59
    MinValue: 0
    MaxValue: 59
  StopHours:
    Description: Specified hours starting instances
    ConstraintDescription: Please enter a number of 0-23
    Type: Number
    MinValue: 0
    MaxValue: 23
  StopDayOfWeek:
    Type: String
    Description: The day of the week starting instances
    ConstraintDescription: Please enter the day of the week
    AllowedPattern: (\*|SUN|MON|TUE|WED|THU|FRI|SAT)?((,|-)(SUN|MON|TUE|WED|THU|FRI|SAT))?

Conditions:
  ExistInstanceId: !Not [!Equals [!Ref InstanceId, ""]]

  ExistStartSchedule:
    !And
      - !Or
        - !Not [!Equals [!Ref StartDayOfWeek, ""]]
        - !Not [!Equals [!Ref StartHours, ""]]
        - !Not [!Equals [!Ref StartMinutes, ""]]
      - !Condition ExistInstanceId

  ExistStopSchedule:
    !And
      - !Or
        - !Not [!Equals [!Ref StopDayOfWeek, ""]]
        - !Not [!Equals [!Ref StopHours, ""]]
        - !Not [!Equals [!Ref StopMinutes, ""]]
      - !Condition ExistInstanceId

Resources:
  ScheduleRole:
    Type: AWS::IAM::Role
    Condition: ExistInstanceId
    Properties: 
      RoleName: !Sub ${AWS::StackName}-ec2-schedule-role
      Path: /
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service: "scheduler.amazonaws.com"
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: !Sub ${AWS::StackName}-ec2-schedule-role-policy
          PolicyDocument:
            Version: "2012-10-17"
            Statement: 
              - Sid: "AllowExecutionOfEC2StartStop"
                Effect: "Allow"
                Action:
                  - "ec2:StartInstances"
                  - "ec2:StopInstances"
                Resource: "*"
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-ec2-schedule-role

  ScheduleGroup:
    Type: AWS::Scheduler::ScheduleGroup
    Condition: ExistInstanceId
    Properties: 
      Name: !Sub ${AWS::StackName}-ec2-schedule-group

  EC2StartSchedule:
    Type: AWS::Scheduler::Schedule
    Condition: ExistStartSchedule
    DependsOn:
      - ScheduleGroup
      - ScheduleRole
    Properties: 
      Description: !Sub ${AWS::StackName}-Start EC2 Instance
      FlexibleTimeWindow: 
        Mode: "OFF"
      GroupName: !Ref ScheduleGroup
      Name: !Sub ${AWS::StackName}-ec2-start-schedule
      ScheduleExpression: !Sub "cron(${StartMinutes} ${StartHours} ? * ${StartDayOfWeek} *)"
      ScheduleExpressionTimezone: "Asia/Tokyo"
      State: "ENABLED"
      Target: 
        Arn: "arn:aws:scheduler:::aws-sdk:ec2:startInstances"
        Input: !Join
          - ""
          - - "{\"InstanceIds\": ["
            - !If [ExistInstanceId, !Sub "\"${InstanceId}\"", ""]
            - "]}"
        RoleArn: !GetAtt ScheduleRole.Arn
        RetryPolicy: 
          MaximumRetryAttempts: 0

  EC2StopSchedule:
    Type: AWS::Scheduler::Schedule
    Condition: ExistStopSchedule
    DependsOn:
      - ScheduleGroup
      - ScheduleRole
    Properties: 
      Description: !Sub ${AWS::StackName}-Stop EC2 Instance
      FlexibleTimeWindow: 
        Mode: "OFF"
      GroupName: !Ref ScheduleGroup
      Name: !Sub ${AWS::StackName}-ec2-stop-schedule
      ScheduleExpression: !Sub "cron(${StopMinutes} ${StopHours} ? * ${StopDayOfWeek} *)"
      ScheduleExpressionTimezone: "Asia/Tokyo"
      State: "ENABLED"
      Target: 
        Arn: "arn:aws:scheduler:::aws-sdk:ec2:stopInstances"
        Input: !Join
          - ""
          - - "{\"InstanceIds\": ["
            - !If [ExistInstanceId, !Sub "\"${InstanceId}\"", ""]
            - "]}"
        RoleArn: !GetAtt ScheduleRole.Arn
        RetryPolicy: 
          MaximumRetryAttempts: 0

YAMLパラメーターの解説

パラメーター名 解説 備考
InstanceId 制御する EC2 のインスタンス ID
StartDayOfWeek 起動する曜日 *,-,SUN,MON,TUE,WED,THU,FRI,SAT
StartHours 起動する時 0 から 23
StartMinutes 起動する分 0 から 59
StopDayOfWeek 停止する曜日 *,-,SUN,MON,TUE,WED,THU,FRI,SAT
StopHours 停止する時 0 から 23
StopMinutes 停止する分 0 から 59

RDS の自動定期起動/停止

YAMLテンプレート

rds-start-stop.yaml(折り畳んでます)
AWSTemplateFormatVersion: '2010-09-09'
Description: RDS instance start/stop schedule

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Resource Configuration
        Parameters:
          - InstanceId
          - StartDayOfWeek
          - StartHours
          - StartMinutes
          - StopDayOfWeek
          - StopHours
          - StopMinutes

Parameters:
  InstanceId:
    Type: String
    Description: DB Instance ID to start and stop
    ConstraintDescription: Please enter DB Instance ID
# ---Start Parameter--- #
  StartMinutes:
    Type: Number
    Description: Specified minutes starting instances
    ConstraintDescription: Please enter a number of 0-59
    MinValue: 0
    MaxValue: 59
  StartHours:
    Description: Specified hours starting instances
    ConstraintDescription: Please enter a number of 0-23
    Type: Number
    MinValue: 0
    MaxValue: 23
  StartDayOfWeek:
    Type: String
    Description: The day of the week starting instances
    ConstraintDescription: Please enter the day of the week
    AllowedPattern: (\*|SUN|MON|TUE|WED|THU|FRI|SAT)?((,|-)(SUN|MON|TUE|WED|THU|FRI|SAT))?
# ---Stop Parameter--- #
  StopMinutes:
    Type: Number
    Description: Specified minutes starting instances
    ConstraintDescription: Please enter a number of 0-59
    MinValue: 0
    MaxValue: 59
  StopHours:
    Description: Specified hours starting instances
    ConstraintDescription: Please enter a number of 0-23
    Type: Number
    MinValue: 0
    MaxValue: 23
  StopDayOfWeek:
    Type: String
    Description: The day of the week starting instances
    ConstraintDescription: Please enter the day of the week
    AllowedPattern: (\*|SUN|MON|TUE|WED|THU|FRI|SAT)?((,|-)(SUN|MON|TUE|WED|THU|FRI|SAT))?

Conditions:
  ExistInstanceId: !Not [!Equals [!Ref InstanceId, ""]]

  ExistStartSchedule:
    !And
      - !Or
        - !Not [!Equals [!Ref StartDayOfWeek, ""]]
        - !Not [!Equals [!Ref StartHours, ""]]
        - !Not [!Equals [!Ref StartMinutes, ""]]
      - !Condition ExistInstanceId

  ExistStopSchedule:
    !And
      - !Or
        - !Not [!Equals [!Ref StopDayOfWeek, ""]]
        - !Not [!Equals [!Ref StopHours, ""]]
        - !Not [!Equals [!Ref StopMinutes, ""]]
      - !Condition ExistInstanceId

Resources:
  ScheduleRole:
    Type: AWS::IAM::Role
    Condition: ExistInstanceId
    Properties: 
      RoleName: !Sub ${AWS::StackName}-rds-schedule-role
      Path: /
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service: "scheduler.amazonaws.com"
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: !Sub ${AWS::StackName}-rds-schedule-role-policy
          PolicyDocument:
            Version: "2012-10-17"
            Statement: 
              - Sid: "AllowExecutionOfRDSStartStop"
                Effect: "Allow"
                Action:
                  - rds:StartDBInstance
                  - rds:StopDBInstance
                Resource: "*"
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-rds-schedule-role

  ScheduleGroup:
    Type: AWS::Scheduler::ScheduleGroup
    Condition: ExistInstanceId
    Properties: 
      Name: !Sub ${AWS::StackName}-rds-schedule-group

  RDSStartSchedule:
    Type: AWS::Scheduler::Schedule
    Condition: ExistStartSchedule
    DependsOn:
      - ScheduleGroup
      - ScheduleRole
    Properties: 
      Description: "Start RDS Instance"
      FlexibleTimeWindow: 
        Mode: "OFF"
      GroupName: !Ref ScheduleGroup
      Name: !Sub ${AWS::StackName}-rds-start-schedule
      ScheduleExpression: !Sub "cron(${StartMinutes} ${StartHours} ? * ${StartDayOfWeek} *)"
      ScheduleExpressionTimezone: "Asia/Tokyo"
      State: "ENABLED"
      Target: 
        Arn: "arn:aws:scheduler:::aws-sdk:rds:startDBInstance"
        Input: !Sub |-
          {
            "DbInstanceIdentifier": "${InstanceId}"
          }
        RoleArn: !GetAtt ScheduleRole.Arn
        RetryPolicy: 
          MaximumRetryAttempts: 0

  RDSStopSchedule:
    Type: AWS::Scheduler::Schedule
    Condition: ExistStopSchedule
    DependsOn:
      - ScheduleGroup
      - ScheduleRole
    Properties: 
      Description: "Stop RDS Instance"
      FlexibleTimeWindow: 
        Mode: "OFF"
      GroupName: !Ref ScheduleGroup
      Name: !Sub ${AWS::StackName}-rds-stop-schedule
      ScheduleExpression: !Sub "cron(${StopMinutes} ${StopHours} ? * ${StopDayOfWeek} *)"
      ScheduleExpressionTimezone: "Asia/Tokyo"
      State: "ENABLED"
      Target: 
        Arn: "arn:aws:scheduler:::aws-sdk:rds:stopDBInstance"
        Input: !Sub |-
          {
            "DbInstanceIdentifier": "${InstanceId}"
          }
        RoleArn: !GetAtt ScheduleRole.Arn
        RetryPolicy: 
          MaximumRetryAttempts: 0

YAMLパラメーターの解説

パラメーター名 解説 備考
InstanceId 制御する RDS の DB 識別子
StartDayOfWeek 起動する曜日 *,-,SUN,MON,TUE,WED,THU,FRI,SAT
StartHours 起動する時 0 から 23
StartMinutes 起動する分 0 から 59
StopDayOfWeek 停止する曜日 *,-,SUN,MON,TUE,WED,THU,FRI,SAT
StopHours 停止する時 0 から 23
StopMinutes 停止する分 0 から 59

Aurora の自動定期起動/停止

YAMLテンプレート

aurora-start-stop.yaml(折り畳んでます)
AWSTemplateFormatVersion: '2010-09-09'
Description: Aurora DB cluster start/stop schedule

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Resource Configuration
        Parameters:
          - ClusterId
          - StartDayOfWeek
          - StartHours
          - StartMinutes
          - StopDayOfWeek
          - StopHours
          - StopMinutes

Parameters:
  ClusterId:
    Type: String
    Description: Cluster ID to start and stop
    ConstraintDescription: Please enter Cluster ID
# ---Start Parameter--- #
  StartMinutes:
    Type: Number
    Description: Specified minutes starting instances
    ConstraintDescription: Please enter a number of 0-59
    MinValue: 0
    MaxValue: 59
  StartHours:
    Description: Specified hours starting instances
    ConstraintDescription: Please enter a number of 0-23
    Type: Number
    MinValue: 0
    MaxValue: 23
  StartDayOfWeek:
    Type: String
    Description: The day of the week starting instances
    ConstraintDescription: Please enter the day of the week
    AllowedPattern: (\*|SUN|MON|TUE|WED|THU|FRI|SAT)?((,|-)(SUN|MON|TUE|WED|THU|FRI|SAT))?
# ---Stop Parameter--- #
  StopMinutes:
    Type: Number
    Description: Specified minutes starting instances
    ConstraintDescription: Please enter a number of 0-59
    MinValue: 0
    MaxValue: 59
  StopHours:
    Description: Specified hours starting instances
    ConstraintDescription: Please enter a number of 0-23
    Type: Number
    MinValue: 0
    MaxValue: 23
  StopDayOfWeek:
    Type: String
    Description: The day of the week starting instances
    ConstraintDescription: Please enter the day of the week
    AllowedPattern: (\*|SUN|MON|TUE|WED|THU|FRI|SAT)?((,|-)(SUN|MON|TUE|WED|THU|FRI|SAT))?

Conditions:
  ExistClusterId: !Not [!Equals [!Ref ClusterId, ""]]

  ExistStartSchedule:
    !And
      - !Or
        - !Not [!Equals [!Ref StartDayOfWeek, ""]]
        - !Not [!Equals [!Ref StartHours, ""]]
        - !Not [!Equals [!Ref StartMinutes, ""]]
      - !Condition ExistClusterId

  ExistStopSchedule:
    !And
      - !Or
        - !Not [!Equals [!Ref StopDayOfWeek, ""]]
        - !Not [!Equals [!Ref StopHours, ""]]
        - !Not [!Equals [!Ref StopMinutes, ""]]
      - !Condition ExistClusterId

Resources:
  ScheduleRole:
    Type: AWS::IAM::Role
    Condition: ExistClusterId
    Properties: 
      RoleName: !Sub ${AWS::StackName}-aurora-schedule-role
      Path: /
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service: "scheduler.amazonaws.com"
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: !Sub ${AWS::StackName}-aurora-schedule-role-policy
          PolicyDocument:
            Version: "2012-10-17"
            Statement: 
              - Sid: "AllowExecutionOfAuroraStartStop"
                Effect: "Allow"
                Action:
                  - rds:StartDBCluster
                  - rds:StopDBCluster
                Resource: "*"
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-aurora-schedule-role

  ScheduleGroup:
    Type: AWS::Scheduler::ScheduleGroup
    Condition: ExistClusterId
    Properties: 
      Name: !Sub ${AWS::StackName}-aurora-schedule-group

  AuroraStartSchedule:
    Type: AWS::Scheduler::Schedule
    Condition: ExistStartSchedule
    DependsOn:
      - ScheduleGroup
      - ScheduleRole
    Properties: 
      Description: "Start Aurora Cluster"
      FlexibleTimeWindow: 
        Mode: "OFF"
      GroupName: !Ref ScheduleGroup
      Name: !Sub ${AWS::StackName}-aurora-start-schedule
      ScheduleExpression: !Sub "cron(${StartMinutes} ${StartHours} ? * ${StartDayOfWeek} *)"
      ScheduleExpressionTimezone: "Asia/Tokyo"
      State: "ENABLED"
      Target: 
        Arn: "arn:aws:scheduler:::aws-sdk:rds:startDBCluster"
        Input: !Sub |-
          {
            "DbClusterIdentifier": "${ClusterId}"
          }
        RoleArn: !GetAtt ScheduleRole.Arn
        RetryPolicy: 
          MaximumRetryAttempts: 0

  AuroraStopSchedule:
    Type: AWS::Scheduler::Schedule
    Condition: ExistStopSchedule
    DependsOn:
      - ScheduleGroup
      - ScheduleRole
    Properties: 
      Description: "Stop Aurora Cluster"
      FlexibleTimeWindow: 
        Mode: "OFF"
      GroupName: !Ref ScheduleGroup
      Name: !Sub ${AWS::StackName}-aurora-stop-schedule
      ScheduleExpression: !Sub "cron(${StopMinutes} ${StopHours} ? * ${StopDayOfWeek} *)"
      ScheduleExpressionTimezone: "Asia/Tokyo"
      State: "ENABLED"
      Target: 
        Arn: "arn:aws:scheduler:::aws-sdk:rds:stopDBCluster"
        Input: !Sub |-
          {
            "DbClusterIdentifier": "${ClusterId}"
          }
        RoleArn: !GetAtt ScheduleRole.Arn
        RetryPolicy: 
          MaximumRetryAttempts: 0

YAMLパラメーターの解説

パラメーター名 解説 備考
ClusterId 制御する Aurora クラスター ID
StartDayOfWeek 起動する曜日 *,-,SUN,MON,TUE,WED,THU,FRI,SAT
StartHours 起動する時 0 から 23
StartMinutes 起動する分 0 から 59
StopDayOfWeek 停止する曜日 *,-,SUN,MON,TUE,WED,THU,FRI,SAT
StopHours 停止する時 0 から 23
StopMinutes 停止する分 0 から 59

Cron について

AWS の Cron は、Apache とお作法が違います。
このテンプレートでは、曜日、時、分のみ設定を可能としていますので、以下を参考に試してみてください。
https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/reference-cron-and-rate-expressions.html

Cron の設定値例

  • 月から金の 9:00 に起動したい
    cron(0 9 ? * MON-FRI *)
  • 月から金の 17:00 に停止したい
    cron(0 17 ? * MON-FRI *)
  • 該当のインスタンスが起動していたら、18:00 に停止したい
    cron(0 18 ? * * *)

確認方法

AWS 管理コンソールの Amazon EventBridge Scheduler で Cron の設定値を確認できます。
次回実行時間も確認できますので、設定値が予定した時間となっているかご確認ください。
設定がうまくいけば、EC2 および RDS、Aurora の起動/停止が制御できます!

注意

  • 例えば 9:00 に起動、9:01 に停止の設定を行った場合、インスタンスの起動に時間がかかり 9:01 時点で起動処理中だったとき、インスタンスの停止が行われませんでした。
    試したところ 10 分ぐらい間をあけて設定すると良さそうです。
    (こんな短時間に起動/停止することはないと思いますが・・・)
  • RDS と Aurora クラスタは 停止から 7 日経過すると自動で再起動する仕様のようです。

まとめ

検証環境は月から金の 9:00 から 17:00 までの利用としたい・・・
EC2 はバッチ処理利用で夜中に 1 時間だけ起動したい・・・
などあると思います。
そういった場合に活用できるかなと思いますので、参考にしてみてください!
この記事が、誰かの役に立てば幸いです。

参考サイト

株式会社グローバルネットコア 有志コミュニティ(β)

Discussion