AWS Lambda(Python,boto3)でElasticIPを付け替える(EC2スポットインスタンス自動化4)

# 背景(再掲)
個人向けに最低費用で運用するため、Ec2をスポットインスタンスで使用しているが、時々スポットリクエストが中断される。その都度、再度スポットリクエストで立ち上げなおしているが、この処理を自動化したい。

# 大まかな流れ(再掲)
スポットインスタンスの中断はイベント通知されるので、それをトリガとして、立ち上げ処理を行う。

全体の流れは第1回参照。本記事はスポットリクエストの中断イベントから対象の情報を取得する部分が対象。

# 記事の全体
1. [AWS BackupでEC2のAMIが作成されるので、その最新のAMI IDをイベントから取得してParameterStoreに登録する。](https://qiita.com/silv224/items/0c1d157d34cd8ffb651b)
1. [AWS Lambda(python)でLINEへ通知する。](https://qiita.com/silv224/items/37453c8e7401f3a77d5f)
1. [AWS Lambda(python)でEC2スポットリクエストを実行する。](https://qiita.com/silv224/items/e089df858dc518a99713)
1. [AWS Lambda(Python,boto3)でElasticIPを付け替える。](https://qiita.com/silv224/items/5e5d4b07a92aa10d1bee)**←今回の内容**
1. [Step FunctionでLambdaでスポットリクエストを実行し、ElasticIPを付け替える。](https://qiita.com/silv224/items/1d8f425eca0f31db7236)

# 調べたこと

付け替えの対象情報を取得する。
https://boto3.amazonaws.com/v1/documentation/api/latest/guide/ec2-example-elastic-ip-addresses.html

elastic ipのタグNameにホスト名を入れているので、その名称で対象を検索する。
```(検索テスト).py
import boto3

ec2 = boto3.client('ec2')
filters = [
    {'Name': 'tag:Name', 'Values': ['ホスト名']}
]
response = ec2.describe_addresses(Filters=filters)
print(response)
```
レスポンス。関連付けされているか否かはAssociationIdの有無で分かる。
これがあったら、開放→新インスタンスに関連付け、なかったら新インスタンスに関連付けのみとする。
```(レスポンス).json
{
  "Addresses": [
    {
      "InstanceId": "i-**********",
      "PublicIp": "パブリックIPアドレス(elasti IPアドレス)",
      "AllocationId": "eipalloc-*****************",　<-- ElasticIPのID(associateで使う)
      "AssociationId": "eipassoc-*****************", <-- 関連付けされている状態。
      "Domain": "vpc",
      "NetworkInterfaceId": "eni-******************",
      "NetworkInterfaceOwnerId": "**********",
      "PrivateIpAddress": "EC2のプライベートIPアドレス",
      "Tags": [{ "Key": "Name", "Value": "ホスト名" }],
      "PublicIpv4Pool": "amazon",
      "NetworkBorderGroup": "ap-northeast-1"
    }
  ],
  "ResponseMetadata": {
   <略>
  }
}
```

付け替える方法。関連付け解除(disassociate)して、関連付け(associate)
```(解除).py
import boto3
from botocore.exceptions import ClientError

ec2 = boto3.client('ec2')

try:
    response = ec2.disassociate_address(AssociationId='eipassoc-*******')
    print(response)
except ClientError as e:
    print(e)
```
```(関連付け).py
import boto3
from botocore.exceptions import ClientError

ec2 = boto3.client('ec2')

try:
    response = ec2.associate_address(AllocationId='eipalloc-**********',
                                     InstanceId='i-************')
    print(response)
except ClientError as e:
    print(e)
```

# Lambda関数
作成したLambda関数。
```(lambda_Function_changeElasticIPAddress).py
import boto3
from botocore.exceptions import ClientError
import json

def lambda_handler(event, context):

    print('input event:' + json.dumps(event))

    instance_ids = event['instance_id']

    ec2 = boto3.client('ec2')
    filters = [
        {'Name': 'tag:Name', 'Values': ['ホスト名']}
    ]
    response = ec2.describe_addresses(Filters=filters)
    print('elastic ip address:' + json.dumps(response))

    allocationId = response['Addresses'][0]['AllocationId']
    returnMessage = 'ip address [' + response['Addresses'][0]['PublicIp'] + '] '

    # elastic ip addressが関連付けされている場合は、先に外してから付ける
    if response['Addresses'][0]['AssociationId']:
        print( 'Associated. (id=' + response['Addresses'][0]['AssociationId'] + ')')

        try:
            # 関連付けを外す
            print('disassociate elastic ip address')
            response = ec2.disassociate_address(AssociationId=response['Addresses'][0]['AssociationId'])
            print('disassociate elastic ip address(response):' + json.dumps(response))
            returnMessage += ' was disassociated,'
        except ClientError as e:
            print(e)
            returnMessage += ' disassociation was failed.'
    try:
        # 新規インスタンスに関連付け
        print('associate elastic ip address')
        response = ec2.associate_address(AllocationId=allocationId,InstanceId=instance_ids[0])
        print('associate elastic ip address(response):' + json.dumps(response))
        returnMessage += ' associated'
    except ClientError as e:
        print(e)
        returnMessage += ' association was failed.'

    responseData = [
        {
            'type' : 'text',
            'text' : returnMessage
        },
        {
            'type' : 'sticker', 
            'packageId' : 6370, 
            'stickerId' : 11088016
        }
    ]
    return ( responseData)
```

Lambda実行の権限に加え、アドレスの付け替えに必要なポリシーを追加しておきます。
```(必要な権限).json
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Sid": "VisualEditor0",
			"Effect": "Allow",
			"Action": [
				"ec2:DisassociateAddress",
				"ec2:DescribeAddresses",
				"ec2:AssociateAddress"
			],
			"Resource": "*"
		}
	]
}
```