3
1

More than 1 year has passed since last update.

AWSで遊ばない時間はNATゲートウェイを削除したい

Last updated at Posted at 2022-08-23

背景

AWSではじめるインフラ構築入門を読んでAWSの再入門に励んでいるのですが、
NATゲートウェイのコストは馬鹿になりません。1円2円を求めてポイ活をしている私にとっては耐え難いレベルです。

使っている時間に課金されてしまうのは仕方ないとして、使っていない時間はNATゲートウェイを削除しておきたいものです。
ところがNATゲートウェイはルートテーブルなどの周辺機能にも影響を及ぼしており、手動で削除/起動するのは結構手間です。

そこでAWS Lambdaを使ってNATゲートウェイの削除/起動を自動化してみます。

要件

要件は以下の通りです。

  • NATゲートウェイの削除/起動や周辺機能の設定変更を自動化したい。
  • 特定の時刻や曜日に自動起動するのではなく、オンデマンドで実行したい。手動でボタンをポチッとするイメージ。

前提

Lambda関数のランタイムは「Python 3.9」を選んでいますが、バージョンは大きな問題ではないでしょう。
CloudFormationなどを使うと上手に配布できるみたいですが、よくわからないのでコードをベタ書きしちゃいます。

VPCにパブリック/プライベートのサブネットがそれぞれ2つあり、両方のパブリックサブネットにNATゲートウェイがある構成を対象としています。要はAWSではじめるインフラ構築入門の構成を前提としています。

関数を使用するにはまずはサブネットのIDやルートテーブルのIDなどが必要になりますので、
まずは書籍にしたがってNATゲートウェイの作成とルートテーブルの設定まで手動で完了させてパラメータを確定させてください。

エラーハンドリングなどはまともにしておりませんので、設定ミスなどで関数が途中で止まってしまった場合、
リソースの状態が中途半端になってしまう可能性があります。そのときはコンソールを手動でポチポチするなりしてください。

準備

  • 以下のポリシーを付与したIAMロールを作成します。
    • CloudWatchLogsFullAccess
    • AmazonVPCFullAccess
    • AWSLambdaBasicExecutionRole

削除用関数

  • Lambdaの画面の[関数を作成]→[一から作成]から作成します。
  • 「準備」で作成したIAMロールを実行ロールとして設定してください。
  • 関数名はdisable-nat-gatewayにしていますがお好きに名前を付けてください。
  • NATゲートウェイの削除にはそれなりに時間がかかるのでタイムアウト時間は6分にしました。

コード

disable-nat-gateway
import os
import boto3
import logging
from time import sleep

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

client = boto3.client('ec2')

def delete_elastic_ip(elastic_ip_id):
    logger.info('Deleting Elastic IP...')
    response = client.release_address(AllocationId=elastic_ip_id)
    logger.info(response)
    
def delete_nat_gateway(subnet_id):
    logger.info('Deleting NAT Gateway...')
    response = client.describe_nat_gateways(
        Filters = [
            {
                'Name': 'subnet-id',
                'Values': [subnet_id]
            },
            {
                'Name': 'state',
                'Values': ['available']
            }
        ]
    )
    logger.info(response)
    nat_gateway_id = response['NatGateways'][0]['NatGatewayId']
    elastic_ip_id = response['NatGateways'][0]['NatGatewayAddresses'][0]['AllocationId'];
    client.delete_nat_gateway(NatGatewayId=nat_gateway_id)
    sleep(120) # 必要に応じて変更せよ
    
    return(elastic_ip_id)
    
def delete_route_to_nat_gateway(route_table_id):
    logger.info('Deleting route to NAT Gateway...')
    
    response = client.delete_route(
        DestinationCidrBlock = '0.0.0.0/0',
        RouteTableId = route_table_id
    )
    logger.info(response)

def lambda_handler(event, context):
    logger.info('Started to Stopping NAT Gateway...')
    
    subnet_id = os.environ['NatGatewayTargetSubnetId1']
    route_table_id = os.environ['PrivateSubnetRouteTableId1']
    delete_route_to_nat_gateway(route_table_id)
    eip_id = delete_nat_gateway(subnet_id)
    delete_elastic_ip(eip_id)
    
    subnet_id = os.environ['NatGatewayTargetSubnetId2']
    route_table_id = os.environ['PrivateSubnetRouteTableId2']
    delete_route_to_nat_gateway(route_table_id)
    eip_id = delete_nat_gateway(subnet_id)
    delete_elastic_ip(eip_id)
    
    logger.info('Finished to Stopping NAT Gateway...')

delete_nat_gateway()の中に謎のsleepがあります。
これはNATゲートウェイが削除されないとElastic IPの削除に失敗することがあるため待ち時間を入れています。

ドキュメントによると下のような削除されるまで上手に待ってくれるwaiterがあるはずなのですが、
実行時にエラーになってしまったのでエイヤッで待ちを入れるようにしてしまいました。いずれ使えるようになるのかな?

これがしたかった
client.get_waiter('nat_gateway_deleted').wait(NatGatewayIds=[nat_gateway_id])

環境変数

[設定]→[環境変数]より以下の環境変数を設定します。
NATゲートウェイが2つあるので変数もそれぞれ1つずつです。ダサいです。

キー 値の例 説明
NatGatewayTargetSubnetId1 subnet-*** 削除したい1つめのNATゲートウェイがあるサブネットID
NatGatewayTargetSubnetId2 subnet-*** 削除したい2つめのNATゲートウェイがあるサブネットID
PrivateSubnetRouteTableId1 rtb-*** 1つめのNATゲートウェイへのデフォルトルートを削除したいルートテーブルID
PrivateSubnetRouteTableId2 rtb-*** 2つめのNATゲートウェイへのデフォルトルートを削除したいルートテーブルID

実行

NATゲートウェイを葬り去りたくなったら、このLambda関数の「Test」ボタンを押下します。
テスト用データは空っぽ{}でかまいません。必要なデータは環境変数から取得します。

起動用関数

  • Lambdaの画面の[関数を作成]→[一から作成]から作成します。
  • 「準備」で作成したIAMロールを実行ロールとして設定してください。
  • 関数名はenable-nat-gatewayにしていますがお好きに名前を付けてください。
  • タイムアウト時間は削除時と同様に6分にしました。

コード

enable-nat-gateway
import os
import boto3
import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

client = boto3.client('ec2')

def allocate_elastic_ip():
    logger.info('Allocating Elastic IP...')
    response = client.allocate_address(Domain='vpc')
    logger.info(response)
    
    return response['AllocationId']
    
def create_nat_gateway(eip_id, subnet_id, nat_gateway_name):
    logger.info('Creating NAT Gateway...')
    response = client.create_nat_gateway(
        AllocationId = eip_id,
        SubnetId = subnet_id,
        TagSpecifications=[
            {
                "ResourceType": "natgateway",
                "Tags": [
                    {"Key": "Name", "Value": nat_gateway_name},
                ]
            }
        ]
    )
    logger.info(response)
    
    nat_gateway_id = response['NatGateway']['NatGatewayId']
    client.get_waiter('nat_gateway_available').wait(NatGatewayIds=[nat_gateway_id])
    
    return(nat_gateway_id)
    
def create_route_to_nat_gateway(nat_gateway_id, route_table_id):
    logger.info('Creating route to NAT Gateway...')
    response = client.create_route(
        DestinationCidrBlock = '0.0.0.0/0',
        NatGatewayId = nat_gateway_id,
        RouteTableId = route_table_id
    )
    logger.info(response)

def lambda_handler(event, context):
    logger.info('Started to Setting up NAT Gateway...')
    
    subnet_id = os.environ['NatGatewayTargetSubnetId1']
    route_id = os.environ['PrivateSubnetRouteTableId1']
    nat_name = os.environ['NatGatewayName1']
    eip_id = allocate_elastic_ip()
    nat_id = create_nat_gateway(eip_id, subnet_id, nat_name)
    create_route_to_nat_gateway(nat_id, route_id)
    
    subnet_id = os.environ['NatGatewayTargetSubnetId2']
    route_id = os.environ['PrivateSubnetRouteTableId2']
    nat_name = os.environ['NatGatewayName2']
    eip_id = allocate_elastic_ip()
    nat_id = create_nat_gateway(eip_id, subnet_id, nat_name)
    create_route_to_nat_gateway(nat_id, route_id)
    
    logger.info('Finished to Setting up NAT Gateway...')

環境変数

[設定]→[環境変数]より以下の環境変数を設定します。

キー 値の例 説明
NatGatewayName1 sample-ngw-01 1つめのNATゲートウェイの名前
NatGatewayName2 sample-ngw-02 2つめのNATゲートウェイの名前
NatGatewayTargetSubnetId1 subnet-*** 1つめのNATゲートウェイを配置したいサブネットのID
NatGatewayTargetSubnetId2 subnet-*** 2つめのNATゲートウェイを配置したいサブネットのID
PrivateSubnetRouteTableId1 rtb-*** 1つめのNATゲートウェイへのデフォルトルートを追加したいルートテーブルID
PrivateSubnetRouteTableId2 rtb-*** 2つめのNATゲートウェイへのデフォルトルートを追加したいルートテーブルID

実行

NATゲートウェイを起動したくなったら、このLambda関数の「Test」ボタンを押下します。
削除時と同様にテスト用データは空っぽ{}でかまいません。

参考文献

3
1
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
3
1