CloudWatch Eventsでサーバの起動管理を行う

任意のタイミングでサーバ起動・停止できる仕組みとしてAWSSimpleConsoleがあります。
https://github.com/mokemokechicken/AWSSimpleConsole

ただ、dockerの使用やElasticBeanstalkでの起動管理など、新しい仕組みを取り入れていく環境の中で、AWSSimpleConsoleで運用するサーバも数台になったことから、今回、CloudWatchのEvents機能を利用した起動制御にリプレイスしました。
また、マネージメントコンソールにおける表示と設定についても注意点がありますので記述します。

### 要件
- 開発上必要なサーバに対して、夜間は不要なため毎日、時間を指定して停止させておく
- 起動は不定期であるため、都度、APIを実行して起動させる


### 方法
- CloudWatch Eventsでスケジュールを使用し、ターゲットにインスタンスを指定する
- Terraformを用いて設計・実装する

### 注意点

- InstanceIdはStringList形式で指定する必要がある
- 一度に指定できる数は最大5つという制限がある
- cronライクにスケジュールを定義できるがUTCのタイムゾーンで記述する必要がある

https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-aws-stopec2instance.html

### 設定

- Event SourceとTargetsの設定をすることでスケジュールがトリガーされたときに起動するターゲットを選択します。そのターゲットとしてSystems Managerには定義済みオートメーションドキュメントが用意されているので `  arn       = "arn:aws:ssm:ap-northeast-1::automation-definition/AWS-StopEC2Instance"` として定義します。

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/automation-documents-reference.html

```cloudwatch_event_rule.tf
resource "aws_cloudwatch_event_rule" "stop_rule_1" {
  name        = "stop-rule-1"
  description = "server stop the dev server at 22:00(JST)"

  schedule_expression = "cron(0 13 * * ? *)"
}

// The number of InstanceId constants is limited within 5
resource "aws_cloudwatch_event_target" "stop_target_1" {
  target_id = "StopInstance"
  arn       = "arn:aws:ssm:ap-northeast-1::automation-definition/AWS-StopEC2Instance"
  rule      = aws_cloudwatch_event_rule.stop_rule_1.name
  role_arn  = aws_iam_role.cloudwatch_event_ssm_role.arn

  input = <<EOF
{
  "InstanceId": ["i-00000000000000000", "i-11111111111111111", "i-22222222222222222", "i-33333333333333333", "i-44444444444444444"]
}
EOF
}

resource "aws_cloudwatch_event_rule" "stop_rule_2" {
  name        = "stop-rule-2"
  description = "server stop the dev server at 22:00(JST)"

  schedule_expression = "cron(0 13 * * ? *)"
}

// The number of InstanceId constants is limited within 5
resource "aws_cloudwatch_event_target" "stop_target_2" {
  target_id = "StopInstance"
  arn       = "arn:aws:ssm:ap-northeast-1::automation-definition/AWS-StopEC2Instance"
  rule      = aws_cloudwatch_event_rule.stop_rule_2.name
  role_arn  = aws_iam_role.cloudwatch_event_ssm_role.arn

  input = <<DOC
{
  "InstanceId": ["i-55555555555555555"]
}
DOC
}
```

```iam_role.tf
resource "aws_iam_role" "cloudwatch_event_ssm_role" {
  name = "cloudwatch_event_ssm_role"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": [
         "events.amazonaws.com"
         ]
     },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}

resource "aws_iam_role_policy" "cloudwatch_event_ssm_rds_policy" {
  name = "cloudwatch_event_ssm_rds_policy"
  role = aws_iam_role.cloudwatch_event_ssm_role.id

  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Action": [
        "rds:StopDBInstance",
        "rds:StartDBInstance",
        "rds:DescribeDBInstances"
      ],
      "Resource": "*"
    }
  ]
}
EOF
}

resource "aws_iam_role_policy_attachment" "AmazonSSMAutomationRole" {
  role       = aws_iam_role.cloudwatch_event_ssm_role.id
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole"
}
```

### tips

- AWS管理のRoleであるcloudwatch_event_ssm_roleだが、なぜかRDSについての権限がないので、RDSインスタンスを指定する場合は別途ポリシーを作成する必要がある。
また、RDSインスタンスのID指定はDB identifierを記述する。

```cloudwatch_event_ssm_role.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "lambda:InvokeFunction"
            ],
            "Resource": [
                "arn:aws:lambda:*:*:function:Automation*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateImage",
                "ec2:CopyImage",
                "ec2:DeregisterImage",
                "ec2:DescribeImages",
                "ec2:DeleteSnapshot",
                "ec2:StartInstances",
                "ec2:RunInstances",
                "ec2:StopInstances",
                "ec2:TerminateInstances",
                "ec2:DescribeInstanceStatus",
                "ec2:CreateTags",
                "ec2:DeleteTags",
                "ec2:DescribeTags",
                "cloudformation:CreateStack",
                "cloudformation:DescribeStackEvents",
                "cloudformation:DescribeStacks",
                "cloudformation:UpdateStack",
                "cloudformation:DeleteStack"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "ssm:*"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "sns:Publish"
            ],
            "Resource": [
                "arn:aws:sns:*:*:Automation*"
            ]
        }
    ]
}
```
---
- マネージメントコンソールにおいてルールを編集するとInstanceIdの入力値がStringList形式ではないカンマ区切りの文字列へと変更されている。（機能改善要求済み）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58788/aa2f9af8-cccc-9eff-5a35-1bd066aa1690.png)


### 確認
- 指定時間に指定したインスタンスが停止していることが確認できる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58788/3cba4215-2993-a71a-18a2-4a1e4dd04f8c.png)