モチベーション
高額な EC2 (r4.16xlarge とか)を立ち上げた後、うっかり止め忘れるととんでもない料金を請求されてしまう。
ただ、利用中の ec2 を停止されるのも困る。
CPU 使用率を見て、使ってなさそうなら止めるようにしたい。
やったこと
Lambda, CloudWatch を使って実装。
停止対象の EC2
- 特定の tag がつけられていること
- 過去3時間のメトリクスが取得できること
- 過去3時間の CPU 使用率が閾値以下であること
構成
処理フロー
- CloudWatch から1時間に1度 Lambda を実行する
- CloudWatch から EC2 のメトリクスを取得する
- 条件を満たす EC2 を停止する
言語
Python3.6
Lambda コードのバージョン管理
lambda のバージョン管理を利用する。
classmethod さんの資料を参考
Lambda の実装
- Key="AutoStop", Value="true" のタグが付与された EC2 の Instance Id を取得する
- CloudWatch から Instance Id 引きで CPU 使用率の datapoint を取得する
- 使用中の EC2 か判定する
- 条件を満たす EC2 の Instance Id を取得・停止する
import boto3
import datetime
CPU_UTILIZATION_THRESHOLD = 1.0
METRIC_PERIOD_SECONDS = 300
METRIC_TIME_DELTA_HOURS = 3
def lambda_handler(event, context):
target_id_list = get_target_instance_id_list()
if target_list:
client = boto3.client('ec2', "ap-northeast-1")
client.stop_instances(InstanceIds=target_list)
def get_target_instance_id_list():
client = boto3.client('ec2', "ap-northeast-1")
resp = client.describe_instances()
target_list = []
for reservation in resp['Reservations']:
print(len(reservation['Instances']))
for instance in reservation['Instances']:
instance_id = get_instance_id_of_target_tag(instance)
if instance_id and not is_using_ec2(instance_id):
target_list.append(instance_id)
return target_list
def get_instance_id_of_target_tag(instance):
if 'Tags' not in instance:
return None
if instance['State']['Name'] != 'running':
return None
for tag in instance['Tags']:
if tag['Key'] == 'AutoStop' and tag['Value'] == 'true':
return instance['InstanceId']
def get_datapoints(instance_id):
client = boto3.client('cloudwatch', region_name='ap-northeast-1')
# Get EC2 CPUUtilization
response = client.get_metric_statistics(
Namespace='AWS/EC2',
MetricName='CPUUtilization',
Dimensions=[
{
'Name': 'InstanceId',
'Value': instance_id
}
],
StartTime=datetime.datetime.now() - datetime.timedelta(hours=METRIC_TIME_DELTA_HOURS),
EndTime=datetime.datetime.now(),
Period=METRIC_PERIOD_SECONDS,
Statistics=['Average']
)
return response['Datapoints']
def is_using_ec2(instance_id):
datapoints = get_datapoints(instance_id)
if len(datapoints) < METRIC_TIME_DELTA_HOURS * 60 * 60 / METRIC_PERIOD_SECONDS:
return False
is_using = False
for datapoint in datapoints:
if datapoint['Average'] > CPU_UTILIZATION_THRESHOLD:
is_in_use = True
return is_using
さいごに
今回は Lambda で実装したが、数が少なければ CloudWatch を使った自動停止 の方がスマートな解決方法かも。
営業日以外は止めたい場合は Instance Scheduler で対応できそう。