開発環境の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が動作しない場合の確認手順
- IP転送が有効か確認
# EC2 Instance Connectで接続(セキュリティグループにSSH許可が必要)
ssh ec2-user@<nat-instance-ip>
# IP転送の確認(1になっているべき)
sudo sysctl net.ipv4.ip_forward
- iptables設定を確認
# NAT設定の確認(ens5へのMASSQUERADEがあるべき)
sudo iptables -t nat -L -n -v
# FORWARD設定の確認(policyがACCEPT、ens5のルールがあるべき)
sudo iptables -L FORWARD -n -v
- ネットワークインターフェース名を確認
# 実際のインターフェース名を確認
ip addr show
- source_dest_checkを確認
# AWSコンソールまたはCLIで確認(Falseである必要がある)
aws ec2 describe-instances --instance-ids <instance-id> \
--query 'Reservations[0].Instances[0].SourceDestCheck'
- ルートテーブルを確認
# プライベートサブネットのルートテーブルで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 インスタンスに移行することで、大幅なコスト削減を実現しました。
重要なポイント:
- Amazon NAT AMI は廃止済み - Amazon Linux 2023 + user_data で自前構築
- source_dest_check = false が必須 - NAT 機能に不可欠
- Amazon Linux 2023ではens5を使用 - eth0ではない
- iptables -P FORWARD ACCEPTが必須 - デフォルトはDROP
- CloudWatch 監視で自動復旧 - 障害時の自動再起動設定
- 環境に応じて使い分け - 本番環境ではNATゲートウェイを推奨
開発環境やテスト環境など、高可用性よりもコスト優先の環境では、NAT インスタンスが有効な選択肢となります。この記事が、AWS コスト最適化の参考になれば幸いです。
Discussion