LoginSignup
2
2

More than 5 years have passed since last update.

CloudWatch Events から Lambda (Python スクリプト) 経由で EC2 を自動起動/停止してみる

Posted at

0.はじめに

これまで、ジョブ管理に「Zabbix」の Add-On の「Job Arranger for Zabbix」というものを使って、

Python スクリプトを定期実行して、EC2 インスタンスの自動起動/停止をしていたんですが、

出来れば AWS 内で完結出来ないかなぁ、と考えていて、

どうやら CloudWatch Events → Lambda (Python スクリプト) で出来るみたいなので試してみました。

1.IAM ポリシーを作成する

  1. 以下のページから、IAM ポリシーを作成します。
    • IAM Management Console

    • ※ SNS の設定は Lambda の DLQ 用です。不要であれば削除して下さい。必要な場合は、事前に SNS にトピックを作成しておきましょう。

    • 1001.png
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeImages",
                "ec2:DeregisterImage",
                "logs:CreateLogStream",
                "ec2:DescribeInstances",
                "ec2:DeleteSnapshot",
                "ec2:StartInstances",
                "ec2:CreateImage",
                "ec2:DescribeSnapshots",
                "ec2:StopInstances",
                "logs:CreateLogGroup",
                "logs:PutLogEvents"
            ],
            "Resource": "*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": "sns:Publish",
            "Resource": "arn:aws:sns:*:************:SysOps-Lambda-DLQ"
        }
    ]
}

2.IAM ロールを作成する

  1. 以下のページから、IAM ロールを作成します。作成した IAM ポリシーを適用します。

3.Lambda の Python スクリプトを作成する

  1. 以下のページから、Lambda のPython スクリプトを作成します。
MaintenanceEC2_Instance_Start.py
#!/usr/bin/env python
# -*- coding: utf-8-unix; -*-
"""AWS Lambda Function - Maintenance EC2 Instances - Start
"""
from __future__ import print_function

import logging
import boto3
import os

# 「Python 3 で少しだけ便利になった datetime の新機能 - Qiita」
# <https://qiita.com/methane/items/d7ac3c9af5a2c659bc51>
from datetime import datetime, timezone, timedelta
TimeZone = timezone(timedelta(hours=+9), 'JST')

# 「Lambdaの本番業務利用を考える① - ログ出力とエラーハンドリング | ナレコムAWSレシピ」
# <https://recipe.kc-cloud.jp/archives/9968>
logger = logging.getLogger()
logLevelTable={'DEBUG':logging.DEBUG,'INFO':logging.INFO,'WARNING':logging.WARNING,'ERROR':logging.ERROR,'CRITICAL':logging.CRITICAL}
if os.environ.get('logging_level') in logLevelTable :
    logger.setLevel(logLevelTable[os.environ['logging_level']])
else:
    logger.setLevel(logging.WARNING)

#
StartDateTime = datetime.now(TimeZone)

def AmazonEC2_DescribeStartInstances(response_instances, TagKey, TagValueType):
    try:
        start_instances = []
        for n, k in enumerate(response_instances["Reservations"]):
            id = response_instances["Reservations"][n]["Instances"][0].get("InstanceId")
            state = response_instances["Reservations"][n]["Instances"][0]["State"].get("Name")
            tags = {}
            for nn, kk in enumerate(response_instances["Reservations"][n]["Instances"][0]["Tags"]):
                tags[response_instances["Reservations"][n]["Instances"][0]["Tags"][nn]["Key"]] = response_instances["Reservations"][n]["Instances"][0]["Tags"][nn]["Value"]
            name = tags.get("Name")
            #
            TagValue = None
            TagCheck = None
            if TagValueType == "DateTime-%H Sat:%H Sun:%H":
                TagValue = tags.get(TagKey)
                if TagValue:
                    TagValues = TagValue.split(" ")
                    TagValue = TagValues[0]
                    # Mon-Fri:0-4 Sat:5 Sun:6
                    StartDateTime_Weekday = StartDateTime.weekday()
                    if StartDateTime_Weekday >= 5:
                        TagValue = None
                        for i, v in enumerate(TagValues):
                            if StartDateTime_Weekday == 5 and v.startswith("Sat:"):
                                TagValue = v[-2:]
                                break
                            if StartDateTime_Weekday == 6 and v.startswith("Sun:"):
                                TagValue = v[-2:]
                                break
                TagCheck = datetime.strftime(StartDateTime, '%H')
            logger.info("TagValue:[%s]", TagValue)
            logger.info("TagCheck:[%s]", TagCheck)
            if TagValue is None or TagCheck is None or TagValue != TagCheck:
                logger.debug("(%04d/%04d) EC2 Instance | [--] ID:[%s] State:[%s] Tags:[%s]",
                n + 1, len(response_instances["Reservations"]), id, state, tags)
                continue
            logger.debug("(%04d/%04d) EC2 Instance | [Do] ID:[%s] State:[%s] Tags:[%s]",
            n + 1, len(response_instances["Reservations"]), id, state, tags)
            start_instances.append({
                "InstanceId" : id,
                "State" : state,
                "Tags" : tags,
            })

    except Exception as e:
        logger.exception('Error dosomething: %s', e)
    else:
        pass
    finally:
        pass
    return start_instances

def lambda_handler(event, context):
    try:
        global StartDateTime
        StartDateTime = datetime.now(TimeZone)
        logger.info("StartDateTime:[%s]", StartDateTime)
        #
        logger.info("event:[%s]", event)
        logger.info("context:[%s]", context)
        #
        DryRun = event.get("DryRun")
        if not DryRun:
            DryRun = False
        logger.info("DryRun:[%s]", DryRun)
        if not isinstance(DryRun, bool):
            return "[ERROR] Parameter DryRun Format is Bool"
        TagKey = event.get("TagKey")
        logger.info("TagKey:[%s]", TagKey)
        if not TagKey:
            return "[ERROR] Parameter TagKey is not set"
        if not isinstance(TagKey, str):
            return "[ERROR] Parameter TagKey Format is Str"
        TagValueType = event.get("TagValueType")
        logger.info("TagValueType:[%s]", TagValueType)
        if not TagValueType:
            return "[ERROR] Parameter TagValueType is not set"
        if not isinstance(TagValueType, str):
            return "[ERROR] Parameter TagValueType Format is Str"
        #
        ec2_client = boto3.client("ec2")
        response_instances = ec2_client.describe_instances()
        # Amazon EC2 - Start
        start_instances = AmazonEC2_DescribeStartInstances(response_instances, TagKey, TagValueType)
        for n, k in enumerate(start_instances):
            instanceid = k["InstanceId"]
            state = k["State"]
            tags = k["Tags"]
            logger.info(("(%04d/%04d) Start [Do] "
                "ID:[%s] State:[%s] Tags:[%s]"),
                n + 1, len(start_instances),
                instanceid, state, tags)
            if DryRun:
                logger.info("Start Instance - Dry Run. (DryRun is %s)", DryRun)
                continue
            response_start_instance = ec2_client.start_instances(
                InstanceIds=[instanceid])
            logger.info("Start Instance Response | [%s] | %s", instanceid, response_start_instance)
    except Exception as e:
        logger.exception('Error dosomething: %s', e)
    else:
        pass
    finally:
        pass
    #
    return "normal end"
MaintenanceEC2_Instance_Stop.py
#!/usr/bin/env python
# -*- coding: utf-8-unix; -*-
"""AWS Lambda Function - Maintenance EC2 Instances - Stop
"""
from __future__ import print_function

import logging
import boto3
import os

# 「Python 3 で少しだけ便利になった datetime の新機能 - Qiita」
# <https://qiita.com/methane/items/d7ac3c9af5a2c659bc51>
from datetime import datetime, timezone, timedelta
TimeZone = timezone(timedelta(hours=+9), 'JST')

# 「Lambdaの本番業務利用を考える① - ログ出力とエラーハンドリング | ナレコムAWSレシピ」
# <https://recipe.kc-cloud.jp/archives/9968>
logger = logging.getLogger()
logLevelTable={'DEBUG':logging.DEBUG,'INFO':logging.INFO,'WARNING':logging.WARNING,'ERROR':logging.ERROR,'CRITICAL':logging.CRITICAL}
if os.environ.get('logging_level') in logLevelTable :
    logger.setLevel(logLevelTable[os.environ['logging_level']])
else:
    logger.setLevel(logging.WARNING)

#
StartDateTime = datetime.now(TimeZone)

def AmazonEC2_DescribeStopInstances(response_instances, TagKey, TagValueType):
    try:
        stop_instances = []
        for n, k in enumerate(response_instances["Reservations"]):
            id = response_instances["Reservations"][n]["Instances"][0].get("InstanceId")
            state = response_instances["Reservations"][n]["Instances"][0]["State"].get("Name")
            tags = {}
            for nn, kk in enumerate(response_instances["Reservations"][n]["Instances"][0]["Tags"]):
                tags[response_instances["Reservations"][n]["Instances"][0]["Tags"][nn]["Key"]] = response_instances["Reservations"][n]["Instances"][0]["Tags"][nn]["Value"]
            name = tags.get("Name")
            #
            TagValue = None
            TagCheck = None
            if TagValueType == "DateTime-%H Sat:%H Sun:%H":
                TagValue = tags.get(TagKey)
                if TagValue:
                    TagValues = TagValue.split(" ")
                    TagValue = TagValues[0]
                    # Mon-Fri:0-4 Sat:5 Sun:6
                    StartDateTime_Weekday = StartDateTime.weekday()
                    if StartDateTime_Weekday >= 5:
                        TagValue = None
                        for i, v in enumerate(TagValues):
                            if StartDateTime_Weekday == 5 and v.startswith("Sat:"):
                                TagValue = v[-2:]
                                break
                            if StartDateTime_Weekday == 6 and v.startswith("Sun:"):
                                TagValue = v[-2:]
                                break
                TagCheck = datetime.strftime(StartDateTime, '%H')
            logger.info("TagValue:[%s]", TagValue)
            logger.info("TagCheck:[%s]", TagCheck)
            if TagValue is None or TagCheck is None or TagValue != TagCheck:
                logger.debug("(%04d/%04d) EC2 Instance | [--] ID:[%s] State:[%s] Tags:[%s]",
                n + 1, len(response_instances["Reservations"]), id, state, tags)
                continue
            logger.debug("(%04d/%04d) EC2 Instance | [Do] ID:[%s] State:[%s] Tags:[%s]",
            n + 1, len(response_instances["Reservations"]), id, state, tags)
            stop_instances.append({
                "InstanceId" : id,
                "State" : state,
                "Tags" : tags,
            })

    except Exception as e:
        logger.exception('Error dosomething: %s', e)
    else:
        pass
    finally:
        pass
    return stop_instances

def lambda_handler(event, context):
    try:
        global StartDateTime
        StartDateTime = datetime.now(TimeZone)
        logger.info("StartDateTime:[%s]", StartDateTime)
        #
        logger.info("event:[%s]", event)
        logger.info("context:[%s]", context)
        #
        DryRun = event.get("DryRun")
        if not DryRun:
            DryRun = False
        logger.info("DryRun:[%s]", DryRun)
        if not isinstance(DryRun, bool):
            return "[ERROR] Parameter DryRun Format is Bool"
        TagKey = event.get("TagKey")
        logger.info("TagKey:[%s]", TagKey)
        if not TagKey:
            return "[ERROR] Parameter TagKey is not set"
        if not isinstance(TagKey, str):
            return "[ERROR] Parameter TagKey Format is Str"
        TagValueType = event.get("TagValueType")
        logger.info("TagValueType:[%s]", TagValueType)
        if not TagValueType:
            return "[ERROR] Parameter TagValueType is not set"
        if not isinstance(TagValueType, str):
            return "[ERROR] Parameter TagValueType Format is Str"
        #
        ec2_client = boto3.client("ec2")
        response_instances = ec2_client.describe_instances()
        # Amazon EC2 - Stop
        stop_instances = AmazonEC2_DescribeStopInstances(response_instances, TagKey, TagValueType)
        for n, k in enumerate(stop_instances):
            instanceid = k["InstanceId"]
            state = k["State"]
            tags = k["Tags"]
            logger.info(("(%04d/%04d) Stop [Do] "
                "ID:[%s] State:[%s] Tags:[%s]"),
                n + 1, len(stop_instances),
                instanceid, state, tags)
            if DryRun:
                logger.info("Stop Instance - Dry Run. (DryRun is %s)", DryRun)
                continue
            response_stop_instance = ec2_client.stop_instances(
                InstanceIds=[instanceid])
            logger.info("Stop Instance Response | [%s] | %s", instanceid, response_stop_instance)
    except Exception as e:
        logger.exception('Error dosomething: %s', e)
    else:
        pass
    finally:
        pass
    #
    return "normal end"

4.CloudWatch Events のルールを作成する

  1. 以下のページから、CloudWatch Events のコンソールにアクセスし、「ルールの作成」を押下します。
  2. 「ステップ 1: ルールの作成」画面が表示されるので、以下の項目を設定し、「設定の詳細」ボタンを押下します。
    • イベントソース : ※任意。今回は、スケージュールドリブンの設定を例として記載。
      • ● 「スケジュール」
        • Cron 式 : 「0 * * * ? *」 ※日本時間で、毎時 00 分に実行。
        • ※ UTC なので注意!!
    • ターゲット : 「Lambda 関数」
      • 機能 : ※作成した Lambda 関数
      • バージョンエイリアスの設定 : ※適当に
      • 入力の設定 :
        • ● 「定数 (JSON テキスト)」
        • { "TagKey": "StartTime", "TagValueType": "DateTime-%H Sat:%H Sun:%H", "DryRun": false }
          • "TagKey" : EC2 インスタンスで参照するタグ名
          • "TagValueType" : EC2 インスタンスで参照するタグの設定形式
          • "DryRun" : ドライランの場合は、true に。

    • 4002.png

  3. 「ステップ 2: ルールの詳細を設定する」画面が表示されるので、以下の項目を設定し、「ルールの作成」ボタンを押下します。
    • 名前 : ※任意
    • 説明 : ※任意

    • 4003.png

5.自動起動させたい EC2 インスタンスにタグを設定する

  1. 以下のページから、所定の EC2 インスタンスにタグを設定します。

99.ハマりポイント

  • 特に、ハマったところはありませんが、

  • Python スクリプトの書き方がこれでいいのか、すこぶる不安です…。ご指摘があれば、よろしくお願いします。

XX.まとめ

次は、EC2 インスタンスの AMI バックアップスクリプトをやろうかと。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2