AWS Systems Manager Automation用のRun BookをCfnで作成 ~EC2とRedshiftをまとめて起動・停止~

EC2&Redshiftのインスタンスを、使わないとき止めておくためのAutomation実行用SSM Document (Runbook)を作成しました。
2021.11.18

データアナリティクス(DA)事業部コンサルティングチームのnkhrです。

DWHのPoC環境(EC2+Redshift)のインスタンスを、使わないときに止めておくためのAutomation実行用SSM Document (Runbook)を作成しました。Redshift単体の機能として、スケジュール起動、停止ができますが、今回は環境内のインスタンス部分(EC2+Redshift)をまとめて起動・停止できるようにしました。

本ブログでは、Run Book作成と、Run Bookの定期実行のCloudformation部分を共有します。サンプルは最後に添付しています。

作成したリソース

テンプレートでは、以下のリソースを作成します。

  • Automation Run book実行用Role
  • EC2&Redshiftをまとめて停止するRunBook
  • EC2&Redshiftをまとめて起動するRunBook
  • 指定時間に停止用Run Bookを実行するためのEvent

テンプレートのCloudformation実行画面

  • ClusterIdentifier:停止したいRedshiftクラスタの識別子。構築時にRunBookを作成する場合はRedshiftを作成するStackのOutputsを設定すればよい。
  • CronPattern:Eventで定期実行する時間を指定。デフォルトはJST 18時に毎日停止処理を実行する設定になっている(起動の自動実行Eventは含まれていない)
  • EventInitState:CloudformationでEventを作成した時のEventの初期値。デフォルトはDISABLEDとしているのでEventは作成するが定期実行は行われない
  • TagKey:停止・起動対象とするEC2のタグKey名を指定する。アカウント内でこのタグのKeyを持ち、かつ、Keyの値(Value)が「true」の場合に起動・停止対象となる

Redshiftの停止に関する留意点

RedshiftはClusterAvailableStatusがAvailableの時以外にPauseCluserを発行すると失敗します。そのため、Available状態でない(Snapshot作成時やメンテナンス中など)の場合は、停止処理をスキップします。そのため、タイミングによってはRedshiftが停止できていない場合があります。

自動停止を設定する場合は、メンテナンスなどの時間外を設定するか、または、Redshift機能のスケジュール停止を利用する(作成するRunBookは使わない)とよいと思います。

下記のリンクにRedshift機能でのスケジュール停止設定の方法が書かれています。

作成したAutomation RunBook

SSM Automationで実行可能な、SSM Documentです。下図のように、SSM Documentの「自己所有」の中に作成したRunbookが表示されます。

作成したRunBook(Automation実行用SSM Document)を開くと、右上に「オートメーションを実行する」ボタンが表示されています。このボタンをクリックすれば、手動でのAutomation実行が可能です。

定期実行する場合は、EventBridgeのスケジュールに対象のRunBookを登録します。(こちらもテンプレートで自動作成されます)

手動実行イメージ

作成したドキュメントの「オートメーションを実行する」をクリックし、パラメータ入力画面(タグの設定)で「実行」をクリックすると、下図のような実行画面が表示されます。ステップ名の処理が順番に実行され、「ステータス」の部分に「失敗」や「成功」の結果が表示されます。

各ステップは以下のような処理をおこないます。

  • StopEC2Instances
    • パラメータ入力画面で指定したタグキーの値がtrueのインスタンスを停止します
  • RedshiftStatusCheck
    • RedshiftにAWS APIでDescribeClusterを実行し、ClusterAvailableStatusを取得します
  • ChoiceStopOrExist
    • RedshiftStatusCheckの出力結果が「Available」かどうかを条件判定し、「Available」の場合はStopRedshiftClusterのステップを実行し、それ以外はsleepActionForSkipのステップを実行します
  • sleepActionForSkip
    • Availableでない場合に実行されるダミーステップです。ダミーアクションが見つからなかったので一番影響がなさそうなsleepアクションを使っています。10秒スリープ後に終了します。
  • StopRedshiftCluster
    • RedshiftにAWS APIでPauseClusterを実行します。この後のステップがないため、処理が終了します。

サンプルテンプレート

Cfnテンプレート(▶の部分をクリックするとコードが表示されます)
AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  TagKey:
    Type: String
    Default: AutoStop  # 停止・起動対象とするEC2のタグ名
  ClusterIdentifier:
    Type: String  # Redshift Clusterの識別子(NestedStackでRedshift作成Stackの結果から設定してもよい)
  CronPattern:
    Type: String
    Default: 00 09 * * ? *  # 毎日PM18(JST)にイベント実行
  EventInitState:
    Type: String
    Default: DISABLED  # Cfnで作成したイベントの初期ステータス

Resources:
# Execution Role 
  AutomationAssumeRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
        - Effect: Allow
          Action: sts:AssumeRole
          Principal:
            Service: 
              - ssm.amazonaws.com
              - ec2.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole
      Policies:
        - PolicyName: CmDwhTestSSMAtomationPolicy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - ec2:StartInstances
                  - ec2:StopInstances
                  - ec2:DescribeInstanceStatus
                Resource: 
                - !Sub "arn:aws:ec2:*:${AWS::AccountId}:instance/*"
              - Effect: Allow
                Action:
                - tag:GetResources 
                Resource: 
                - "*"
              - Effect: Allow
                Action:
                  - redshift:PauseCluster
                  - redshift:ResumeCluster
                Resource: 
                - !Sub "arn:aws:redshift:ap-northeast-1:${AWS::AccountId}:cluster:${ClusterIdentifier}"     
              - Effect: Allow
                Action:
                  - redshift:DescribeClusters
                Resource: 
                - !Sub "arn:aws:redshift:ap-northeast-1:${AWS::AccountId}:cluster:*"               
              - Effect: Allow
                Action: iam:PassRole
                Resource:
                - !Sub "arn:aws:iam::${AWS::AccountId}:role/SSMAtomationRole"
                Condition:
                  StringLikeIfExists:
                    iam:PassedToService: ssm.amazonaws.com
      Path: "/"
      RoleName: SSMAtomationRole
      Tags:
        - Key: Name
          Value: SSMAutomationRole

# SSM Document
  StopSSMDocument:
    Type: AWS::SSM::Document
    Properties:
      Name: Stop-InstanceAndRedshiftCluster  # 任意の名前
      DocumentFormat: YAML
      DocumentType: Automation  # Automation実行用の場合
      Content:
        schemaVersion: "0.3"  # DocumentTypeがAutomationの場合、2021/11時点では0.3を選択
        description: Stop Target Tag EC2Instance and Redshift
        assumeRole: !GetAtt AutomationAssumeRole.Arn
        parameters:  # Automation実行の入力パラメータ
          tagname:
            type: String
            default: !Ref TagKey   
        mainSteps:  # Automationで実行する内容(ステップ)
          - name: StopEC2Instances  # ステップ名
            action: aws:executeAwsApi
            inputs:
              Service: ssm
              Api: StartAutomationExecution
              DocumentName: AWS-StopEC2Instance  # AWSが用意しているAutomation RunBookの呼び出し
              TargetParameterName: InstanceId
              Targets:
                - Key: 'tag:{{ tagname }}'
                  Values: 
                    - 'true'  #「AutoStop」タグの値がtrueのインスタンスを停止対象とする
          - name: RedshiftStatusCheck  
            # Redshiftのステータス確認(pause状態でpause実行するとエラーになるため)
            action: aws:executeAwsApi
            inputs:
              Service: redshift
              Api: DescribeClusters
              ClusterIdentifier: !Ref ClusterIdentifier
            outputs:
              - Name: status
                Selector: $.Clusters[0].ClusterAvailabilityStatus
                Type: String
          - name: ChoiceStopOrExit
            action: aws:branch  # 条件分岐のアクション
            inputs:
              Choices: # 条件に一致したNextStepを実行。一致がなければDefaultを実行
              - NextStep: StopRedshiftCluster
                Variable: "{{RedshiftStatusCheck.status}}"
                StringEquals: Available   # Statusがavailableの場合このNextStepを実行
              Default: 
                sleepActionForSkip
          - name: sleepActionForSkip # 終了のためのダミーStep
            action: aws:sleep
            inputs:
              Duration: PT10S
            isEnd: true
          - name: StopRedshiftCluster 
            action: aws:executeAwsApi
            inputs:
              Service: redshift
              Api: PauseCluster
              ClusterIdentifier: !Ref ClusterIdentifier
            outputs:
              - Name: Response
                Selector: $
                Type: StringMap
      Tags:
        - Key: Name
          Value: "stop-ssm-docs"

  StartSSMDocument:
    Type: AWS::SSM::Document
    Properties:
      Name: "Start-InstanceAndRedshiftCluster"
      DocumentFormat: YAML
      DocumentType: Automation
      Content:
        schemaVersion: "0.3"
        description: Start Target Tag EC2Instance and Redshift
        assumeRole: !GetAtt AutomationAssumeRole.Arn
        parameters:
          tagname:
            type: String
            default: !Ref TagKey
        mainSteps:
          - name: StartEC2Instance
            action: aws:executeAwsApi
            inputs:
              Service: ssm
              Api: StartAutomationExecution
              DocumentName: AWS-StartEC2Instance
              TargetParameterName: InstanceId
              Targets:
                - Key: 'tag:{{ tagname }}'
                  Values: 
                    - 'true'
          - name: RedshiftStatusCheck
            action: aws:executeAwsApi
            inputs:
              Service: redshift
              Api: DescribeClusters
              ClusterIdentifier: !Ref ClusterIdentifier
            outputs:
              - Name: status
                Selector: $.Clusters[0].ClusterStatus
                Type: String
          - name: ChoiceStopOrExit
            action: aws:branch
            inputs:
              Choices:
              - NextStep: StartRedshiftCluster
                Variable: "{{RedshiftStatusCheck.status}}"
                StringEquals: paused
              Default: 
                sleepActionForSkip
          - name: sleepActionForSkip #終了のためのダミーStep
            action: aws:sleep
            inputs:
              Duration: PT10S
            isEnd: true
          - name: StartRedshiftCluster
            action: aws:executeAwsApi
            inputs:
              Service: redshift
              Api: ResumeCluster
              ClusterIdentifier: !Ref ClusterIdentifier
            outputs:
              - Name: Response
                Selector: $
                Type: StringMap
      Tags:
        - Key: Name
          Value: "start-ssm-docs"

# Event
  StopAutomationEvent:
    Type: AWS::Events::Rule
    Properties:
      Name: "StopAutomationEvent"
      ScheduleExpression: !Sub 'cron(${CronPattern})'
      State: !Ref EventInitState
      Targets:
        - Arn: !Sub "arn:aws:ssm:ap-northeast-1:${AWS::AccountId}:automation-definition/${StopSSMDocument}:$DEFAULT"
          Id: TargetStopRedshiftAndEC2Instance
          RoleArn: !GetAtt AutomationAssumeRole.Arn

Outputs:
  StopSSMDocument:
    Value: !Sub "https://ap-northeast-1.console.aws.amazon.com/systems-manager/documents/${StopSSMDocument}/description?region=ap-northeast-1"
  StartSSMDocument:
    Value: !Sub "https://ap-northeast-1.console.aws.amazon.com/systems-manager/documents/${StartSSMDocument}/description?region=ap-northeast-1"

最後に

AWSでは、OPEXコスト(ランニングやメンテナンスコストの総称:Operating Expense)を抑えるために利用できる機能が数多く存在するので、今後も色々試してみたいと思います。