三层子网实战:公网 ALB、私网应用与隔离数据库
验证:未记录
- 适用版本
- 未记录
- 验证范围
- 尚未登记验证记录。
目标:部署一个双可用区订单系统,实际完成订单写入、异步账本与查询,并验证公网入口、NAT 出口和数据库隔离。复用企业订单工程,不是返回 Hello World 的连通性演示。
1. 先记住这张规划表#
VPC(虚拟私有云)使用 10.64.0.0/18,地址范围为 10.64.0.0–10.64.63.255。本例仅配置 IPv4。部署前与公司、VPN(虚拟专用网络)、其他云及灾备网段检查冲突。
| 层 | AZ-A 子网 | AZ-B 子网 | 放置资源 | 路由 |
|---|---|---|---|---|
| 公网入口 | 10.64.9.0/24 | 10.64.25.0/24 | ALB、各 AZ 的 Public NAT Gateway | local;0.0.0.0/0 → IGW |
| 私网应用 | 10.64.0.0/21 | 10.64.16.0/21 | 每 AZ 一台 EC2,各运行 API + worker | local;0.0.0.0/0 → 同 AZ NAT |
| 隔离数据 | 10.64.8.0/24 | 10.64.24.0/24 | RDS PostgreSQL Multi-AZ 主备 | 只有 local,无互联网默认路由 |
AZ = Availability Zone,可用区;ALB = Application Load Balancer,应用负载均衡器;EC2 为云服务器;RDS 为托管关系数据库;API 为应用程序接口;IGW 为互联网网关;NAT 为网络地址转换。预留第三段 32–63 等未分配空间,不创建额外子网。
客户端 → IGW → 公网 ALB:443 → local → 私网 EC2:8080 → local → RDS:5432
客户端 ← IGW ← ALB ← EC2 响应(不走 NAT)
EC2 主动出网 → 同 AZ 的 NAT → IGW → 公网服务只有 IGW attach 到 VPC;本例的 zonal NAT 创建在公网子网,应用通过路由表引用它。 应用和数据库靠 local 路由互通,不需要内部 ALB;数据子网没有 NAT 路由,不代表阻断内网。
| SG(Security Group,安全组) | 入站 | 出站 |
|---|---|---|
| ALB SG | 批准的测试出口 CIDR → 443 | 应用 SG → 8080 |
| 应用 SG | ALB SG → 8080;不开 SSH | 数据库 SG → 5432;HTTPS 443 → 公网 |
| 数据库 SG | 应用 SG → 5432 | 无新增连接放行;已跟踪连接回包允许 |
本例保留默认允许型 NACL(网络访问控制列表),把访问控制集中到 SG;企业有自定义 NACL 时按流量路径加入业务和回包规则,不能只开放 443/5432。应用 443 出站用于镜像、运维和秘密服务;生产域名级白名单需出口代理/防火墙,SG 无法按域名过滤。
2. 准备代码与环境#
在隔离预生产账号执行;会创建两个 NAT、ALB、两台 EC2 和 Multi-AZ RDS,按实际运行计费。需要 Terraform ≥1.6、AWS CLI v2、Session Manager plugin、Docker、Python 3.12+、jq,以及目标区域已签发的 ACM(证书管理服务)证书和测试域名。
短期部署身份需要创建本例资源和受限 iam:PassRole 权限;初始化身份还需 RDS 主凭据的 GetSecretValue、运行秘密的 PutSecretValue,以及批准的 SSM(Systems Manager,系统管理)会话/端口转发权限。EC2 角色只读运行秘密,不授予主密码权限;主密码由 RDS 托管,秘密值不进入 Terraform。RDS 托管密码
从本站仓库根目录执行,演练目录放在仓库外:
export SOURCE_ROOT="$PWD"
export LAB_DIR="$HOME/three-tier-lab"
mkdir -m 700 "$LAB_DIR" # 使用不存在的新目录
cp static/examples/aws-three-tier/* "$LAB_DIR/"
cd "$LAB_DIR"
python3 -m venv .venv
.venv/bin/pip install --require-hashes -r "$SOURCE_ROOT/static/examples/enterprise-orders/requirements.txt"配套文件:main.tf、user-data、初始化脚本、部署脚本、systemd 服务。
使用已通过企业订单工程业务测试及扫描的 linux/amd64 镜像,将其推送到本账号、本区域的 ECR(容器镜像仓库),记录 digest;不要使用 latest。若已有合格镜像,跳过以下发布步骤:
export AWS_REGION='us-east-1' # 后续所有命令与配置保持一致
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export ECR_REPOSITORY='three-tier-orders' # 本例专属的新仓库名
aws ecr create-repository --repository-name "$ECR_REPOSITORY" --image-tag-mutability IMMUTABLE --image-scanning-configuration scanOnPush=true
export ECR_URI="${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPOSITORY}"
# local-approved:release 必须是前述已验收的 amd64 镜像。
docker image inspect local-approved:release --format '{{.Architecture}}'
aws ecr get-login-password | docker login --username AWS --password-stdin "${ECR_URI%%/*}"
docker tag local-approved:release "$ECR_URI:release-1"
docker push "$ECR_URI:release-1"
aws ecr describe-images --repository-name "$ECR_REPOSITORY" --image-ids imageTag=release-1 --query 'imageDetails[0].imageDigest' --output text3. 部署网络、计算和数据库#
保存 terraform.tfvars,替换所有输入。AMI(机器镜像)必须是已批准的 Amazon Linux 2023 x86_64,内置 Python 3、AWS CLI v2 和 SSM Agent;EC2 不分配公网 IP,安装 Docker/连接 SSM 通过 NAT 的 HTTPS 出口。SSM 网络要求
account_id = "REPLACE_ACCOUNT_ID"
region = "us-east-1"
name = "orders-network-lab"
azs = ["us-east-1a", "us-east-1b"]
ami_id = "REPLACE_APPROVED_AL2023_AMI"
certificate_arn = "REPLACE_SAME_REGION_ACM_CERTIFICATE_ARN"
client_cidr = "REPLACE_APPROVED_CLIENT_PUBLIC_IP/32"
orders_image = "REPLACE_ECR_URI@sha256:REPLACE_64_HEX_DIGEST"
ecr_repository_arn = "REPLACE_ECR_REPOSITORY_ARN"
db_engine_version = "REPLACE_APPROVED_POSTGRES_17_MINOR"
final_snapshot_id = "orders-network-lab-final-REPLACE_UNIQUE_SUFFIX"aws sts get-caller-identity
terraform init
terraform fmt -check
terraform validate
terraform plan -out=create.tfplan
# 核对账号、CIDR、两 AZ、数据库不公开、无应用公网 IP 后执行。
terraform apply create.tfplan
terraform output -json lab > outputs.json
curl --fail --proto '=https' --tlsv1.2 https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem -o rds-ca.pem独立演练可使用本地 state,目录权限保持 700,不提交 Git;团队共享时改用组织批准的加密远程 backend。ALB 此时目标不健康是预期状态,尚未初始化业务。把证书覆盖的测试域名 CNAME/Route 53 Alias 指向输出的 alb_dns,不要用不匹配证书的 ALB 域名跳过 TLS 校验。
4. 初始化真实订单数据与运行身份#
先在控制台确认两个 EC2 已成为 SSM managed node;使用 Session Manager 进入各主机,运行 sudo cloud-init status --wait,要求完成且无错误。EC2 启动模板只安装运行环境,不含密码。
终端 A 建立受控隧道,保持会话运行:
cd "$LAB_DIR"
export INSTANCE_ID=$(jq -r '.instances | to_entries[0].value' outputs.json)
export DB_HOST=$(jq -r .db_host outputs.json)
jq -n --arg host "$DB_HOST" '{host:[$host],portNumber:["5432"],localPortNumber:["15432"]}' > tunnel.json
aws ssm start-session --target "$INSTANCE_ID" --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters file://tunnel.json终端 B 设置相同的 LAB_DIR、SOURCE_ROOT、区域和短期身份,再执行:
cd "$LAB_DIR"
.venv/bin/python bootstrap.py --outputs outputs.json --source "$SOURCE_ROOT/static/examples/enterprise-orders" --secure-dir "$LAB_DIR/secrets" --ca rds-ca.pem脚本通过本地隧道连接,但以真实 RDS hostname 验证 TLS 证书;创建业务表、最小权限 API/worker 角色及两个合成租户 token,将运行凭据写入 Secrets Manager。它拒绝已初始化数据库,避免重新生成密码造成身份不一致。若只在秘密上传阶段失败,确认 secrets/migration-complete 存在后,用原 runtime.json 重试 put-secret-value,不要重新生成密码。
随后通过 SSM 在两台 EC2 分别执行:
sudo systemctl enable --now three-tier.service
sudo systemctl status three-tier.service --no-pager
sudo docker ps部署脚本使用实例短期身份获取指定秘密、拉取 digest 镜像,以非 root、只读根目录运行 API/worker;数据库强制 TLS,运行角色无 DDL(数据定义操作)权限。秘密文件保存在主机 /run 内存目录,重启由 systemd 重新获取。关闭初始化隧道,将本地恢复凭据转存企业秘密系统,不保留在普通共享目录。
5. 验收:三个层次分别证明#
cd "$LAB_DIR"
export BASE_URL='https://REPLACE_CERTIFIED_TEST_HOST'
export TOKEN_FILE="$LAB_DIR/secrets/token"
export OTHER_TOKEN_FILE="$LAB_DIR/secrets/other-token"
.venv/bin/python "$SOURCE_ROOT/static/examples/enterprise-orders/tests/acceptance.py"
aws elbv2 describe-target-health --target-group-arn "$(jq -r .target_group outputs.json)"
aws ec2 describe-route-tables --route-table-ids "$(jq -r .data_route_table outputs.json)"订单验收实际检查:16 个并发同幂等键请求只创建一笔订单、冲突返回 409、越权被拒绝、worker 写入账本和查询投影。两个 ALB 目标应健康;数据路由表只包含 VPC local 路由。
| 验收动作 | 预期 |
|---|---|
| EC2 通过 RDS 私有 endpoint 完成订单处理 | 证明应用层 → local → 数据层通信 |
经 SSM 在每台 EC2 执行 curl --max-time 10 https://checkip.amazonaws.com |
各自显示输出 nat_eips 中同 AZ 的 EIP;只发起普通 IP 查询 |
检查 EC2 无公网地址,RDS PubliclyAccessible=false |
业务入口由 ALB 提供;数据库无直接公网入口 |
| 检查数据库 SG 仅允许应用 SG:5432,数据表无 NAT/IGW 默认路由 | 证明隔离设计;不要以“能连数据库”替代隔离验收 |
一台 EC2 执行 sudo docker stop orders-api,等目标 unhealthy 后重跑订单验收 |
另一 AZ 继续处理;恢复 sudo docker start orders-api 并等健康 |
保持另一台健康时才演练;ALB 在全部目标 unhealthy 时可能 fail-open,不把健康检查当访问控制。单机停容器只验证应用副本故障,不等同于 AZ 故障或 RDS failover 测试。RDS 受 AWS 管理,无法登录它执行 curl;无公网默认路由针对客户业务网络,不意味着 AWS 无法进行托管备份/维护。
6. 清理与适用边界#
此案例交付真实网络、TLS、短期云身份、业务持久化与双 AZ 副本;EC2 使用固定实例,尚未包含 Auto Scaling 自动替换、WAF、集中审计和生产发布编排。上生产前接入现有平台能力,不把网络演练当成完整生产平台。
演练结束移除测试 DNS,结束 SSM 会话;确认凭据已转存和最终快照命名唯一。将 deletion_protection=false 写入 tfvars,先 plan/apply 关闭 ALB/RDS 删除保护,再 terraform plan -destroy -out=destroy.tfplan,核对只删除本例资源后 terraform apply destroy.tfplan。RDS 保留最终快照,运行秘密进入 7 天恢复期;快照、手工创建的 ECR 仓库/镜像仍需按保留策略清理,勿用全账号批量删除。
配套 Terraform 与脚本可本地校验;本次文档编写未连接真实 AWS 部署,实际验收以以上步骤产生的记录为准。