Amazon VPC 基礎
AWS のネットワーク基盤である VPC を理解し、サブネット設計・ルートテーブル・IGW/NAT GW を使った本番ネットワーク構成を実践的に習得する
Amazon VPC 基礎
AWS のネットワーク基盤である VPC を理解し、サブネット設計・ルートテーブル・IGW/NAT GW を使った本番ネットワーク構成を実践的に習得する
この章で学ぶこと
- VPC の基本アーキテクチャ — CIDR 設計、サブネット分割、AZ 配置の設計判断
- ルーティングとゲートウェイ — ルートテーブル、IGW、NAT GW の役割と構成
- セキュリティ制御 — セキュリティグループ、ネットワーク ACL、VPC エンドポイント
- VPC 間接続 — VPC Peering、Transit Gateway、PrivateLink の使い分け
- VPC Flow Logs と監視 — ネットワークトラフィックの可視化とトラブルシューティング
前提知識
このガイドを読む前に、以下の知識があると理解が深まります:
- 基本的なプログラミングの知識
- 関連する基礎概念の理解
1. VPC アーキテクチャ全体像
+----------------------------------------------------------------------+
| AWS Region (ap-northeast-1) |
| +----------------------------------------------------------------+ |
| | VPC: 10.0.0.0/16 (65,536 IPs) | |
| | | |
| | +-- AZ-1a ----------------+ +-- AZ-1c ----------------+ | |
| | | | | | | |
| | | Public Subnet | | Public Subnet | | |
| | | 10.0.1.0/24 | | 10.0.2.0/24 | | |
| | | [ALB] [NAT GW] | | [ALB] [NAT GW] | | |
| | | | | | | |
| | | Private Subnet (App) | | Private Subnet (App) | | |
| | | 10.0.11.0/24 | | 10.0.12.0/24 | | |
| | | [ECS/EC2] | | [ECS/EC2] | | |
| | | | | | | |
| | | Private Subnet (DB) | | Private Subnet (DB) | | |
| | | 10.0.21.0/24 | | 10.0.22.0/24 | | |
| | | [RDS] [ElastiCache] | | [RDS Standby] | | |
| | +-------------------------+ +-------------------------+ | |
| +----------------------------------------------------------------+ |
| | |
| +----+----+ |
| | IGW | <--> Internet |
| +---------+ |
+----------------------------------------------------------------------+
VPC の主要コンポーネント一覧
| コンポーネント | 説明 | スコープ |
|---|---|---|
| VPC | 仮想ネットワークの論理的な隔離空間 | リージョン |
| サブネット | VPC 内の IP アドレス範囲 | アベイラビリティゾーン |
| ルートテーブル | サブネットのトラフィックルーティング規則 | VPC |
| Internet Gateway (IGW) | VPC とインターネット間の接続ポイント | VPC |
| NAT Gateway | プライベートサブネットからの外向きインターネット接続 | AZ |
| セキュリティグループ | インスタンスレベルのステートフルファイアウォール | VPC |
| ネットワーク ACL | サブネットレベルのステートレスファイアウォール | VPC |
| VPC エンドポイント | VPC 内から AWS サービスへのプライベート接続 | VPC |
| Elastic IP | 静的なパブリック IPv4 アドレス | リージョン |
| ENI | 仮想ネットワークインターフェースカード | AZ |
コード例 1: VPC とサブネットの作成(AWS CLI)
# VPC の作成
VPC_ID=$(aws ec2 create-vpc \
--cidr-block 10.0.0.0/16 \
--tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=prod-vpc}]' \
--query 'Vpc.VpcId' --output text)
# DNS 有効化
aws ec2 modify-vpc-attribute --vpc-id $VPC_ID --enable-dns-support
aws ec2 modify-vpc-attribute --vpc-id $VPC_ID --enable-dns-hostnames
# パブリックサブネット(AZ-1a, AZ-1c)
PUB_1A=$(aws ec2 create-subnet --vpc-id $VPC_ID \
--cidr-block 10.0.1.0/24 --availability-zone ap-northeast-1a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=pub-1a}]' \
--query 'Subnet.SubnetId' --output text)
PUB_1C=$(aws ec2 create-subnet --vpc-id $VPC_ID \
--cidr-block 10.0.2.0/24 --availability-zone ap-northeast-1c \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=pub-1c}]' \
--query 'Subnet.SubnetId' --output text)
# プライベートサブネット(App層 / DB層)
PRIV_APP_1A=$(aws ec2 create-subnet --vpc-id $VPC_ID \
--cidr-block 10.0.11.0/24 --availability-zone ap-northeast-1a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=priv-app-1a}]' \
--query 'Subnet.SubnetId' --output text)
PRIV_APP_1C=$(aws ec2 create-subnet --vpc-id $VPC_ID \
--cidr-block 10.0.12.0/24 --availability-zone ap-northeast-1c \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=priv-app-1c}]' \
--query 'Subnet.SubnetId' --output text)
PRIV_DB_1A=$(aws ec2 create-subnet --vpc-id $VPC_ID \
--cidr-block 10.0.21.0/24 --availability-zone ap-northeast-1a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=priv-db-1a}]' \
--query 'Subnet.SubnetId' --output text)
PRIV_DB_1C=$(aws ec2 create-subnet --vpc-id $VPC_ID \
--cidr-block 10.0.22.0/24 --availability-zone ap-northeast-1c \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=priv-db-1c}]' \
--query 'Subnet.SubnetId' --output text)
# パブリックサブネットの自動パブリック IP 割り当て有効化
aws ec2 modify-subnet-attribute \
--subnet-id $PUB_1A \
--map-public-ip-on-launch
aws ec2 modify-subnet-attribute \
--subnet-id $PUB_1C \
--map-public-ip-on-launch2. CIDR 設計
CIDR ブロックサイズ早見表
| CIDR | IP 数 | 利用可能 IP | 用途例 |
|---|---|---|---|
| /16 | 65,536 | 65,531 | VPC 全体(推奨) |
| /20 | 4,096 | 4,091 | 大規模サブネット |
| /24 | 256 | 251 | 標準サブネット |
| /26 | 64 | 59 | 小規模サブネット |
| /28 | 16 | 11 | 最小サブネット |
AWS はサブネットごとに 5 IP を予約する(ネットワーク、VPC ルーター、DNS、将来予約、ブロードキャスト)
推奨 CIDR 設計パターン
VPC: 10.0.0.0/16 の設計例
============================
Public Subnets (各 /24 = 251 IP)
AZ-a: 10.0.1.0/24
AZ-c: 10.0.2.0/24
AZ-d: 10.0.3.0/24
Private App (各 /20 = 4,091 IP)
AZ-a: 10.0.16.0/20
AZ-c: 10.0.32.0/20
AZ-d: 10.0.48.0/20
Private DB (各 /24 = 251 IP)
AZ-a: 10.0.64.0/24
AZ-c: 10.0.65.0/24
AZ-d: 10.0.66.0/24
予備 10.0.128.0/17 (将来の拡張用に確保)
RFC 1918 プライベート IP アドレス範囲
| 範囲 | CIDR | IP 数 | 推奨用途 |
|---|---|---|---|
| 10.0.0.0 - 10.255.255.255 | 10.0.0.0/8 | 16,777,216 | 大規模ネットワーク(推奨) |
| 172.16.0.0 - 172.31.255.255 | 172.16.0.0/12 | 1,048,576 | 中規模ネットワーク |
| 192.168.0.0 - 192.168.255.255 | 192.168.0.0/16 | 65,536 | 小規模ネットワーク |
マルチ VPC 環境での CIDR 割当計画
マルチアカウント・マルチ VPC の IP 割当例:
=============================================
本番環境 (Production Account)
prod-vpc: 10.0.0.0/16
shared-svc-vpc: 10.1.0.0/16
ステージング環境 (Staging Account)
staging-vpc: 10.2.0.0/16
開発環境 (Development Account)
dev-vpc: 10.3.0.0/16
セキュリティ環境 (Security Account)
security-vpc: 10.4.0.0/16
ログ集約環境 (Logging Account)
logging-vpc: 10.5.0.0/16
ポイント:
- VPC 間で CIDR が重複しないように計画する
- VPC Peering / Transit Gateway で接続する場合は重複不可
- 10.0.0.0/8 の範囲を複数 /16 に分割して割当
- オンプレミスのネットワークとも重複しないよう調整
- Secondary CIDR の追加も検討(最大 5 つ)
コード例 2: Secondary CIDR の追加
# VPC に Secondary CIDR ブロックを追加
aws ec2 associate-vpc-cidr-block \
--vpc-id $VPC_ID \
--cidr-block 100.64.0.0/16
# 追加した CIDR でサブネットを作成
aws ec2 create-subnet --vpc-id $VPC_ID \
--cidr-block 100.64.1.0/24 \
--availability-zone ap-northeast-1a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=eks-pods-1a}]'
# 利用シーン:
# - EKS Pod のカスタムネットワーキング(Pod に VPC IP を割当)
# - IP アドレス空間が不足した場合の拡張
# - RFC 6598 (100.64.0.0/10) は CGN 用だが VPC 内では利用可能コード例 3: Terraform による VPC 定義
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.5.0"
name = "prod-vpc"
cidr = "10.0.0.0/16"
azs = ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
private_subnets = ["10.0.16.0/20", "10.0.32.0/20", "10.0.48.0/20"]
database_subnets = ["10.0.64.0/24", "10.0.65.0/24", "10.0.66.0/24"]
enable_nat_gateway = true
single_nat_gateway = false # 本番: AZ ごとに NAT GW
one_nat_gateway_per_az = true
enable_dns_hostnames = true
enable_dns_support = true
create_database_subnet_group = true
create_database_subnet_route_table = true
enable_flow_log = true
create_flow_log_cloudwatch_log_group = true
create_flow_log_iam_role = true
# パブリックサブネットのタグ(EKS ALB Ingress Controller 用)
public_subnet_tags = {
"kubernetes.io/role/elb" = "1"
}
# プライベートサブネットのタグ(EKS 内部 LB 用)
private_subnet_tags = {
"kubernetes.io/role/internal-elb" = "1"
}
tags = {
Environment = "production"
Terraform = "true"
}
}
# VPC のアウトプット
output "vpc_id" {
value = module.vpc.vpc_id
}
output "private_subnet_ids" {
value = module.vpc.private_subnets
}
output "public_subnet_ids" {
value = module.vpc.public_subnets
}
output "database_subnet_group_name" {
value = module.vpc.database_subnet_group_name
}コード例 4: CloudFormation による VPC 定義
AWSTemplateFormatVersion: '2010-09-09'
Description: Production VPC with 3-tier subnet architecture
Parameters:
EnvironmentName:
Type: String
Default: prod
VpcCIDR:
Type: String
Default: 10.0.0.0/16
Resources:
# VPC
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCIDR
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-vpc
# Internet Gateway
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-igw
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
# Public Subnets
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [0, !GetAZs '']
CidrBlock: 10.0.1.0/24
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-pub-1a
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [1, !GetAZs '']
CidrBlock: 10.0.2.0/24
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-pub-1c
# Private App Subnets
PrivateAppSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [0, !GetAZs '']
CidrBlock: 10.0.11.0/24
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-priv-app-1a
PrivateAppSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [1, !GetAZs '']
CidrBlock: 10.0.12.0/24
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-priv-app-1c
# Private DB Subnets
PrivateDBSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [0, !GetAZs '']
CidrBlock: 10.0.21.0/24
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-priv-db-1a
PrivateDBSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [1, !GetAZs '']
CidrBlock: 10.0.22.0/24
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-priv-db-1c
# NAT Gateway (AZ-1a)
NatGateway1EIP:
Type: AWS::EC2::EIP
DependsOn: InternetGatewayAttachment
Properties:
Domain: vpc
NatGateway1:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatGateway1EIP.AllocationId
SubnetId: !Ref PublicSubnet1
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-nat-1a
# NAT Gateway (AZ-1c)
NatGateway2EIP:
Type: AWS::EC2::EIP
DependsOn: InternetGatewayAttachment
Properties:
Domain: vpc
NatGateway2:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatGateway2EIP.AllocationId
SubnetId: !Ref PublicSubnet2
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-nat-1c
# Public Route Table
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-pub-rt
DefaultPublicRoute:
Type: AWS::EC2::Route
DependsOn: InternetGatewayAttachment
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet1
PublicSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet2
# Private Route Tables (AZ ごと)
PrivateRouteTable1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-priv-rt-1a
DefaultPrivateRoute1:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable1
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway1
PrivateAppSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable1
SubnetId: !Ref PrivateAppSubnet1
PrivateRouteTable2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-priv-rt-1c
DefaultPrivateRoute2:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable2
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway2
PrivateAppSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable2
SubnetId: !Ref PrivateAppSubnet2
Outputs:
VpcId:
Value: !Ref VPC
Export:
Name: !Sub ${EnvironmentName}-VpcId
PublicSubnets:
Value: !Join [',', [!Ref PublicSubnet1, !Ref PublicSubnet2]]
Export:
Name: !Sub ${EnvironmentName}-PublicSubnets
PrivateAppSubnets:
Value: !Join [',', [!Ref PrivateAppSubnet1, !Ref PrivateAppSubnet2]]
Export:
Name: !Sub ${EnvironmentName}-PrivateAppSubnets
PrivateDBSubnets:
Value: !Join [',', [!Ref PrivateDBSubnet1, !Ref PrivateDBSubnet2]]
Export:
Name: !Sub ${EnvironmentName}-PrivateDBSubnets3. ルートテーブルとゲートウェイ
ルーティングの仕組み
======================
[Public Subnet ルートテーブル]
+--------------------+-----------+
| Destination | Target |
+--------------------+-----------+
| 10.0.0.0/16 | local | <-- VPC 内通信
| 0.0.0.0/0 | igw-xxx | <-- インターネットへ
+--------------------+-----------+
[Private App Subnet ルートテーブル]
+--------------------+-----------+
| Destination | Target |
+--------------------+-----------+
| 10.0.0.0/16 | local | <-- VPC 内通信
| 0.0.0.0/0 | nat-xxx | <-- NAT GW 経由
+--------------------+-----------+
[Private DB Subnet ルートテーブル]
+--------------------+-----------+
| Destination | Target |
+--------------------+-----------+
| 10.0.0.0/16 | local | <-- VPC 内通信のみ
+--------------------+-----------+
ルートテーブルの評価ルール
ルートテーブルでは、宛先 IP に対して最も具体的な(プレフィックスが長い)ルートが優先される。例えば、10.1.0.0/16 と 0.0.0.0/0 の両方が存在する場合、10.1.x.x 宛のトラフィックは 10.1.0.0/16 のルートに従う。local ルートは常に最優先で、削除できない。
コード例 5: IGW と NAT GW の設定
# Internet Gateway
IGW_ID=$(aws ec2 create-internet-gateway \
--tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=prod-igw}]' \
--query 'InternetGateway.InternetGatewayId' --output text)
aws ec2 attach-internet-gateway --internet-gateway-id $IGW_ID --vpc-id $VPC_ID
# Elastic IP + NAT Gateway (AZ-1a)
EIP_1A=$(aws ec2 allocate-address --domain vpc --query 'AllocationId' --output text)
NAT_1A=$(aws ec2 create-nat-gateway \
--subnet-id $PUB_1A --allocation-id $EIP_1A \
--tag-specifications 'ResourceType=natgateway,Tags=[{Key=Name,Value=nat-1a}]' \
--query 'NatGateway.NatGatewayId' --output text)
# NAT Gateway の作成完了を待機
aws ec2 wait nat-gateway-available --nat-gateway-ids $NAT_1A
# Elastic IP + NAT Gateway (AZ-1c)
EIP_1C=$(aws ec2 allocate-address --domain vpc --query 'AllocationId' --output text)
NAT_1C=$(aws ec2 create-nat-gateway \
--subnet-id $PUB_1C --allocation-id $EIP_1C \
--tag-specifications 'ResourceType=natgateway,Tags=[{Key=Name,Value=nat-1c}]' \
--query 'NatGateway.NatGatewayId' --output text)
aws ec2 wait nat-gateway-available --nat-gateway-ids $NAT_1C
# Public ルートテーブル
PUB_RT=$(aws ec2 create-route-table --vpc-id $VPC_ID \
--tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=pub-rt}]' \
--query 'RouteTable.RouteTableId' --output text)
aws ec2 create-route --route-table-id $PUB_RT \
--destination-cidr-block 0.0.0.0/0 --gateway-id $IGW_ID
aws ec2 associate-route-table --route-table-id $PUB_RT --subnet-id $PUB_1A
aws ec2 associate-route-table --route-table-id $PUB_RT --subnet-id $PUB_1C
# Private ルートテーブル AZ-1a(NAT GW 経由)
PRIV_RT_1A=$(aws ec2 create-route-table --vpc-id $VPC_ID \
--tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=priv-rt-1a}]' \
--query 'RouteTable.RouteTableId' --output text)
aws ec2 create-route --route-table-id $PRIV_RT_1A \
--destination-cidr-block 0.0.0.0/0 --nat-gateway-id $NAT_1A
aws ec2 associate-route-table --route-table-id $PRIV_RT_1A --subnet-id $PRIV_APP_1A
aws ec2 associate-route-table --route-table-id $PRIV_RT_1A --subnet-id $PRIV_DB_1A
# Private ルートテーブル AZ-1c(NAT GW 経由)
PRIV_RT_1C=$(aws ec2 create-route-table --vpc-id $VPC_ID \
--tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=priv-rt-1c}]' \
--query 'RouteTable.RouteTableId' --output text)
aws ec2 create-route --route-table-id $PRIV_RT_1C \
--destination-cidr-block 0.0.0.0/0 --nat-gateway-id $NAT_1C
aws ec2 associate-route-table --route-table-id $PRIV_RT_1C --subnet-id $PRIV_APP_1C
aws ec2 associate-route-table --route-table-id $PRIV_RT_1C --subnet-id $PRIV_DB_1CNAT Gateway vs NAT Instance 比較
| 項目 | NAT Gateway | NAT Instance |
|---|---|---|
| 可用性 | AZ 内で高可用(AWS 管理) | 手動でフェイルオーバー構成 |
| 帯域幅 | 最大 100 Gbps | インスタンスタイプ依存 |
| メンテナンス | AWS 管理(パッチ不要) | ユーザー管理 |
| コスト | ~$0.062/時 + $0.062/GB | インスタンス料金のみ |
| セキュリティグループ | 関連付け不可 | 関連付け可能 |
| ポートフォワーディング | 非対応 | 対応 |
| Bastion ホスト兼用 | 不可 | 可能 |
| 推奨 | 本番環境 | 開発/テスト環境(コスト重視) |
コード例 6: NAT Instance による低コスト構成(開発環境向け)
# NAT Instance の作成(Amazon Linux 2023 AMI)
NAT_INSTANCE=$(aws ec2 run-instances \
--image-id ami-0abcdef1234567890 \
--instance-type t4g.nano \
--subnet-id $PUB_1A \
--security-group-ids $NAT_SG \
--key-name my-key \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=nat-instance}]' \
--query 'Instances[0].InstanceId' --output text)
# Source/Destination Check を無効化(NAT に必須)
aws ec2 modify-instance-attribute \
--instance-id $NAT_INSTANCE \
--no-source-dest-check
# NAT Instance 用のセキュリティグループ
NAT_SG=$(aws ec2 create-security-group \
--group-name nat-sg --description "NAT Instance SG" \
--vpc-id $VPC_ID --query 'GroupId' --output text)
# プライベートサブネットからの全トラフィックを許可
aws ec2 authorize-security-group-ingress --group-id $NAT_SG \
--protocol -1 --cidr 10.0.0.0/16
# プライベートルートテーブルのデフォルトルートを NAT Instance に設定
aws ec2 create-route --route-table-id $PRIV_RT_1A \
--destination-cidr-block 0.0.0.0/0 \
--instance-id $NAT_INSTANCE4. セキュリティグループとネットワーク ACL
SG vs NACL 比較表
| 特性 | セキュリティグループ (SG) | ネットワーク ACL (NACL) |
|---|---|---|
| 適用レベル | ENI(インスタンス単位) | サブネット単位 |
| ステート | ステートフル(戻りは自動許可) | ステートレス(戻りも明示必要) |
| ルール | 許可のみ | 許可 + 拒否 |
| 評価順序 | 全ルールを評価 | 番号順に評価、最初の一致 |
| デフォルト | 全アウトバウンド許可 | 全トラフィック許可 |
| 推奨用途 | 主要なアクセス制御 | 追加の防御層(サブネットレベル) |
コード例 7: 3層アーキテクチャの SG 設計
# ALB 用 SG
ALB_SG=$(aws ec2 create-security-group \
--group-name alb-sg --description "ALB SG" \
--vpc-id $VPC_ID --query 'GroupId' --output text)
aws ec2 authorize-security-group-ingress --group-id $ALB_SG \
--protocol tcp --port 443 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress --group-id $ALB_SG \
--protocol tcp --port 80 --cidr 0.0.0.0/0
# App 用 SG(ALB からのみ受信)
APP_SG=$(aws ec2 create-security-group \
--group-name app-sg --description "App SG" \
--vpc-id $VPC_ID --query 'GroupId' --output text)
aws ec2 authorize-security-group-ingress --group-id $APP_SG \
--protocol tcp --port 8080 --source-group $ALB_SG
# DB 用 SG(App からのみ受信)
DB_SG=$(aws ec2 create-security-group \
--group-name db-sg --description "DB SG" \
--vpc-id $VPC_ID --query 'GroupId' --output text)
aws ec2 authorize-security-group-ingress --group-id $DB_SG \
--protocol tcp --port 3306 --source-group $APP_SG
aws ec2 authorize-security-group-ingress --group-id $DB_SG \
--protocol tcp --port 6379 --source-group $APP_SG
# Bastion 用 SG(特定 IP からのみ SSH)
BASTION_SG=$(aws ec2 create-security-group \
--group-name bastion-sg --description "Bastion SG" \
--vpc-id $VPC_ID --query 'GroupId' --output text)
aws ec2 authorize-security-group-ingress --group-id $BASTION_SG \
--protocol tcp --port 22 --cidr 203.0.113.0/32
# App SG に Bastion からの SSH を追加
aws ec2 authorize-security-group-ingress --group-id $APP_SG \
--protocol tcp --port 22 --source-group $BASTION_SG
# VPC Endpoint 用 SG
ENDPOINT_SG=$(aws ec2 create-security-group \
--group-name endpoint-sg --description "VPC Endpoint SG" \
--vpc-id $VPC_ID --query 'GroupId' --output text)
aws ec2 authorize-security-group-ingress --group-id $ENDPOINT_SG \
--protocol tcp --port 443 --cidr 10.0.0.0/16コード例 8: ネットワーク ACL によるサブネットレベルの防御
# DB サブネット用 NACL
DB_NACL=$(aws ec2 create-network-acl --vpc-id $VPC_ID \
--tag-specifications 'ResourceType=network-acl,Tags=[{Key=Name,Value=db-nacl}]' \
--query 'NetworkAcl.NetworkAclId' --output text)
# インバウンド: App サブネットからの MySQL/Redis のみ許可
aws ec2 create-network-acl-entry --network-acl-id $DB_NACL \
--rule-number 100 --protocol tcp --port-range From=3306,To=3306 \
--cidr-block 10.0.11.0/24 --rule-action allow --ingress
aws ec2 create-network-acl-entry --network-acl-id $DB_NACL \
--rule-number 110 --protocol tcp --port-range From=3306,To=3306 \
--cidr-block 10.0.12.0/24 --rule-action allow --ingress
aws ec2 create-network-acl-entry --network-acl-id $DB_NACL \
--rule-number 120 --protocol tcp --port-range From=6379,To=6379 \
--cidr-block 10.0.11.0/24 --rule-action allow --ingress
aws ec2 create-network-acl-entry --network-acl-id $DB_NACL \
--rule-number 130 --protocol tcp --port-range From=6379,To=6379 \
--cidr-block 10.0.12.0/24 --rule-action allow --ingress
# アウトバウンド: エフェメラルポート(戻りトラフィック)のみ許可
aws ec2 create-network-acl-entry --network-acl-id $DB_NACL \
--rule-number 100 --protocol tcp --port-range From=1024,To=65535 \
--cidr-block 10.0.11.0/24 --rule-action allow --egress
aws ec2 create-network-acl-entry --network-acl-id $DB_NACL \
--rule-number 110 --protocol tcp --port-range From=1024,To=65535 \
--cidr-block 10.0.12.0/24 --rule-action allow --egress
# 全拒否ルール(デフォルトルールだが明示的に記載)
aws ec2 create-network-acl-entry --network-acl-id $DB_NACL \
--rule-number 32767 --protocol -1 --cidr-block 0.0.0.0/0 \
--rule-action deny --ingress
# NACL をサブネットに関連付け
aws ec2 replace-network-acl-association \
--association-id aclassoc-xxxxx \
--network-acl-id $DB_NACLセキュリティグループのベストプラクティス
1. ソースには CIDR ではなく SG ID を指定
✕ --cidr 10.0.11.0/24
○ --source-group sg-app-xxxxx
理由: IP が変わっても追従する。意図が明確。
2. 用途ごとに SG を分離
✕ 1つの SG に全ルールを集約
○ ALB用、App用、DB用、管理用で分離
理由: 最小権限の原則。変更の影響範囲を限定。
3. 説明フィールドを必ず記載
aws ec2 authorize-security-group-ingress --group-id $SG \
--ip-permissions '[{
"IpProtocol": "tcp",
"FromPort": 443,
"ToPort": 443,
"IpRanges": [{"CidrIp": "0.0.0.0/0", "Description": "HTTPS from Internet"}]
}]'
4. 定期的な棚卸し
# 使用されていない SG の検出
aws ec2 describe-security-groups \
--query 'SecurityGroups[?length(IpPermissions)==`0` && length(IpPermissionsEgress)==`1`].[GroupId,GroupName]' \
--output table
5. VPC エンドポイント
VPC エンドポイントの種類
==========================
Gateway Endpoint (無料)
対応: S3, DynamoDB
ルートテーブルにエントリ追加
App --> Route Table --> S3 (AWS 内部ネットワーク)
Interface Endpoint (有料: ~$0.014/時 + データ転送)
対応: ほぼ全 AWS サービス
サブネットに ENI を作成
App --> ENI --> AWS サービス (PrivateLink)
Gateway Load Balancer Endpoint
対応: サードパーティアプライアンス
ネットワークトラフィックのインスペクション用
App --> GWLB Endpoint --> Firewall Appliance --> 宛先
コード例 9: VPC エンドポイントの作成
# Gateway エンドポイント(S3)- 無料
aws ec2 create-vpc-endpoint \
--vpc-id $VPC_ID \
--service-name com.amazonaws.ap-northeast-1.s3 \
--route-table-ids $PRIV_RT_1A $PRIV_RT_1C \
--vpc-endpoint-type Gateway \
--tag-specifications 'ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=s3-endpoint}]'
# Gateway エンドポイント(DynamoDB)- 無料
aws ec2 create-vpc-endpoint \
--vpc-id $VPC_ID \
--service-name com.amazonaws.ap-northeast-1.dynamodb \
--route-table-ids $PRIV_RT_1A $PRIV_RT_1C \
--vpc-endpoint-type Gateway \
--tag-specifications 'ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=dynamodb-endpoint}]'
# Interface エンドポイント(Secrets Manager)- 有料
aws ec2 create-vpc-endpoint \
--vpc-id $VPC_ID \
--service-name com.amazonaws.ap-northeast-1.secretsmanager \
--vpc-endpoint-type Interface \
--subnet-ids $PRIV_APP_1A $PRIV_APP_1C \
--security-group-ids $ENDPOINT_SG \
--private-dns-enabled \
--tag-specifications 'ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=secretsmanager-endpoint}]'
# Interface エンドポイント(ECR - Docker Pull 用)
# ECR は 2 つのエンドポイントが必要
aws ec2 create-vpc-endpoint \
--vpc-id $VPC_ID \
--service-name com.amazonaws.ap-northeast-1.ecr.dkr \
--vpc-endpoint-type Interface \
--subnet-ids $PRIV_APP_1A $PRIV_APP_1C \
--security-group-ids $ENDPOINT_SG \
--private-dns-enabled \
--tag-specifications 'ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=ecr-dkr-endpoint}]'
aws ec2 create-vpc-endpoint \
--vpc-id $VPC_ID \
--service-name com.amazonaws.ap-northeast-1.ecr.api \
--vpc-endpoint-type Interface \
--subnet-ids $PRIV_APP_1A $PRIV_APP_1C \
--security-group-ids $ENDPOINT_SG \
--private-dns-enabled \
--tag-specifications 'ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=ecr-api-endpoint}]'
# Interface エンドポイント(CloudWatch Logs)
aws ec2 create-vpc-endpoint \
--vpc-id $VPC_ID \
--service-name com.amazonaws.ap-northeast-1.logs \
--vpc-endpoint-type Interface \
--subnet-ids $PRIV_APP_1A $PRIV_APP_1C \
--security-group-ids $ENDPOINT_SG \
--private-dns-enabled \
--tag-specifications 'ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=logs-endpoint}]'
# Interface エンドポイント(STS - IAM ロール引き受け用)
aws ec2 create-vpc-endpoint \
--vpc-id $VPC_ID \
--service-name com.amazonaws.ap-northeast-1.sts \
--vpc-endpoint-type Interface \
--subnet-ids $PRIV_APP_1A $PRIV_APP_1C \
--security-group-ids $ENDPOINT_SG \
--private-dns-enabled \
--tag-specifications 'ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=sts-endpoint}]'ECS/EKS で必要な VPC エンドポイント一覧
| サービス | エンドポイントタイプ | 必要性 | 用途 |
|---|---|---|---|
| S3 | Gateway (無料) | 必須 | ECR イメージレイヤーの取得 |
| ECR (dkr) | Interface | 必須 | Docker イメージの Pull |
| ECR (api) | Interface | 必須 | ECR API コール |
| CloudWatch Logs | Interface | 推奨 | ログ送信 |
| STS | Interface | EKS で必須 | IAM Roles for Service Accounts |
| Secrets Manager | Interface | 推奨 | シークレット取得 |
| SSM | Interface | 推奨 | パラメータストア、Session Manager |
S3 Gateway エンドポイントのポリシー設定
# S3 エンドポイントへのポリシー設定(特定バケットのみ許可)
aws ec2 modify-vpc-endpoint \
--vpc-endpoint-id vpce-xxxxx \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSpecificBuckets",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-app-bucket",
"arn:aws:s3:::my-app-bucket/*",
"arn:aws:s3:::prod-*"
]
},
{
"Sid": "AllowECRBucket",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::prod-ap-northeast-1-starport-layer-bucket/*"
}
]
}'6. VPC Peering と Transit Gateway
VPC Peering
VPC Peering の構成:
VPC-A (10.0.0.0/16) <----> VPC-B (10.1.0.0/16)
| |
+---- Peering 接続 --------+
| |
ルートテーブルに ルートテーブルに
10.1.0.0/16 -> pcx-xxx 10.0.0.0/16 -> pcx-xxx
制約:
- 推移的ルーティング不可 (A-B, B-C でも A-C は通信不可)
- CIDR 重複不可
- リージョン間対応 (Inter-Region Peering)
コード例 10: VPC Peering の設定
# VPC Peering 接続の作成
PEERING_ID=$(aws ec2 create-vpc-peering-connection \
--vpc-id $VPC_ID --peer-vpc-id vpc-0abc123 \
--tag-specifications 'ResourceType=vpc-peering-connection,Tags=[{Key=Name,Value=prod-to-shared}]' \
--query 'VpcPeeringConnection.VpcPeeringConnectionId' --output text)
# 承認(相手側アカウントで実行、またはクロスアカウントの場合)
aws ec2 accept-vpc-peering-connection --vpc-peering-connection-id $PEERING_ID
# 双方のルートテーブルに Peering ルート追加
# VPC-A 側
aws ec2 create-route --route-table-id $PRIV_RT_1A \
--destination-cidr-block 10.1.0.0/16 \
--vpc-peering-connection-id $PEERING_ID
# VPC-B 側
aws ec2 create-route --route-table-id rtb-shared-xxx \
--destination-cidr-block 10.0.0.0/16 \
--vpc-peering-connection-id $PEERING_ID
# DNS 解決の有効化(Peering 先の プライベート DNS を解決)
aws ec2 modify-vpc-peering-connection-options \
--vpc-peering-connection-id $PEERING_ID \
--requester-peering-connection-options AllowDnsResolutionFromRemoteVpc=true \
--accepter-peering-connection-options AllowDnsResolutionFromRemoteVpc=trueTransit Gateway
Transit Gateway の構成(ハブ&スポーク):
+-------------------+
| Transit Gateway |
| (ハブ) |
+---+-----+-----+--+
| | |
+-----------+ | +-----------+
| | |
+----+----+---+ +---+----+----+ +---+----+----+
| VPC-Prod | | VPC-Staging | | VPC-Shared |
| 10.0.0.0/16 | | 10.2.0.0/16| | 10.1.0.0/16 |
+-------------+ +------------+ +-------------+
|
VPN / Direct Connect
|
+------+------+
| On-Premises |
+-------------+
利点:
- 推移的ルーティング対応 (A-B, B-C → A-C 通信可能)
- ルートテーブルで通信制御
- VPN / Direct Connect もアタッチ可能
- 複数アカウント対応 (RAM で共有)
コード例 11: Transit Gateway の作成と VPC アタッチ
# Transit Gateway の作成
TGW_ID=$(aws ec2 create-transit-gateway \
--description "Central hub for all VPCs" \
--options '{
"AmazonSideAsn": 64512,
"AutoAcceptSharedAttachments": "enable",
"DefaultRouteTableAssociation": "enable",
"DefaultRouteTablePropagation": "enable",
"DnsSupport": "enable",
"VpnEcmpSupport": "enable"
}' \
--tag-specifications 'ResourceType=transit-gateway,Tags=[{Key=Name,Value=central-tgw}]' \
--query 'TransitGateway.TransitGatewayId' --output text)
# VPC アタッチメントの作成
aws ec2 create-transit-gateway-vpc-attachment \
--transit-gateway-id $TGW_ID \
--vpc-id $VPC_ID \
--subnet-ids $PRIV_APP_1A $PRIV_APP_1C \
--tag-specifications 'ResourceType=transit-gateway-attachment,Tags=[{Key=Name,Value=prod-vpc-attach}]'
# VPC のルートテーブルに TGW 経由のルート追加
aws ec2 create-route --route-table-id $PRIV_RT_1A \
--destination-cidr-block 10.1.0.0/16 \
--transit-gateway-id $TGW_ID
aws ec2 create-route --route-table-id $PRIV_RT_1A \
--destination-cidr-block 10.2.0.0/16 \
--transit-gateway-id $TGW_ID
# TGW ルートテーブルの確認
aws ec2 search-transit-gateway-routes \
--transit-gateway-route-table-id tgw-rtb-xxxxx \
--filters "Name=type,Values=propagated"Peering vs Transit Gateway 使い分け
| 項目 | VPC Peering | Transit Gateway |
|---|---|---|
| 接続トポロジー | ポイントツーポイント | ハブ&スポーク |
| 推移的ルーティング | 不可 | 可能 |
| 最大接続数 | VPC あたり 125 | 5,000 アタッチメント |
| コスト | データ転送料のみ | $0.07/時 + データ転送料 |
| 帯域幅 | 制限なし | VPC アタッチメントあたり 50 Gbps |
| VPN/DX 統合 | 不可 | 可能 |
| 推奨 | 2-3 VPC の少数接続 | 4+ VPC、VPN/DX 統合 |
7. VPC Flow Logs
VPC Flow Logs の概要
VPC Flow Logs は VPC 内のネットワークインターフェース間のトラフィック情報を記録する機能である。セキュリティ分析、ネットワーク監視、トラブルシューティングに不可欠である。
Flow Log の記録レベル:
VPC レベル → VPC 内の全 ENI のトラフィックを記録
サブネットレベル → 特定サブネット内の全 ENI のトラフィックを記録
ENI レベル → 特定の ENI のトラフィックのみ記録
送信先:
CloudWatch Logs → リアルタイム分析、メトリクスフィルター
S3 → 長期保存、Athena でクエリ(推奨)
Kinesis Firehose → リアルタイム加工、SIEM 連携
コード例 12: VPC Flow Logs の設定
# CloudWatch Logs への Flow Log 設定
aws ec2 create-flow-logs \
--resource-type VPC \
--resource-ids $VPC_ID \
--traffic-type ALL \
--log-destination-type cloud-watch-logs \
--log-group-name /vpc/flow-logs/prod \
--deliver-logs-permission-arn arn:aws:iam::123456789012:role/flowlogs-role \
--max-aggregation-interval 60 \
--tag-specifications 'ResourceType=vpc-flow-log,Tags=[{Key=Name,Value=prod-flow-log}]'
# S3 への Flow Log 設定(推奨: 長期保存・Athena 分析向き)
aws ec2 create-flow-logs \
--resource-type VPC \
--resource-ids $VPC_ID \
--traffic-type ALL \
--log-destination-type s3 \
--log-destination arn:aws:s3:::my-flowlogs-bucket/prod/ \
--log-format '${version} ${account-id} ${interface-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${packets} ${bytes} ${start} ${end} ${action} ${log-status} ${vpc-id} ${subnet-id} ${az-id} ${sublocation-type} ${sublocation-id} ${pkt-srcaddr} ${pkt-dstaddr} ${region} ${pkt-src-aws-service} ${pkt-dst-aws-service} ${flow-direction} ${traffic-path}' \
--max-aggregation-interval 60
# Flow Logs 用の IAM ロール
cat > flowlogs-trust-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "vpc-flow-logs.amazonaws.com"},
"Action": "sts:AssumeRole"
}
]
}
EOF
aws iam create-role \
--role-name flowlogs-role \
--assume-role-policy-document file://flowlogs-trust-policy.json
aws iam put-role-policy \
--role-name flowlogs-role \
--policy-name flowlogs-policy \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams"
],
"Effect": "Allow",
"Resource": "*"
}
]
}'コード例 13: Athena で Flow Logs を分析
-- Athena テーブルの作成(S3 に保存した Flow Logs 用)
CREATE EXTERNAL TABLE IF NOT EXISTS vpc_flow_logs (
version int,
account_id string,
interface_id string,
srcaddr string,
dstaddr string,
srcport int,
dstport int,
protocol bigint,
packets bigint,
bytes bigint,
start bigint,
`end` bigint,
action string,
log_status string,
vpc_id string,
subnet_id string,
az_id string,
sublocation_type string,
sublocation_id string,
pkt_srcaddr string,
pkt_dstaddr string,
region string,
pkt_src_aws_service string,
pkt_dst_aws_service string,
flow_direction string,
traffic_path int
)
PARTITIONED BY (dt string)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ' '
LOCATION 's3://my-flowlogs-bucket/prod/AWSLogs/123456789012/vpcflowlogs/ap-northeast-1/'
TBLPROPERTIES ("skip.header.line.count"="1");
-- 拒否されたトラフィックの分析
SELECT srcaddr, dstaddr, dstport, protocol, action, SUM(packets) as total_packets
FROM vpc_flow_logs
WHERE action = 'REJECT'
AND dt = '2026/02/15'
GROUP BY srcaddr, dstaddr, dstport, protocol, action
ORDER BY total_packets DESC
LIMIT 20;
-- 特定 IP からのトラフィック量分析
SELECT dstport, protocol, SUM(bytes) as total_bytes, COUNT(*) as flow_count
FROM vpc_flow_logs
WHERE srcaddr = '10.0.11.15'
AND dt >= '2026/02/01'
GROUP BY dstport, protocol
ORDER BY total_bytes DESC;
-- NAT Gateway 経由のトラフィック量(コスト分析用)
SELECT pkt_dstaddr, dstport, SUM(bytes) as total_bytes
FROM vpc_flow_logs
WHERE interface_id IN (
SELECT network_interface_id FROM nat_gw_enis
)
AND flow_direction = 'egress'
GROUP BY pkt_dstaddr, dstport
ORDER BY total_bytes DESC
LIMIT 50;8. AWS CDK による VPC 構築
// lib/vpc-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import { Construct } from 'constructs';
export class VpcStack extends cdk.Stack {
public readonly vpc: ec2.Vpc;
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// VPC の作成(3層サブネット)
this.vpc = new ec2.Vpc(this, 'ProdVpc', {
vpcName: 'prod-vpc',
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
maxAzs: 3,
natGateways: 3, // AZ ごとに 1 つ
subnetConfiguration: [
{
subnetType: ec2.SubnetType.PUBLIC,
name: 'Public',
cidrMask: 24,
},
{
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
name: 'PrivateApp',
cidrMask: 20,
},
{
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
name: 'PrivateDB',
cidrMask: 24,
},
],
// Flow Logs
flowLogs: {
's3': {
destination: ec2.FlowLogDestination.toS3(),
trafficType: ec2.FlowLogTrafficType.ALL,
},
},
});
// S3 Gateway Endpoint(無料)
this.vpc.addGatewayEndpoint('S3Endpoint', {
service: ec2.GatewayVpcEndpointAwsService.S3,
});
// DynamoDB Gateway Endpoint(無料)
this.vpc.addGatewayEndpoint('DynamoDBEndpoint', {
service: ec2.GatewayVpcEndpointAwsService.DYNAMODB,
});
// ECR Interface Endpoints
this.vpc.addInterfaceEndpoint('EcrDockerEndpoint', {
service: ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER,
});
this.vpc.addInterfaceEndpoint('EcrApiEndpoint', {
service: ec2.InterfaceVpcEndpointAwsService.ECR,
});
// CloudWatch Logs Interface Endpoint
this.vpc.addInterfaceEndpoint('CloudWatchLogsEndpoint', {
service: ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS,
});
// セキュリティグループ: ALB
const albSg = new ec2.SecurityGroup(this, 'AlbSg', {
vpc: this.vpc,
description: 'Security group for ALB',
allowAllOutbound: true,
});
albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'HTTPS');
albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'HTTP redirect');
// セキュリティグループ: App
const appSg = new ec2.SecurityGroup(this, 'AppSg', {
vpc: this.vpc,
description: 'Security group for App tier',
allowAllOutbound: true,
});
appSg.addIngressRule(albSg, ec2.Port.tcp(8080), 'From ALB');
// セキュリティグループ: DB
const dbSg = new ec2.SecurityGroup(this, 'DbSg', {
vpc: this.vpc,
description: 'Security group for DB tier',
allowAllOutbound: false,
});
dbSg.addIngressRule(appSg, ec2.Port.tcp(3306), 'MySQL from App');
dbSg.addIngressRule(appSg, ec2.Port.tcp(6379), 'Redis from App');
// アウトプット
new cdk.CfnOutput(this, 'VpcId', { value: this.vpc.vpcId });
new cdk.CfnOutput(this, 'PublicSubnets', {
value: this.vpc.publicSubnets.map(s => s.subnetId).join(','),
});
new cdk.CfnOutput(this, 'PrivateSubnets', {
value: this.vpc.privateSubnets.map(s => s.subnetId).join(','),
});
new cdk.CfnOutput(this, 'IsolatedSubnets', {
value: this.vpc.isolatedSubnets.map(s => s.subnetId).join(','),
});
}
}9. IPv6 対応
デュアルスタック VPC の構成
デュアルスタック VPC:
IPv4 CIDR: 10.0.0.0/16 (プライベート)
IPv6 CIDR: 2600:1f18:xxxx::/56 (AWS 割当パブリック)
サブネット:
Public: 10.0.1.0/24 + 2600:1f18:xxxx:0100::/64
Private: 10.0.11.0/24 + 2600:1f18:xxxx:0b00::/64
ルーティング:
Public: ::/0 → igw-xxx (IPv6 インターネット直接)
Private: ::/0 → eigw-xxx (Egress-only Internet Gateway)
Egress-only Internet Gateway:
- IPv6 のアウトバウンドのみ許可(NAT GW の IPv6 版)
- インバウンドは拒否
- 無料
コード例 14: IPv6 対応 VPC の設定
# VPC に IPv6 CIDR を関連付け
aws ec2 associate-vpc-cidr-block \
--vpc-id $VPC_ID \
--amazon-provided-ipv6-cidr-block
# サブネットに IPv6 CIDR を割当
aws ec2 associate-subnet-cidr-block \
--subnet-id $PUB_1A \
--ipv6-cidr-block 2600:1f18:xxxx:0100::/64
# Egress-only Internet Gateway の作成
EIGW_ID=$(aws ec2 create-egress-only-internet-gateway \
--vpc-id $VPC_ID \
--query 'EgressOnlyInternetGateway.EgressOnlyInternetGatewayId' --output text)
# プライベートサブネットの IPv6 ルート(アウトバウンドのみ)
aws ec2 create-route --route-table-id $PRIV_RT_1A \
--destination-ipv6-cidr-block ::/0 \
--egress-only-internet-gateway-id $EIGW_ID
# パブリックサブネットの IPv6 ルート(双方向)
aws ec2 create-route --route-table-id $PUB_RT \
--destination-ipv6-cidr-block ::/0 \
--gateway-id $IGW_IDアンチパターン
1. 全リソースをパブリックサブネットに配置
問題: EC2、RDS、ElastiCache をすべてパブリックサブネットに配置すると、セキュリティグループの設定ミスで内部リソースがインターネットに露出するリスクがある。多層防御の原則に反する。
対策: 3層サブネット設計を採用する。パブリックには ALB/NAT GW のみ配置し、アプリケーションとデータベースはプライベートサブネットに配置する。
2. CIDR の過小設計
問題: VPC を /24 のような小さい CIDR で作成すると、EKS ノード、Lambda ENI、ElastiCache ノードなど予想外に IP を消費するサービスで IP 枯渇が発生する。VPC の CIDR は後から変更できない。
対策: VPC は /16 で作成し、サブネットは用途に応じて /20 〜 /24 で分割する。将来の拡張用に CIDR 空間の半分は予約しておく。
3. シングル AZ 構成
問題: コスト削減のため 1 つの AZ にのみリソースを配置すると、AZ 障害時にサービス全体が停止する。AWS の AZ 障害は年に数回発生している。
対策: 最低 2 AZ、可能であれば 3 AZ 構成にする。NAT Gateway もマルチ AZ にすることで、単一 AZ の障害がプライベートサブネットのインターネットアクセスに影響しないようにする。
4. セキュリティグループの過剰許可
問題: 開発の便宜のために 0.0.0.0/0 からの全ポート許可を設定し、本番にそのまま持ち込む。
対策: SG のソースには他の SG の ID を指定する。ポートは必要最小限に限定する。AWS Config の restricted-ssh や restricted-common-ports ルールで自動検出する。
5. VPC エンドポイントを使わない NAT Gateway 経由のアクセス
問題: S3 や DynamoDB へのアクセスを NAT Gateway 経由で行うと、不要な NAT Gateway 料金($0.062/GB)が発生する。
対策: S3 と DynamoDB は Gateway Endpoint(無料)を使用する。ECR やその他の AWS サービスも Interface Endpoint を検討し、NAT Gateway のデータ処理量を削減する。
FAQ
Q1: NAT Gateway のコストが高い場合の対策は?
A: NAT GW は約 $0.062/時 + データ処理 $0.062/GB で、月額約 $45 + データ転送量です。コスト削減策:
- 開発環境: NAT Instance(t4g.nano: 約 $3/月)で代替
- VPC エンドポイント: S3・DynamoDB は Gateway Endpoint(無料)で NAT GW を経由しない
- ECR Image Pull: VPC エンドポイントで NAT GW トラフィックを削減
- シングル NAT GW: 開発環境では AZ ごとではなく1つの NAT GW を共有
- Flow Logs 分析: NAT GW を経由しているトラフィックの内訳を分析し、エンドポイント化可能なものを特定
Q2: Peering と Transit Gateway はどう使い分けますか?
A:
- VPC Peering: 2-3 VPC の接続。無料(データ転送料のみ)。1対1接続
- Transit Gateway: 多数の VPC/オンプレミス接続。ハブ&スポーク構成。時間課金(約 $0.07/時) VPC が 3 つ以下なら Peering、4 つ以上や VPN 接続がある場合は Transit Gateway が効率的です。
Q3: VPC Flow Logs は有効にすべきですか?
A: プロダクション環境では必須です。セキュリティインシデント調査、ネットワークトラブルシュート、コンプライアンスで必要です。コスト最適化のため、送信先は S3(CloudWatch Logs より安価)を選び、カスタムフォーマットで必要なフィールドのみ記録してください。
Q4: AWS Network Firewall は必要ですか?
A: 基本的な要件はセキュリティグループと NACL で十分ですが、以下の場合に Network Firewall を検討してください:
- IDS/IPS が必要: Suricata ベースのルールで侵入検知・防止
- ドメインフィルタリング: 特定のドメインへのアウトバウンドのみ許可
- TLS インスペクション: 暗号化されたトラフィックの検査が必要
- コンプライアンス要件: PCI DSS や HIPAA で要求される場合
Q5: セキュリティグループの上限に達した場合はどうしますか?
A: デフォルトでは VPC あたり 2,500 SG、SG あたり 60 インバウンドルール + 60 アウトバウンドルールです。対策:
- プレフィックスリストの活用: 複数の CIDR を 1 つのプレフィックスリストにまとめる
- SG の整理: 未使用の SG を削除、類似ルールの SG を統合
- Service Quotas で上限引き上げ: AWS Support から引き上げを申請
- NACL の活用: サブネットレベルのルールを NACL に移行して SG ルールを削減
Q6: VPC の DNS 設定でハマりやすいポイントは?
A: 以下の点に注意してください:
- enableDnsSupport: true にしないと VPC 内の DNS 解決が動作しない
- enableDnsHostnames: true にしないと EC2 インスタンスにパブリック DNS ホスト名が付与されない
- DHCP オプションセット: カスタム DNS サーバーを指定する場合に変更
- Route 53 Resolver: オンプレミスとの DNS 統合に必要(インバウンド/アウトバウンドエンドポイント)
- プライベートホストゾーン: VPC 内部のサービスディスカバリに活用
まとめ
| 項目 | 要点 |
|---|---|
| VPC 設計 | /16 CIDR、3層サブネット(Public/Private App/Private DB)、マルチ AZ |
| サブネット | Public: ALB/NAT、Private App: ECS/EC2、Private DB: RDS/Cache |
| ルーティング | Public -> IGW、Private -> NAT GW、DB -> local のみ |
| セキュリティ | SG でアクセス制御(主)、NACL で追加防御(補助) |
| VPC エンドポイント | S3/DynamoDB は Gateway(無料)、その他は Interface |
| VPC 間接続 | 少数は Peering、多数は Transit Gateway |
| Flow Logs | S3 + Athena でコスト効率の良い分析 |
| コスト注意 | NAT GW が主要コスト要素。VPC エンドポイントで削減 |
| IPv6 | デュアルスタック対応、Egress-only IGW でプライベートアクセス |
次に読むべきガイド
- RDS 基礎 — VPC 内でのデータベース配置
- ElastiCache — プライベートサブネットでのキャッシュ構築
- DynamoDB — VPC エンドポイントでの接続最適化
- Route 53 — VPC のプライベートホストゾーンと DNS 設計
参考文献
- AWS 公式ドキュメント: Amazon VPC ユーザーガイド — VPC の全機能リファレンス
- AWS Well-Architected Framework: セキュリティの柱 — ネットワークセキュリティのベストプラクティス
- AWS ブログ: VPC ベストプラクティス — 実践的な VPC 設計パターン
- AWS re:Invent: NET305 - Advanced VPC design and new capabilities — 高度な VPC 設計
- AWS ドキュメント: VPC Flow Logs — トラフィック分析と監視