0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

個人開発でNAT Gatewayは高すぎる。EC2で自前NATにしてAWSコストを月$40節約した

0
Last updated at Posted at 2026-06-03

LambdaをVPC内に置いてRDSに接続する構成を作ると、インターネットへの通信にNATが必要になります。AWSのマネージドサービス「NAT Gateway」を使うと月額約$45(東京リージョン、固定費のみ)かかります。

個人開発でこれは痛い。EC2 t3.nanoで代替すると月約$4になります。本記事ではTerraformのコードを含めて手順を説明します。

なぜVPC内LambdaにNATが必要か

LambdaをVPC内のプライベートサブネットに配置すると、デフォルトではインターネットに出られません。GitHub APIやAnthropic APIなど外部サービスへのアクセスがすべて失敗します。

インターネットへの経路を作るにはNATが必要で、選択肢は2つです。

NAT Gateway EC2 NATインスタンス
月額 約$45〜 約$4(t3.nano)
可用性 高(AWS管理) 低(単一EC2)
設定 簡単 要セットアップ
帯域 自動スケール インスタンス依存

個人開発・小規模サービスなら可用性よりコストを取るのが現実的です。

構成

[Lambda(プライベートサブネット)]
        ↓
[EC2 NATインスタンス(パブリックサブネット)]
        ↓
[Internet Gateway]
        ↓
[インターネット]

EC2インスタンスをNATルーターとして機能させ、プライベートサブネットのルートテーブルでデフォルトゲートウェイをそのEC2に向けます。

Terraformの実装

1. VPC・サブネット

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true
}

# パブリックサブネット(NATインスタンスを配置)
resource "aws_subnet" "public" {
  count                   = 2
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.${count.index + 1}.0/24"
  availability_zone       = ["ap-northeast-1a", "ap-northeast-1c"][count.index]
  map_public_ip_on_launch = true
}

# プライベートサブネット(LambdaとRDSを配置)
resource "aws_subnet" "private" {
  count             = 2
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.${count.index + 11}.0/24"
  availability_zone = ["ap-northeast-1a", "ap-northeast-1c"][count.index]
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
}

# パブリックサブネット用ルートテーブル
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }
}

resource "aws_route_table_association" "public" {
  count          = 2
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

2. NATインスタンス用セキュリティグループ

Lambdaからの通信だけ受け付け、外向きは全許可します。

resource "aws_security_group" "nat" {
  name   = "${var.project}-${var.env}-nat"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port       = 0
    to_port         = 0
    protocol        = "-1"
    security_groups = [aws_security_group.lambda.id]  # Lambdaのみ
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

3. NATインスタンス

source_dest_check = falseが必須です。これを設定しないとNATとして機能しません(デフォルトでは送信元・送信先が自分でないパケットを破棄します)。

resource "aws_instance" "nat" {
  ami                         = var.nat_ami_id  # Amazon Linux 2023のAMI
  instance_type               = "t3.nano"
  subnet_id                   = aws_subnet.public[0].id
  vpc_security_group_ids      = [aws_security_group.nat.id]
  associate_public_ip_address = true
  source_dest_check           = false           # ← これが必須
  iam_instance_profile        = aws_iam_instance_profile.nat.name

  user_data = file("${path.module}/nat_user_data.sh")
}

4. プライベートサブネットのルートテーブル

デフォルトゲートウェイをNATインスタンスのネットワークインターフェースに向けます。

resource "aws_route_table" "private" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block           = "0.0.0.0/0"
    network_interface_id = aws_instance.nat.primary_network_interface_id
  }
}

resource "aws_route_table_association" "private" {
  count          = 2
  subnet_id      = aws_subnet.private[count.index].id
  route_table_id = aws_route_table.private.id
}

5. user_data(NATの設定スクリプト)

EC2起動時にIPフォワーディングを有効化し、NATの設定を入れます。Amazon Linux 2023はnftablesを使います(iptablesではありません)。

#!/bin/bash
set -e

# IPフォワーディングを有効化
echo "net.ipv4.ip_forward = 1" > /etc/sysctl.d/99-ip-forward.conf
sysctl -p /etc/sysctl.d/99-ip-forward.conf

# nftablesをインストール
dnf install -y nftables

# NATルールを設定
# AL2023のnftables.serviceは /etc/sysconfig/nftables.conf を読む
cat > /etc/sysconfig/nftables.conf << 'NFTEOF'
#!/usr/sbin/nft -f
flush ruleset

table ip nat {
    chain POSTROUTING {
        type nat hook postrouting priority srcnat; policy accept;
        oifname != "lo" masquerade
    }
}
NFTEOF

# nftablesサービスを有効化・起動
systemctl enable --now nftables

masqueradeはIPマスカレードで、プライベートIPアドレスをEC2のパブリックIPに変換して外に出します。

6. IAMロール(SSM Session Manager用)

SSM Session Managerを使うとSSHなしでEC2にアクセスできます。NATインスタンスにSSHポートを開けずに済むので、セキュリティグループをシンプルに保てます。

resource "aws_iam_role" "nat_instance" {
  name = "${var.project}-${var.env}-nat"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Action    = "sts:AssumeRole"
      Principal = { Service = "ec2.amazonaws.com" }
    }]
  })
}

resource "aws_iam_role_policy_attachment" "nat_ssm" {
  role       = aws_iam_role.nat_instance.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

resource "aws_iam_instance_profile" "nat" {
  name = "${var.project}-${var.env}-nat"
  role = aws_iam_role.nat_instance.name
}

AMI IDの取得方法

var.nat_ami_idに入れるAMI IDはAWSコンソールかCLIで取得します。

# Amazon Linux 2023の最新AMI(東京リージョン)
aws ec2 describe-images \
  --owners amazon \
  --filters "Name=name,Values=al2023-ami-2023.*-x86_64" \
  --query "sort_by(Images, &CreationDate)[-1].ImageId" \
  --output text \
  --region ap-northeast-1

AMI IDは定期的に更新されますが、Terraformで自動追従させると意図しないEC2の再作成が起きるため、固定値をvariables.tfに書いておく方が安全です。

注意点

単一障害点になる

NATインスタンスが1台なので、EC2が落ちるとLambdaからの外部通信がすべて止まります。個人開発・小規模サービスなら許容範囲ですが、止まったときはAWSコンソールかTerraformでEC2を再起動してください。

EC2の再作成でENIが変わる

terraform applyでEC2が再作成されるとネットワークインターフェースIDが変わり、プライベートルートテーブルの参照が壊れます。AMI IDを固定しておけば基本的には起きませんが、インスタンスタイプを変更するときは注意が必要です。

コスト比較

NAT Gateway t3.nano
固定費 $0.062/時 ≒ $45/月 $0.0058/時 ≒ $4/月
データ転送 $0.062/GB なし
合計(転送少量) 約$45〜/月 約$4/月

差額は約$40/月。個人開発なら年間約$480の節約になります。


可用性が求められるプロダクションサービスにはNAT Gatewayを使うべきですが、個人開発や検証環境では十分な代替手段です。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?