0.はじめに
これまで、ジョブ管理に「Zabbix」の Add-On の「Job Arranger for Zabbix」というものを使って、
Python スクリプトを定期実行して、EC2 インスタンスの自動起動/停止をしていたんですが、
出来れば AWS 内で完結出来ないかなぁ、と考えていて、
どうやら CloudWatch Events → Lambda (Python スクリプト) で出来るみたいなので試してみました。
1.IAM ポリシーを作成する
- 以下のページから、IAM ポリシーを作成します。
-
IAM Management Console
-
※ SNS の設定は Lambda の DLQ 用です。不要であれば削除して下さい。必要な場合は、事前に SNS にトピックを作成しておきましょう。
-
IAM Management Console
{
"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 ロールを作成する
- 以下のページから、IAM ロールを作成します。作成した IAM ポリシーを適用します。
3.Lambda の Python スクリプトを作成する
- 以下のページから、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 のルールを作成する
- 以下のページから、CloudWatch Events のコンソールにアクセスし、「ルールの作成」を押下します。
- 「ステップ 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 に。
-
- イベントソース : ※任意。今回は、スケージュールドリブンの設定を例として記載。
5.自動起動させたい EC2 インスタンスにタグを設定する
- 以下のページから、所定の EC2 インスタンスにタグを設定します。
99.ハマりポイント
- 特に、ハマったところはありませんが、
- Python スクリプトの書き方がこれでいいのか、すこぶる不安です…。ご指摘があれば、よろしくお願いします。
XX.まとめ
次は、EC2 インスタンスの AMI バックアップスクリプトをやろうかと。