关于 aws-vpc
aws-vpc 是面向 AWS 运维与架构人员的 VPC 网络设计和管理技能,涵盖子网隔离、路由与安全配置,并提供故障排查指导。
主要功能
- 01规划公有与私有子网
- 02配置 NAT、路由与网关
- 03设置安全组和网络 ACL
- 04创建 VPC 端点与对等连接
- 05排查网络连通性问题
适用场景
- 01为生产、预发布或开发环境搭建 VPC
- 02为私有子网配置互联网访问
- 03连接多个 VPC 或 AWS 服务
- 04定位资源间的网络故障
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ bagelhole/ devops-security-agent-skills --skill aws-vpc安装后在 Claude Code 中输入/aws-vpc确认已加载。
详细介绍
AWS VPC
为生产环境中的 AWS 设计和管理虚拟私有云网络,确保子网隔离、路由和安全配置得当。
适用场景
- 为生产、预发布或开发环境构建新的 VPC
- 在多个 AZ 之间设置公有/私有子网架构
- 配置 NAT Gateway,为私有子网提供互联网访问
- 创建安全组和 NACL,实现网络分段
- 设置 VPC 对等连接或 Transit Gateway,实现多个 VPC 互连
- 实施 VPC 端点,以便私下访问 AWS 服务
- 排查资源之间的连接问题
前提条件
- 已安装并配置 AWS CLI v2
- IAM 权限:
ec2:*(或限定为与 VPC 相关的操作) - 已完成 CIDR 范围规划(避免与本地部署环境或其他 VPC 重叠)
- 如需配置 VPC 对等连接:可以访问两个 VPC(相同或不同账户)
网络架构
VPC (10.0.0.0/16) - 65,536 IPs
├── Public Subnets (internet-facing via IGW)
│ ├── 10.0.1.0/24 (us-east-1a) - 256 IPs - ALBs, NAT GW, bastion
│ ├── 10.0.2.0/24 (us-east-1b) - 256 IPs
│ └── 10.0.3.0/24 (us-east-1c) - 256 IPs
├── Private Subnets (app tier, NAT GW for outbound)
│ ├── 10.0.11.0/24 (us-east-1a) - 256 IPs - ECS, EC2, Lambda
│ ├── 10.0.12.0/24 (us-east-1b) - 256 IPs
│ └── 10.0.13.0/24 (us-east-1c) - 256 IPs
├── Data Subnets (isolated, no internet)
│ ├── 10.0.21.0/24 (us-east-1a) - 256 IPs - RDS, ElastiCache
│ ├── 10.0.22.0/24 (us-east-1b) - 256 IPs
│ └── 10.0.23.0/24 (us-east-1c) - 256 IPs
├── Internet Gateway
├── NAT Gateways (one per AZ for HA)
├── Route Tables (public, private, data)
└── VPC Flow Logs → CloudWatch / S3
使用 CLI 创建 VPC
# Create the VPC
VPC_ID=$(aws ec2 create-vpc \
--cidr-block 10.0.0.0/16 \
--tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=production-vpc},{Key=Environment,Value=production}]' \
--query 'Vpc.VpcId' --output text)
# Enable DNS support and hostnames
aws ec2 modify-vpc-attribute --vpc-id $VPC_ID --enable-dns-support '{"Value":true}'
aws ec2 modify-vpc-attribute --vpc-id $VPC_ID --enable-dns-hostnames '{"Value":true}'
# Create public subnets
PUB_SUB_A=$(aws ec2 create-subnet \
--vpc-id $VPC_ID \
--cidr-block 10.0.1.0/24 \
--availability-zone us-east-1a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=public-a},{Key=Tier,Value=public}]' \
--query 'Subnet.SubnetId' --output text)
PUB_SUB_B=$(aws ec2 create-subnet \
--vpc-id $VPC_ID \
--cidr-block 10.0.2.0/24 \
--availability-zone us-east-1b \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=public-b},{Key=Tier,Value=public}]' \
--query 'Subnet.SubnetId' --output text)
# Enable auto-assign public IP on public subnets
aws ec2 modify-subnet-attribute --subnet-id $PUB_SUB_A --map-public-ip-on-launch
aws ec2 modify-subnet-attribute --subnet-id $PUB_SUB_B --map-public-ip-on-launch
# Create private subnets
PRIV_SUB_A=$(aws ec2 create-subnet \
--vpc-id $VPC_ID \
--cidr-block 10.0.11.0/24 \
--availability-zone us-east-1a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=private-a},{Key=Tier,Value=private}]' \
--query 'Subnet.SubnetId' --output text)
PRIV_SUB_B=$(aws ec2 create-subnet \
--vpc-id $VPC_ID \
--cidr-block 10.0.12.0/24 \
--availability-zone us-east-1b \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=private-b},{Key=Tier,Value=private}]' \
--query 'Subnet.SubnetId' --output text)
# Create data subnets (isolated)
DATA_SUB_A=$(aws ec2 create-subnet \
--vpc-id $VPC_ID \
--cidr-block 10.0.21.0/24 \
--availability-zone us-east-1a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=data-a},{Key=Tier,Value=data}]' \
--query 'Subnet.SubnetId' --output text)
DATA_SUB_B=$(aws ec2 create-subnet \
--vpc-id $VPC_ID \
--cidr-block 10.0.22.0/24 \
--availability-zone us-east-1b \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=data-b},{Key=Tier,Value=data}]' \
--query 'Subnet.SubnetId' --output text)
Internet Gateway 和 NAT Gateway
# Create and attach Internet Gateway
IGW_ID=$(aws ec2 create-internet-gateway \
--tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=production-igw}]' \
--query 'InternetGateway.InternetGatewayId' --output text)
aws ec2 attach-internet-gateway --vpc-id $VPC_ID --internet-gateway-id $IGW_ID
# Create public route table
PUB_RT=$(aws ec2 create-route-table \
--vpc-id $VPC_ID \
--tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=public-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_SUB_A
aws ec2 associate-route-table --route-table-id $PUB_RT --subnet-id $PUB_SUB_B
# Allocate Elastic IPs for NAT Gateways (one per AZ for HA)
EIP_A=$(aws ec2 allocate-address --domain vpc --query 'AllocationId' --output text)
EIP_B=$(aws ec2 allocate-address --domain vpc --query 'AllocationId' --output text)
# Create NAT Gateways in public subnets
NAT_A=$(aws ec2 create-nat-gateway \
--subnet-id $PUB_SUB_A \
--allocation-id $EIP_A \
--tag-specifications 'ResourceType=natgateway,Tags=[{Key=Name,Value=nat-a}]' \
--query 'NatGateway.NatGatewayId' --output text)
NAT_B=$(aws ec2 create-nat-gateway \
--subnet-id $PUB_SUB_B \
--allocation-id $EIP_B \
--tag-specifications 'ResourceType=natgateway,Tags=[{Key=Name,Value=nat-b}]' \
--query 'NatGateway.NatGatewayId' --output text)
# Wait for NAT Gateways
aws ec2 wait nat-gateway-available --nat-gateway-ids $NAT_A $NAT_B
# Create private route tables (one per AZ for HA NAT)
PRIV_RT_A=$(aws ec2 create-route-table \
--vpc-id $VPC_ID \
--tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=private-rt-a}]' \
--query 'RouteTable.RouteTableId' --output text)
aws ec2 create-route --route-table-id $PRIV_RT_A --destination-cidr-block 0.0.0.0/0 --nat-gateway-id $NAT_A
aws ec2 associate-route-table --route-table-id $PRIV_RT_A --subnet-id $PRIV_SUB_A
PRIV_RT_B=$(aws ec2 create-route-table \
--vpc-id $VPC_ID \
--tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=private-rt-b}]' \
--query 'RouteTable.RouteTableId' --output text)
aws ec2 create-route --route-table-id $PRIV_RT_B --destination-cidr-block 0.0.0.0/0 --nat-gateway-id $NAT_B
aws ec2 associate-route-table --route-table-id $PRIV_RT_B --subnet-id $PRIV_SUB_B
安全组
# ALB security group (public-facing)
ALB_SG=$(aws ec2 create-security-group \
--group-name alb-sg \
--description "Application Load Balancer" \
--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
# Application security group (only from ALB)
APP_SG=$(aws ec2 create-security-group \
--group-name app-sg \
--description "Application tier" \
--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
# Database security group (only from app tier)
DB_SG=$(aws ec2 create-security-group \
--group-name db-sg \
--description "Database tier" \
--vpc-id $VPC_ID \
--query 'GroupId' --output text)
aws ec2 authorize-security-group-ingress \
--group-id $DB_SG \
--protocol tcp \
--port 5432 \
--source-group $APP_SG
# List all security groups in the VPC
aws ec2 describe-security-groups \
--filters "Name=vpc-id,Values=$VPC_ID" \
--query "SecurityGroups[].{Name:GroupName,ID:GroupId,Description:Description}" \
--output table
VPC 端点(私下访问 AWS 服务)
# Gateway endpoint for S3 (free, route-table based)
aws ec2 create-vpc-endpoint \
--vpc-id $VPC_ID \
--service-name com.amazonaws.us-east-1.s3 \
--route-table-ids $PRIV_RT_A $PRIV_RT_B \
--tag-specifications 'ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=s3-endpoint}]'
# Gateway endpoint for DynamoDB (free)
aws ec2 create-vpc-endpoint \
--vpc-id $VPC_ID \
--service-name com.amazonaws.us-east-1.dynamodb \
--route-table-ids $PRIV_RT_A $PRIV_RT_B
# Interface endpoint for Secrets Manager (ENI-based, has hourly cost)
aws ec2 create-vpc-endpoint \
--vpc-id $VPC_ID \
--vpc-endpoint-type Interface \
--service-name com.amazonaws.us-east-1.secretsmanager \
--subnet-ids $PRIV_SUB_A $PRIV_SUB_B \
--security-group-ids $APP_SG \
--private-dns-enabled \
--tag-specifications 'ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=secretsmanager-endpoint}]'
VPC 流日志
# Enable VPC flow logs to CloudWatch
aws ec2 create-flow-log \
--resource-type VPC \
--resource-ids $VPC_ID \
--traffic-type ALL \
--log-destination-type cloud-watch-logs \
--log-group-name /vpc/production-flow-logs \
--deliver-logs-permission-arn arn:aws:iam::123456789012:role/VPCFlowLogRole \
--max-aggregation-interval 60 \
--tag-specifications 'ResourceType=vpc-flow-log,Tags=[{Key=Name,Value=production-flow-log}]'
# Enable VPC flow logs to S3 (cheaper for long-term storage)
aws ec2 create-flow-log \
--resource-type VPC \
--resource-ids $VPC_ID \
--traffic-type ALL \
--log-destination-type s3 \
--log-destination arn:aws:s3:::my-flow-logs-bucket/vpc-logs/ \
--max-aggregation-interval 60
VPC 对等连接
# Request peering connection
PEERING_ID=$(aws ec2 create-vpc-peering-connection \
--vpc-id vpc-requester \
--peer-vpc-id vpc-accepter \
--peer-owner-id 987654321098 \
--peer-region us-west-2 \
--tag-specifications 'ResourceType=vpc-peering-connection,Tags=[{Key=Name,Value=prod-to-shared}]' \
--query 'VpcPeeringConnection.VpcPeeringConnectionId' --output text)
# Accept peering (from the accepter account/region)
aws ec2 accept-vpc-peering-connection --vpc-peering-connection-id $PEERING_ID
# Add routes in both VPCs
aws ec2 create-route --route-table-id rtb-requester --destination-cidr-block 10.1.0.0/16 --vpc-peering-connection-id $PEERING_ID
aws ec2 create-route --route-table-id rtb-accepter --destination-cidr-block 10.0.0.0/16 --vpc-peering-connection-id $PEERING_ID
Terraform VPC 模块
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "production-vpc"
Environment = "production"
}
}
resource "aws_subnet" "public" {
count = 3
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index + 1)
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = {
Name = "public-${data.aws_availability_zones.available.names[count.index]}"
Tier = "public"
}
}
resource "aws_subnet" "private" {
count = 3
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index + 11)
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "private-${data.aws_availability_zones.available.names[count.index]}"
Tier = "private"
}
}
resource "aws_subnet" "data" {
count = 3
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index + 21)
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "data-${data.aws_availability_zones.available.names[count.index]}"
Tier = "data"
}
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = { Name = "production-igw" }
}
resource "aws_eip" "nat" {
count = 2
domain = "vpc"
tags = { Name = "nat-eip-${count.index}" }
}
resource "aws_nat_gateway" "main" {
count = 2
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = { Name = "nat-${count.index}" }
}
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
}
tags = { Name = "public-rt" }
}
resource "aws_route_table_association" "public" {
count = 3
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table" "private" {
count = 2
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main[count.index].id
}
tags = { Name = "private-rt-${count.index}" }
}
resource "aws_route_table_association" "private" {
count = 2
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private[count.index].id
}
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${data.aws_region.current.name}.s3"
route_table_ids = aws_route_table.private[*].id
tags = { Name = "s3-endpoint" }
}
resource "aws_flow_log" "main" {
vpc_id = aws_vpc.main.id
traffic_type = "ALL"
log_destination_type = "s3"
log_destination = "${aws_s3_bucket.flow_logs.arn}/vpc-logs/"
max_aggregation_interval = 60
}
故障排查
| 问题 | 原因 | 修复方法 |
|---|---|---|
| 无法从私有子网访问互联网 | 缺少 NAT Gateway 路由 | 在私有路由表中添加指向 NAT GW 的 0.0.0.0/0 路由 |
| 无法从公有子网访问互联网 | 未附加 IGW 或缺少路由 | 附加 IGW;在公有 RT 中添加指向 IGW 的 0.0.0.0/0 路由 |
| EC2 无法访问 S3 | 没有 VPC 端点或 NAT | 添加 S3 网关端点(免费),或确保存在 NAT GW 路由 |
| 安全组规则未生效 | 方向错误(入站与出站) | SG 是有状态的;检查目标上的入站规则 |
| NACL 阻止流量 | NACL 是无状态的;需要配置两个方向 | 添加匹配的入站和出站规则,并配置正确的端口 |
| VPC 对等连接仅单向可用 | 一个 VPC 中缺少路由 | 在两个 VPC 的路由表中都添加路由 |
| DNS 解析失败 | VPC 未启用 DNS 主机名 | 在 VPC 上启用 enableDnsHostnames |
| NAT Gateway 费用过高 | 所有 AZ 都通过一个 NAT 路由 | 每个 AZ 部署一个 NAT GW,并使用独立路由表 |
| 跨 AZ 数据传输费用 | 不同 AZ 中的资源相互通信 | 将紧密耦合的服务部署在同一个 AZ 中 |
相关技能
- aws-ec2 - 部署在 VPC 子网中的实例
- aws-ecs-fargate - 使用 VPC 网络的 ECS 任务
- aws-rds - 数据库子网组
- terraform-aws - 用于 VPC 基础设施的 IaC
- firewall-config - 网络安全控制
评论
支持 Markdown