1. ホーム
  2. 記事一覧
  3. 【Terraformハンズオン】NatGatewayを使ってプライベートな通信を実現してみよう

2024.08.24

【Terraformハンズオン】NatGatewayを使ってプライベートな通信を実現してみよう

NAT Gatewayは、AWS環境で重要な役割を果たすリソースの一つです。この記事では、NAT Gatewayの基本的な概念と特徴、Terraformでの実装方法を解説します。

AWSを扱う実際の現場では、NAT Gatewayを使うことでより安全なインターネット通信を実現することができます。ぜひ一緒に理解を深めながら、Terraformでの実装方法を学んでいきましょう。

NAT Gatewayとは

NAT Gatewayは、AWSが提供するNAT(Network Address Translation)サービスです。

このNAT Gatewayを使用することで、プライベートサブネット内のリソースがNAT GatewayのIPアドレスを使用してインターネットへ通信することが可能になります。

プライベートサブネットのルートテーブルにはインターネットゲートウェイ(IGW)へのルートが設定されていないため、外部からの通信は受け付けません。つまり、インターネットから直接アクセスされることはなく、内部リソースのセキュリティを保つことができます。

VPC内部からの通信はNAT Gatewayを使用して行われるため、より安全な通信を実現することができます。

NATとは

NAT Gatewayが提供する機能であるNATとは、グローバルIPアドレスとプライベートIPアドレスを変換する技術のことです。

NATはSNAT(Source NAT)とDNAT(Destination NAT)の2種類に分類されます。SNATは送信元のIPアドレスを変換するのに対し、DNATは送信先のIPアドレスを変換します。NAT GatewayはこのうちSNATを担当し、プライベートネットワーク内のリソースがインターネットにアクセスする際に使用されます。

NATに関しては、以下の記事を参照ください。

https://envader.plus/article/15

NAT Gatewayの仕組み

ここでは、NAT Gatewayがどのように機能するかについて詳しく説明します。

プライベートサブネットからのアウトバウンド通信

プライベートサブネット内のリソースは、インターネットと直接通信することはできません。そこで、NAT Gatewayが仲介役として機能します。

NAT Gatewayは受け取ったリクエストの送信元IPアドレス(プライベートIPアドレス)を、自身のElastic IPアドレスに変換し、インターネットに送信します。こうすることで、プライベートサブネット内のリソースは、NAT GatewayのパブリックIPアドレスを介してインターネットにアクセスすることができます。

インターネットからのインバウンド通信

インターネット上のリソースからのレスポンスは、NAT GatewayのパブリックIPアドレスを介して受信します。NAT Gatewayはこのレスポンスの宛先IPアドレスを、元のリクエストを送信したプライベートサブネット内にあるリソースのプライベートIPアドレスに変換し、プライベートサブネット内のリソースに通信を送ります。

理解しておくポイントとして、インバウンド通信はプライベートサブネット内からのアウトバウンド通信が発生した場合にのみ許可されるという点です。この仕組みのおかげで、外部からの不正なアクセスが直接プライベートサブネット内のリソースに届くことを防ぎます。

https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/vpc-nat-gateway.html

冗長性を確保する必要がある

NAT Gatewayで冗長性を確保するためには、各アベイラビリティゾーン(AZ)に少なくとも1つのNAT Gatewayを配置する必要があります。こうすることで、特定のAZが障害を起こした場合でも他のAZで動作するNAT Gatewayを通じて通信が継続され、高可用性を実現できます。

耐障害性を高めるための方法として、公式ドキュメントに記載されています。

https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/nat-gateway-basics.html

NAT Gatewayのトラブルシューティングに関しては、以下記事で詳しく解説しています。

https://envader.plus/article/260

NAT Gatewayの料金

NAT Gatewayの料金は、主に以下の2つの要素に基づいて計算することができます。

利用時間

NAT Gatewayの利用料金は、作成されたNAT Gatewayの稼働時間に対して課金されます。

作成後は1時間単位で料金が加算されるため、検証で使用する場合は無駄なコストが発生しないよう、検証後の削除は必ず行いましょう。2024年8月時点で、1時間/0.062USDの料金が発生します。(東京リージョン)

また、NAT Gatewayを複数のアベイラビリティゾーンごとにデプロイした場合、それぞれのNAT Gatewayに対して料金が発生することになるため注意が必要です。

データ処理量

NAT Gatewayを通じて転送されるデータの量に応じても料金が発生します。データの種類としては、NAT Gatewayを介してインターネットに送信されるデータと、インターネットから受信されるデータが含まれます。

こちらは2024年8月時点で、処理したデータ量1GB/0.062USDの料金が発生します。(東京リージョン)

https://aws.amazon.com/jp/vpc/pricing/#NAT_Gateway

TerraformでNAT Gatewayを構築してみよう

ここからは、Terraformを使って実際にNAT Gatewayを構築してみましょう。

main.tfにリソースを集約した場合、以下のようになります。

# main.tf
locals {
  vpc = {
    cidr_block          = "10.0.0.0/16"
    subnet_cidr         = "10.0.10.0/24"
    subnet_cidr_private = "10.0.20.0/24"
  }
  dev_ec2 = {
    prefix           = "dev-ec2"
    ami_id           = data.aws_ami.amazonlinux2_latest.id # amzn2-ami-hvm-2.0.20231116.0-x86_64-gp2
    instance_type    = "t3.nano"
    root_volume_type = "gp3"
    root_volume_size = 30
  }
}

# AMIs
data "aws_ami" "amazonlinux2_latest" {
  owners      = ["amazon"]
  most_recent = true
  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-2.0.*.0-x86_64-gp2"]
  }
}

# EC2へAmazonSSMManagedInstanceCoreをアタッチするためのassumerole
data "aws_iam_policy_document" "assumerole_ec2" {
  statement {
    actions = ["sts:AssumeRole"]
    principals {
      type        = "Service"
      identifiers = ["ec2.amazonaws.com"]
    }
  }
}

# VPC
resource "aws_vpc" "main" {
  cidr_block           = local.vpc.cidr_block
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = {
    Name = "main"
  }
}

# Subnet
resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = local.vpc.subnet_cidr
  availability_zone       = "ap-northeast-1a"
  map_public_ip_on_launch = true
  tags = {
    Name = "public"
  }
}

resource "aws_subnet" "private" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = local.vpc.subnet_cidr_private
  availability_zone = "ap-northeast-1a"
  tags = {
    Name = "private"
  }
}

# RouteTable
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id
  tags = {
    Name = "public"
  }
}

# RouteTableの関連付け
resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

# InternetGateway
resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.main.id
  tags = {
    Name = "igw"
  }
}

# Route
resource "aws_route" "public" {
  route_table_id         = aws_route_table.public.id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.igw.id
}

# Private subnetのRouteTable
resource "aws_route_table" "private" {
  vpc_id = aws_vpc.main.id
  tags = {
    Name = "private"
  }
}

# Private subnetのRouteTable関連付け
resource "aws_route_table_association" "private" {
  subnet_id      = aws_subnet.private.id
  route_table_id = aws_route_table.private.id
}

# Private subnetのRoute
resource "aws_route" "private" {
  route_table_id         = aws_route_table.private.id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = aws_nat_gateway.nat_gateway.id
}

# Elastic IP
resource "aws_eip" "nat" {
  vpc = true
  tags = {
    Name = "nat-eip"
  }
}

# NAT Gateway
resource "aws_nat_gateway" "nat_gateway" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.public.id
  tags = {
    Name = "nat-gateway"
  }
  depends_on = [aws_eip.nat]
}

# EC2インスタンス
resource "aws_instance" "dev_ec2" {
  ami                    = local.dev_ec2.ami_id
  instance_type          = local.dev_ec2.instance_type
  iam_instance_profile   = aws_iam_instance_profile.dev_ec2.name
  subnet_id              = aws_subnet.private.id
  vpc_security_group_ids = [
    aws_security_group.dev_ec2.id
  ]
  root_block_device {
    volume_type = local.dev_ec2.root_volume_type
    volume_size = local.dev_ec2.root_volume_size
  }
  tags = {
    Name = local.dev_ec2.prefix
  }
}

# SecurityGroup
resource "aws_security_group" "dev_ec2" {
  name        = "${local.dev_ec2.prefix}-sg"
  description = "${local.dev_ec2.prefix}-sg"
  vpc_id      = aws_vpc.main.id
  tags = {
    Name = "${local.dev_ec2.prefix}-sg"
  }
}

# アウトバウンドのみ定義
resource "aws_security_group_rule" "dev_ec2_rule_egress" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = -1
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.dev_ec2.id
}

# InstanceProfile
resource "aws_iam_instance_profile" "dev_ec2" {
  name = local.dev_ec2.prefix
  role = aws_iam_role.dev_ec2.name
}

# IAM Role
resource "aws_iam_role" "dev_ec2" {
  name               = local.dev_ec2.prefix
  path               = "/"
  assume_role_policy = data.aws_iam_policy_document.assumerole_ec2.json
}

# IAM Role Policy Attachment
resource "aws_iam_role_policy_attachment" "dev_ec2" {
  role       = aws_iam_role.dev_ec2.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

# インスタンスIDを取得する
output "ec2_instance_id" {
  value = aws_instance.dev_ec2.id
}

Elastic IPの取得

Elastic IPをTerraformを使って取得します。

# Elastic IP
resource "aws_eip" "nat" {
  vpc = true
  tags = {
    Name = "nat-eip"
  }
}

ここで取得したElastic IPを、NAT Gatewayに関連付けます。

# NAT Gateway
resource "aws_nat_gateway" "nat_gateway" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.public.id
  tags = {
    Name = "nat-gateway"
  }
  depends_on = [aws_eip.nat]
}

allocation_idで、NAT Gatewayに関連付けるElastic IPのIDを指定します。

subnet_idでは、NAT Gatewayを配置するサブネットを指定します。

depends_onで、Elastic IPを作成された後にNAT Gatewayを作成するよう指定しています。

planで作成するリソースを確認し、applyでリソースを作成します。

terraform plan

terraform apply

NAT Gatewayからの通信を確認する

リソース作成後、NAT GatewayのElastic IPを確認します。

terraform state showでリソースの詳細を確認し、表示された内容のpublic_ipがElastic IPになります。

# 確認コマンド
terraform state show aws_nat_gateway.nat_gateway

# aws_nat_gateway.nat_gateway:
resource "aws_nat_gateway" "nat_gateway" {
     allocation_id                      = "eipalloc-0993aeeb1a58a0cae"
     association_id                     = "eipassoc-084725d2d12dd2f63"
     connectivity_type                  = "public"
     id                                 = "nat-0fb4f96f52c2aafb6"
     network_interface_id               = "eni-0145433da74f94d0d"
     private_ip                         = "10.0.10.12"
     public_ip                          = "52.195.125.184" # NAT GatewayのElastic IP
     secondary_private_ip_address_count = 0
     secondary_private_ip_addresses     = []
     subnet_id                          = "subnet-0a5014a7b4b37ff49"
     tags                               = {
         "Name" = "nat-gateway"
     }
     tags_all                           = {
         "Name" = "nat-gateway"
    }
}

apply時にoutputで取得したインスタンスIDを使用して、SSMでEC2へ接続します。

aws ssm start-session --target <instance-id>

インスタンスから、外部に接続できるかpingコマンドを実行します。

ping 8.8.8.8

PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=116 time=3.31 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=116 time=2.91 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=116 time=2.88 ms
64 bytes from 8.8.8.8: icmp_seq=4 ttl=116 time=2.92 ms

EC2インスタンスから、外部へ接続できることを確認できれば完了です。

まとめ

この記事では、NAT Gatewayの基本の解説と、Terraformを使用してNAT Gatewayを作成する方法を解説しました。NAT Gatewayは、AWSにおけるセキュリティ対策の一環として、プライベートIPアドレスを持つリソースがインターネットにアクセスする際の重要な役割を果たします。

意識しておきたい点として、NAT Gatewayの料金は配置しているだけで料金が発生することです。無駄なコストが発生しないよう、リソース作成後不要になった場合は削除するように注意しましょう。

https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/nat-gateway-pricing.html

【番外編】USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話

IT未経験者必見 USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話

プログラミング塾に半年通えば、一人前になれると思っているあなた。それ、勘違いですよ。「なぜ間違いなの?」「正しい勉強法とは何なの?」ITを学び始める全ての人に知って欲しい。そう思って書きました。是非読んでみてください。

「フリーランスエンジニア」

近年やっと世間に浸透した言葉だ。ひと昔まえ、終身雇用は当たり前で、大企業に就職することは一種のステータスだった。しかし、そんな時代も終わり「優秀な人材は転職する」ことが当たり前の時代となる。フリーランスエンジニアに高価値が付く現在、ネットを見ると「未経験でも年収400万以上」などと書いてある。これに釣られて、多くの人がフリーランスになろうとITの世界に入ってきている。私もその中の1人だ。数年前、USBも知らない状態からITの世界に没入し、そこから約2年間、毎日勉学を行なった。他人の何十倍も努力した。そして、企業研修やIT塾で数多くの受講生の指導経験も得た。そこで私は、伸びるエンジニアとそうでないエンジニアをたくさん見てきた。そして、稼げるエンジニア、稼げないエンジニアを見てきた。

「成功する人とそうでない人の違いは何か?」

私が出した答えは、「量産型エンジニアか否か」である。今のエンジニア市場には、量産型エンジニアが溢れている!!ここでの量産型エンジニアの定義は以下の通りである。

比較的簡単に学習可能なWebフレームワーク(WordPress, Rails)やPython等の知識はあるが、ITの基本概念を理解していないため、単調な作業しかこなすことができないエンジニアのこと。

多くの人がフリーランスエンジニアを目指す時代に中途半端な知識や技術力でこの世界に飛び込むと返って過酷な労働条件で働くことになる。そこで、エンジニアを目指すあなたがどう学習していくべきかを私の経験を交えて書こうと思った。続きはこちらから、、、、

note記事3000いいね超えの殿堂記事 今すぐ読む

エンベーダー編集部

エンベーダーは、ITスクールRareTECHのインフラ学習教材として誕生しました。 「遊びながらインフラエンジニアへ」をコンセプトに、インフラへの学習ハードルを下げるツールとして運営されています。

RareTECH 無料体験授業開催中! オンラインにて実施中! Top10%のエンジニアになる秘訣を伝授します! RareTECH講師への質疑応答可

関連記事