AWS: lambdaを使わないでEC2の定期起動・定期停止するCloudFormationを作る

## はじめに
EventBridgeを使ってEC2の定期起動・停止するCFnを作る時たくさん悩んだので、未来の私がCFn見直した時に思い出せるようにするためのメモ。

~~※投稿してしまったのですが確認したら今CFnが思うとおりに動いていないので修正中です(2020/07/30)~~
<b>=>修正しました!(2020/08/13)</b>


## CloudFormation


### 1. このCFnでつくられるもの。

<b> ■ CloudWatch Event Rule</b>

- 起動させるイベントルール
- 停止させるイベントルール

<br>

<b> ■ IAM Role</b>

- 自動化用のAutomationAssumeRole
    - AWSマネージドポリシー
- 起動イベント用のロール
    - カスタムポリシー
- 停止イベント用のロール
    - カスタムポリシー


---

### 2. つくったテンプレート。

```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: EC2_Start_Stop

Parameters: 
  EC2InstanceID1:
    Type: String
    Description: EC2InstanceID1
    Default: "i-xxxxxxxxxxxxxxx"
  EC2InstanceID2:
    Type: String
    Description: EC2InstanceID2
    Default: "i-yyyyyyyyyyyyyy"

  StartTime:
    Type: String
    Description: start time of Ec2
    Default: 00 00 ? * MON-FRI *
  StopTime:
    Type: String
    Description: stop time of Ec2
    Default: 00 09 ? * MON-FRI *




Resources:
# ------------------------------------------------------------#
# Event
# ------------------------------------------------------------# 
  EventRuleEc2Start:
    Type: AWS::Events::Rule
    Properties:
      Name: Ec2StartRule
      Description: !Sub ${StartTime} Start
      ScheduleExpression: !Sub 'cron(${StartTime})'
      State: ENABLED
      Targets:
        - Arn: arn:aws:ssm:ap-northeast-1::automation-definition/AWS-StartEC2Instance:$DEFAULT
          Id: TargetStartEC2Instance1
          RoleArn: !Sub ${StartEc2Role.Arn}
          Input: !Sub "{\"InstanceId\":[\"${EC2InstanceID1}\"],\"AutomationAssumeRole\":[\"${EC2AmazonSSMAutomationRole.Arn}\"]}"
        - Arn: arn:aws:ssm:ap-northeast-1::automation-definition/AWS-StartEC2Instance:$DEFAULT
          Id: TargetStartEC2Instance2
          RoleArn: !Sub ${StartEc2Role.Arn}
          Input: !Sub "{\"InstanceId\":[\"${EC2InstanceID2}\"],\"AutomationAssumeRole\":[\"${EC2AmazonSSMAutomationRole.Arn}\"]}"

  EventRuleEc2Stop:
    Type: AWS::Events::Rule
    Properties:
      Name: Ec2StopRule
      Description: !Sub ${StopTime} Stop 
      ScheduleExpression: !Sub 'cron(${StopTime})'
      State: ENABLED
      Targets:
        - Arn: arn:aws:ssm:ap-northeast-1::automation-definition/AWS-StopEC2Instance:$DEFAULT
          Id: TargetStopEC2Instance1
          RoleArn: !Sub ${StopEc2Role.Arn}
          Input: !Sub "{\"InstanceId\":[\"${EC2InstanceID1}\"],\"AutomationAssumeRole\":[\"${EC2AmazonSSMAutomationRole.Arn}\"]}"
        - Arn: arn:aws:ssm:ap-northeast-1::automation-definition/AWS-StopEC2Instance:$DEFAULT
          Id: TargetStopEC2Instance2
          RoleArn: !Sub ${StopEc2Role.Arn}
          Input: !Sub "{\"InstanceId\":[\"${EC2InstanceID2}\"],\"AutomationAssumeRole\":[\"${EC2AmazonSSMAutomationRole.Arn}\"]}"


# ------------------------------------------------------------#
# AutomationAssumeRole
# ------------------------------------------------------------# 
  EC2AmazonSSMAutomationRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
              - ssm.amazonaws.com
              - ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole
      Path: "/"



#------------------------------------------------------------#
# StartEc2
# ------------------------------------------------------------# 
  StartEc2Role:    
    Type: AWS::IAM::Role
    Properties: 
      RoleName: "StartEC2Role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - "events.amazonaws.com"
            Action: 
              - sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: StartEc2Role_policy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ssm:StartAutomationExecution
                Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:automation-definition/AWS-StartEC2Instance:$DEFAULT
              - Effect: Allow
                Action: "iam:PassRole"
                Resource: !GetAtt EC2AmazonSSMAutomationRole.Arn
                Condition: 
                  StringLikeIfExists: 
                    iam:PassedToService: ssm.amazonaws.com

# ------------------------------------------------------------#
# StopEc2
# ------------------------------------------------------------# 
  StopEc2Role:    
    Type: AWS::IAM::Role
    Properties: 
      RoleName: "StopEC2Role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - "events.amazonaws.com"
            Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: StopEc2Role_policy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: ssm:StartAutomationExecution
                Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:automation-definition/AWS-StopEC2Instance:$DEFAULT
              - Effect: Allow
                Action: "iam:PassRole"
                Resource: !GetAtt EC2AmazonSSMAutomationRole.Arn
                Condition: 
                  StringLikeIfExists: 
                    iam:PassedToService: ssm.amazonaws.com
```

---

### 3. テンプレートのなかみ。
#### ■ parameter
```yaml
Parameters: 
  EC2InstanceID1:
    Type: String
    Description: EC2InstanceID1
    Default: "i-xxxxxxxxxxxxxxx"
  EC2InstanceID2:
    Type: String
    Description: EC2InstanceID2
    Default: "i-yyyyyyyyyyyyyy"

  StartTime:
    Type: String
    Description: start time of Ec2
    Default: 00 00 ? * MON-FRI *
  StopTime:
    Type: String
    Description: stop time of Ec2
    Default: 00 09 ? * MON-FRI *
```
定期起動・停止したいインスタンスのインスタンスIDと、実行する時間をparameterで出してます。

同じ時間で起動・停止させるEC2インスタンス2つに対して設定したかったのでIDを入力するところは2つ。
時間は[cron式](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/events/ScheduledEvents.html#CronExpressions)で指定します。このテンプレートはデフォルトで日本時間の平日9:00に起動、18:00に停止するようになってます。


#### ■ イベント
```yaml
  EventRuleEc2Start:
    Type: AWS::Events::Rule
    Properties:
      Name: Ec2StartRule
      Description: !Sub ${StartTime} Start
      ScheduleExpression: !Sub 'cron(${StartTime})'
      State: ENABLED
      Targets:
        - Arn: arn:aws:ssm:ap-northeast-1::automation-definition/AWS-StartEC2Instance:$DEFAULT
          Id: TargetStartEC2Instance1
          RoleArn: !Sub ${StartEc2Role.Arn}
          Input: !Sub "{\"InstanceId\":[\"${EC2InstanceID1}\"],\"AutomationAssumeRole\":[\"${EC2AmazonSSMAutomationRole.Arn}\"]}"
        - Arn: arn:aws:ssm:ap-northeast-1::automation-definition/AWS-StartEC2Instance:$DEFAULT
          Id: TargetStartEC2Instance2
          RoleArn: !Sub ${StartEc2Role.Arn}
          Input: !Sub "{\"InstanceId\":[\"${EC2InstanceID2}\"],\"AutomationAssumeRole\":[\"${EC2AmazonSSMAutomationRole.Arn}\"]}"
```

[AWS::Events::Rule - AWS CloudFormation](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-events-rule.html#cfn-events-rule-targets)


トリガーとなるイベントの作成。


`Targets`にルールがトリガーされたときに呼び出されるリソースをリスト型で書いていきます。


<b>EventRuleEc2Start</b>では起動させるロールを割り当てたいので、`Arn`に[StartEC2Instance](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/automation-aws-startec2instance.html)を参照させて、`RoleArn`にはルールがトリガーされた時にこのターゲットに使用されるIAMロールの ARNを書きます。ここには、同じCFn内の<b>StartEc2Role</b>を使いたいので、`!Sub ${StartEc2Role.Arn}`でStartEc2RoleのARNを参照しています。（`!Sub`と`.Arn`でARN取ってこれるの知らなかった。）



同様に<b>EventRuleEc2Stop</b>では停止させるイベントを作成します。Startの部分がStopになるくらいなので割愛。

#### ■ 自動化用ロール
```yaml
  EC2AmazonSSMAutomationRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
              - ssm.amazonaws.com
              - ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole
      Path: "/"
```
マネージドポリシーの<b>AmazonSSMAutomationRole</b>を作成します。

`AssumeRolePolicyDocument`にこのロールに関連付けられている信頼ポリシーを書くのですが、`ssm.amazonaws.com`と`ec2.amazonaws.com`をこのロールを引き受けることができるエンティティに指定したいので`Service`にリスト型で書いておきます。



#### ■ 起動・停止用ロール
```yaml
  StartEc2Role:    
    Type: AWS::IAM::Role
    Properties: 
      RoleName: "StartEC2Role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - "events.amazonaws.com"
            Action: 
              - sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: StartEc2Role_policy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ssm:StartAutomationExecution
                Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:automation-definition/AWS-StartEC2Instance:$DEFAULT
              - Effect: Allow
                Action: "iam:PassRole"
                Resource: !GetAtt EC2AmazonSSMAutomationRole.Arn
                Condition: 
                  StringLikeIfExists: 
                    iam:PassedToService: ssm.amazonaws.com

```
[AWS::IAM::Role - AWS CloudFormation](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)

起動・停止用のカスタムロールを作成します。

イベントをトリガーにしているので信頼されるエンティティは`events.amazonaws.com`。

そして、ロールに<b>StartEc2Role_policy</b>というインラインポリシーを追加させるために`Policies`以下に定義していきます。AWSサービスにロールを渡すにはPassRoleのアクセス許可が必要なので、ここで<b>EC2AmazonSSMAutomationRole</b>にアクセス許可を付与しています。

イベントと同じくStopの方は割愛。


---
#### 4. テンプレート展開してみる。
指定した時間通り動いた！わーい！



## 参考
公式ドキュメントと先人と先輩に感謝。

- [AWS サービスにロールを渡すアクセス許可をユーザーに許可する - AWS Identity and Access Management](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_use_passrole.html)
- [AWS-StartEC2Instance - AWS Systems Manager](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/automation-aws-startec2instance.html)
- [AWS Systems Manager AutomationでEC2の自動停止 | ヤマムギ](https://www.yamamanx.com/aws-systems-manager-automation-ec2/)
- [EC2インスタンスのスケジュール起動がお手軽に実現できるようになっていた！-Qiita](https://qiita.com/t-toyota/items/5d7455568f1856fd2f4e)


## おわりに
CloudFormationでリソースを作成できても一向にEC2が起きてこなかったり、1つは起動するのにもう1つが起動しなかったりとたくさんはまりました。

間違いや修正点等あったら教えてください！

早くCloudFormationと仲良くなりたい、、