ec2にタグ判定で自動起動/停止を設定する
1. 目的
常時起動する必要のないEC2インスタンスを料金の削減のために自動で7:00に起動、22:00に停止するようなスケジューラを作成します。
次の要件を満たすことが条件です。
- cron式を使って定時実行を設定します。
- 指定のタグからインスタンスIDを抽出して実行します。
- インスタンスIDの指定の場合は対象を変更する場合再設定が必要になるため、柔軟に対応可能なタグでの実装とします。
参考にさせていただいた記事
Amazon EventBridgeからEC2インスタンスを起動•停止•再起動する簡易な方法5選 | DevelopersIO
[AWS] Pythonのboto3でタグ付きEC2だけを停止させる
検討事項
- インスタンスのタグを対象として実装するためにLambdaを使用します。
- EC2の自動起動と停止にはAutomationドキュメント、API、Lambdaを使用するパターンがあります。
- AutomationドキュメントやAPIを使用する場合はインスタンスタグではなくインスタンスIDを使用する必要があります。
- 起動/停止日時を指定するためにEventBridge Rulesで実装します。
- cron式を使用することができます。
2. 実現方法
実装案
今回はLambdaでコードを書いてEventBridge Rulesで定時実行する案を考えます。
必要なリソース
起動/停止用
- Lambda実行用のIAM Role x1
- 停止と起動は同じロールを参照します。
- Lambda実行用のIAM policy x1
- LambdaがEC2を停止/起動する権限
起動用
- Lambda関数 x1
- EventBridge Rules(cron式) x1
停止用
- Lambda関数 x1
- EventBridge Rules(cron式) x1
3. 実装
リソースの作成
IAM Roleの作成
Lambdaに必要な権限は下記の通りです。
-
EC2のリストとインスタンスのパラメータ参照権限
- EC2インスタンスのタグを読み込み、インスタンスIDを取得するために使用します。
- マネージドポリシーを使用します。
AmazonEC2ReadOnlyAccess{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "ec2:Describe*", "Resource": "*" }, { "Effect": "Allow", "Action": "elasticloadbalancing:Describe*", "Resource": "*" }, { "Effect": "Allow", "Action": [ "cloudwatch:ListMetrics", "cloudwatch:GetMetricStatistics", "cloudwatch:Describe*" ], "Resource": "*" }, { "Effect": "Allow", "Action": "autoscaling:Describe*", "Resource": "*" } ] }
-
EC2の開始/停止権限
EC2を開始/起動させるだけのマネージドポリシーがないため新規作成します。
lambda-ec2-automation-iam-policy-common{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "ec2:StartInstances", "ec2:StopInstances" ], "Resource": "arn:aws:ec2:ap-northeast-1:{yourAccountId}:instance/*" } ] }
Lambda関数の作成
Lambda関数ではBoto3というPythonライブラリを使用することで、AWSのAPIコールを実行することができます。
今回書きたい処理は下記の通りです。
- 起動中のEC2インスタンスから特定のタグが付いたリソースのIDを配列で取得
- 配列に格納されたIDに起動/停止コマンドを実行
起動と停止はそれぞれ別のLambda関数を作成し、それぞれのスケジューラに紐づけることにします。
- 関数の作成画面
コードは下記の通りで、boto3からインスタンスの記述と停止のAPIコールを呼び出ししています。
import json
import boto3
def lambda_handler(event, context):
stop_ec2()
def stop_ec2():
client = boto3.client('ec2', region_name='ap-northeast-1')
response = client.describe_instances(Filters=[
{ # EC2が稼働中
'Name': 'instance-state-name',
'Values': ['running'],
},
{ # EC2のタグ「AutoStop」の値が「true」
'Name': 'tag:AutoStop',
'Values': ['true'],
},
])
instance_ids = []
for instance_dic in response['Reservations']:
instance_ids.append(instance_dic['Instances'][0]['InstanceId'])
for instance in instance_ids:
print('instanceid:' + (instance))
response = client.stop_instances(InstanceIds=instance_ids)
print(response)
タイムアウトはデフォルトで3秒ですが、APIの実行時間を加味して10秒に変更しました。
ターゲットとなるインスタンスにタグをつけます。
実行結果
function-lambda-stop-ec2-commonをテスト実行すると、インスタンスが停止中のステータスとなりました。
Test Event Name
test
Response
null
Function Logs
START RequestId: fef43f2c-630f-4038-96f0-df4f7b13ff8c Version: $LATEST
instanceid:i-07xxxxxxxxxxxxxx
{'StoppingInstances': [{'CurrentState': {'Code': 64, 'Name': 'stopping'}, 'InstanceId': 'i-07xxxxxxxxxxxxxx', 'PreviousState': {'Code': 16, 'Name': 'running'}}], 'ResponseMetadata': {'RequestId': 'ffb55593-646b-491b-a0c1-59ea61eaa962', 'HTTPStatusCode': 200, 'HTTPHeaders': {'x-amzn-requestid': 'ffb55593-646b-491b-a0c1-59ea61eaa962', 'cache-control': 'no-cache, no-store', 'strict-transport-security': 'max-age=31536000; includeSubDomains', 'content-type': 'text/xml;charset=UTF-8', 'content-length': '579', 'date': 'Tue, 27 Feb 2024 01:43:50 GMT', 'server': 'AmazonEC2'}, 'RetryAttempts': 0}}
END RequestId: fef43f2c-630f-4038-96f0-df4f7b13ff8c
REPORT RequestId: fef43f2c-630f-4038-96f0-df4f7b13ff8c Duration: 3506.24 ms Billed Duration: 3507 ms Memory Size: 128 MB Max Memory Used: 89 MB Init Duration: 363.37 ms
Request ID
fef43f2c-630f-4038-96f0-df4f7b13ff8c
停止用の関数でのテスト結果は問題ないようなので、同様にstartの関数も作成します。
import json
import boto3
def lambda_handler(event, context):
start_ec2()
def start_ec2():
client = boto3.client('ec2', region_name='ap-northeast-1')
response = client.describe_instances(Filters=[
{ # EC2が停止中
'Name': 'instance-state-name',
'Values': ['stopped'],
},
{ # EC2のタグ「AutoStart」の値が「true」
'Name': 'tag:AutoStart',
'Values': ['true'],
},
])
instance_ids = []
for instance_dic in response['Reservations']:
instance_ids.append(instance_dic['Instances'][0]['InstanceId'])
for instance in instance_ids:
print('instanceid:' + (instance))
response = client.start_instances(InstanceIds=instance_ids)
print(response)
こちらもテストを実行すると、インスタンス起動が開始しました。
Lambda関数の作成はこれで完了です。
EventBridgeで定期的にLambda関数を呼び出す
ここまで作成した関数は手動で呼び出さなければ動作しないので、EventBridge Rulesで定時実行するように設定します。
startはcronで平日7時から起動するパターンをスケジュールします。
Lambda > 関数 > 設定タブ > トリガー > トリガーの追加を選択
cron式を入力するだけで、IAMロールを作成せずにEventBridge Rulesを作成することができます。
時刻の設定を失敗
cron式をEventBridge Rulesから確認してみると、ローカルタイムゾーンではなくUTCで設定されています。
Amazon EventBridge > ルール > schedule-eventbridge-start-ec2-common > ルールを編集 からルールを修正します。
UTCとのずれは(-9:00)なので設定したい時刻に9時間減算して22時に変更します。
Stopも同様に設定します。
4. まとめ
EC2インスタンスにつけられたタグごとに定時起動/停止を設定したい場合はLambda関数を作成してEventBridgeでcron式で起動する仕組みで実現することができます。
Discussion