Elastic IP をケチりたい
たまにしか起動しないインスタンスに Elastic IP をアタッチするのはもったいない。といって、起動するたびにIPアドレスが変わるとホスト名でアクセスできなくて面倒くさい。
……というテーマで以前、記事を書きました。
今回はそれを推し進めて CloudWatch Event でインスタンスの起動を監視して、起動したら自動でIPアドレスを Route53 のAレコードに登録する、という Lambda を書きました。これでEIP(と無駄な支出)とはおさらばだ!
あと、余計かもしれないけどインスタンスを停止したときにレコードを削除するようにしてみました。意味あるかなぁ。
必要な権限
登録したい Route 53 のホストゾーンのレコード一覧を取得・変更するための権限:
route53:ChangeResourceRecordSets
route53:ListResourceRecordSets
インスタンスの情報を取得するための権限:
ec2:DescribeInstances
あとは Lambda 利用の基本ポリシーである AWSLambdaBasicExecutionRole
Lambda のコード
import json
import os
from datetime import datetime
import boto3
def json_dt(o):
if isinstance(o, datetime):
return o.isoformat()
def change_record(action, host_name, host_addr):
domain_name = os.environ.get('DomainName')
if domain_name:
host_name = F"{host_name}.{domain_name}."
client = boto3.client('route53')
change_batch = {
"Comment": "optional comment about the changes in this change batch request",
"Changes": [
{
"Action": action,
"ResourceRecordSet": {
"Name": host_name,
"Type": "A",
"TTL": 300,
"ResourceRecords": [
{
"Value": host_addr
}
]
}
}
]
}
print("change_batch: " + json.dumps(change_batch, default=json_dt))
response = client.change_resource_record_sets(
HostedZoneId=os.environ.get('HostedZoneId'),
ChangeBatch=change_batch
)
print("result: " + json.dumps(response, default=json_dt))
return response
def get_hostname_from_tags(tags):
host_name = ''
for tag in tags:
if tag['Key'].lower() == 'hostname':
host_name = tag['Value'].lower()
return host_name
def check_action(state):
if state == 'running':
action = 'UPSERT'
elif state == 'stopping':
action = 'DELETE'
else:
action = ''
return action
def lambda_handler(event, context):
result = dict()
print('event: ' + json.dumps(event, default=json_dt))
action = check_action(event['detail']['state'])
if action:
ec2 = boto3.resource('ec2')
instance = ec2.Instance(event['detail']['instance-id'])
host_name = get_hostname_from_tags(instance.tags)
if host_name:
host_addr = instance.public_ip_address
result = change_record(action, host_name, host_addr)
return json.dumps(result, default=json_dt)
ざっくり説明
Hostname
タグがあるインスタンスだけ対象にします。
Lambda の設定
環境変数
今この Lambda を使っている環境では、ドメイン名と Route53 のホストゾーンIDはひとつだけなので Lambda の環境変数で設定するようにしました。
複数のホストゾーンにまたがって動くようにすると、結構めんどうくさそう。
HostedZoneId=ZABCDEFGHIJKL
CloudWatch Event の設定
スクリーンショットを参考に、なんとか……。
Lambda に渡される event の例
こんな感じで、InstanceId
と State
が取れます。
{
"version": "0",
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"detail-type": "EC2 Instance State-change Notification",
"source": "aws.ec2",
"account": "123456789012",
"time": "2019-11-20T08:52:00Z",
"region": "ap-northeast-1",
"resources": [
"arn:aws:ec2:ap-northeast-1:123456789012:instance/i-0123456789abcdefg"
],
"detail": {
"instance-id": "i-0123456789abcdefg",
"state": "stopping"
}
}
注意点
レコードを DELETE
するときは、レコードの Value
が現在のものでないと失敗するんだそうで。
In the case of a DELETE action, if the current value does not match the actual value, an error is returned.
エラーハンドリングなにもしていないので、あとで考えます。
change_resource_record_sets
のドキュメントがこれだけ長いと、なにかの拍子で例外が上がるかもしれないですね……。
いずれエラーハンドリングをきちんとして、記事を更新したいと思います。いずれ……。