LoginSignup
2
1

More than 1 year has passed since last update.

【AWSコスト削減の道 for 開発環境】 NATゲートウェイ編

Last updated at Posted at 2021-12-21

【AWSコスト削減の道 for 開発環境】NATゲートウェイ編

結論

  • NATゲートウェイを利用していない時間に対して、自動作成及び自動削除を行うことで、料金の約62%を低下

前提

  • 以前ページにて紹介しているTerraoformを用いて環境を構築している
  • 本記事内で記載している料金については、2021/12/21時点の料金を表示している
  • 料金は「東京リージョン」の料金を表示している

目次

前提
背景
本記事内の利用技術について
利用料金について
料金停止効果について
Lambda内の処理について
NATゲートウェイのライフサイクル
参考URL

概要

  • 開発環境に使用しているNATを業務時間以外は削除する
  • NATゲートウェイの作成及び削除は自動的に行う
    • NATゲートウェイはEC2のように停止という概念がないため毎回、作成、削除を行う必要がある

背景

  • 業務時間外にも課金状態となっているAWSリソースに対してコストを削減しようとしたため

本記事内の利用技術について

  • NATゲートウェイ

    • プライベートIPアドレスをパブリックIPアドレスに変更するサービス
  • Lambda

    • 特定のイベントに応じて実行される関数 ※ サーバなどを考えずに処理内容だけ考えればいいので便利!
  • EventBrige

    • AWSリソースからのイベント通知を受け取り、特定の処理をさせることができるサービス
    • 本記事では、特定の時間にLambdaを実行させるようにする設定をしている

利用料金について

前提

  • データ処理料金については加味していない ※ 利用していない時間の為、データ処理は「0」と判断したため

詳細

  • NAT ゲートウェイあたりの料金 (USD/時)
    • 0.062USD

停止時間について

  • 停止曜日は以下としている
    • 月 ~ 金 ※ 祝日は考慮していない
  • 各曜日の停止時間は以下としている
    • PM10:00 ~ AM8:00

料金停止効果について

結論

約62%の節約!!

  • 0.61... = 27.28USD(停止後の料金) / 44.64USD(停止前の料金)

停止前

  • 720時間(1か月の料金) * 0.062USD = 44.64USD

停止後

  • 440時間(1か月の料金) * 0.062USD = 27.28USD
    • 1日の起動時間: 14時間 = 24時間 - 10時間(一日あたりの停止時間)
    • 1週間の起動時間: 70時間 = 168時間 - (10 * 5)時間(1週間内の平日停止時間) - 48時間(1週間内の土、日停止時間)
    • 1か月の起動時間: 440時間 = 720時間 - (70 * 4)時間

停止方法について

  • EventBrigeを用いて特定の時間にLambdaを実行するようにしている

考慮した点について

  • NATゲートウェイは停止という概念がないため、使用していない時間はNATゲートウェイを削除している。
    削除後は設定情報も削除されるので、設定情報を保持するために、Lambdaの環境変数にあらかじめNATゲートウェイを作成する際の設定情報を保持させている

Lambda内の処理について

nat_handler.py
import calendar
import os
import json
import urllib.request
from datetime import datetime
from enum import Enum
from dataclasses import dataclass

import boto3

ec2 = boto3.client('ec2', region_name='ap-northeast-1')
# 処理対象
processing_objects = {
    # 環境変数にElasticIPやSubnetIDをあらかじめ保存して置き必要なときに呼び出せるようにしている
    'NAT_INFOMATION': os.environ['NAT_INFOMATION'],
}
destination_cidr_block = '0.0.0.0/0'


class LaunchType(Enum):
    CREATE = 'create'
    DESTROY = 'destroy'


@dataclass
class LambdaResponse:
    processed: dict
    launch_type: LaunchType

    def successful_format(self) -> dict:
        return {'statusCode': 200, 'processed {}'.format(self.launch_type.value): self.processed}

    def failed_format(self) -> dict:
        return {'statusCode': 500, 'processed {}'.format(self.launch_type.value): self.processed}


def is_holiday() -> bool:
    class HolidayName(Enum):
        SATURDAY = 'Saturday'
        SUNDAY = 'Sunday'

    # 実行時の曜日を文字列で取得(JST)
    today_str = calendar.day_name[datetime.today().weekday()]

    return today_str == HolidayName.SATURDAY.value or today_str == HolidayName.SUNDAY.value


def get_launch_type(event: dict) -> LaunchType:
    key_name = 'status'
    if event[key_name] is None:
        raise Exception('control tag name the None: variable name is {}'.format(key_name))

    launch_type = event[key_name]
    if launch_type == LaunchType.CREATE.value:
        return LaunchType.CREATE
    elif launch_type == LaunchType.DESTROY.value:
        return LaunchType.DESTROY
    else:
        raise Exception('control tag name illegal value: variable name is {}'.format(key_name))


def nat_handler(launch_type: LaunchType) -> LambdaResponse:
    processed_result = {}
    if launch_type == LaunchType.CREATE:
        for value in processing_objects.values():
            info = json.loads(value)
            response = ec2.create_nat_gateway(
                AllocationId=info['EipAllocationId'],
                SubnetId=info['SubnetId'],
                TagSpecifications=[
                    {
                        'ResourceType': 'natgateway',
                        'Tags': [
                            {
                                'Key': 'Name',
                                'Value': info['NatName']
                            }
                        ]
                    }
                ])
            natgw_id = response['NatGateway']['NatGatewayId']
            ec2.get_waiter('nat_gateway_available').wait(NatGatewayIds=[natgw_id])
            ec2.create_route(DestinationCidrBlock=destination_cidr_block, NatGatewayId=natgw_id,
                             RouteTableId=info['PrivateRouteTableId'])

            processed_result[info['NatName']] = value
    elif launch_type == LaunchType.DESTROY:
        for value in processing_objects.values():
            info = json.loads(value)
            filters = [
                {
                    'Name': 'subnet-id',
                    'Values': [info['SubnetId']]
                },
                {
                    'Name': 'state',
                    'Values': ['available']
                },
                {
                    'Name': 'tag:Name',
                    'Values': [info['NatName']]
                }
            ]
            response = ec2.describe_nat_gateways(Filters=filters)
            if len(response['NatGateways']) == 0:
                continue

            natgw = response['NatGateways'][0]['NatGatewayId']
            ec2.delete_nat_gateway(NatGatewayId=natgw)
            ec2.delete_route(DestinationCidrBlock=destination_cidr_block, RouteTableId=info['PrivateRouteTableId'])

            processed_result[info['NatName']] = value
    else:
        raise Exception('control tag name illegal value: variable name is launch_type')

    return LambdaResponse(processed_result, launch_type)


def lambda_handler(event, context):

    # 休日の場合は処理を実行しない
    if is_holiday():
        return {'statusCode': 300, 'message': 'today is holiday! Take a rest now!!'}

    launch_type = get_launch_type(event)
    response = nat_handler(launch_type)

    return response.successful_format()

NATゲートウェイのライフサイクル

nat_life_cycle.drawio.png

参考URL

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