TerraformでNATインスタンスを管理する
個人のAWS環境でプライベートサブネット構成を検証したいけどNATゲートウェイに毎月3,500円も払えない*1ので,NATインスタンスのTerraformモジュール int128/nat-instance/aws を作りました.主な特徴はこちらです.
- Auto Scaling GroupによるAuto Healingに対応.インスタンスが落ちても自動復旧します.
- スポットインスタンスで低コストを実現.t3a.nanoなら月100円程度で運用できます.
- ENIの付け替えによる固定ソースIPを実現.
使い方
VPCとサブネットを作成するには terraform-aws-modules/vpc/aws モジュールが便利です.VPCとサブネットに加えてNATインスタンスを作成するには下記のように定義します.
module "vpc" { source = "terraform-aws-modules/vpc/aws" name = "main" cidr = "172.18.0.0/16" azs = ["us-west-2a", "us-west-2b", "us-west-2c"] private_subnets = ["172.18.64.0/20", "172.18.80.0/20", "172.18.96.0/20"] public_subnets = ["172.18.128.0/20", "172.18.144.0/20", "172.18.160.0/20"] enable_dns_hostnames = true } module "nat" { source = "int128/nat-instance/aws" name = "main" vpc_id = module.vpc.vpc_id public_subnet = module.vpc.public_subnets[0] private_subnets_cidr_blocks = module.vpc.private_subnets_cidr_blocks private_route_table_ids = module.vpc.private_route_table_ids }
Terraformを実行すると,下図のようなリソースが作成されます.(プライベートサブネットの各EC2インスタンスは除く)
動作確認のため,AWSコンソールでプライベートサブネットにEC2インスタンスを作成しましょう.作成したEC2インスタンスにSSMセッションマネージャでログインして,外部にアクセスできるか確認してみましょう.
仕組み
Terraformを実行すると,Auto Scaling Groupとそれに必要なリソース群(セキュリティグループ,ENI,EIPなど)が作成されます.また,プライベートサブネットのデフォルトルートがENIに設定されます.これにより,プライベートサブネットから外部への通信はENIを通るようになります.
Auto Scaling GroupがNATインスタンスを起動すると,User Dataにある以下のスクリプトが実行されます.
- あらかじめ確保しておいたENIをeth1にアタッチする.
- IP forwardingのカーネルパラメータを有効にする.
- iptablesでIP masqueradeを有効にする.
- デフォルトゲートウェイをeth1に変更する.
これにより,プライベートサブネット内のEC2インスタンスはENIとNATインスタンスを経由して通信できるようになります.
もしNATインスタンスが停止した場合は,Auto Scaling Groupによって新しいNATインスタンスが作成されます.既存のENIとEIPをアタッチするため,外部通信のソースIPを固定できます.
User DataでENIのアタッチとデフォルトゲートウェイを変更する部分は試行錯誤が必要でかなり苦労しました.
おまけ
マネージドサービスとは逆行する使い方になりますが,iptablesでDNATを有効にするとNATインスタンスのポートをプライベートサブネットに転送できます.例えば,以下のようなスクリプトを追加すれば,NATインスタンスの443ポートをプライベートサブネットにあるEC2インスタンスに転送できます.NLBに課金する代わりにどうぞ.
# Look up the target instance tag_name="TARGET_TAG" target_private_ip="$(aws ec2 describe-instances --filters "Name=tag:Name,Values=$tag_name" | jq -r .Reservations[0].Instances[0].PrivateIpAddress)" # Expose the port of the NAT instance. iptables -t nat -A PREROUTING -m tcp -p tcp --dst "${eni_private_ip}" --dport 8080 -j DNAT --to-destination "$target_private_ip:8080"
Terraformモジュールの詳細については下記を参照してください.