SSM Change Calendar で EC2 起動停止スケジュールを管理してみた

概要

以下のような構成をセットアップして、カレンダー形式で EC2 インスタンスの起動停止スケジュールを管理してみます。

Change Calendar とは

AWS Systems Manager (SSM) の一機能である Change Calendar は、同じく SSM の Automation などのアクションを実行できる日時をカレンダー形式で管理するものです。
上記画像のカレンダーは DEFAULT_CLOSED という設定になっています。
この場合、カレンダーに予定(イベント)を作成すると、そのイベント中は OPEN というステータスになります。

カレンダーのステータスが変更される時、EventBridge イベントが発生します。
本記事ではこれを利用して EC2 インスタンスの起動停止スケジュールを管理します。
AWS Systems ManagerChange Calendar Events

準備・設定

以下のうち、カレンダーと EC2 インスタンスを作成しておきます。 カレンダーは DEFAULT_CLOSED で作成します。
カレンダーとインスタンスを作成したら、以下の CloudFormation テンプレートをデプロイします。
AWSTemplateFormatVersion: 2010-09-09

Parameters: 
  Prefix:
    Type: String
  InstanceId:
    Type: AWS::EC2::Instance::Id
  CalendarName:
    Type: String

Resources:
  StartEventsRule:
    Type: AWS::Events::Rule
    Properties:
      Name: !Sub ${Prefix}-start-rule
      EventPattern:
        source:
          - aws.ssm
        detail-type:
          - Calendar State Change
        resources:
          - !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:document/${CalendarName}
        detail:
          state:
            - OPEN
      State: ENABLED
      Targets:
        - Id: AWS-StartEC2Instance
          Arn: !Sub arn:aws:ssm:${AWS::Region}::automation-definition/AWS-StartEC2Instance:$DEFAULT
          RoleArn: !GetAtt EventsRole.Arn
          Input: !Sub "{\"InstanceId\":[\"${InstanceId}\"]}"
          DeadLetterConfig:
            Arn: !GetAtt DLQueue.Arn

  StopEventsRule:
    Type: AWS::Events::Rule
    Properties:
      Name: !Sub ${Prefix}-stop-rule
      EventPattern:
        source:
          - aws.ssm
        detail-type:
          - Calendar State Change
        resources:
          - !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:document/${CalendarName}
        detail:
          state:
            - CLOSED
      State: ENABLED
      Targets:
        - Id: AWS-StopEC2Instance
          Arn: !Sub arn:aws:ssm:${AWS::Region}::automation-definition/AWS-StopEC2Instance:$DEFAULT
          RoleArn: !GetAtt EventsRole.Arn
          Input: !Sub "{\"InstanceId\":[\"${InstanceId}\"]}"
          DeadLetterConfig:
            Arn: !GetAtt DLQueue.Arn

  EventsRole:    
    Type: AWS::IAM::Role
    Properties: 
      RoleName: !Sub ${Prefix}-events-role
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - events.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: policy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: ssm:StartAutomationExecution
                Resource:
                  - !Sub arn:aws:ssm:${AWS::Region}::automation-definition/AWS-StartEC2Instance:$DEFAULT
                  - !Sub arn:aws:ssm:${AWS::Region}::automation-definition/AWS-StopEC2Instance:$DEFAULT
              - Effect: Allow
                Action:
                  - ec2:StartInstances
                  - ec2:StopInstances
                Resource: !Sub arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:instance/${InstanceId}
              - Effect: Allow
                Action: ec2:DescribeInstanceStatus
                Resource: "*"

  DLQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: !Sub ${Prefix}-dlqueue

  QueuePolicy:
    Type: AWS::SQS::QueuePolicy
    Properties:
      PolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: events.amazonaws.com
            Action: SQS:SendMessage
            Resource: !GetAtt DLQueue.Arn
      Queues:
        - !Ref DLQueue
次に、カレンダーに予定(イベント)を作成します。
イベント開始時(カレンダーが OPEN になった時)、インスタンスが起動されます。
逆にイベント終了時(カレンダーが CLOSED になった時)は、インスタンスが停止されます。
つまり、インスタンスを稼働させておきたい時間帯を予定(イベント)として登録しておく、という使い方になっています。
※Change Calendar の仕様で開始~終了まで5分以上空ける必要があります。

動作確認

カレンダーに登録したイベント通りにインスタンスが起動・停止されているか確認してみましょう。
また、SSM Automation コンソールから起動・停止の実行履歴を確認することが出来ます。

補足

EventBridge で指定するリソースARN

CloudFormation テンプレートの AWS::Events::Rule の EventPattern を見ると、resources に SSM ドキュメントの ARN が指定されてます。
Change Calendar のイベントなので、最初は “arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:calendar/${CalendarName}” のようになると思っていたのですが違いました。
source:
  - aws.ssm
detail-type:
  - Calendar State Change
resources:
  - !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:document/${CalendarName}
detail:
  state:
    - OPEN
以下に答えがありました。
AWS Systems Manager Change Calendar
Change Calendar エントリを作成すると、ChangeCalendar タイプの Systems Manager ドキュメントが作成されます。
このSSMドキュメントはマネジメントコンソール上では確認できませんでしたが、CLI から参照することは出来ました。
$ aws ssm describe-document --name StartStopScheduler
{
    "Document": {
        "Hash": "xxxxxxxxxx",
        "HashType": "Sha256",
        "Name": "StartStopScheduler",
        "Owner": "111122223333",
        "CreatedDate": "2022-05-31T03:29:05.315000+00:00",
        "Status": "Active",
        "DocumentVersion": "17",
        "Description": "",
        "PlatformTypes": [],
        "DocumentType": "ChangeCalendar",
        "LatestVersion": "17",
        "DefaultVersion": "17",
        "DocumentFormat": "TEXT",
        "Tags": [],
        "Category": [],
        "CategoryEnum": []
    }
}

SQS について

今回の CloudFormation テンプレートには、EventBridge + SSM Automation の実行が失敗した場合のデッドレターキュー(SQS)を設定していました。 EventBridge + SSM Automation を設定した場合の困りごとの1つが、Automation の実行できなかった場合のトラブルシューティングです。
イベントパターンに該当するイベントが発生したのに Automation が実行されないような場合、その失敗原因を示すログが見当たりません。

ですが Automation が失敗した場合のデッドレターキューを設定しておくと、そのあたりのトラブルシューティングが容易になります。
Automation が実行できなかった際、そのイベントが設定したキュー(SQS)にメッセージングされますが、そのメッセージの属性に有用な ERROR_MESSAGE が記載されています。