#AWSの料金が思ったより高かった
AWSに限らず、クラウドでは簡単にサーバを立てて使う事ができるので、ちょっとした実験やプログラムの勉強、PoC環境や開発環境を作るのに、とても便利です。
ですが、こういう用途の場合、予算があまり無い事が多いので、月末に請求書を見て「えっ、思っていたより高い」と思う事があります(ありました)。
そこで、AWSの料金を安くするために色々試してみました。
##AWS料金の大半はEC2
請求書の内訳をみると、80~90%はEC2でした。
なので、EC2の料金を抑えるのがAWS料金を抑えるのに最も効果的です。
EC2料金の内訳は以下の通りです。
- Amazon Elastic Compute Cloud NatGateway
- NATに対する時間課金
- Amazon Elastic Compute Cloud running Linux/UNIX
- EC2に対する時間課金
- EBS
- EC2が使っているEBSに対するサイズ課金(EC2を止めていても掛かる)
- Elastic IP Addresses
- インスタンスにアタッチされていないIPアドレスに対する時間課金
- Elastic Load Balancing - Application
- ロードバランサーに対する時間課金
###EBS以外は時間課金
EC2の料金はEBS以外は時間課金です。使わなければ料金は掛かりません。
しかし、EBSのサイズ課金はEC2を止めていても掛かるので、無駄にデカいサイズのSSDを使っていると、無駄に課金されます。
EC2作成時のディスサイズは必要最低限にして、足りなくなったら追加しましょう。
AMIで保存されるイメージのサイズも課金対象なので、不要なAMIは削除しましょう。
#使っていない時間は止めれば良いよね
時間課金を抑えるには、インスタンスの稼働時間を減らせば良いのですが、使う時間を減らすのでは本末転倒なので、使っていない時間帯(夜中、人によっては朝)にインスタンスを止めたいと思います。
##Lambda関数でインスタンスを止める
使わない時に止めたいのはEC2インスタンスですが、NATゲートウェイを使っている場合、この課金も安くないので、止めたいところです。残念ながら、2018年11月現在、NATゲートウェイはEC2インスタンスのように停止する事が出来ず、削除することしかできません。
NATゲートウェイを削除した場合、アタッチされているElastic IP(以下EIP)が不使用状態になって、(NATゲートウェイを止めている間)EIPに時間課金が掛かります。
EIPはネットワークインターフェースにアッタッチされているので、NATゲートウェイを削除しても直ぐにリリースする事が出来ません。なので、EC2やNATを停止、削除するLambda関数と、使っていないEIPを削除するLambda関数を2つ作る必要があります。
###Lambda関数用のIAMロール作成
Lambda関数に、EC2インスタンスの起動・停止、NATゲートウェイの作成・削除、EIPの作成・解放権限を持たせるために、以下のロールを付与したIAMロールを作成します。
- AmazonEC2FullAccess
- AmazonVPCFullAccess
- AWSLambdaBasicExecutionRole
###インスタンス停止用Lambda
上記で作成したロールを使って、停止用のLambdaを作成します。
インスタンスを停止するスクリプトはこんな感じです。 |
記述できる言語は、C#、Go、Java、Node.js、Pythonです。 |
ここではPythonでサンプルを書いてみました。 |
import os
import boto3
subnet = os.environ['SUBNET_ID']
rtb = os.environ['RTB_ID']
region = os.environ['REGION']
ec2ids = os.environ['EC2']
client = boto3.client('ec2')
def stop_ec2():
instances = [id.strip() for id in ec2ids.split(',')]
ec2 = boto3.client('ec2', region_name=region)
ec2.stop_instances(InstanceIds=instances)
def stop_natgw():
filters = [ { 'Name': 'subnet-id', 'Values': [subnet] },
{ 'Name': 'state', 'Values': ['available'] } ]
response = client.describe_nat_gateways(Filters=filters)
natgw = response['NatGateways'][0]['NatGatewayId']
client.delete_nat_gateway(NatGatewayId=natgw)
def detach_natgw():
response = client.delete_route(
DestinationCidrBlock = '0.0.0.0/0',
RouteTableId = rtb
)
def lambda_handler(event, context):
stop_ec2()
detach_natgw()
stop_natgw()
この関数はリージョンID、サブネットID、ルータID、EC2インスタンスIDを環境変数で定義しています。
環境変数には文字列しか記述できませんので、停止したいEC2インスタンスが複数ある場合は、カンマ(,)で区切って複数記述してください。
###不使用EIPリリース用Lambda |
ネットワークインターフェースにアタッチされていないEIPをリリースするLambdaはこんな感じです。 |
import boto3
client = boto3.client('ec2')
def delete_ip():
response = client.describe_addresses(
Filters=[
{'Name': 'domain','Values': ['vpc'] }
]
)
for rec in response['Addresses']:
if "NetworkInterfaceId" not in rec:
client.release_address(AllocationId=rec['AllocationId'])
def lambda_handler(event, context):
delete_ip()
#使う時には動いていて欲しいよね
という事で、止めたままだと寂しいので、自動起動用のLambdaも作ってみましょう。
###インスタンス起動用Lambda
NATゲートウェイ作成時に自動的にEIPを作成してアタッチします。
環境変数の指定は停止用Lambdaと同じです。
スクリプトはこんな感じです。
import os
import boto3
subnet = os.environ['SUBNET_ID']
rtb = os.environ['RTB_ID']
region = os.environ['REGION']
ec2ids = os.environ['EC2']
client = boto3.client('ec2')
def start_ec2():
instances = [id.strip() for id in ec2ids.split(',')]
ec2 = boto3.client('ec2', region_name=region)
ec2.start_instances(InstanceIds=instances)
def start_natgw():
# EIPを新規作成
result = client.allocate_address(
Domain='vpc'
)
eip = result['AllocationId']
# IPを固定にしたい場合は、使いたいEIPのアロケーションIDを環境変数にセットして以下を使う
# eip = os.environ['EIP']
response = client.create_nat_gateway(
AllocationId=eip,
SubnetId=subnet
)
natid = response['NatGateway']['NatGatewayId']
client.get_waiter('nat_gateway_available').wait(NatGatewayIds=[natid])
return(natid)
def atatch_natgw(natgw):
response = client.create_route(
DestinationCidrBlock = '0.0.0.0/0',
NatGatewayId = natgw,
RouteTableId = rtb
)
def lambda_handler(event, context):
start_ec2()
natgw = start_natgw()
atatch_natgw(natgw)
ここで注意する事は、EIPを新規に作成する毎にIPアドレスが変わる事です。
NATゲートウェイのIPを固定にしたい場合は、creata_nat_gateway()のAllocationIDパラメータにアッタッチしたいEIPのアロケーションIDをセットします(その場合、NATゲートウェイ停止中もEIPを保持し続けるので、EIPに時間課金が発生します)。
##CloudWatchで自動起動・停止
Lambdaを手動で動かして、動くことが確認できれば、自動起動・停止にしてしまいましょう。
CloudWatchのルール設定を使って、cron形式の指定でLambda関数を実行することができます。
時間に指定はUTCなので注意が必要です。
例えば、平日の午前8時に起動したい時は以下のように指定します。
0 23 ? * SUN-THU *
或いは、平日の午後8時に起動したい時は以下のように指定します。
0 11 ? * MON-FRI *
#さいごに
AWSのサービスを操作するためのPythonのライブラリ群は、こちらのドキュメントを参考にしました。今回はEC2のClientだけで作ってみましたが、色々なサービスを操作出来るようなので、また試してみたいと思っています。
さて、ここで一つ注意点(落とし穴)があります。
EC2インスタンスの時間課金は秒単位ですが、NATゲートウェイやEIPは時間単位のようです。
上記のLambdaをテストする際、起動・停止する度に、(数秒しか使っていなくても)NATゲートウェイやEIPが1時間分づつ課金されてしまいます。
20回くらいテストしたので20時間分課金されてました。。。
課金を安くしようとしたのに、余計な課金が発生するという罠が!
それはともかく、新年早々、余計な課金に驚かなくて済むように、年末年始は使っていないリソースを止めましょう!(なお、本記事はSRA Advent Calendar 2018に掲載のものです)