はじめに
こんにちは、はやぴー(@HayaP)です。
皆さん、サーバーの節約対策していますか?
クラウドを使うのであれば、なるべくサーバーを
効率的に使いたいものです。
そこで、今回は
EC2インスタンスの自動起動を、より柔軟に実装した
経験を記したいと思います。
対象読者
- サーバー代を節約したい。
- サーバー毎に起動、停止時間を柔軟にしたい。
この記事が、クライドネイティブな運用の参考になれればと願っています。
概要
実現方法
AWS公式記事や、技術ブログを見ていると
複数のやり方がありますが、主に下記だと思います。
- インスタンススケジューラー(AWS ソリューションライブラリー)
- System Managerを用いた方法
- Lambdaを用いた方法
今回は、Lambdaを用いて
より柔軟なサーバーの自動起動停止を実現したいと思います。
要件
タイトルにもありますが、今回はより柔軟に自動起動停止を実現したいと思います。
具体的には、
- サーバー毎に、1h単位で自動起動時間を設定したい
- 平日と休日でも、起動パターンを変えたい
- サーバー数が、今後増える想定のためスケーラブルに
というのが要件です。
例:
サーバーA
起動:08:00
停止:18:00
サーバーB
起動:10:00
停止:24:00
サーバーC
起動:(平日のみ)10:00
停止:(平日のみ)18:00
問題
開発、運用面を考えると、AWS ソリューションライブラリーが
ベストだと思います。
しかし、要件にあるような柔軟さを実現しようとすると
複数の起動パターンを作る必要があり、最適とは言えません。
解決策
そのため、EC2タグと、Lambdaを用いた方法で
よりシンプルに実現する方法を考えました。
かなり雑ですが、構成は下記のとおりです。
では、具体的な方法を記していきます。
詳細
リソース毎に説明をします。順序ではないので、注意してください。
EventBridge
スケジュールを作成します。
毎時起動したいため、
cron 0 * * * ? *
を指定します。
ターゲットは、一つ目のLambda(ターゲット検索)を指定します。
SNS
トピックを作成します。タイプはスタンダードにしています。
要件に応じて変更をしてください。
次に、サブスクリプションを作成します。
エンドポイントは、後続で作成するSQSキューを指定してください。
SQS
キューを作成します。
タイプは標準にしています。
要件に応じて変更をしてください。
次に、Lambdaトリガーを設定します。
後続で作成するLambdaを指定してください。
Lambda
最後に、
2つのLambdaを作成します。
1. ターゲット検索
2. 自動起動/停止実行
設定は、基本的にデフォルトでOKです。要件に応じて変更してください。
下記は、サンプルコードです。
1. ターゲット検索
import boto3
import datetime
from datetime import datetime
from zoneinfo import ZoneInfo
REGION = "ap-northeast-1"
TIME_ZONE = "Asia/Tokyo"
TOPIC_ARN = "xxx"
AUTO_START_TAG_KEY = "AutoStart"
AUTO_STOP_TAG_KEY = "AutoStop"
ec2_boto3 = boto3.client('ec2', region_name=REGION)
sns_boto3 = boto3.client('sns')
def get_instance_info(instance_state, tag_key, tag_value):
return ec2_boto3.describe_instances(Filters=[
{
'Name': 'instance-state-name',
'Values': [instance_state],
},
{
'Name': f'tag:{tag_key}',
'Values': [tag_value],
},
])
def create_instance_id_list(response):
instance_id_list = []
for instance_info in response['Reservations']:
instance_id_list.append(instance_info["Instances"][0]["InstanceId"])
return instance_id_list
def publish_sns(message, subject):
sns_boto3.publish(
TopicArn=TOPIC_ARN,
Message=message,
Subject=subject,
)
def lambda_handler(event, context):
try:
now = datetime.now(ZoneInfo(TIME_ZONE))
hour = now.strftime("%H")
# 5->土曜日, 6->日曜日
if now.weekday() != (5 or 6):
start_target_list = get_instance_info(
"stopped", AUTO_START_TAG_KEY, "wd" + hour)
stop_target_list = get_instance_info(
"running", AUTO_STOP_TAG_KEY, "wd" + hour)
if start_target_list["Reservations"]:
for instance_id in start_target_list["Reservations"][0]["Instances"]:
publish_sns(instance_id["InstanceId"], "start")
if stop_target_list["Reservations"]:
for instance_id in stop_target_list["Reservations"][0]["Instances"]:
publish_sns(instance_id["InstanceId"], "stop")
start_target_list = get_instance_info(
"stopped", AUTO_START_TAG_KEY, hour)
stop_target_list = get_instance_info(
"running", AUTO_STOP_TAG_KEY, hour)
if start_target_list["Reservations"]:
for instance_id in start_target_list["Reservations"][0]["Instances"]:
publish_sns(instance_id["InstanceId"], "start")
if stop_target_list["Reservations"]:
for instance_id in stop_target_list["Reservations"][0]["Instances"]:
publish_sns(instance_id["InstanceId"], "stop")
return {
'statusCode': 200
}
except Exception as e:
print("errorMessage:", e)
raise Exception
import json
import boto3
ec2_boto3 = boto3.client('ec2')
ssm_boto3 = boto3.client('ssm')
lambda_boto3 = boto3.client('lambda')
def parse_event(event):
instance_id = json.loads(event["Records"][0]["body"])["Message"]
execution_type = json.loads(event["Records"][0]["body"])["Subject"]
return instance_id, execution_type
def lambda_handler(event, context):
try:
# execution_type = "start" or "stop"
instance_id, execution_type = parse_event(event)
print("instance_id:",instance_id,"execution_type:",execution_type)
if execution_type == "start":
ec2_boto3.start_instances(InstanceIds=[instance_id])
elif execution_type == "stop":
ec2_boto3.stop_instances(InstanceIds=[instance_id])
else:
raise
return {
'statusCode': 200
}
except Exception as e:
print("errorMessage:",e)
raise Exception
まとめ
いかがだったでしょうか?
今回は、Lambdaを使用しましたが、
初めに紹介した方法に加え、StepFunctionsでも実装できると思います。
要件と、ケイパビリティによって技術選定をしましょう。
是非、活用してみてください!