概要
- コスト削減のため、ECSやRDSを休日に停止させるコードを記述する
- 平日に起動させる処理はまた別途作成が必要(後述の処理を流用して書き換える)
システム構成
- Lambda
- ECS
- RDS(RDS Proxy)
- EventBridge
Lambdaのコード
python
import boto3
import os
import logging
import time
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def retry(func, retries=3, delay=5):
for attempt in range(retries):
try:
return func()
except Exception as e:
if attempt < retries - 1:
logger.warning(f'Retry {attempt + 1} failed: {str(e)}. Retrying...')
time.sleep(delay)
else:
logger.error(f'Failed after {retries} attempts: {str(e)}')
raise
def stop_ecs_service(ecs_client, ecs_cluster_name, ecs_service_name):
ecs_client.update_service(
cluster=ecs_cluster_name,
service=ecs_service_name,
desiredCount=0
)
def stop_rds_cluster(rds_client, rds_cluster_identifier):
rds_client.stop_db_cluster(
DBClusterIdentifier=rds_cluster_identifier
)
def wait_for_rds_cluster_stop(rds_client, rds_cluster_identifier):
while True:
response = rds_client.describe_db_clusters(DBClusterIdentifier=rds_cluster_identifier)
status = response['DBClusters'][0]['Status']
logger.info(status)
if status == 'stopped':
break
time.sleep(30)
def remove_rds_proxy_targets(rdsproxy_client, rds_proxy_name, rds_instance_identifier):
response = rdsproxy_client.describe_db_proxies(DBProxyName=rds_proxy_name)
logger.info(f'Describe DB Proxies response: {response}')
if 'DBProxies' in response and len(response['DBProxies']) > 0:
db_proxy = response['DBProxies'][0]
logger.info(db_proxy)
target_groups = db_proxy.get('TargetGroups', [])
if target_groups:
for target_group in target_groups:
rdsproxy_client.deregister_db_proxy_targets(
DBProxyName=rds_proxy_name,
TargetGroupName=target_group['TargetGroupName'],
DBInstanceIdentifiers=[rds_instance_identifier]
)
logger.info(f'Target groups removed for DB Proxy {rds_proxy_name}')
else:
logger.warning(f"No target groups found for DB Proxy {rds_proxy_name}")
else:
logger.warning(f"No DB Proxies found with name {rds_proxy_name}")
def lambda_handler(event, context):
ecs_client = boto3.client('ecs')
rds_client = boto3.client('rds')
rdsproxy_client = boto3.client('rds')
ecs_cluster_name = os.environ['ECS_CLUSTER_NAME']
ecs_service_name = os.environ['ECS_SERVICE_NAME']
rds_cluster_identifier = os.environ['RDS_CLUSTER_IDENTIFIER']
rds_instance_identifier = os.environ['RDS_INSTANCE_IDENTIFIER']
rds_proxy_name = os.environ['RDS_PROXY_NAME']
try:
retry(lambda: stop_ecs_service(ecs_client, ecs_cluster_name, ecs_service_name))
logger.info('ECSサービスを停止しました')
retry(lambda: stop_rds_cluster(rds_client, rds_cluster_identifier))
logger.info('RDSクラスターの停止処理が開始しました')
wait_for_rds_cluster_stop(rds_client, rds_cluster_identifier)
logger.info('RDSクラスターが停止しました')
retry(lambda: remove_rds_proxy_targets(rdsproxy_client, rds_proxy_name, rds_instance_identifier))
logger.info('RDS Proxyターゲットグループの削除処理が完了しました')
message = 'Successfully stopped ECS, RDS, and updated RDS Proxy'
logger.info(message)
except Exception as e:
message = f'Failed to stop ECS, RDS, or update RDS Proxy: {str(e)}'
logger.error(message)
raise e
return {
'statusCode': 200,
'body': message
}
上記のコードは同期処理のため、動かしてみて処理が15分以上かかる場合は非同期処理にする
コード上でリトライ処理を実装しているが、EventBridgeのルール作成でもリトライ処理は設定可能
必要なこと
IAM
- ロール作成
- ポリシー作成
- LambdaExcecution
- CloudWatchLog
- RDS
- ECS
- RDSProxy
→作成したロールをLambdaにアタッチ
セキュリティグループ
インバウンドルールまたはアウトバウンドルールを設定する
RDS及びECSのインバウンドルールに、Lambdaのアクセス許可設定は不要。停止処理は管理者操作のため。
EventBridge
- cron式でスケジュール設定する(設定方法はAWS内のツールチップを参照)
- 実装したLambda関数を指定する
おわりに
- 過去に個人で実装した経験を思い出しながら執筆しました
- このコードで問題なく動作した記憶があるので、問題ないはずです
- 仮に動作しない場合は構成が違っている可能性があるので、コードを修正して実装してみてください
以上