背景
このAWS Lambda関数は、タグ値で制御してEC2インスタンスの起動や停止を行うことができます。EventBridgeを使用した夜間の定期停止や、AWS Change Calendarを利用した不定期な長期停止など、様々なシナリオで活用できる汎用的なEC2インスタンス管理ツールです。Spotインスタンスの停止ができるようになったので、さらなるコスト削減のために作りました。
実行環境
エンジン
Python3.12
環境
Lambda
関数の概要
この関数は以下の構造のJSONペイロードを受け取ります:
{
"region": string, # 対象リージョン
"tagName": string, # 対象EC2インスタンスのタグ名
"action": string, # start(起動)またはstop(停止)
"test": bool # テストモード(true/false)
}
機能
- 指定された
tagName
の値がaction
または"true"であるEC2インスタンスIDを取得します。 -
action
が"start"の場合、インスタンスIDとNameタグを表示し、インスタンスを起動します(test
がtrueの場合を除く)。 -
action
が"stop"の場合、インスタンスIDとNameタグを表示し、インスタンスを停止します(test
がtrueの場合を除く)。
セットアップ
- AWSアカウントで新しいLambda関数を作成します。
- ランタイムとしてPython 3.12を使用します。
- 提供されたPythonコードをLambda関数にコピーします。
- 適切なIAMロールを設定し、必要最小限の権限を付与します(IAMポリシーセクションを参照)。
IAMポリシー
Lambda関数の実行ロールに以下のIAMポリシーを付与してください:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:StartInstances",
"ec2:StopInstances"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
}
]
}
このポリシーは、EC2インスタンスの詳細取得、起動/停止、およびログの書き込みに必要な権限を付与します。
使用方法
Lambda関数をJSONペイロードで呼び出します。例:
{
"region": "ap-northeast-1",
"tagName": "Environment",
"action": "start",
"test": false
}
これにより、ap-northeast-1リージョンで"Environment"タグの値が"start"または"true"であるすべてのEC2インスタンスが起動されます。
テスト
ペイロードのtest
パラメータをtrue
に設定すると、実際にインスタンスを起動/停止せずに関数を実行できます。これにより、どのインスタンスが影響を受けるかを確認できます。
ログ
関数は、インスタンスIDとそのNameタグ、および実行されたアクションをCloudWatch Logsに記録します。詳細な実行情報については、Lambda関数のCloudWatchロググループを確認してください。
関数の中身
import boto3
import json
def lambda_handler(event, context):
region = event['region']
tag_name = event['tagName']
action = event['action']
test = event['test']
ec2 = boto3.client('ec2', region_name=region)
# Get EC2 instances with the specified tag
response = ec2.describe_instances(
Filters=[
{'Name': f'tag:{tag_name}', 'Values': [action, 'true']}
]
)
instances = []
for reservation in response['Reservations']:
for instance in reservation['Instances']:
instance_id = instance['InstanceId']
name = next((tag['Value'] for tag in instance.get('Tags', []) if tag['Key'] == 'Name'), 'N/A')
instances.append((instance_id, name))
if not instances:
return {
'statusCode': 200,
'body': json.dumps(f'No instances found with tag {tag_name}={action} or {tag_name}=true')
}
for instance_id, name in instances:
print(f"Instance ID: {instance_id} ({name})")
instance_ids = [instance[0] for instance in instances]
if not test:
if action == 'start':
ec2.start_instances(InstanceIds=instance_ids)
message = f"Started instances: {', '.join([f'{id} ({name})' for id, name in instances])}"
elif action == 'stop':
ec2.stop_instances(InstanceIds=instance_ids)
message = f"Stopped instances: {', '.join([f'{id} ({name})' for id, name in instances])}"
else:
message = f"Invalid action: {action}. Use 'start' or 'stop'."
else:
message = f"Test mode: Would have {action}ed instances: {', '.join([f'{id} ({name})' for id, name in instances])}"
return {
'statusCode': 200,
'body': json.dumps(message)
}