💰

開発環境のNATゲートウェイをNATインスタンスに移行してコスト大幅削減した話

に公開

はじめに

AWS の開発環境で運用しているインフラで、NAT ゲートウェイから NAT インスタンスへ移行することで、大幅なコスト削減に成功しました。この記事では、移行の背景、実装方法、監視設定、そして注意すべきトレードオフについて解説します。

背景と課題

私たちのプロジェクトでは、DEV/STG/PRD の 3 環境で AWS インフラを運用しています。各環境で NAT ゲートウェイを使用していましたが、開発環境では以下の課題がありました:

  • コストが高い: NAT ゲートウェイは時間単位の料金とデータ転送料が発生
  • 開発環境で冗長性は不要: 高可用性よりもコスト優先
  • トラフィック量が少ない: 開発環境の外部通信は限定的

コスト比較

NAT ゲートウェイと比較して、NAT インスタンス(t3.nano)を使用することで大幅なコスト削減が可能です:

  • NAT ゲートウェイ: 時間単位の料金 + データ処理料金が発生
  • NAT インスタンス (t3.nano): EC2 インスタンス料金のみ(非常に低コスト)
  • 削減効果: 開発環境では約 80-90%のコスト削減を実現可能

実装方法

1. NAT インスタンスの作成

AWS 公式の NAT 専用 AMI は廃止されたため、Amazon Linux 2023 で NAT 機能を自前で設定します。

/**
 * 最新のAmazon Linux 2023 AMIを取得
 */
data "aws_ami" "amazon_linux_2023" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  filter {
    name   = "state"
    values = ["available"]
  }
}

/**
 * NATインスタンス
 */
resource "aws_instance" "nat" {
  ami                         = data.aws_ami.amazon_linux_2023.id
  instance_type               = "t3.nano"
  subnet_id                   = aws_subnet.public_1.id
  vpc_security_group_ids      = [aws_security_group.nat_instance.id]
  associate_public_ip_address = true
  source_dest_check           = false # NAT機能のため無効化必須
  user_data_replace_on_change = true  # user_data変更時に自動再作成

  # NATインスタンスの設定
  user_data = <<-EOF
              #!/bin/bash
              set -e
              exec > >(tee /var/log/user-data.log)
              exec 2>&1

              echo "Starting NAT instance configuration..."

              # IP転送を有効化
              echo "net.ipv4.ip_forward=1" > /etc/sysctl.d/90-nat.conf
              sysctl -w net.ipv4.ip_forward=1

              # iptablesとiptables-nftのインストール
              yum install -y iptables iptables-nft

              # iptables設定(Amazon Linux 2023ではens5を使用)
              iptables -P FORWARD ACCEPT
              iptables -t nat -A POSTROUTING -o ens5 -j MASQUERADE
              iptables -A FORWARD -i ens5 -m state --state RELATED,ESTABLISHED -j ACCEPT
              iptables -A FORWARD -o ens5 -j ACCEPT

              # iptables設定をrc.localで永続化 (Amazon Linux 2023対応)
              cat > /etc/rc.d/rc.local <<'RCLOCAL'
              #!/bin/bash
              iptables -P FORWARD ACCEPT
              iptables -t nat -A POSTROUTING -o ens5 -j MASQUERADE
              iptables -A FORWARD -i ens5 -m state --state RELATED,ESTABLISHED -j ACCEPT
              iptables -A FORWARD -o ens5 -j ACCEPT
              RCLOCAL

              chmod +x /etc/rc.d/rc.local
              systemctl enable rc-local

              echo "NAT instance configuration completed successfully"
              EOF

  tags = {
    Name = "dev-nat-instance"
    Env  = "dev"
  }

  lifecycle {
    create_before_destroy = true
  }
}

2. セキュリティグループの設定

resource "aws_security_group" "nat_instance" {
  name        = "dev-nat-instance"
  description = "Security group for NAT instance"
  vpc_id      = aws_vpc.main.id

  # プライベートサブネットからのすべての通信を許可
  ingress {
    description = "Allow all traffic from private subnets"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = [
      var.vpc.private_subnet_1_cidr,
      var.vpc.private_subnet_2_cidr
    ]
  }

  # アウトバウンドはすべて許可
  egress {
    description = "Allow all outbound traffic"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "dev-nat-instance"
    Env  = "dev"
  }
}

3. ルートテーブルの更新

# プライベートサブネットのルートをNATインスタンスに向ける
resource "aws_route" "nat_instance_from_private_1" {
  route_table_id         = aws_route_table.private_1.id
  network_interface_id   = aws_instance.nat.primary_network_interface_id
  destination_cidr_block = "0.0.0.0/0"
}

resource "aws_route" "nat_instance_from_private_2" {
  route_table_id         = aws_route_table.private_2.id
  network_interface_id   = aws_instance.nat.primary_network_interface_id
  destination_cidr_block = "0.0.0.0/0"
}

4. Elastic IP の関連付け

既存の EIP を再利用できます:

resource "aws_eip_association" "nat_instance" {
  instance_id   = aws_instance.nat.id
  allocation_id = aws_eip.nat_gateway_public_1.id
}

CloudWatch 監視の設定

NAT インスタンスは単一障害点になるため、しっかりとした監視が必須です。

1. インスタンス障害の検知と自動再起動

resource "aws_cloudwatch_metric_alarm" "nat_instance_status_check_failed_instance" {
  alarm_name          = "dev-NAT-Instance-StatusCheckFailed-Instance"
  alarm_description   = "NAT instance failure detection with auto-reboot"
  dimensions = {
    InstanceId = aws_instance.nat.id
  }
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 2
  metric_name         = "StatusCheckFailed_Instance"
  namespace           = "AWS/EC2"
  period              = 300
  statistic           = "Average"
  threshold           = 1
  treat_missing_data  = "missing"

  # 自動再起動 + SNS通知
  alarm_actions = [
    aws_sns_topic.infra_notification_sns.arn,
    "arn:aws:automate:${var.region}:ec2:reboot"
  ]
  ok_actions = [
    aws_sns_topic.infra_notification_sns.arn
  ]
}

2. システム障害の検知

resource "aws_cloudwatch_metric_alarm" "nat_instance_status_check_failed_system" {
  alarm_name          = "dev-NAT-Instance-StatusCheckFailed-System"
  alarm_description   = "NAT instance system failure detection"
  dimensions = {
    InstanceId = aws_instance.nat.id
  }
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 2
  metric_name         = "StatusCheckFailed_System"
  namespace           = "AWS/EC2"
  period              = 300
  statistic           = "Average"
  threshold           = 1
  treat_missing_data  = "missing"

  alarm_actions = [
    aws_sns_topic.infra_notification_sns.arn
  ]
  ok_actions = [
    aws_sns_topic.infra_notification_sns.arn
  ]
}

3. CPU 使用率の監視

resource "aws_cloudwatch_metric_alarm" "nat_instance_high_cpu" {
  alarm_name          = "dev-NAT-Instance-HighCPU"
  alarm_description   = "NAT instance high CPU utilization"
  dimensions = {
    InstanceId = aws_instance.nat.id
  }
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 3
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = 300
  statistic           = "Average"
  threshold           = 80
  treat_missing_data  = "notBreaching"

  alarm_actions = [
    aws_sns_topic.infra_notification_sns.arn
  ]
  ok_actions = [
    aws_sns_topic.infra_notification_sns.arn
  ]
}

user_data の解説

NAT インスタンスの核心部分である user_data について詳しく説明します。

IP 転送の有効化

echo "net.ipv4.ip_forward=1" > /etc/sysctl.d/90-nat.conf
sysctl -w net.ipv4.ip_forward=1

Linuxカーネルでパケット転送を有効化します。これがないとNATとして機能できません。

iptables による NAT 設定

# FORWARDチェーンのデフォルトポリシーをACCEPTに設定(重要!)
iptables -P FORWARD ACCEPT

# MASQUERADEでNAT機能を実装(ens5を使用)
iptables -t nat -A POSTROUTING -o ens5 -j MASQUERADE

# フォワーディングルールの設定
iptables -A FORWARD -i ens5 -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -o ens5 -j ACCEPT

重要ポイント:

  • Amazon Linux 2023ではネットワークインターフェース名がens5 - eth0ではありません
  • iptables -P FORWARD ACCEPTが必須 - デフォルトはDROPのため、これがないとパケットが転送されません
  • MASQUERADE: プライベートIPをインスタンスのパブリックIPに変換
  • FORWARD: パケット転送ルールで、確立済み接続とプライベートからの新規接続を許可

iptables設定の永続化

Amazon Linux 2023ではiptables-servicesが非推奨のため、rc.localを使用します:

cat > /etc/rc.d/rc.local <<'RCLOCAL'
#!/bin/bash
iptables -P FORWARD ACCEPT
iptables -t nat -A POSTROUTING -o ens5 -j MASQUERADE
iptables -A FORWARD -i ens5 -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -o ens5 -j ACCEPT
RCLOCAL

chmod +x /etc/rc.d/rc.local
systemctl enable rc-local

ハマりポイントと解決方法

問題1:ネットワークインターフェース名の違い

症状: NATインスタンスを作成してもプライベートサブネットからインターネットに接続できない

原因: Amazon Linux 2023では予測可能なネットワークインターフェース名(ens5)が使用されますが、古い記事ではeth0を使用しているコードが多い

解決方法:

  • user_data内のすべてのeth0をens5に変更
  • インスタンス内でip addr showコマンドで実際のインターフェース名を確認可能

問題2:FORWARDチェーンのデフォルトポリシー

症状: iptablesの設定は正しいのにパケットが転送されない

原因: FORWARDチェーンのデフォルトポリシーがDROPになっている

解決方法:

iptables -P FORWARD ACCEPT

この1行を追加するだけで解決します。

問題3:設定が再起動後に消える

症状: インスタンス再起動後にNATが動作しなくなる

原因: Amazon Linux 2023ではiptables-servicesによる永続化が動作しない

解決方法: rc.localを使用して起動時に設定を再適用

トレードオフと注意点

メリット

✅ 大幅なコスト削減 - 開発環境では約 80-90%のコスト削減が可能
✅ 柔軟なカスタマイズ - iptables ルールを自由に設定可能
✅ 既存 EIP の再利用 - IP アドレス変更不要

デメリット

❌ 可用性の低下 - 単一障害点、AZ 障害で影響を受ける
❌ パフォーマンス制限 - t3.nano は限定的なスペック
❌ 運用負荷 - OS パッチ適用などの管理が必要
❌ スケーラビリティ - 手動でのインスタンスタイプ変更が必要

推奨環境

以下の条件に当てはまる環境で推奨:

  • 開発環境またはテスト環境
  • トラフィック量が少ない(目安: 1GB/日未満)
  • 一時的なダウンタイムが許容できる
  • コスト最適化が優先課題

本番環境での使用について

本番環境では、以下の理由から NAT ゲートウェイの使用を推奨します:

  • 高可用性: マルチ AZ 構成が容易
  • 自動スケーリング: トラフィック増加に自動対応
  • マネージドサービス: AWS 管理でメンテナンス不要
  • 高パフォーマンス: 最大 45Gbps の帯域幅

トラブルシューティング

NATが動作しない場合の確認手順

  1. IP転送が有効か確認
# EC2 Instance Connectで接続(セキュリティグループにSSH許可が必要)
ssh ec2-user@<nat-instance-ip>

# IP転送の確認(1になっているべき)
sudo sysctl net.ipv4.ip_forward
  1. iptables設定を確認
# NAT設定の確認(ens5へのMASSQUERADEがあるべき)
sudo iptables -t nat -L -n -v

# FORWARD設定の確認(policyがACCEPT、ens5のルールがあるべき)
sudo iptables -L FORWARD -n -v
  1. ネットワークインターフェース名を確認
# 実際のインターフェース名を確認
ip addr show
  1. source_dest_checkを確認
# AWSコンソールまたはCLIで確認(Falseである必要がある)
aws ec2 describe-instances --instance-ids <instance-id> \
  --query 'Reservations[0].Instances[0].SourceDestCheck'
  1. ルートテーブルを確認
# プライベートサブネットのルートテーブルで0.0.0.0/0がNATインスタンスを指しているか確認
aws ec2 describe-route-tables --filters "Name=tag:Name,Values=*Private*"

デプロイ手順

# 環境ディレクトリに移動
cd env/dev

# Terraformプランの確認
terraform plan -out .terraform/terraform.plan -var-file secrets.tfvars

# 適用
terraform apply .terraform/terraform.plan

動作確認

デプロイ後、以下を確認します:

# NATインスタンスの状態確認
aws ec2 describe-instances \
  --filters "Name=tag:Name,Values=dev-nat-instance" \
  --query 'Reservations[0].Instances[0].[InstanceId,State.Name,PublicIpAddress]'

# CloudWatchアラームの確認
aws cloudwatch describe-alarms \
  --alarm-name-prefix "dev-NAT-Instance"

# プライベートサブネット内のリソースから外部接続テスト
# (ECSタスクやEC2インスタンスから)
curl -I https://www.google.com

まとめ

開発環境の NAT ゲートウェイを NAT インスタンスに移行することで、大幅なコスト削減を実現しました。

重要なポイント:

  1. Amazon NAT AMI は廃止済み - Amazon Linux 2023 + user_data で自前構築
  2. source_dest_check = false が必須 - NAT 機能に不可欠
  3. Amazon Linux 2023ではens5を使用 - eth0ではない
  4. iptables -P FORWARD ACCEPTが必須 - デフォルトはDROP
  5. CloudWatch 監視で自動復旧 - 障害時の自動再起動設定
  6. 環境に応じて使い分け - 本番環境ではNATゲートウェイを推奨

開発環境やテスト環境など、高可用性よりもコスト優先の環境では、NAT インスタンスが有効な選択肢となります。この記事が、AWS コスト最適化の参考になれば幸いです。

参考資料

株式会社スプリックス IT戦略部・SPRIX Enginieering Lab

Discussion