share facebook facebook facebook twitter twitter menu hatena pocket slack

2021.04.14 WED

AWS ChaliceでEC2とRDSを自動で起動停止するLambdaを作成

西田 駿史

WRITTEN BY 西田 駿史

任意のタグが付いたEC2、RDSインスタンスをスケジュールで起動停止するためのLambdaです。

Chaliceで作ってあるのでハンドラーを変えればAPIエンドポイントから実行したりもできます。

以下実装です。

「AutoStartStop」タグが付けられたインスタンスを対象にして、平日の10時に起動、平日の19時に停止します。

app.py

from chalice import Chalice
import logging
import os
import boto3

app = Chalice(app_name='schedule-startstop')
app.log.setLevel(logging.DEBUG)

env_dryrun = False if os.environ['DRYRUN'].upper() == 'FALSE' else True


def change_instances_state(event, state='Start', dryrun=True):

    rds = boto3.client('rds')
    ec2 = boto3.client('ec2')

    if state == 'Stop':  # 停止させる条件
        filter_ec2_instance_state = 'running'
        filter_rds_instance_state = 'available'
    else:  # 起動する条件
        filter_ec2_instance_state = filter_rds_instance_state = 'stopped'

    app.log.info("Search Target EC2 Instances.")

    # 対象のEC2インスタンスを取得
    target_ec2_instances = ec2.describe_instances(
        Filters=[
            {
                'Name': 'tag-key',
                'Values': ['AutoStartStop']
            },
            {
                'Name': 'instance-state-name',
                'Values': [filter_ec2_instance_state]
            }
        ]
    ).get(
        'Reservations', []
    )

    # 対象のEC2インスタンスのIDを取得
    target_ec2_instance_ids = []
    for instance in target_ec2_instances:
        target_ec2_instance_ids.append(
            instance['Instances'][0]['InstanceId'])

    if len(target_ec2_instance_ids) > 0:
        try:
            # 対象のEC2インスタンスを起動or停止
            app.log.info("%s EC2 Instances:%s" %
                         (state, ",".join(target_ec2_instance_ids)))
            if state == 'Stop':
                ec2.stop_instances(
                    InstanceIds=target_ec2_instance_ids, DryRun=dryrun)
            else:
                ec2.start_instances(
                    InstanceIds=target_ec2_instance_ids, DryRun=dryrun)

        except Exception as e:
            app.log.error(e)
    else:
        app.log.info("No Target EC2 Instances.")

    app.log.info("Search Target RDS Instances.")

    # 対象のRDSインスタンスを取得
    target_rds_instances = rds.describe_db_instances().get('DBInstances', [])

    for instance in target_rds_instances:

        # タグと状態でフィルタ
        instance_state = instance['DBInstanceStatus']
        instance_tags = rds.list_tags_for_resource(
            ResourceName=instance['DBInstanceArn']).get('TagList', [])
        has_tag = next(iter(filter(
            lambda tag: tag['Key'] == 'AutoStartStop', instance_tags)), None)

        if instance_state == filter_rds_instance_state and has_tag:
            try:
                # 対象のRDSインスタンスを起動
                instance_id = instance['DBInstanceIdentifier']
                app.log.info("%s RDS Instance:%s" % (state, instance_id))

                if state == 'Stop':
                    if not dryrun:
                        rds.stop_db_instance(
                            DBInstanceIdentifier=instance_id)
                    else:
                        app.log.info("Dry run stop_db_instance.")

                else:
                    if not dryrun:
                        rds.start_db_instance(
                            DBInstanceIdentifier=instance_id)
                    else:
                        app.log.info("Dry run start_db_instance.")

            except Exception as e:
                app.log.error(e)


@app.schedule('cron(0 1 ? * MON-FRI *)')  # 平日10時起動(UTC:1時)
def cron_start_handler(event):
    change_instances_state(event, 'Start', env_dryrun)


@app.schedule('cron(0 10 ? * MON-FRI *)')  # 平日19時停止(UTC:10時)
def cron_stop_handler(event):
    change_instances_state(event, 'Stop', env_dryrun)

requirements.txt

boto3

.chalice/config.json

{
  "version": "2.0",
  "app_name": "schedule-startstop",
  "stages": {
    "dev": {
      "api_gateway_stage": "api",
      "lambda_timeout": 10,
      "environment_variables": {
        "DRYRUN": "True"
      }
    }
  }
}

このコードでは環境変数「DRYRUN」にTrueをセットして、実際に起動停止処理が走らないようにしています。
事前に狙ったインスタンスが処理対象になるか確認するのが目的です。
本当に起動停止処理を走らせる場合は「DRYRUN」にFalseをセットしてデプロイしてください。

後は処理対象にしたいEC2およびRDSのタグのKeyに(Valueは空でいいです)「AutoStartStop」を入れるだけで、時間が来たら自動で起動停止がかかります。

起動停止のタイミングを修正する場合はcron式を修正してください(UTCなので注意)。

こちらの記事の祝日対応版です。Google Calendar APIを使って起動日が祝日の場合は処理を行わないように。また、Google Calendar APIだと正月や会社の休日に対応できないので、環境変数に別途例外休日...

元記事はこちら

AWS ChaliceでEC2とRDSを自動で起動停止するLambdaを作成

西田 駿史

西田 駿史

2019年4月入社。第四開発事業部グループリーダー。海岸オフィス所属

cloudpack

cloudpackは、Amazon EC2やAmazon S3をはじめとするAWSの各種プロダクトを利用する際の、導入・設計から運用保守を含んだフルマネージドのサービスを提供し、バックアップや24時間365日の監視/障害対応、技術的な問い合わせに対するサポートなどを行っております。
AWS上のインフラ構築およびAWSを活用したシステム開発など、案件のご相談はcloudpack.jpよりご連絡ください。