EventBridgeスケジューラを使ってEC2の定期起動/停止を行う方法

EventBridgeスケジューラの登場により、EventBridgeから直接EC2の起動、停止が可能になりました。利用しない時間はインスタンスを停止してコストを削減しましょう。
2023.01.26

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、データアナリティクス事業本部の八木です。

EC2が必要なのは一部の時間帯だけ。。。使わない時間は停止してコスト削減したいな。。。といった悩みをお持ちの方、いるのではないでしょうか?

数分の実行であればLambda、数時間の実行であればECSタスクに乗り換えるという手段もありますが、既存処理を移植しにくい、作業工数を取れないといった場合もあるかと思います。
そんな時に思い当たるのがEC2の定期起動/停止ではないでしょうか。

EC2の定期起動/停止方法として、従来はEC2のEventBridge + Lambda1や、EventBridge + Systems Manager2を利用する構成がとられてきました。
しかし前者の手法はLambdaのコードを書く必要があったり、後者の手法ではEC2がSSMサービスに通信可能でなければいけないなどの課題がありました。
(追記:SSMのAWS-StartEC2Instanceドキュメントでは、インスタンスはSSM管理下である必要はないため、SSMサービスへの通信は必要ありませんでした。)

そんな中、EventBridgeの新機能、EventBridgeスケジューラが登場しました!!!
EventBridgeから直接EC2の起動/停止が行えたら楽なのになーと思っていたのですが、まさにこれが可能になりました。

EventBridgeスケジューラは、スケジュール実行のEventBridgeルールのほぼ上位互換的な機能となっており、EventBridgeルールでは実行できなかったEC2の起動/停止アクションができるようになりました。

今回はこの機能を利用して、EC2の定期起動/停止をしてみます。

やってみた

前提

  • 停止済みのEC2インスタンスが存在する

EventBridgeスケジューラ用IAMロールの作成

まず、EventBridgeスケジューラが利用するIAMロールを作成します。このロールはEC2の起動/停止アクションを許可します。

IAMロールの作成画面で信頼されたエンティティタイプに「カスタム信頼ポリシー」を選択し、以下のJSONを入力します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "scheduler.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

ポリシー選択では、EC2フルアクセスを許可します。(実際はec2:StartInstancesec2:StopInstancesの権限があれば十分です。)

定期起動のEventBridgeスケジューラ作成

EC2インスタンスを起動するEventBridgeスケジューラを作成します。
EventBridgeのコンソール→スケジュール→スケジュールの作成と進みます。

スケジュールのパターンで定期的なスケジュールを作成します。今回は1日回実行を行うように設定しました。

続いてターゲットAPIで「Amazon EC2」→「StartInstances」を選択します。

入力の項目ではStartInstancesのAPIリクエストパラメータ3を指定します。
以下のように起動を行うEC2インスタンスのIDを指定します。(複数指定可能です)

{
  "InstanceIds": [
    "i-yyyyyyyyyyyyyy"
  ]
}

IAMロールは先に作成したロールを指定します。

以上で設定は完了です。

指定した時間にEC2を確認してみると、EC2が起動し始めていました。

CloudTrailも確認してみると、インスタンスのステータスがstoppedからpendingに変わり、起動されたことがわかります。

{
    "eventVersion": "1.08",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "AROAxxxxxxxxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxx",
        "arn": "arn:aws:sts::123456789012:assumed-role/ec2-scheduler/xxxxxxxxxxxxxxxx",
        "accountId": "123456789012",
        "accessKeyId": "ASIAxxxxxxxxxxxxx",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "AROAxxxxxxxxxxxxxxxx",
                "arn": "arn:aws:iam::123456789012:role/ec2-scheduler",
                "accountId": "123456789012",
                "userName": "ec2-scheduler"
            },
            "webIdFederationData": {},
            "attributes": {
                "creationDate": "2023-01-25T03:54:44Z",
                "mfaAuthenticated": "false"
            }
        }
    },
    "eventTime": "2023-01-25T03:55:15Z",
    "eventSource": "ec2.amazonaws.com",
    "eventName": "StartInstances",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "54.150.111.13",
    "userAgent": "AmazonEventBridgeScheduler, aws-sdk-java/2.19.14 Linux/4.14.301-224.520.amzn2.x86_64 OpenJDK_64-Bit_Server_VM/11.0.17+11-LTS Java/11.0.17 kotlin/1.3.72-release-468 (1.3.72) vendor/Amazon.com_Inc. md/internal exec-env/AWS_ECS_FARGATE io/async http/NettyNio cfg/retry-mode/legacy",
    "requestParameters": {
        "instancesSet": {
            "items": [
                {
                    "instanceId": "i-yyyyyyyyyyyyyy"
                }
            ]
        }
    },
    "responseElements": {
        "requestId": "caa45bfb-32e0-4410-9a44-65337b5e9be7",
        "instancesSet": {
            "items": [
                {
                    "instanceId": "i-yyyyyyyyyyyyyy",
                    "currentState": {
                        "code": 0,
                        "name": "pending"
                    },
                    "previousState": {
                        "code": 80,
                        "name": "stopped"
                    }
                }
            ]
        }
    },
    "requestID": "caa45bfb-32e0-4410-9a44-65337b5e9be7",
    "eventID": "48c04297-345a-4e6d-bbf3-a166ea5bd540",
    "readOnly": false,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "123456789012",
    "eventCategory": "Management",
    "tlsDetails": {
        "tlsVersion": "TLSv1.2",
        "cipherSuite": "ECDHE-RSA-AES128-GCM-SHA256",
        "clientProvidedHostHeader": "ec2.ap-northeast-1.amazonaws.com"
    }
}

定期停止のEventBridgeスケジューラ作成

続いてEC2インスタンスの停止を行うEventBridgeスケジューラを作成します。

起動の時と同様、cronでスケジュールを登録しました。

ターゲットAPIで「Amazon EC2」→「StopInstances」を選択します。

入力にはStopInstancesのAPIリクエストパラメータ4を入力します。
以下のように停止を行うEC2インスタンスのIDを指定します。(複数指定可能です)

{
  "InstanceIds": [
    "i-yyyyyyyyyyyyyy"
  ]
}

IAMロールには起動の時と同様のIAMロールを指定しました。

以上でスケジューラの設定は完了です。

指定時間にEC2インスタンスを確認すると、停止済みになっていました。想定通りにインスタンスが停止したようです。

CloudTrailのログを確認しても、期待通りにインスタンスの停止リクエストが記録されていました。

{
    "eventVersion": "1.08",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "AROAxxxxxxxxxxxxxxxxxxxx",
        "arn": "arn:aws:sts::123456789012:assumed-role/ec2-scheduler/xxxxxxxxxxxxxxxxxxxx",
        "accountId": "123456789012",
        "accessKeyId": "ASIAxxxxxxxxxxxxxxxxxxxx",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "AROAxxxxxxxxxxxxxxxxxxxx",
                "arn": "arn:aws:iam::123456789012:role/ec2-scheduler",
                "accountId": "123456789012",
                "userName": "ec2-scheduler"
            },
            "webIdFederationData": {},
            "attributes": {
                "creationDate": "2023-01-25T05:04:54Z",
                "mfaAuthenticated": "false"
            }
        }
    },
    "eventTime": "2023-01-25T05:05:21Z",
    "eventSource": "ec2.amazonaws.com",
    "eventName": "StopInstances",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "54.150.111.13",
    "userAgent": "AmazonEventBridgeScheduler, aws-sdk-java/2.19.14 Linux/4.14.301-224.520.amzn2.x86_64 OpenJDK_64-Bit_Server_VM/11.0.17+11-LTS Java/11.0.17 kotlin/1.3.72-release-468 (1.3.72) vendor/Amazon.com_Inc. md/internal exec-env/AWS_ECS_FARGATE io/async http/NettyNio cfg/retry-mode/legacy",
    "requestParameters": {
        "instancesSet": {
            "items": [
                {
                    "instanceId": "i-yyyyyyyyyyyyyy"
                }
            ]
        },
        "force": false
    },
    "responseElements": {
        "requestId": "aed457a8-b15a-4b67-91d4-b1bb16d79b66",
        "instancesSet": {
            "items": [
                {
                    "instanceId": "i-yyyyyyyyyyyyyy",
                    "currentState": {
                        "code": 64,
                        "name": "stopping"
                    },
                    "previousState": {
                        "code": 16,
                        "name": "running"
                    }
                }
            ]
        }
    },
    "requestID": "aed457a8-b15a-4b67-91d4-b1bb16d79b66",
    "eventID": "2bd2eca7-f6f8-4edc-b6a0-e3ca83b702d6",
    "readOnly": false,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "123456789012",
    "eventCategory": "Management",
    "tlsDetails": {
        "tlsVersion": "TLSv1.2",
        "cipherSuite": "ECDHE-RSA-AES128-GCM-SHA256",
        "clientProvidedHostHeader": "ec2.ap-northeast-1.amazonaws.com"
    }
}

最後に

今回はEventBridgeスケジューラによるEC2インスタンスの定期起動/停止方法をご紹介しました。
従来はLambdaなどを組み合わせる必要がありましたが、EventBridge単体で出来るようになったのが嬉しいポイントです。
ぜひこの機会に常時起動のEC2運用を見直してはいかがでしょうか?

以上、八木でした!