はじめに
AWS 利用料削減のため、EC2 インスタンスを指定の時間で停止・起動させる必要がある場合、どのような方式で実装するのがベストでしょうか?
今回、私が担当するお客様の案件にてこの要件があったため、ベストな方式を検討しました。結論として、SSM メンテナンスウィンドウ(AWS Systems Manager Maintenance Windows)を採用し、リソースグループとタグで対象インスタンスを管理、CloudFormation でコード管理する構成にしました。
この記事では、方式の比較から実際の構築手順、やってみてわかった注意点までをまとめます。
EC2 自動起動・停止の方式比較
EC2 の自動起動・停止を実現する方法は主に以下の 4 つがあります。
| 方式 | 追加コスト | 対象の管理方法 | IaC 管理 | 祝日対応 | 導入の手軽さ |
|---|---|---|---|---|---|
| SSM メンテナンスウィンドウ | 無し | タグ(リソースグループ経由) | ○ | × | ○ |
| EventBridge + Lambda | Lambda 実行分 | タグ(Lambda 内で直接指定可) | ○ | ○(要コード実装) | △ |
| Instance Scheduler(AWS ソリューション) | DynamoDB + Lambda | タグ | △ | ○ | △ |
| Auto Scaling Scheduled Actions | 無し | ASG の台数制御 | ○ | × | ×(ASG 前提) |
EventBridge + Lambda は柔軟性が高いですが、Lambda 関数のコードを書いてメンテナンスする必要があります。Instance Scheduler は AWS 公式のソリューションで祝日対応もできますが、裏で DynamoDB や Lambda が動いていて、トラブルシュート時に追いにくいです。Auto Scaling Scheduled Actions は ASG(Auto Scaling グループ)の利用が前提なので既存の単体 EC2 には使えません。また、台数を 0 にすることで停止を実現する仕組みのため、インスタンスは停止ではなく終了(Terminate)されます。
SSM メンテナンスウィンドウは、Lambda 不要で追加コストもかからず、CloudFormation でそのまま管理できます。祝日対応ができないという弱点はありますが、「平日の業務時間帯だけ起動しておきたい」というシンプルな要件であれば十分です。
今回の案件では、以下の要件がありました。
- Lambda 等のコードの運用保守コストをかけたくない
- お客様から起動・停止時刻の変更依頼が度々あるため、IaC で管理してすぐに対応できるようにしたい
- インスタンスのリストア等で対象インスタンスが変わることがあるため、インスタンス ID ではなくタグで対象を管理したい
これらを踏まえて、SSM メンテナンスウィンドウ + リソースグループ(タグベース)+ CloudFormation の構成を採用しました。
SSM メンテナンスウィンドウの仕組み
SSM メンテナンスウィンドウは、以下の 3 つの要素で構成されています。
- メンテナンスウィンドウ:スケジュール(cron 式)と実行枠(Duration)を定義する
- ターゲット:処理対象のインスタンスを定義する
- タスク:実行する処理(起動や停止)を定義する
EC2 の起動・停止には、AWS が用意している SSM ドキュメント AWS-StartEC2Instance と AWS-StopEC2Instance をタスクとして使います。
Duration について
メンテナンスウィンドウには Duration(実行枠)という設定があります。
スケジュールで指定した時刻は「この時刻ちょうどに実行される」という意味ではなく、「この時刻から Duration 時間の枠内で実行される」という仕様です。例えば、スケジュールが 8:30 で Duration が 2 時間の場合、タスクは 8:30〜10:30 の枠内で実行されます。
ただ、実際に試してみたところ、スケジュール時刻の直後に実行されていたので、基本的には指定した時刻に実行されると思って大丈夫です。「仕様上はそうなっている」という認識だけ持っておけば問題ないと思います。
祝日対応について
SSM メンテナンスウィンドウのスケジュールは cron 式で定義するため、「月〜金」のような曜日指定はできますが、祝日を除外することはできません。祝日にもインスタンスが起動してしまうのが許容できない場合は、EventBridge + Lambda など別の方式を検討する必要があります。
タグで対象インスタンスを管理したい → リソースグループを経由する
SSM メンテナンスウィンドウでインスタンスを起動・停止する場合、ターゲットの指定方法は以下の 2 つです。
ResourceType: INSTANCEでインスタンス ID を直接指定するResourceType: RESOURCE_GROUPでリソースグループを指定する
「タグが付いたインスタンスを自動的に対象にしたい」と思って調べたのですが、AUTOMATION タスク(AWS-StartEC2Instance / AWS-StopEC2Instance)では、ターゲットにタグを直接指定することができませんでした。Run Command タスクであれば Key=tag:タグキー の形式でタグ指定ができるのですが、AUTOMATION タスクではこの指定方法が使えないようです。
参考:RegisterTargetWithMaintenanceWindow – AWS Systems Manager API Reference
リソースグループで回避する
そこで使うのが AWS Resource Groups です。
Resource Groups では、タグベースのリソースグループを作成できます。例えば「AUTO_STARTSTOP: enabled というタグが付いた EC2 インスタンス」をグループとして定義しておけば、メンテナンスウィンドウのターゲットにそのリソースグループを指定するだけで、タグが付いたインスタンスが自動的に処理対象になります。
この方式の良いところは、インスタンスの追加・削除がタグの付け外しだけで済むことです。新しいインスタンスを起動・停止の対象に加えたい場合は、そのインスタンスにタグを付けるだけで済みます。CloudFormation テンプレートやメンテナンスウィンドウの設定を変更する必要はありません。逆に、一時的に対象から外したい場合はタグを削除するだけで対応できます。
CloudFormation での実装手順
ここからは、実際に CloudFormation で構築する手順を説明します。
前提条件
- SSM メンテナンスウィンドウ用の IAM ロールが作成済みであること
- IAM ロールに以下の 2 つのマネージドポリシーがアタッチされていること
AmazonSSMAutomationRoleAmazonSSMMaintenanceWindowRole
- IAM ロールの信頼ポリシーで
ssm.amazonaws.comがsts:AssumeRoleできること
SSM メンテナンスウィンドウはタスク実行時にこの IAM ロールを引き受けて(AssumeRole して)動作します。そのため、信頼ポリシーで SSM サービスからの AssumeRole を許可しておく必要があります。
AmazonSSMMaintenanceWindowRole には resource-groups:ListGroups、resource-groups:ListGroupResources、tag:GetResources の権限が含まれているので、リソースグループをターゲットにする場合でも追加のポリシーは不要です。
手順 1:対象インスタンスにタグを付与
起動・停止の対象にしたい EC2 インスタンスにタグを付けます。
EC2 コンソールでインスタンスを選択し、「タグ」タブ → 「タグを管理」から以下のタグを追加します。
| Key | Value |
|---|---|
MY_SERVER_AUTOMATION_STARTSTOP |
enabled |
タグの Key と Value は任意ですが、何の用途のタグかわかりやすい名前にしておくと良いです。
手順 2:CloudFormation テンプレートの作成
以下のテンプレートで、リソースグループ、メンテナンスウィンドウ(起動用・停止用)、ターゲット、タスクをまとめて作成します。
AWSTemplateFormatVersion: '2010-09-09'
Description: 'SSM Maintenance Windows for EC2 Start/Stop with Resource Group'
Parameters:
ResourceGroupName:
Type: String
Default: MyServerResourceGroup
Description: Resource Group name
TagKey:
Type: String
Default: MY_SERVER_AUTOMATION_STARTSTOP
Description: Tag key to identify target EC2 instances
TagValue:
Type: String
Default: enabled
Description: Tag value to identify target EC2 instances
ServiceRoleArn:
Type: String
Description: ARN of the IAM Role for SSM Maintenance Window
Resources:
# --- リソースグループ ---
# 指定タグが付いた EC2 インスタンスを自動的にグループ化
ServerResourceGroup:
Type: AWS::ResourceGroups::Group
Properties:
Name: !Ref ResourceGroupName
Description: Resource group for target EC2 instances
ResourceQuery:
Type: TAG_FILTERS_1_0
Query:
ResourceTypeFilters:
- AWS::EC2::Instance
TagFilters:
- Key: !Ref TagKey
Values:
- !Ref TagValue
# --- 起動用メンテナンスウィンドウ(平日 08:30 JST)---
EC2StartMaintenanceWindow:
Type: AWS::SSM::MaintenanceWindow
Properties:
Name: MW-MyServer-Start
Description: Start EC2 instances
Schedule: "cron(30 8 ? * MON-FRI *)"
ScheduleTimezone: "Asia/Tokyo"
Duration: 2
Cutoff: 0
AllowUnassociatedTargets: true
EC2StartMaintenanceTarget:
Type: AWS::SSM::MaintenanceWindowTarget
Properties:
WindowId: !Ref EC2StartMaintenanceWindow
Name: Target-MyServer
ResourceType: RESOURCE_GROUP
Targets:
- Key: resource-groups:Name
Values:
- !Ref ResourceGroupName
EC2StartWindowTask:
Type: AWS::SSM::MaintenanceWindowTask
Properties:
Name: Task-MyServer-Start
WindowId: !Ref EC2StartMaintenanceWindow
Targets:
- Key: WindowTargetIds
Values:
- !Ref EC2StartMaintenanceTarget
TaskArn: AWS-StartEC2Instance
TaskType: AUTOMATION
TaskInvocationParameters:
MaintenanceWindowAutomationParameters:
DocumentVersion: $DEFAULT
Parameters:
InstanceId:
- '{{RESOURCE_ID}}'
Priority: 1
MaxConcurrency: "100%"
MaxErrors: "100%"
ServiceRoleArn: !Ref ServiceRoleArn
# --- 停止用メンテナンスウィンドウ(平日 20:00 JST)---
EC2StopMaintenanceWindow:
Type: AWS::SSM::MaintenanceWindow
Properties:
Name: MW-MyServer-Stop
Description: Stop EC2 instances
Schedule: "cron(0 20 ? * MON-FRI *)"
ScheduleTimezone: "Asia/Tokyo"
Duration: 2
Cutoff: 0
AllowUnassociatedTargets: true
EC2StopMaintenanceTarget:
Type: AWS::SSM::MaintenanceWindowTarget
Properties:
WindowId: !Ref EC2StopMaintenanceWindow
Name: Target-MyServer
ResourceType: RESOURCE_GROUP
Targets:
- Key: resource-groups:Name
Values:
- !Ref ResourceGroupName
EC2StopWindowTask:
Type: AWS::SSM::MaintenanceWindowTask
Properties:
Name: Task-MyServer-Stop
WindowId: !Ref EC2StopMaintenanceWindow
Targets:
- Key: WindowTargetIds
Values:
- !Ref EC2StopMaintenanceTarget
TaskArn: AWS-StopEC2Instance
TaskType: AUTOMATION
TaskInvocationParameters:
MaintenanceWindowAutomationParameters:
DocumentVersion: $DEFAULT
Parameters:
InstanceId:
- '{{RESOURCE_ID}}'
Priority: 1
MaxConcurrency: "100%"
MaxErrors: "100%"
ServiceRoleArn: !Ref ServiceRoleArn
Outputs:
ResourceGroupName:
Value: !Ref ServerResourceGroup
Description: Resource Group Name
StartMaintenanceWindowId:
Value: !Ref EC2StartMaintenanceWindow
Description: Maintenance Window ID for Start
StopMaintenanceWindowId:
Value: !Ref EC2StopMaintenanceWindow
Description: Maintenance Window ID for Stop
テンプレートのポイントをいくつか補足します。
ResourceQuery.Type: TAG_FILTERS_1_0でタグベースのリソースグループを作成しています。ResourceTypeFiltersにAWS::EC2::Instanceを指定することで、EC2 インスタンスだけがグループに含まれるようにしています- ターゲットの
ResourceTypeはRESOURCE_GROUPを指定し、Key: resource-groups:Nameでリソースグループ名を参照しています - タスクの
InstanceIdパラメータに{{RESOURCE_ID}}を指定しています。これはリソースグループ内の各インスタンス ID に自動展開されるプレースホルダーです - 起動用と停止用でメンテナンスウィンドウを分けています。Duration 枠が重ならないように、起動は 8:30(枠:8:30〜10:30)、停止は 20:00(枠:20:00〜22:00)としています
手順 3:スタックのデプロイ
CloudFormation コンソールからスタックを作成します。
まず、CloudFormation コンソールを開き「スタックの作成」→「新しいリソースを使用」を選択します。
「テンプレートファイルのアップロード」で上記の yml ファイルをアップロードし、「次へ」をクリックします。
スタック名を入力します(例:ssm-maintenance-window-myserver-stack)。
パラメータの入力画面が表示されるので、以下を確認・入力します。
ResourceGroupName:リソースグループ名(デフォルトのままでも OK)ServiceRoleArn:SSM メンテナンスウィンドウ用 IAM ロールの ARNTagKey:手順 1 で付けたタグの KeyTagValue:手順 1 で付けたタグの Value
「スタックオプションの設定」画面と「レビュー」画面はデフォルトのまま進めて、最後に「送信」をクリックします。
スタックのステータスが CREATE_COMPLETE になれば完了です。
手順 4:リソースグループの確認
Resource Groups コンソール(AWS Resource Groups & Tag Editor)を開き、作成したリソースグループを確認します。
「グループリソース」タブに、手順 1 でタグを付けた EC2 インスタンスが表示されていれば OK です。
手順 5:実行結果の確認
メンテナンスウィンドウのスケジュール時刻が来たら、Systems Manager コンソール → メンテナンスウィンドウ → 該当ウィンドウの「履歴」タブで実行結果を確認できます。ステータスが 成功 になっていれば、起動・停止が正常に実行されています。
今回は検証のため、インスタンスを事前に手動で停止した状態にしておき、起動用メンテナンスウィンドウが実行されてインスタンスが起動されるかを確認しました。また、スケジュール時刻はテンプレートに記載した 8:30 / 20:00 ではなく、検証用に変更して実行しています。
EC2 コンソールでもインスタンスの状態(実行中 / 停止済み)を確認してみてください。
今回は起動のジョブを実行したので実行中になっていることが確認できました。
おわりに
SSM メンテナンスウィンドウを使った EC2 の自動起動・停止を、リソースグループと CloudFormation で管理する方法を試してみました。
正直なところ、AUTOMATION タスクでタグを直接指定できないのは驚きましたが、リソースグループを経由すれば結果的にタグベースで管理できるので、運用上は問題ありませんでした。むしろ CloudFormation でリソースグループごとコード管理できるので、設定の見通しが良くなったと感じています。
祝日対応ができない点は割り切りが必要ですが、「Lambda を書かずにタグベースで起動・停止対象を管理したい」という要件であれば、この構成はなかなか使いやすいです。
同じような要件で悩んでいる方の参考になれば幸いです。







