🕰️ 背景:EC2自動起動停止の歴史
従来
EC2インスタンスのコスト最適化として「営業時間外は停止する」というものはよくある構成かなと思います。
従来は、これを実現するために以下のような複雑な構成が必要でした。
- Lambda関数でEC2の起動・停止処理を実装
- EventBridge Rulesでスケジュール実行
- IAMロールの適切な権限設定
- エラーハンドリングやログ出力の実装
EventBridge Schedulerの登場
2022年11月にリリースされたEventBridge Schedulerにより、この状況が大きく改善され、非常にシンプルな形でこの構成を組むことが可能になりました。
この時点ではCDKはL1コンストラクトのみの対応であり、なかなか使用しにくい物でした。
- Lambdaを書かずに直接AWSサービスを呼び出し可能
- より柔軟なスケジュール設定(ワンタイム実行、複雑なcron式など)
- 組み込みのエラーハンドリングとリトライ機能
CDK L2コンストラクト対応の実現
そして2025年4月、ついにAWS CDKのL2コンストラクトとしてEventBridge Schedulerが利用可能になりました!
参考:AWS CDK Construct Library now supports Amazon EventBridge Scheduler
これにより、Infrastructure as Codeでの管理がより簡単になり、今回のようなEC2自動起動停止もCDKをデプロイするのみで実装できるようになりました!
今回はCDKでこの構成を構築します。
🔧 作成すべきリソース
今回のEC2自動起動停止を実現するために、以下のリソースを作成する必要があります。
- Scheduler用のIAMロール - EC2の起動停止権限を持つ
- ターゲット - 実際に実行するアクションを定義
- Scheduler Group(任意) - スケジューラーの管理単位
- Scheduler本体 - 実際のスケジュール設定
📖前提
EC2は既にCDKで作成済みである想定です。
const instance = new ec2.Instance(this, 'Instance', {
...略
})
💻 実装
1. Scheduler用のIAMロール
まず、EventBridge SchedulerがEC2を起動停止するためのIAMロールを作成します:
import * as cdk from 'aws-cdk-lib';
import { aws_iam as iam } from 'aws-cdk-lib';
// ----- Scheduler Role -----
const schedulerRole = new iam.Role(this, 'SchedulerRole', {
assumedBy: new iam.ServicePrincipal('scheduler.amazonaws.com'),
description: 'IAM role for EventBridge Scheduler to start/stop EC2',
});
schedulerRole.addToPolicy(
new iam.PolicyStatement({
actions: ['ec2:StartInstances', 'ec2:StopInstances'],
resources: [
cdk.Stack.of(this).formatArn({
service: 'ec2',
resource: 'instance',
resourceName: props.targetEc2.instanceId,
}),
],
}),
);
scheduler.amazonaws.comサービスプリンシパルを使用し、対象のEC2インスタンスに対してのみ起動停止権限を付与しています。
本来であれば、起動用と停止用で分けるべきかと思いますが、今回は一緒にしちゃいます。
2. ターゲットの定義
次に、実際に実行するアクションを定義します:
import { aws_scheduler_targets as targets } from 'aws-cdk-lib';
const startTarget = new targets.Universal({
service: 'ec2',
action: 'startInstances',
input: scheduler.ScheduleTargetInput.fromObject({
InstanceIds: [props.targetEc2.instanceId],
}),
role: schedulerRole,
});
const stopTarget = new targets.Universal({
service: 'ec2',
action: 'stopInstances',
input: scheduler.ScheduleTargetInput.fromObject({
InstanceIds: [props.targetEc2.instanceId],
}),
role: schedulerRole,
});
aws_scheduler_targetsには、いくつかのテンプレートが用意されています。
マネコン経由で確認した場合、以下の種類があるようです。
ここに記載のないサービスに関しては、Universalを使って定義する必要があります。
今回はEC2が対象なので、上記のように記載します。
inputに関しては、マネジメントコンソールから実際のフォーマットを確認するのが早いです。
EC2の場合は以下のようになりますので、同じようにCDK上で指定しています。
3. Scheduler Group(任意)
import { aws_scheduler as scheduler } from 'aws-cdk-lib';
const scheduleGroup = new scheduler.ScheduleGroup(this, 'Group');
Scheduler Groupは、必須でというわけではありませんが、以下の観点から作成をしています。
- メトリクスの管理: グループ単位でスケジューラーの実行状況を確認可能
- 権限管理: グループレベルでのアクセス制御
指定しなければ「Default」のScheduler Groupが使用されます。
4. Scheduler本体
最後に、実際のスケジュール設定を行います。
import { aws_scheduler as scheduler } from 'aws-cdk-lib';
new scheduler.Schedule(this, 'StartSchedule', {
schedule: scheduler.ScheduleExpression.cron({
// 月曜日から金曜日の8:45に起動する
minute: '45',
hour: '8',
month: '*',
weekDay: 'MON-FRI',
year: '*',
timeZone: cdk.TimeZone.ASIA_TOKYO,
}),
target: startTarget,
scheduleGroup: scheduleGroup,
});
視認性向上のため各項目を明示的に指定しています。省略した項目は自動的に「*」が設定されるので、ご注意ください。
また、weekDayとdayは同時に定義不可です。
同様に停止用のスケジューラーも作成します。
new scheduler.Schedule(this, 'StopSchedule', {
schedule: scheduler.ScheduleExpression.cron({
// 毎日の20:00に停止する
minute: '00',
hour: '20',
day: '*',
month: '*',
year: '*',
timeZone: cdk.TimeZone.ASIA_TOKYO,
}),
target: stopTarget,
scheduleGroup: scheduleGroup,
});
📊 作成されたリソースの確認
デプロイ後、AWSマネジメントコンソールで以下のように確認できます。
Scheduler Group
Scheduler
メトリクスを見る限り、無事に動作していることが確認できます!
🎯 まとめ
EventBridge SchedulerとCDKでEC2の停止機構を実装してみました。
DBとの連携が合って、起動順を考慮する必要がある場合はもう少し検討の余地ありですが、単にEC2を起動、停止するのみであれば、とても簡単に実装することができます。
これが少しでもお役に立てば幸いです!



