GeekFactory

int128.hatenablog.com

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インスタンスは除く)

https://raw.githubusercontent.com/int128/terraform-aws-nat-instance/v0.4.1/diagram.svg?sanitize=true

動作確認のため,AWSコンソールでプライベートサブネットにEC2インスタンスを作成しましょう.作成したEC2インスタンスSSMセッションマネージャでログインして,外部にアクセスできるか確認してみましょう.

仕組み

Terraformを実行すると,Auto Scaling Groupとそれに必要なリソース群(セキュリティグループ,ENI,EIPなど)が作成されます.また,プライベートサブネットのデフォルトルートがENIに設定されます.これにより,プライベートサブネットから外部への通信はENIを通るようになります.

Auto Scaling GroupがNATインスタンスを起動すると,User Dataにある以下のスクリプトが実行されます.

  1. あらかじめ確保しておいたENIをeth1にアタッチする.
  2. IP forwardingのカーネルパラメータを有効にする.
  3. iptablesでIP masqueradeを有効にする.
  4. デフォルトゲートウェイを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モジュールの詳細については下記を参照してください.

github.com

*1:NATゲートウェイの代わりにクラフトビールに課金したい