1_AWS核心服务实战技巧

1. 基础:Region / AZ / VPC

在 AWS 上跑应用,先把三个名词分清:Region(区域)、AZ(Availability Zone,可用区)、VPC(Virtual Private Cloud,虚拟私有云)。这三个词决定了你的资源在地球上的哪里、容灾级别多高、网络隔离边界划在哪。

环境准备:本文示例统一基准 Region 为 us-west-2(俄勒冈),CLI 命令均带 --region us-west-2。如果你刚开通账户,请先在 IAM 里建一个有 EC2/VPC 只读权限的子账户用于练习,避免直接用 root。

1.1 概念三件套:Region / AZ / VPC

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'primaryTextColor': '#1E3A5F', 'primaryBorderColor': '#2563EB', 'lineColor': '#60A5FA'}}}%%
flowchart TD
    subgraph Region ["Region(区域)"]
        direction TB
        subgraph AZ1 ["可用区 A"]
            DC1["数据中心 1"]
            DC2["数据中心 2"]
        end
        subgraph AZ2 ["可用区 B"]
            DC3["数据中心 3"]
            DC4["数据中心 4"]
        end
        subgraph AZ3 ["可用区 C"]
            DC5["数据中心 5"]
        end
    end

    AZ1 <--> |"低延迟 ≤2ms"| AZ2
    AZ2 <--> |"低延迟 ≤2ms"| AZ3

    classDef region fill:#DBEAFE,stroke:#2563EB,color:#1E3A5F
    classDef az fill:#D1FAE5,stroke:#059669,color:#065F46
    classDef dc fill:#FEF3C7,stroke:#D97706,color:#92400E

    class Region region
    class AZ1,AZ2,AZ3 az
    class DC1,DC2,DC3,DC4,DC5 dc
概念定义类比
Region(区域)AWS 在全球的物理地理区域(如 us-east-1ap-northeast-1城市
AZ(Availability Zone,可用区)Region 内物理隔离的数据中心集群,独立电力/冷却/网络城市内的独立园区
VPC(Virtual Private Cloud,虚拟私有云)Region 内逻辑隔离的私有网络,由你掌控 IP/子网/路由园区里你租下的那栋楼

一个 Region 通常含 3 个或以上 AZ;同 Region 内 AZ 之间走 AWS 自己的高带宽光纤,往返延迟一般 1-2 ms。VPC 是 Region 级资源——一个 VPC 不能跨 Region,但可以跨 AZ(其子网分别落在不同 AZ)。

1.2 Region 选择策略

入门最常见的纠结是「我该把服务部署在哪个 Region」。按下面四个维度排顺序基本不会错:

维度决策建议
延迟选离用户最近的 Region。中国大陆访问国外服务器,东京 ap-northeast-1 / 新加坡 ap-southeast-1 通常优于美西
合规涉及个人数据要符合当地法规(欧盟 GDPR、中国《个人信息保护法》、美国 HIPAA 等)时,必须选数据落地在合规辖区的 Region
灾备关键业务建议主备分别部署在两个 Region。主区与备区距离不要太近(如 us-east-1 + us-west-2 而非 us-east-1 + us-east-2)
成本同样的 EC2 机型在不同 Region 价格能差 10–30%。us-east-1(弗吉尼亚)通常最便宜,ap-northeast-1(东京)通常最贵

实操原则:先满足合规与延迟,再在剩余候选里挑成本最低的。

1.3 多可用区架构与成本

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6'}}}%%
flowchart TB
    subgraph VPC ["VPC: my-vpc (10.0.0.0/16)"]
        subgraph AZA ["us-east-1a"]
            PubA["public-subnet-a 10.0.1.0/24"]
            PriA["private-subnet-a 10.0.2.0/24"]
        end
        subgraph AZB ["us-east-1b"]
            PubB["public-subnet-b 10.0.3.0/24"]
            PriB["private-subnet-b 10.0.4.0/24"]
        end
    end

    PubA --> |"ELB, NAT GW"| PriA
    PubB --> |"ELB 节点"| PriB
    PriA --> |"EC2, RDS主"| PriB
    PriB --> |"EC2, RDS备"| PriA

    classDef pub fill:#DBEAFE,stroke:#2563EB
    classDef pri fill:#FEE2E2,stroke:#DC2626

    class PubA,PubB pub
    class PriA,PriB pri

💰 跨 AZ 流量:us-west-2 内同 Region 跨 AZ 流量 $0.01/GB——发送方和接收方各按出方向计一次费,所以一次 1 GB 的跨 AZ 调用,账单上是 $0.02。
来源:AWS EC2 Pricing API JSON (us-west-2)

AZ 间延迟 1–2 ms,对绝大多数应用可忽略。核心结论:跨 AZ 部署是性价比最高的高可用方案,单 AZ 故障不会拖垮服务。但要警惕大流量服务的跨 AZ 账单:EC2 ↔ RDS 主备频繁同步、应用层与缓存层不在同一 AZ,月底账单可能比想象的多一截。

1.4 VPC 子网三分法

把 VPC 内的子网按互联网可达性分成三类,是几乎所有生产架构的默认起手式:

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6'}}}%%
flowchart LR
    Internet((Internet))

    subgraph VPC ["VPC 10.0.0.0/16"]
        direction TB
        subgraph Pub ["公有子网 Public"]
            ALB["ALB / NAT GW / 堡垒机"]
        end
        subgraph Pri ["私有子网 Private"]
            APP["EC2 / ECS 应用层"]
        end
        subgraph Iso ["隔离子网 Isolated"]
            DB["RDS / ElastiCache 数据层"]
        end
    end

    Internet <--> |"IGW"| Pub
    Pub --> |"NAT GW"| Pri
    Pri --> Iso

    classDef pub fill:#DBEAFE,stroke:#2563EB,color:#1E3A5F
    classDef pri fill:#FEF3C7,stroke:#D97706,color:#92400E
    classDef iso fill:#FEE2E2,stroke:#DC2626,color:#7F1D1D

    class Pub,ALB pub
    class Pri,APP pri
    class Iso,DB iso
  • 公有子网(Public Subnet):路由表含 0.0.0.0/0 → IGW,子网内资源可双向访问公网。放对外暴露的组件:ALB(应用负载均衡器)、NAT Gateway、堡垒机。
  • 私有子网(Private Subnet):路由表含 0.0.0.0/0 → NAT GW,能访问公网(拉镜像、调外部 API),但公网无法主动连入。放业务应用层:EC2、ECS、Lambda 的 VPC ENI。
  • 隔离子网(Isolated Subnet):路由表无 0.0.0.0/0 默认路由,与公网完全断开。放数据层:RDS、ElastiCache、自建数据库。

为什么要这么分?两件事:最小爆破半径与安全分层。万一应用层被攻陷,攻击者也无法从私有子网直接 SSH 出去外联 C2 服务器(无入站路径);万一应用层 SSRF 漏洞被利用,隔离子网的数据库也连不上外网,无法把数据偷偷传出去。每多一层路由隔离,就多一道兜底防线。三分法是把网络风险降到合理水平的最小代价。

1.5 路由表与互联网网关

理清两个组件的关系就够了:

  • IGW(Internet Gateway,互联网网关):VPC 边界的「互联网门」,挂在 VPC 上,本身没有带宽限制也不收费
  • 路由表(Route Table):每个子网必须关联一个路由表;表里有 0.0.0.0/0 → igw-xxx 的子网就是公有子网

最小路由表示例(YAML 风格伪码,仅示意字段结构,非 CloudFormation/Terraform):

1
2
3
4
5
6
7
8
9
10
11
12
13
# 公有子网路由表
routes:
- destination: 10.0.0.0/16 # VPC 内部
target: local
- destination: 0.0.0.0/0 # 默认路由 → IGW
target: igw-0abc1234

# 私有子网路由表
routes:
- destination: 10.0.0.0/16
target: local
- destination: 0.0.0.0/0 # 默认路由 → NAT GW
target: nat-0def5678

判断一个子网是公有还是私有,只看它关联的路由表里 0.0.0.0/0 指向哪里,不看子网的名字。

1.6 Security Group Vs Network ACL

VPC 里有两层防火墙:

  • Security Group(SG,安全组):作用在实例网卡(ENI)
  • Network ACL(NACL,网络访问控制列表):作用在子网

新手最容易混淆两者,下面这张决策表:

维度Security GroupNetwork ACL
作用层级实例(ENI)子网
状态有状态(自动放回程)无状态(出入站都要规则)
规则类型仅 allowallow + deny
默认入站全拒绝默认 NACL 全允许;自建 NACL 全拒绝
评估顺序全部规则求并集按规则号从小到大,命中即返回
可引用对象SG / CIDR仅 CIDR
典型用途应用层细粒度访问控制子网级粗粒度黑名单

实操上,新手 90% 时候只用 SG 就够。SG 之间可以互相引用(如 ALB-SG 作为 EC2-SG 的 source),表达力强,且有状态省心。NACL 通常用于两种场景:阻挡某段恶意 IP(用 deny 规则)、合规要求子网级强制隔离。除此之外可以保持默认 NACL 全开。

1.7 查看 EC2 所在区域

EC2 是 Region 级资源,控制台必须切到对应 Region 才能看到实例——很多新手「实例消失了」的疑问,95% 是 Region 切错。AZ(如 us-west-2a)在 EC2 实例列表「可用区」一列直接展示。CLI 查询一行:

1
2
3
4
5
# 列出 us-west-2 下所有运行中实例的 ID、AZ、私网 IP
aws ec2 describe-instances --region us-west-2 \
--filters "Name=instance-state-name,Values=running" \
--query "Reservations[].Instances[].[InstanceId,Placement.AvailabilityZone,PrivateIpAddress]" \
--output table

💡 进阶 / IAM:可以用 IAM Condition 限制某个角色只能在指定 VPC 内创建 EC2 实例,避免误操作把资源开到别的网络里。示例:

1
2
3
4
5
"Condition": {
"StringEquals": {
"ec2:Vpc": "arn:aws:ec2:us-west-2:<account-id>:vpc/<vpc-id>"
}
}

把这段挂到 ec2:RunInstances 的 IAM 策略上,调用方若没指定该 VPC 就会被拒绝。生产账户里给开发人员做权限隔离时很常用。

1.8 常见踩坑

  1. 私有子网实例 apt update / yum update 卡死
    • 现象:从堡垒机 SSH 进私有子网的 EC2,跑 apt update 一直 hang,超时退出
    • 根因:私有子网的路由表里没有 0.0.0.0/0 → NAT GW 这条出口;或路由有但 NAT Gateway 还没建,或建在了别的 AZ 不在同一路由域
    • 处理:检查私有子网关联的路由表,确认默认路由指向 NAT GW;NAT GW 自身必须放在公有子网(它要走 IGW 出网)。NAT GW 与私有子网不强制同 AZ,但生产环境建议每 AZ 各部署一个独立 NAT GW,避免跨 AZ 流量费 + 单 AZ 故障影响
  2. 跨 AZ 部署后流量账单暴涨
    • 现象:上线多 AZ 架构后第一个月账单里 Regional Data Transfer 一项比预期高几倍
    • 根因:应用层与数据层、缓存层未就近部署在同 AZ;或 RDS Multi-AZ 主备频繁切换;或 ALB 的 Cross-Zone Load Balancing 把流量均匀打到所有 AZ 的后端,引起大量 ALB → EC2 的跨 AZ 流量
    • 处理:让应用 EC2 与对应 RDS 主实例尽量同 AZ;评估关闭 ALB Cross-Zone Load Balancing 或接受其代价;对内部高频通信链路(如应用 → 缓存)按 AZ 局部性部署
  3. SG 修改后旧 TCP 连接还在通
    • 现象:把某 IP 从 EC2-SG 入站规则里删掉,但已经登录的 SSH 会话仍然没断
    • 根因:SG 是有状态的,已建立连接(established)不受新规则影响,新规则只对后续新建连接生效
    • 处理:要立刻切断已有连接,需在实例端 kill 掉对应进程或重启 sshd;或在 NACL 加一条 deny 规则强制切断(NACL 无状态,对存量连接也生效)

VPC、子网、SG 是房子的骨架。骨架立好后,下一步是把外部流量引进来——这归 ELB 管。

2. 入站流量:ELB 与 HTTPS

ELB(Elastic Load Balancing,弹性负载均衡)是 AWS 应用入口的事实标准。它把外部流量分发给后端 EC2、容器或 Lambda,承担 HTTPS 终止、健康检查、跨 AZ 容灾。

2.1 三种 ELB 选型决策

类型OSI 层协议典型场景何时不选价格量级
ALB(Application Load Balancer)L7HTTP / HTTPS / gRPCWeb 应用、微服务路径/Host 路由、WebSocket、Lambda target极致低延迟、TCP 直通、需要静态入口 IP$0.0225/h + $0.008/LCU
NLB(Network Load Balancer)L4TCP / UDP / TLS高并发长连接、游戏、DB 代理、需要静态 IP(每 AZ 一个 EIP)、TLS 透传需要 HTTP 路由 / Host / Path$0.0225/h + $0.006/NLCU
CLB(Classic Load Balancer)L4 / L7HTTP / TCP仅老系统兼容新项目一律不选$0.025/h + $0.008/GB 数据处理

实操原则:99% 的新项目从 ALB 起手;只有当应用是纯 TCP/UDP(如游戏、自建 DB 代理)或要求静态入口 IP 时才考虑 NLB;CLB 已是历史遗留,新项目不要碰。

💰 ALB 计费:$0.0225/h + $0.008/LCU-h。LCU(Load Balancer Capacity Unit,负载均衡容量单元)按 4 维度取最高维计费:每秒新建连接 25、活动连接 3,000、处理流量 1 GB/h、规则评估 1,000/s。一个轻量 Web 应用通常每月 $20-30 起步。
来源:AWS ELB Pricing

2.2 ELB → Target Group → Targets 三层关系

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6'}}}%%
flowchart LR
    User["用户请求"] --> ALB["ALB"]
    ALB --> |"/api/*"| TG1["API Target Group"]
    ALB --> |"/web/*"| TG2["Web Target Group"]
    TG1 --> EC2A["EC2-API-1"]
    TG1 --> EC2B["EC2-API-2"]
    TG2 --> EC2C["EC2-Web-1"]
    TG2 --> EC2D["EC2-Web-2"]

    classDef alb fill:#3B82F6,stroke:#2563EB,color:#fff
    classDef tg fill:#10B981,stroke:#059669,color:#fff
    classDef ec2 fill:#F59E0B,stroke:#D97706,color:#fff

    class ALB alb
    class TG1,TG2 tg
    class EC2A,EC2B,EC2C,EC2D ec2

ALB 不直连 EC2,而是通过 Target Group(目标组)解耦。一个 ALB 可以根据路径或 Host 把流量分给不同 Target Group;同一个 Target Group 也可以同时挂在多个 ALB 上。Target Group 内部按健康检查筛选可用 target,再按算法(轮询 / 最少未完成请求)分发流量。这层解耦让蓝绿发布、按路径拆服务、共享后端实例等场景都很自然——只换 Target Group 的成员,不动 ALB 本身。

2.3 Target Group 与健康检查

Target Group 配置示例(YAML 风格伪码,仅示意字段结构,非 CloudFormation/Terraform):

1
2
3
4
5
6
7
8
9
10
11
12
13
target_type: "IP addresses"  # 或 instance(按实例 ID)/ lambda
protocol: HTTP
port: 80
vpc: vpc-0abc1234 # 占位符:你的 VPC ID
health_check:
path: /readiness # 业务侧专门的健康检查端点,避免触发副作用
protocol: HTTP
port: traffic-port # 与业务端口同
matcher: 200-299 # 期望的状态码范围
interval_seconds: 30 # 检查间隔
timeout_seconds: 5 # 单次检查超时
healthy_threshold: 2 # 连续 2 次成功 → 健康
unhealthy_threshold: 2 # 连续 2 次失败 → 摘除

健康检查关键决策:

  • path 指向轻量、不触发副作用的端点(如 /healthz /readiness),不要用 /——首页可能很重或带统计副作用,把 ALB 的探活流量误算进 PV
  • matcher 默认 200,业务有 302 / 204 等正常响应时记得放宽到 200-299 或显式列出
  • interval × unhealthy_threshold ≈ 故障摘除时间。30 s × 2 = 60 s 是合理起步;过短会被瞬时抖动误摘,过长则故障实例久久无法剔除
  • 负载均衡算法:轮询(Round Robin,默认)/ 最少未完成请求(Least Outstanding Requests);后者更适合后端处理时长差异大的场景

2.4 创建 ALB

监听器(Listener)配置示例(YAML 风格伪码,仅示意字段结构,非 CloudFormation/Terraform):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
scheme: internet-facing      # 或 internal(仅 VPC 内可达)
ip_type: ipv4
subnets:
- subnet-public-a # 必须至少 2 个不同 AZ 的公有子网
- subnet-public-b
security_group: sg-alb # ALB 自身的 SG(详见 2.6)
listeners:
- port: 80
default_action:
type: redirect # 强制重定向到 HTTPS
target: HTTPS:443
- port: 443
protocol: HTTPS
ssl_policy: ELBSecurityPolicy-TLS13-1-2-2021-06
certificate_arn: arn:aws:acm:us-west-2:<account-id>:certificate/<cert-id>
default_action:
type: forward
target_group_arn: arn:aws:elasticloadbalancing:us-west-2:<account-id>:targetgroup/web-tg

CLI 创建 ALB(前置:已有 VPC、跨 AZ 公有子网、ALB 安全组、Target Group;已配置 awscli,IAM 拥有 elasticloadbalancing:CreateLoadBalancer 权限):

1
2
3
4
5
6
7
aws elbv2 create-load-balancer \
--name web-alb \
--type application \
--scheme internet-facing \
--subnets subnet-pub-a subnet-pub-b \
--security-groups sg-alb \
--region us-west-2

2.5 HTTPS 与 ACM 证书

最常见的模式:浏览器 ↔ ALB 走 HTTPS(TLS 1.3),ALB 在边界处终止 TLS,转给后端 EC2 走 HTTP(VPC 内)。理由:

  • 证书集中在 ALB 管理,无需在每台 EC2 配置和续签
  • TLS 解密耗 CPU,由 ALB 集中处理,后端 EC2 节省算力
  • VPC 内部网络已是受信环境(AWS 物理隔离 + 你自己的 SG / NACL 控制),HTTP 足够
  • 后端只需监听 80 端口,配置最简

ACM(AWS Certificate Manager,证书管理器)申请要点:

  1. 在 us-west-2(与 ALB 同 Region)申请——ACM 证书是 Region 级资源,跨 Region 不可用。例外:CloudFront 必须用 us-east-1,详见第 6 章
  2. 推荐 DNS 验证(自动续订)而非邮箱验证(每年要手动确认,错过即过期)
  3. 公有证书对接 ALB / CloudFront / API Gateway 等 AWS 服务完全免费

💰 ACM 公有证书:免费(仅在对接 AWS 集成服务时;可导出证书 $7/年起,通配符 $79/年)。
来源:AWS Certificate Manager Pricing

2.6 安全组最佳实践:SG 引用而非 CIDR

新手常见做法:把 EC2 SG 入站 80 端口的 source 设为 0.0.0.0/0,让 ALB 来连。这意味着任何人只要拿到 EC2 公网 IP 就能直接连后端,绕过了 ALB——健康检查、HTTPS、WAF 全部失效。

正确做法:把 EC2 SG 入站 source 设为 ALB 的 SG(SG 互引用,AWS 原生支持,下方 YAML 风格伪码仅示意字段结构):

1
2
3
4
5
6
7
8
9
10
11
12
# ALB SG
sg-alb:
inbound:
- { protocol: tcp, port: 443, source: 0.0.0.0/0 } # 公网用户
- { protocol: tcp, port: 80, source: 0.0.0.0/0 } # 公网用户(重定向用)
outbound: all

# EC2 SG(关键:source 写 sg-alb 而不是 CIDR)
sg-ec2:
inbound:
- { protocol: tcp, port: 80, source: sg-alb } # 仅允许从 ALB 连入
outbound: all

效果:即使 EC2 有公网 IP,外部也无法绕过 ALB 直连——因为 EC2 SG 入站只放行了 ALB SG 成员。这是 VPC 内部「零信任」模式的最小落地,也是新手到生产实践的关键一步。

💡 进阶 / 成本:ALB 的 LCU 4 个维度按最高维计费——意味着如果你的应用规则评估很多(如 50+ 条 host / path 规则),即使流量小也会被「规则评估」维度顶到很高的 LCU。省钱方向:合并通配符规则、把高频路由放前面(前 10 条规则免费)、对长尾业务考虑用单独的 ALB 隔离 LCU 计费、避免在单条规则里塞过多条件触发额外评估。

2.7 常见踩坑

  1. 健康检查一直 unhealthy 但服务能访问

    • 现象:浏览器访问 ALB 域名能正常打开页面,但 Target Group 一直显示所有 target unhealthy
    • 根因:EC2 SG 入站 source 写的是 CIDR 而不是 sg-alb,且 ALB 节点 IP 段没加进来;或健康检查路径返回 302 / 204,但 matcher 默认只接受 200
    • 处理:EC2 SG 入站 source 改为 sg-alb;放宽 matcher 为 200-299 或显式列出 200,302;确认健康检查路径在 EC2 本地 curl localhost/<path> 能拿到预期状态码
  2. ACM 证书在 ALB 列表里看不到

    • 现象:明明在 ACM 控制台看到证书已签发,但绑定 ALB 时下拉框找不到
    • 根因:证书申请时选错了 Region(如在 ap-northeast-1 申请,但 ALB 在 us-west-2)
    • 处理:删掉旧证书,在与 ALB 同 Region(us-west-2)重新申请——ACM 证书不能跨 Region 复制;CloudFront 例外,必须 us-east-1
  3. 80 端口直接 forward 而非 redirect 到 443

    • 现象:用户访问 http://example.com 没有被强制升级到 HTTPS,浏览器地址栏显示「不安全」
    • 根因:80 端口监听器的默认 action 选了 forward 到 Target Group,而非 redirect 到 HTTPS:443
    • 处理:在 ALB 80 端口监听器上把默认 action 改为 redirect → HTTPS:443,状态码用 HTTP_301(永久重定向,便于浏览器与搜索引擎缓存)

入口流量解决了,下一个问题是出口:私有子网的实例怎么访问公网,又不开放公网入站?

3. 出站流量:NAT 与 VPC Endpoint

私有子网(Private Subnet)里的 EC2 想拉镜像、调外部 API、装系统包,需要一条到公网的出口,但又不能让公网直接连进来。AWS 给了三条路:NAT Gateway(NAT 网关)、NAT Instance(自建 NAT EC2)、VPC Endpoint(VPC 终端节点)。三者价格、场景、维护代价差别很大。

3.1 私网出公网的三条路

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6'}}}%%
flowchart LR
    EC2["私有子网 EC2"]
    NATGW["NAT Gateway<br/>(公有子网)"]
    NATIns["NAT Instance<br/>(公有子网)"]
    VPCE["VPC Endpoint"]
    IGW["Internet Gateway"]
    Backbone["AWS 内部骨干网"]
    Internet((Internet))
    AWSSvc["S3 / DynamoDB<br/>SQS / KMS 等"]

    EC2 --> |"路径 1"| NATGW
    EC2 --> |"路径 2"| NATIns
    EC2 --> |"路径 3"| VPCE
    NATGW --> IGW
    NATIns --> IGW
    IGW --> Internet
    VPCE --> Backbone
    Backbone --> AWSSvc

    classDef ec2 fill:#FEF3C7,stroke:#D97706,color:#92400E
    classDef gw fill:#DBEAFE,stroke:#2563EB,color:#1E3A5F
    classDef ok fill:#D1FAE5,stroke:#059669,color:#065F46
    classDef bad fill:#FEE2E2,stroke:#DC2626,color:#7F1D1D

    class EC2 ec2
    class NATGW,IGW,Backbone gw
    class VPCE,AWSSvc ok
    class NATIns bad

何时用哪条:

  • 想访问任意公网(GitHub、外部 API、包管理器仓库):生产用 NAT GW;测试/极小流量场景才考虑 NAT Instance
  • 只想访问特定 AWS 服务(S3、DynamoDB、SQS、KMS、Secrets Manager 等):VPC Endpoint,省钱且延迟低,流量不出 AWS 骨干网

3.2 NAT Gateway 计费拆解

NAT GW 是出站流量的「按表收费的水龙头」,计费分两段:小时费 + 数据处理费。

💰 NAT Gateway:按小时 $0.045 + 按数据处理 $0.045/GB(us-west-2)。一个 100 GB/天流量的服务每月账单 ≈ $32.4(小时费 30×24×0.045)+ $135(数据处理 100×30×0.045)= 约 $167/月。
来源:AWS VPC Pricing

注意:NAT GW 的数据处理费是叠加在出向公网流量费之上。EC2 → 互联网 1 GB,账单上是 $0.045(NAT 处理)+ $0.09(互联网出向)= $0.135/GB。这是新手最容易忽略的「双重收费」。

💰 互联网出方向流量(叠加在 NAT 数据处理费之上):us-west-2 出向公网前 10 TB $0.09/GB,next 40 TB $0.085/GB,next 100 TB $0.07/GB,>150 TB $0.05/GB。每月有 100 GB 免费额度(聚合所有 AWS 服务和 Region)。
来源:AWS Data Transfer Pricing API JSON

3.3 NAT Gateway Vs NAT Instance

维度NAT Gateway(托管)NAT Instance(自建 EC2)
管理AWS 全托管,无需打补丁自己运维 EC2
高可用单 AZ 内冗余;多 AZ 需各部署一个需手动配置 + Auto Scaling
吞吐量弹性 5–100 Gbps受所选实例规格限制
小时成本$0.045/h + $0.045/GB 数据处理仅 EC2 实例费(如 t3.nano $0.0052/h)
运维成本几乎为零高(OS 升级、Source/Dest Check 配置)
适用99% 生产场景仅极低流量 / 学习实验

实操原则:生产一律用 NAT Gateway。NAT Instance 看起来更便宜,但单点故障 + 自维护成本远超那点节省。

3.4 VPC Endpoint:Gateway Vs Interface

VPC Endpoint 让你直接走 AWS 内部骨干网访问 AWS 服务,不经过 NAT GW 和互联网。分两类:

类型支持服务计费工作方式
Gateway Endpoint仅 S3、DynamoDB免费在路由表加一条目标为 vpce-xxx 的路由
Interface Endpoint(PrivateLink)大部分 AWS 服务(SQS / SNS / KMS / Secrets Manager / ECR / Lambda 等)按小时(每 AZ 一个 ENI)+ 按数据处理 $0.01/GB(前 1 PB)在子网创建一个 ENI(弹性网卡,带私有 IP),通过私有 DNS 路由请求

💰 Interface Endpoint 数据处理:$0.01/GB(前 1 PB),$0.006/GB(next 4 PB),$0.004/GB(>5 PB)。小时费请以 AWS PrivateLink Pricing 为准(页面未明示具体小时费数字,建议用 AWS Pricing Calculator 校验)。
来源:AWS PrivateLink Pricing

3.5 省钱场景:S3 / DynamoDB 走 Gateway Endpoint 绕过 NAT GW

假设你的应用频繁向 S3 上传日志,月流量 1 TB(1024 GB):

方案NAT GW 数据处理互联网出向Gateway Endpoint月成本(仅流量)
走 NAT GW1024 × $0.045 = $461024 × $0.09 = $92$138
走 Gateway Endpoint免费$0(NAT GW 小时费 $32.4 仍在)

仅靠一个免费 Gateway Endpoint,每月省 $138。实战要点:在路由表里给 S3/DynamoDB 加一条目标为 vpce-xxx 的路由,应用代码无需改动,SDK 走默认 endpoint 即可被路由命中。

💡 进阶 / 容灾:NAT Gateway 在单 AZ 内自动冗余,但单 AZ 故障时该 AZ 内私有子网会失去出网能力。生产建议:每个 AZ 部署一个独立 NAT GW,对应 AZ 的私有子网路由表指向本 AZ 的 NAT GW。这样既消除跨 AZ 故障传播,又避免 NAT GW 处理跨 AZ 流量产生额外的 $0.01/GB 跨 AZ 流量费。代价是 NAT GW 数量翻倍(小时费 × N AZ)——3 AZ 部署的小时费基线就从 $32.4 涨到 $97.2/月,需要权衡可用性与成本。

3.6 常见踩坑

  1. NAT GW 部署在私有子网导致无法工作

    • 现象:建好 NAT GW 后,私有子网实例仍然无法访问公网
    • 根因:NAT GW 必须放在公有子网(路由表含 0.0.0.0/0 → IGW),它本身需要走 IGW 才能联通互联网;放在私有子网等于让一个不能上网的人当你的网关
    • 处理:把 NAT GW 重建到公有子网;私有子网的路由表 0.0.0.0/0 → nat-xxx 不变
  2. Interface Endpoint 配了但 SDK 还是走公网

    • 现象:在 VPC 里加了 SQS 的 Interface Endpoint,但应用调 SQS 仍然走 NAT GW + 公网,账单上没看到流量下降
    • 根因:创建 Endpoint 时没勾选「启用私有 DNS」(Private DNS),SDK 仍然解析到公共域名 sqs.us-west-2.amazonaws.com → 命中默认路由走 NAT GW
    • 处理:编辑 Endpoint 启用 Private DNS;或在 SDK 里把 endpoint URL 显式指向 vpce-xxx-xxx.sqs.us-west-2.vpce.amazonaws.com
  3. 跨 AZ NAT GW 流量费暴涨

    • 现象:单一 NAT GW 服务多个 AZ 的私有子网,月底账单跨 AZ 流量费比预期高很多
    • 根因:NAT GW 在 AZ-a,私有子网 b、c 的实例出网时流量先跨 AZ 到 NAT GW($0.01/GB)再走 NAT GW 出向($0.045/GB + $0.09/GB),账单上有三段费用
    • 处理:每 AZ 独立部署一个 NAT GW,路由表按 AZ 隔离指向本 AZ 的 NAT GW,消除跨 AZ 流量费

入口、出口、子网都画好了,接下来该把真正跑应用的「机器」放进去——EC2。

4. 计算:EC2 实例与存储

EC2(Elastic Compute Cloud,弹性计算云)是 AWS 上跑应用的根基——Web、数据库、批处理最终都落到一台 EC2 上。这一章按「选机型 → 启动方式 → 安全 → 自动化 → 远程登录 → 磁盘」六块展开。

4.1 实例族速查

族系类型典型场景代表机型特点
T(Burstable)突发型低流量 Web、开发测试、CI Runnert3.medium、t4g.mediumCPU 积分制,闲时积累、忙时消耗
M(General)通用型Web 应用、微服务、中等数据库m6i.large、m7g.largeCPU/内存均衡(1:4)
C(Compute)计算优化视频编码、批量计算、HPCc7g.large、c7i.largeCPU 强、内存少(1:2)
R(Memory)内存优化内存数据库、缓存、Sparkr6i.large、r7g.large内存大(1:8)
X(High Memory)大内存SAP HANA、超大内存数据库x2iezn.metal内存极大(>1 TB)
G/P(GPU)GPU 计算机器学习、图形渲染g5.xlarge、p4d.24xlargeNVIDIA GPU
I(Storage)存储优化NoSQL、数据仓库i4i.large本地 NVMe SSD

带 g 后缀的是 ARM (Graviton):性价比比 Intel/AMD 同级别高 20-40%,绝大多数 Linux 应用可无痛迁移。

💰 EC2 实例按需价(us-west-2 Linux):t3.medium $0.0416/h(约 $30/月),m6i.large $0.0960/h(约 $69/月),c7g.large $0.0725/h(约 $52/月),r6i.large $0.1260/h(约 $91/月)。
来源:AWS EC2 On-Demand Pricing API JSON (us-west-2)

实操原则:先按内存/CPU 比例选族系(默认起手 M 族),再按性价比选代际(新代际优先 Graviton)。机型不必一步到位,跑稳几周后用 CloudWatch 看实际利用率再纵向调整。

4.2 AMI 与 Launch Template

AMI(Amazon Machine Image,亚马逊机器镜像)= 操作系统 + 预装软件 + 启动配置的快照。AWS 官方提供 Amazon Linux 2023、Ubuntu、Windows 等基础镜像;你也可以从一个跑好的 EC2 制作自定义 AMI。

Launch Template(启动模板)= AMI + 实例类型 + SG + IAM Role + user-data 等所有启动参数的封装,是 Auto Scaling Group(第 9 章)的前置基础。

为什么不直接「Launch Instance」?

  • Launch Template 可版本化(v1 → v2 → vN),方便回滚
  • ASG 必须基于 Launch Template 才能批量起实例
  • 团队协作时一份模板共享,避免每人手动凑参数出错

CLI 创建 Launch Template(前置:已有 AMI ID、SG、IAM Instance Profile,IAM 拥有 ec2:CreateLaunchTemplate 权限):

1
2
3
4
5
6
7
8
9
10
11
aws ec2 create-launch-template \
--launch-template-name web-app-tpl \
--version-description "v1: nginx + app" \
--launch-template-data '{
"ImageId": "ami-0abcdef1234567890",
"InstanceType": "t3.medium",
"SecurityGroupIds": ["sg-0123456789abcdef0"],
"IamInstanceProfile": {"Name": "ec2-app-role"},
"UserData": "<base64-encoded user-data script>"
}' \
--region us-west-2

4.3 IMDSv2:把 SSRF 攻击堵死

IMDS(Instance Metadata Service,实例元数据服务)让 EC2 实例可以访问 http://169.254.169.254/ 拿到自己的元数据,包括临时 IAM 凭证。这意味着任何能在实例上发起 HTTP 请求的代码都能拿到凭证——典型威胁就是 SSRF(Server-Side Request Forgery,服务端请求伪造)。

Capital One 2019 年那次 1.06 亿用户数据泄露就是这条链:WAF 规则配错 → 攻击者发起 SSRF → 访问 IMDS 拿到 EC2 凭证 → 用这把 “ 钥匙 “ 读 S3。

IMDSv2 的解决方案是引入会话 token:调用方先 PUT 一个请求拿 token,再带 token GET 元数据。绝大多数 SSRF payload 只能发 GET,无法构造 PUT,从根上断了路径。

强制实例只允许 IMDSv2(前置:实例已存在;IAM 拥有 ec2:ModifyInstanceMetadataOptions 权限):

1
2
3
4
5
aws ec2 modify-instance-metadata-options \
--instance-id i-0123456789abcdef0 \
--http-tokens required \
--http-put-response-hop-limit 1 \
--region us-west-2

hop-limit 1 防止容器(多一跳)拿到元数据;如果你跑的是 ECS/EKS,按官方文档设为 2。新建实例时通过 Launch Template 默认就强制 IMDSv2,是最佳实践。

4.4 user-data:开机自动化

user-data 是 EC2 第一次启动时自动执行的脚本(root 权限),用来装软件、拉应用、写配置:

1
2
3
4
5
6
7
#!/bin/bash
# 前置:实例已附带可访问 S3 的 IAM Role
yum update -y
yum install -y nginx
aws s3 cp s3://my-app-bucket/app.tar.gz /tmp/
tar -xzf /tmp/app.tar.gz -C /opt/app/
systemctl enable --now nginx

把这段脚本放进 Launch Template 的 UserData 字段(base64 编码),新起实例自动执行——配合 ASG,30 秒内一台新机器就能加入服务。

注意:user-data 默认只在首次启动执行;后续重启不会再跑。如果你需要每次启动都执行,配合 cloud-init 的 cloud-init-per 或 systemd unit 实现。

4.5 Session Manager:不开 22 端口的远程登录

传统 SSH 模式的痛点:必须给实例开 22 端口入站、管理 SSH key 分发、堡垒机维护成本。Session Manager(AWS Systems Manager 的功能)让你无需开任何端口就能登录实例。前置:实例已附带 AmazonSSMManagedInstanceCore 策略,且 SSM Agent 在跑(Amazon Linux 2 起默认安装)。

1
aws ssm start-session --target i-0123456789abcdef0 --region us-west-2

工作原理:实例上的 SSM Agent 主动出站连到 SSM 服务(走 443),形成长连接。你的 CLI 通过 SSM API 把会话流量隧道过去。

安全收益:

  • EC2 SG 完全可以不开 22 端口——零网络入站攻击面
  • 不用管理 SSH key
  • 所有会话被 CloudTrail 记录,操作可审计
  • 可以基于 IAM 策略授权 “ 只能登录某些 tag 的实例 “

额外好处:私有子网实例不需要堡垒机也能登录(Agent 走 NAT GW 或 Interface Endpoint 出站连 SSM)。

4.6 磁盘扩容三步走

EBS 卷扩容是新手最常踩的 “ 操作分两层 “ 问题:先在 AWS 控制面把卷扩大,再在 OS 内部把分区和文件系统扩大。三步走(前置:实例正在运行;IAM 拥有 ec2:ModifyVolume 权限):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Step 1: AWS 控制面扩卷
aws ec2 modify-volume \
--volume-id vol-0abcdef1234567890 \
--size 100 \
--region us-west-2

# 等待状态变为 optimizing 或 completed
aws ec2 describe-volumes-modifications \
--volume-id vol-0abcdef1234567890 \
--region us-west-2

# Step 2: 在实例内扩展分区
sudo growpart /dev/nvme0n1 1 # 假设根分区是 /dev/nvme0n1p1
df -hT # 看一下文件系统类型

# Step 3: 扩展文件系统
sudo resize2fs /dev/nvme0n1p1 # ext4
# 如果是 XFS(Amazon Linux 2023 默认):
sudo xfs_growfs /

只做 Step 1 不做 Step 2/3 的后果df -h 看到磁盘还是原来大小,AWS 已经在多收费。

4.7 EBS 类型决策

类型介质基准 IOPS基准 Throughput价格量级典型场景
gp3(推荐通用)SSD3,000(可加)125 MiB/s(可加)$0.08/GB-月 + IOPS/Throughput 加成Web 应用、中小数据库
gp2(旧通用)SSD与卷大小挂钩(3 IOPS/GB)与卷大小挂钩$0.10/GB- 月仅历史遗留,新建一律选 gp3
io2(高性能)SSD自定义(最高 256k)自定义$0.125/GB-月 + $0.065/IOPS- 月大型生产数据库、低延迟关键应用
st1(吞吐型 HDD)HDD较低$0.045/GB-月大文件顺序读写:日志、大数据
sc1(冷归档 HDD)HDD极低较低$0.015/GB- 月极少访问的归档

💰 EBS 价格:gp3 $0.08/GB-月 + $0.005/IOPS- 月(超出 3,000)+ $0.06/MB·s-月(超出 125);io2 $0.125/GB- 月 + $0.065/IOPS-月(前 32k IOPS);st1 $0.045/GB- 月;sc1 $0.015/GB- 月。这些是页面 example 措辞中的数字,未明示 us-west-2,请以 AWS Pricing Calculator 校验。
来源:AWS EBS Pricing

实操原则:99% 新项目选 gp3——比 gp2 便宜 20%,且 IOPS/Throughput 可独立调高,不用「为了 IOPS 多买容量」。

4.8 IOPS 与 Throughput

指标定义类比影响
IOPS(Input/Output Per Second)每秒读写操作次数收费站每秒通过的车辆数响应速度、小文件并发处理能力
Throughput(吞吐量)每秒传输的数据量(MiB/s)高速公路的总车道宽度大文件传输效率

OLTP 数据库受 IOPS 制约(小事务多);视频处理 / 日志归档受 Throughput 制约(大文件少)。gp3 的优势就是两者可独立加量,不必通过加卷大小间接拉高。

4.9 快照 Vs AMI

概念内容用途
EBS 快照仅一块卷的数据数据备份、跨 Region 复制单卷
AMI实例所有卷的快照 + 启动配置(实例类型、ENA、卷映射)整机备份、克隆实例、分发到多账户

跨 Region 复制 AMI 一行命令(前置:源 Region 有目标 AMI;IAM 拥有 ec2:CopyImage 权限):

1
2
3
4
5
aws ec2 copy-image \
--source-region us-west-2 \
--source-image-id ami-0abcdef1234567890 \
--region us-east-1 \
--name "web-app-v1-copy"

跨 Region AMI 复制是异地容灾的常见做法:主区故障时,备区已有最新镜像可立即拉起。

💡 进阶 / 成本:On-Demand 是默认按小时按量付费,最贵;想长期省钱有两条路。Reserved Instances(RI,预留实例):承诺 1 年或 3 年用某机型,省 30-60%,但绑定机型;Savings Plans(储蓄计划):承诺每小时付 $X 用任意 EC2/Fargate/Lambda,更灵活,省 27-66%。起步建议:跑稳 3 个月、机型相对固定的工作负载,买 1 年期 Compute Savings Plan,立省 1/3 账单。

4.10 常见踩坑

  1. growpart 后 df -h 不变

    • 现象:执行了 aws ec2 modify-volume 把卷从 50 GB 扩到 100 GB,但实例里 df -h 仍显示 50 GB
    • 根因:只扩了 AWS 控制面的卷大小,没扩 OS 内的分区和文件系统
    • 处理:依次 growpart /dev/nvme0n1 1(扩分区)+ resize2fsxfs_growfs(扩文件系统)
  2. IMDSv1 被 SSRF 利用偷走凭证

    • 现象:应用日志里突然出现大量自己没发起的 S3 / DynamoDB 调用,账单异常
    • 根因:实例允许 IMDSv1,应用层有 SSRF 漏洞,攻击者通过它访问 IMDS 拿到 IAM Role 临时凭证后调 AWS API
    • 处理:立即 revoke 该 IAM Role 的临时凭证(IAM → 角色 → 撤销 active sessions);强制实例改 IMDSv2(--http-tokens required);新实例通过 Launch Template 默认强制 IMDSv2
  3. Launch Template 改了版本但 ASG 还在用旧版

    • 现象:更新了 Launch Template 到 v2,新拉的实例还是 v1 的镜像/配置
    • 根因:ASG 引用 Launch Template 时绑定了具体版本号(如 v1),不会自动跟随最新版
    • 处理:在 ASG 里把 Launch Template 版本改为 $Latest$Default;或显式更新 ASG 引用到 v2,再触发实例替换(rolling update)

EC2 跑起来了,但应用产生的图片、日志、备份不该堆在 EBS 上——那是对象存储 S3 的工作。

5. 对象存储:S3 进阶

S3(Simple Storage Service)是 AWS 最古老、使用率最高的服务,几乎每个账户都开了一个 bucket(桶)。但「会用 S3」和「用好 S3」差距很大——存储类、生命周期、桶策略/IAM/ACL、签名 URL、私有桶 + CloudFront 每一项都有讲究。这一章按「省钱 → 安全 → 性能」展开。

5.1 6 种存储类决策

存储类$/GB-月(us-west-2 首阶梯)最小存储期最小对象大小取回延迟典型场景
Standard$0.023ms 级热数据:网站静态资源、应用上传文件
Standard-IA(Infrequent Access,低频访问)$0.012530 天128 KBms 级月级访问:备份、日志归档
One Zone-IA$0.0130 天128 KBms 级可重生的冷数据(容灾仍要 Standard-IA)
Glacier Instant Retrieval(即时取回)$0.00490 天128 KBms 级季度访问:医疗影像、新闻档案
Glacier Flexible Retrieval(灵活取回)$0.003690 天128 KB分钟–小时备份归档:年访问几次
Glacier Deep Archive(深度归档)$0.00099180 天128 KB12 小时合规归档:7-10 年留存

💰 S3 存储类:从 Standard ($0.023/GB-月) 到 Glacier Deep Archive ($0.00099/GB- 月),价差最高 23 倍。1 TB 数据从 Standard 转到 Deep Archive,月省约 $22。
来源:AWS S3 Pricing API JSON

实操原则:

  • 写完后 30 天内会再访问的:留 Standard
  • 写完后多半不会再读的:转 Standard-IA
  • 仅合规留存、几乎不会读的:转 Glacier Flexible 或 Deep Archive
  • 用生命周期规则自动转储(见 5.3),不手动搬

5.2 基础操作:cp Vs Sync

1
2
3
4
5
# 下载整个 path 下的对象
aws s3 sync s3://bucket-name/path/ . --region us-west-2

# 上传当前目录到 S3
aws s3 sync . s3://bucket-name/path/ --region us-west-2

cp vs sync 的关键区别:

命令行为适用场景
aws s3 cp无条件复制所有文件,直接覆盖单文件传输、强制重新上传
aws s3 sync仅复制新增/更新的文件;配合 --delete 删除目标多余文件目录同步、增量备份

新手最常踩:用 aws s3 cp 覆盖整个目录,把 100 MB 文件每次都重传一遍——浪费时间和钱。同步类操作一律 sync

5.3 生命周期规则

让 S3 按对象年龄自动转储/删除,是省钱主力工具。一份典型生命周期 JSON(30 天 → IA、90 天 → Glacier、365 天删除):

1
2
3
4
5
6
7
8
9
10
11
12
{
"Rules": [{
"ID": "logs-tiering",
"Filter": {"Prefix": "logs/"},
"Status": "Enabled",
"Transitions": [
{ "Days": 30, "StorageClass": "STANDARD_IA" },
{ "Days": 90, "StorageClass": "GLACIER" }
],
"Expiration": { "Days": 365 }
}]
}

应用方式:

1
2
3
4
aws s3api put-bucket-lifecycle-configuration \
--bucket my-app-logs \
--lifecycle-configuration file://lifecycle.json \
--region us-west-2

关键决策:

  • IA 类对小文件不划算——按最少 128 KB 计费,1 KB 文件存 IA 反而比 Standard 贵
  • Glacier 类有最小存储期:转入后不到期就删,仍按最小期付费
  • 复杂业务可用 Intelligent-Tiering(智能分层):S3 自动按访问模式分层,省心但有少量监控费

5.4 桶策略 Vs IAM 策略 Vs ACL

工具作用对象适合场景推荐度
IAM 策略IAM 用户 / 角色「我的应用能否读这个 bucket」——权限挂在主体上⭐⭐⭐ 默认首选
桶策略(Bucket Policy)桶本身「这个 bucket 能被谁访问」——跨账户访问、CloudFront OAC、公开静态站点⭐⭐⭐ 跨边界场景必用
ACL(Access Control List,访问控制列表)桶或对象历史遗留:单对象级控制⭐ 基本不要用,AWS 自己也在推荐关掉

实操:账户内部访问全用 IAM 策略;跨账户、对接 AWS 服务(CloudFront、Lambda)用桶策略;ACL 在创建桶时直接选「Bucket owner enforced」(桶所有者强制)禁用即可。

5.5 签名 URL:临时下发上传/下载凭证

签名 URL(Presigned URL,预签名 URL)= 一段带签名的临时链接,任何人拿到就能访问对应对象,不需要 AWS 凭证。常见用途:让前端直传文件到 S3,不经过你的服务器中转。

生成上传签名 URL(Go SDK v2 示例):

1
2
3
4
5
6
7
8
// 前置:已配置 AWS 凭证;s 是包含 *s3.PresignClient 的 service struct
obj, err := s.preSignClient.PresignPutObject(ctx, &s3.PutObjectInput{
Bucket: &bucket,
Key: &key,
}, func(po *s3.PresignOptions) {
po.Expires = 15 * time.Minute // 时效 15 分钟
})
// obj.URL 就是签名 URL,前端拿到后用 PUT 直传

生成下载签名 URL:

1
2
3
4
5
6
obj, err := s.preSignClient.PresignGetObject(ctx, &s3.GetObjectInput{
Bucket: &bucket,
Key: &key,
}, func(po *s3.PresignOptions) {
po.Expires = 5 * time.Minute // 下载链接通常更短
})

安全要点:

  • 时效尽量短(5-15 分钟);越长泄露风险越大
  • 不要把签名 URL 写日志(默认 SDK 不写,但自定义 logging 中间件可能会)
  • 私密文件下载建议每次访问重新签发而非缓存

5.6 S3 Transfer Acceleration(加速模式)

Transfer Acceleration(传输加速)把上传/下载流量经过 CloudFront 边缘节点中转,给跨地域大文件传输加速:

1
2
3
4
5
6
7
8
obj, err := s.preSignClient.PresignPutObject(ctx, &s3.PutObjectInput{
Bucket: &bucket,
Key: &key,
}, func(po *s3.PresignOptions) {
po.ClientOptions = append(po.ClientOptions, func(o *s3.Options) {
o.UseAccelerate = true
})
})

启用顺序:先在桶上启用 Transfer Acceleration → 再上线代码(避免代码调用未启用的桶报错)。

关闭顺序:先下线代码 → 再关闭桶上的 Transfer Acceleration。

加速本身要额外收费(约 $0.04/GB),收益要看实际地理跨度。经验法则:用户与桶不同大陆、单文件 > 100 MB 时,加速明显;同 Region 内部上传不用开。

5.7 私有桶 + CloudFront OAC

让桶完全 Block all public access(屏蔽所有公开访问),但允许 CloudFront 通过 OAC(Origin Access Control,源访问控制)访问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "cloudfront.amazonaws.com" },
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-bucket/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::<account-id>:distribution/<distribution-id>"
}
}
}]
}

OAC 是 OAI(Origin Access Identity,源访问身份,旧版)的替代品,2022 年起推荐使用。用 OAC 后:

  • 桶可以彻底关闭公网访问
  • CloudFront 边缘节点用 SigV4(AWS 签名版本 4)签名访问 S3,安全且统计完整
  • Condition 限定来源 distribution 防止「借桥」(其他 CloudFront 分发借用你的桶)

💡 进阶 / 安全:开启桶版本控制(Versioning)后,每次覆盖或删除对象都保留旧版本,可恢复误操作。配合 MFA Delete(多因素认证删除,要求删除版本时通过 MFA 二次验证),即使 IAM 凭证泄露,攻击者也无法永久删除数据。代价:旧版本占存储空间,需配生命周期规则自动清理 N 天前的旧版本。

5.8 常见踩坑

  1. S3 OAC 配错策略导致 CloudFront 返回 403

    • 现象:私有桶 + CloudFront 部署后,访问 CDN 域名一直 403
    • 根因:桶策略 Condition 的 SourceArn 写错(distribution ID 不对、ARN 格式错误),或忘了把 Block all public access 中「阻止公开桶策略」那项放开
    • 处理:核对 distribution ID;Block public access 4 项里只勾「阻止 ACL 公开」和「阻止公开 ACL」,让「通过桶策略授权」放行
  2. 生命周期转 IA 但小文件多反而更贵

    • 现象:开启 30 天 → IA 规则后,账单不降反升
    • 根因:bucket 里大量 < 128 KB 的小文件,IA 按 128 KB 起步计费 + 还有 $0.01/1000 次的转换 API 费
    • 处理:转 IA 前先用 S3 Storage Lens(存储分析)看文件大小分布;小文件多的桶不开 IA,或先做对象合并(如把零散日志打包成 Parquet)
  3. 签名 URL 时效设太长被泄露

    • 现象:用户反馈一个文件下载链接被陌生人下载;查日志发现签名 URL 过期时间 7 天
    • 根因:开发图省事把 Expires 设成 7×24×3600 秒;用户分享链接到群聊或被搜索引擎抓走
    • 处理:上传链接 ≤ 15 分钟、下载链接 ≤ 5 分钟;前端「下载」动作每次重新请求后端签发;敏感对象额外要求登录态校验

S3 解决了「存到哪」,下一步是「怎么让全球用户读得快」——CDN 上场。

6. 内容分发:CloudFront

CloudFront 是 AWS 的 CDN(Content Delivery Network,内容分发网络),把静态资源缓存到全球 600+ 边缘节点。用户从最近节点拿内容,既把首字节时间压到几十毫秒,也大幅降低源站(Origin)出向带宽费。

6.1 工作原理:用户 → 边缘节点 → 源站

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6'}}}%%
flowchart LR
    User["用户浏览器"] -->|"请求"| Edge["最近的 CloudFront<br/>边缘节点"]
    Edge -->|"缓存命中"| User
    Edge -.->|"缓存未命中"| Origin["源站<br/>(S3 / ALB / 自定义)"]
    Origin -.->|"返回内容"| Edge
    Edge -.->|"缓存 + 返回"| User

    classDef user fill:#FEF3C7,stroke:#D97706,color:#92400E
    classDef edge fill:#3B82F6,stroke:#2563EB,color:#fff
    classDef origin fill:#10B981,stroke:#059669,color:#fff

    class User user
    class Edge edge
    class Origin origin

DNS 把 CDN 域名解析到离用户最近的边缘节点(Edge Location)。命中时直接返回,未命中才回源拉取并顺带缓存到本节点。典型静态站点接入后缓存命中率(Cache Hit Ratio)通常 80-95%——源站只扛 5-20% 的请求,S3 出向流量、ALB LCU、EC2 CPU 都跟着下降。CloudFront 既加速响应,也省源站带宽费,是「内容能缓存」类应用的默认起手式。

6.2 缓存策略三件套

CloudFront 的「缓存键」(Cache Key)由三件套 Policy 共同决定,是命中率的关键:

Policy作用关键字段
Cache Policy(缓存策略)决定缓存键 + TTL(Time To Live,缓存有效期)是否包含 Query String、Header、Cookie;min/max/default TTL
Origin Request Policy(源请求策略)决定哪些信息转发到源站(不影响缓存键)Header、Query String、Cookie
Response Headers Policy(响应头策略)给响应加/改 HeaderCORS、Strict-Transport-SecurityCache-Control

默认起手三件套:

  • Cache Policy 选 AWS 托管的 Managed-CachingOptimized(不带 Cookie / Query String,TTL 24 h)——绝大多数静态资源够用
  • Origin Request Policy 默认即可
  • Response Headers Policy 加一个 SecurityHeadersPolicy(一键开 HSTS、X-Frame-Options 等安全头)

误区:把 Cache Policy 配成「转发所有 Cookie / All Headers」。缓存键会随每个用户的 sessionid / UA 爆炸膨胀,命中率骤降到接近 0——CloudFront 等于白开。Cookie / Header 转发只在确需个性化的资源(如登录后页面)开启,并用单独的 Cache Behavior 限定到特定路径。

6.3 域名绑定与 ACM 证书

cdn.example.com 绑到 CloudFront 分发的步骤:

  1. 在 us-east-1 申请 ACM 证书(注意不是 ALB 所在 Region,原因见 6.4)
  2. CloudFront 分发设置 → SSL/TLS → 选刚签发的证书
  3. 添加 Alternate domain name(CNAME 别名):cdn.example.com
  4. DNS 服务商创建 CNAME 记录:cdn.example.com → dxxxxxx.cloudfront.net
  5. 等几分钟全球边缘节点生效

CLI 验证证书已在 us-east-1 就绪(前置:已配置 awscli,IAM 拥有 acm:ListCertificates 权限):

1
2
3
# CloudFront 强制证书在 us-east-1,CLI 也必须显式指定该 Region
aws acm list-certificates --region us-east-1 \
--query 'CertificateSummaryList[?DomainName==`cdn.example.com`]'

6.4 为什么 CloudFront 证书必须在 Us-east-1

CloudFront 是全球服务:分发(Distribution)配置由 AWS 全球控制面统一管理,而控制面的物理实例就在 us-east-1(弗吉尼亚)。证书要被全球边缘节点统一拉取使用,就必须放在控制面所在的 ACM Region——也就是 us-east-1。其它 Region 的 ACM 证书 CloudFront 根本看不到。

对比:ALB 是 Region 级资源,证书必须与 ALB 同 Region。新手最容易混的两条路:

  • 应用域名(指向 ALB)→ 证书在 ALB 所在 Region(如 us-west-2)
  • CDN 域名(指向 CloudFront)→ 证书必须在 us-east-1

如果偷懒只在 us-east-1 申请一张,ALB 用不了——ACM 证书不能跨 Region 引用。生产实践通常是:同一个域名在 us-east-1 与 ALB 所在 Region 各申一张,DNS 验证共用同一组 CNAME 记录。

6.5 缓存失效(Invalidation)

更新源站资源后,需要强制让 CloudFront 重新回源拉取(不然全球边缘节点还在按 TTL 返回旧版本):

  1. 控制台:分发详情页 → Invalidations → Create Invalidation
  2. 输入路径:
    • 单个对象:/path/to/object.jpg
    • 整个目录:/static/css/*
    • 全部失效:/*(紧急情况用,慎用)

CLI 一键失效(前置:已配置 awscli,IAM 拥有 cloudfront:CreateInvalidation 权限):

1
2
3
4
5
# CloudFront 命令统一带 --region us-east-1
aws cloudfront create-invalidation \
--distribution-id E1ABCDEF12345 \
--paths "/static/*" "/index.html" \
--region us-east-1

💰 CloudFront 失效:每月前 1,000 路径免费,超出后 $0.005/路径(全球统一价)。频繁全量失效(/*)只算 1 个路径,但会让全球缓存命中率瞬间归零、源站压力瞬时暴涨——慎用。
来源:AWS CloudFront Pricing

💰 CloudFront 流量:北美 / 欧洲 edge 出向每月前 1 TB 免费(永久免费层,不限月份),超出后北美/欧洲约 $0.085/GB。HTTPS 请求每月前 1,000 万次免费,超出后 $0.0100 / 10,000 次。
来源:AWS CloudFront Pricing

最佳实践:版本化文件名替代失效。把 app.css 改成 app.v1.2.3.css——加版本号即新文件,永远不需要失效;CI/CD 构建时自动注入 hash(如 app.[contenthash].css),从根上免去失效操作。

6.6 CloudFront Functions Vs Lambda@Edge(边缘计算决策表)

CloudFront 支持两种边缘计算选项,差别巨大,选错会多花 100 倍钱

维度CloudFront FunctionsLambda@Edge
运行时JavaScript(轻量 V8 引擎)Node.js / Python
触发点viewer-request / viewer-response(2 个)viewer-request / viewer-response / origin-request / origin-response(4 个)
执行时长上限< 1 ms< 5 s(viewer 触发)/ 30 s(origin 触发)
内存2 MB128 – 10,240 MB
包大小10 KB1 – 50 MB
价格量级极低($0.10 / 百万次调用)较高(按 Lambda 标准 + 边缘溢价)
能否调外部 API / 网络 IO
典型用途URL 改写、Header 修改 / 加签、A/B 简单分流、SEO 跳转鉴权(JWT 验证)、ESI 拼装、调用外部 API、复杂路由、个性化响应

实操原则:能用 CloudFront Functions 做的就别用 Lambda@Edge——便宜 100×、延迟低 10×。只有需要调外部 API 或复杂逻辑(如 JWT 解析 + 远程鉴权)才上 Lambda@Edge。两者也可以配合:viewer-request 用 Functions 做轻量改写,origin-request 用 Lambda@Edge 做重逻辑。

💡 进阶 / 可观测:CloudFront 访问日志(Standard Logs)可写到 S3 桶,每条记录含 viewer IP、URI、状态码、传输字节、x-edge-result-type(缓存命中状态)等字段。配合 Athena(基于 S3 的 Serverless SQL 查询服务)一行 SQL 即可查「过去 1 小时 5xx 来源 Top 10」或「缓存命中率最低的 10 个 URL」。建表语句和典型查询模板见 AWS 官方指南。日志写入 S3 免费(仅占用存储费),Athena 查询按扫描数据量 $5/TB 计费,按日期分区可把单次查询压到几分钱。

6.7 常见踩坑

  1. 改了 S3 内容但 CDN 没失效,用户拿到旧版本

    • 现象:S3 里更新了 style.css,浏览器仍加载旧样式,强刷也无效
    • 根因:CloudFront 默认按 Cache Policy 的 TTL 缓存(Managed-CachingOptimized 默认 24 h),不会自动感知源站变化
    • 处理:发布流程加 aws cloudfront create-invalidation --paths /style.css --region us-east-1;更稳妥的方案是改用版本化文件名(style.v1.2.3.css),CI 构建时用 contenthash 自动生成
  2. Cache Policy 把 Cookie 全转发,命中率接近 0

    • 现象:开了 CloudFront 但源站流量几乎没降,命中率长期 < 5%
    • 根因:Cache Policy 配了「All Cookies」转发到缓存键,每个用户的 sessionid / _ga 不同 → 缓存键不同 → 资源几乎不被复用
    • 处理:改用 Managed-CachingOptimized(不含 Cookie / Header);某些路径确需个性化(如 /account/*)就单独建一个 Cache Behavior 仅对这些路径转发指定 Cookie
  3. 证书在 ap-northeast-1 申请,CloudFront 列表里看不到

    • 现象:在 us-east-1 之外的 Region 申请了 ACM 证书,CloudFront 域名设置下拉框找不到
    • 根因:CloudFront 强制要求证书在 us-east-1,跨 Region 不识别
    • 处理:在 us-east-1 重新申请同域名证书;DNS 验证 CNAME 可与原证书共用同一组记录

静态资源的分发讲完了,接下来是动态数据的家——关系数据库。

7. 关系数据库:RDS

RDS(Relational Database Service)是 AWS 托管的关系数据库。你只管 schema、SQL、索引;AWS 负责打补丁、备份、监控、主备切换这些「机房 DBA」的活。

7.1 引擎选择:MySQL / PostgreSQL / Aurora

引擎兼容性性能存储扩展典型场景起步成本
RDS MySQL原生 MySQL,社区生态最广中等手动加卷中小项目、与开源工具无缝db.t3.medium 单 AZ $0.068/h
RDS PostgreSQL原生 PostgreSQL中等手动加卷复杂查询、JSONB、地理空间同量级
Aurora MySQL/PG兼容 MySQL/PG高(约 RDS 3-5×)自动扩到 128 TB中大型生产、读写分离要求高比同规格 RDS 略高,请以 AWS Pricing Calculator 为准

💰 RDS MySQL(us-west-2):db.t3.medium 单 AZ $0.068/h(约 $49/月)、Multi-AZ $0.136/h(精确 2×);db.m6i.large 单 AZ $0.171/h(约 $123/月)、Multi-AZ $0.342/h;备份存储超出免费额度部分 $0.095/GB- 月。
来源:AWS RDS Pricing API JSON (us-west-2)

实操:个人 / 小项目用 RDS MySQL 或 PostgreSQL 起 t3 类;中大型生产关心 P99 延迟和读写分离用 Aurora。

7.2 创建数据库实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 前置:已有 VPC、隔离子网(数据层)、SG、Secrets Manager 中存好密码
aws rds create-db-instance \
--db-instance-identifier my-app-db \
--db-instance-class db.t3.medium \
--engine mysql \
--engine-version 8.0.35 \
--allocated-storage 100 \
--storage-type gp3 \
--multi-az \
--master-username admin \
--master-user-password '<from-secrets-manager>' \
--vpc-security-group-ids sg-rds \
--db-subnet-group-name rds-isolated-subnets \
--backup-retention-period 7 \
--region us-west-2

要点:选隔离子网(无 0.0.0.0/0);SG 入站只放 EC2/应用 SG 作 source(不开 CIDR);--multi-az 立即开同步副本;--backup-retention-period 7 自动备份保留 7 天(最长 35)。

7.3 参数组:修改 max_connections

RDS 实例的参数(max_connectionsinnodb_buffer_pool_size 等)通过参数组修改,不能直接 SET GLOBAL。完整流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Step 1 创建自定义参数组
aws rds create-db-parameter-group --db-parameter-group-name my-mysql-pg \
--db-parameter-group-family mysql8.0 --description "Custom MySQL 8.0" --region us-west-2

# Step 2 修改参数(dynamic 立即生效;static 需重启)
aws rds modify-db-parameter-group --db-parameter-group-name my-mysql-pg \
--parameters "ParameterName=max_connections,ParameterValue=500,ApplyMethod=immediate" --region us-west-2

# Step 3 关联到实例(首次关联需要重启)
aws rds modify-db-instance --db-instance-identifier my-app-db \
--db-parameter-group-name my-mysql-pg --apply-immediately --region us-west-2

# Step 4 重启使关联生效
aws rds reboot-db-instance --db-instance-identifier my-app-db --region us-west-2

参数分 dynamic(修改即生效)和 static(必须重启):max_connections 是 dynamic、innodb_log_file_size 是 static。

7.4 备份机制:自动备份 Vs 手动快照

类型触发保留期恢复粒度用途
自动备份(Automated)每天一次完整备份 + 持续事务日志1-35 天(实例删除后默认清除)PITR(Point-in-Time Recovery,时间点恢复)到任意秒日常容灾
手动快照(Manual Snapshot)用户主动触发永久(直到手动删除)仅快照那一刻升级/迁移前备份;长期归档

PITR 恢复示例:

1
2
3
4
aws rds restore-db-instance-to-point-in-time \
--source-db-instance-identifier my-app-db \
--target-db-instance-identifier my-app-db-restore \
--restore-time 2026-04-26T10:30:00Z --region us-west-2

关键:删除实例时默认会一并清掉自动备份;想保留得提前手动 snapshot。

7.5 Multi-AZ ≠ 只读副本

新手最常混淆这两个概念,它们解决的问题完全不同

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6'}}}%%
flowchart LR
    subgraph MultiAZ ["Multi-AZ:高可用"]
        AppA["应用"] --> Primary["主库 (AZ-a)<br/>读写"]
        Primary -.->|"同步复制"| Standby["备库 (AZ-b)<br/>不可读"]
    end

    subgraph ReadReplica ["只读副本:读写分离"]
        AppB["应用"] -->|"写"| Master["主库"]
        AppB -->|"读"| RR1["只读副本 1"]
        AppB -->|"读"| RR2["只读副本 2"]
        Master -.->|"异步复制"| RR1
        Master -.->|"异步复制"| RR2
    end

    classDef primary fill:#3B82F6,stroke:#2563EB,color:#fff
    classDef standby fill:#FEE2E2,stroke:#DC2626,color:#7F1D1D
    classDef rr fill:#10B981,stroke:#059669,color:#fff
    classDef app fill:#FEF3C7,stroke:#D97706,color:#92400E

    class Primary,Master primary
    class Standby standby
    class RR1,RR2 rr
    class AppA,AppB app
维度Multi-AZ只读副本(Read Replica)
复制方式同步(强一致)异步(秒级延迟)
备库可读不可读(待命)可读(专门用来读)
故障切换自动 60-120 秒手动提升为主库
解决的问题高可用读写分离、降低主库压力
计费主备同价(约 2× 单 AZ)每个副本按实例费另算

典型生产架构:Multi-AZ + 多个只读副本——同时拿到高可用 + 读扩展。

7.6 只读副本与链式副本

1
2
3
4
aws rds create-db-instance-read-replica \
--db-instance-identifier my-app-db-rr1 \
--source-db-instance-identifier my-app-db \
--db-instance-class db.t3.medium --region us-west-2

链式副本(Chained Replication):从一个只读副本再起一个只读副本,形成级联结构。常见于跨 Region 容灾:主库 → 同 Region 副本 → 跨 Region 副本。

⚠️ 副本与主库共享相同用户名密码。若用 Secrets Manager 自动轮换密码,副本会自动同步。

7.7 Performance Insights:定位慢查询

Performance Insights 是 RDS 内置的性能分析工具:

  • Top SQL:按等待时间排名当前最耗资源的 SQL
  • 等待事件:CPU、IO、锁等各维度时间分布
  • 保留期:免费 7 天;扩展到 2 年另收费

启用方式:创建实例时勾选 “Enable Performance Insights”,或后续 modify-db-instance --enable-performance-insights。新手用法:进控制台 RDS → 实例 → Performance Insights,鼠标点点就能定位 “ 哪条 SQL 占了 60% CPU”。

7.8 用户管理(MySQL)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 创建读写用户
CREATE USER 'app_rw'@'%' IDENTIFIED BY 'secure_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON database_name.* TO 'app_rw'@'%';
GRANT CREATE, DROP, ALTER, INDEX ON database_name.* TO 'app_rw'@'%';
FLUSH PRIVILEGES;

-- 创建只读用户(给报表/BI 用)
CREATE USER 'app_ro'@'%' IDENTIFIED BY 'readonly_password';
GRANT SELECT ON database_name.* TO 'app_ro'@'%';
FLUSH PRIVILEGES;

-- 验证权限
SELECT User, Host FROM mysql.user WHERE User LIKE 'app_%';
SHOW GRANTS FOR 'app_rw'@'%';

最佳实践:master 账号只用来建库建用户,应用一律用业务账号;按 “ 读/写/管理 “ 分至少 3 套账号。

7.9 Secrets Manager 自动轮换

把数据库密码放 Secrets Manager 而非配置文件:

  • 应用启动时通过 SDK 拉密码(带缓存)
  • Secrets Manager 可配置每 30 天自动轮换:调用 Lambda 函数生成新密码 → 更新到 RDS → 更新到 Secret
  • 轮换期间新旧密码并存一段,靠 Secret 的 AWSPENDING / AWSCURRENT 阶段标识区分

只读副本与主库共享主用户名密码——开了自动轮换后,主库密码改了,副本自动同步,应用层无感。

💡 进阶 / 容灾:单 Region 故障会让 Multi-AZ 也失效。生产关键库建议开启跨 Region 自动备份(Cross-Region Automated Backups):备份会异步复制到指定的备 Region,主 Region 整体故障时可在备 Region 用备份恢复。代价:跨 Region 备份存储费 + 跨 Region 流量费,按 GB 计;RPO 通常分钟级(取决于复制延迟)。

7.10 常见踩坑

  1. 修改参数组但实例没重启,参数没生效

    • 现象:把 max_connections 从 100 改到 500,应用仍报 “too many connections”
    • 根因:dynamic 参数确实立即生效,但首次关联自定义参数组到实例这步本身需要重启;或修改的是 static 参数(如 innodb_log_file_size)必须重启
    • 处理:SHOW VARIABLES LIKE 'max_connections' 确认;若仍是旧值,reboot-db-instance
  2. 误把 Multi-AZ 当读写分离用

    • 现象:开了 Multi-AZ 想分流读流量到备库,结果连不上备库的 endpoint
    • 根因:Multi-AZ 备库不对外可读,仅作待命;只读副本才可读
    • 处理:开只读副本(Read Replica)做读写分离;Multi-AZ 解决的是 “AZ 故障自动切换 “
  3. 快照恢复后用户/参数组都没了

    • 现象:从快照恢复一个新实例,发现自定义用户、参数组关联全丢失
    • 根因:快照只包含数据;用户存在 mysql.user 表里随快照走,但参数组关联和 SG 关联是实例级配置不在快照里
    • 处理:恢复后立即 modify-db-instance 重新关联参数组、SG、备份保留期等;写到 IaC 模板里避免漏

主库扛得住事务,但顶不住高频热点读——这就需要在 RDS 前面再加一层缓存。

8. 缓存与文档库:ElastiCache & MongoDB Atlas

缓存层和 NoSQL 文档库是 Web 应用常见的两个「侧路」组件。缓存做快路径:把热点数据从 DB 抬到内存,挡掉 80% 的读。文档库做灵活存储:schema 多变、嵌套结构、迭代快的业务对象。AWS 自家用 ElastiCache 覆盖前者;但没有原汁原味的托管 MongoDB(DocumentDB 兼容性有限),需走 Marketplace 订阅 MongoDB Atlas。

8.1 ElastiCache 三种形态决策

ElastiCache 现在有三种部署形态,先选形态再选引擎:

形态自动扩缩数据分片适用备注
Serverless自动自动不想管节点;流量波动大按 ECPU + 数据存储计费
Cluster Mode Disabled手动单分片 + 副本小数据集(< 节点内存上限)、需 Redis 全部命令类似自建 Redis 主从
Cluster Mode Enabled手动多分片(slot 分布)大数据集、高吞吐客户端必须 cluster-aware

💰 ElastiCache:Serverless 按 ECPU $0.0023/百万次(Valkey/Redis OSS)+ 数据存储 $0.084/GB·h(页面 example,未明示 us-west-2)。节点形态按实例小时费 + 备份存储 $0.085/GiB- 月(页面明确)。具体小时费请以 AWS Pricing Calculator 为准。
来源:AWS ElastiCache Pricing

8.2 Redis Vs Memcached

形态选完,再选引擎。AWS ElastiCache 同时支持 Redis(含 Valkey 这个开源 fork)和 Memcached:

维度RedisMemcached
数据结构丰富(String/List/Hash/Set/Sorted Set/Stream/Geo)仅 String
持久化RDB / AOF 可选
主从复制原生支持无(多线程多核)
集群Cluster Mode客户端分片
多线程Redis 6+ I/O 多线程;命令仍单线程原生多线程
典型用途会话、计数器、排行榜、消息队列、限流纯读缓存(缓存 SQL 结果、HTML 片段)

实操:99% 新项目选 Redis——Memcached 只在 “ 我就要纯字符串多线程缓存 “ 的极致场景用。AWS 推荐用 Valkey(Redis 7.x 的开源 fork,与 Redis OSS 协议兼容,价格更低)。

8.3 ElastiCache Serverless 简介

Serverless 形态默认开启 Cluster Mode,数据自动分布到多个 AWS 内部分片:

  • 无需选实例类型、节点数、副本数
  • 容量自动扩缩,按实际使用计费
  • 内置高可用(多 AZ)

适合流量波动大、不想运维节点的项目。注意:Serverless 不支持某些 Redis 管理命令(如 CONFIG SET),如果应用代码强依赖这些命令,先在测试环境验证兼容性。

创建命令:

1
2
3
4
5
6
7
# 前置:已有 VPC、隔离子网、SG(开 6379 端口入站只放应用 SG)
aws elasticache create-serverless-cache \
--serverless-cache-name my-app-cache \
--engine valkey \
--subnet-ids subnet-iso-a subnet-iso-b \
--security-group-ids sg-cache \
--region us-west-2

8.4 MongoDB Atlas:通过 Marketplace 订阅

虽然 AWS 没有自家的 “ 托管 MongoDB”(DocumentDB 兼容性有限),但可以通过 AWS Marketplace 直接订阅 MongoDB 官方的 Atlas,账单合并到 AWS:

  1. AWS Marketplace 搜 “MongoDB Atlas (pay-as-you-go)” → Subscribe
  2. View purchase options → 完成账户关联(跳到 Atlas 完成 OAuth)
  3. 在 Atlas 控制台创建 Cluster:
    • 模式:Dedicated
    • 实例类型:M10(2 GB RAM, 2 vCPU),生产建议 M30+
    • 关闭:Global Writes、BI Connector(按需开)

好处:Atlas 账单按小时计入 AWS Bill,一处对账;MongoDB 官方支持比 DocumentDB 兼容性更全。

把应用 EC2 与 Atlas Cluster 私网联通的两种方式:

维度VPC PeeringPrivateLink
配置简单(双方接受请求即可)较复杂(需建 Endpoint Service)
流量方向双向单向(消费者 → 提供者)
跨账户支持支持
跨 Region支持(额外费)不支持
成本仅 AWS 跨 AZ / 跨 Region 流量费端点小时费 + 数据处理费
IP 重叠不能重叠可重叠(无需 CIDR 协调)
典型用途你的 VPC 与 Atlas Cluster VPC 直连多个独立 VPC 都要访问 Atlas,且不想协调 CIDR

实操推荐:起步用 VPC Peering——配置简单、成本低;只有当多个独立项目要访问同一 Atlas Cluster 且 CIDR 难以协调时再上 PrivateLink。

💡 进阶 / 成本:缓存层主要成本是内存。省钱思路:(1) 给所有 key 设合理的 TTL(如 SETEX user:123 600 ...),自然过期不占内存;(2) 配置内存淘汰策略 maxmemory-policy allkeys-lru,达到内存上限时自动淘汰最少使用的 key;(3) 大对象(> 1 MB)尽量序列化压缩或拆小存。反例:忘了 TTL,1 个月内存涨到爆,被迫升级实例规格——比一开始就配 TTL 贵几倍。

8.6 常见踩坑

  1. ElastiCache Cluster Mode Enabled 客户端没用 cluster-aware SDK

    • 现象:连 cluster 形态 Redis,调 GET 偶尔报 MOVED 错误,命令失败
    • 根因:cluster 模式下不同 key 分布在不同分片,普通 Redis 客户端只连一个节点,命中其它分片 key 时 Redis 返回 MOVED 重定向,但普通客户端不识别
    • 处理:用支持 cluster 的客户端(go-redis 的 ClusterClient、Lettuce 的 RedisClusterClient、ioredis 的 Redis.Cluster);或改用 Cluster Mode Disabled 形态
  2. Atlas 默认 0.0.0.0/0 IP 白名单忘改

    • 现象:Atlas Cluster 创建后任何 IP 都能连,被扫描器找到后字典攻击数据库
    • 根因:Atlas 创建向导默认 IP Access List 为 0.0.0.0/0(所有 IP),便于初始连接测试
    • 处理:建好后立即在 Atlas → Network Access 把 0.0.0.0/0 删掉,改为 VPC Peering 后自动生效的私有 CIDR;或精确白名单办公出口 IP
  3. VPC Peering 后两侧路由表都要加路由

    • 现象:Peering 状态显示 “Active”,但 EC2 ping 不通对端 VPC 内的 IP
    • 根因:Peering 只是建立 “ 关系 “,两侧 VPC 的路由表都需要手动加目标为对端 CIDR、target 为 pcx-xxx 的路由,否则流量不知道怎么走
    • 处理:在双方 VPC 的所有相关路由表里都加上对端 CIDR → pcx-xxx 的路由

数据层的容量与缓存解决了,回过头看应用层——固定数量的 EC2 撑不起波动的流量,下一章把它变弹性。

9. 弹性伸缩:Auto Scaling Group

流量随时间天然波动:白天高夜里低、周末高工作日低、活动瞬间冲峰。固定数量 EC2 只有两条糟糕选择:按峰值配,平时大量算力闲置;按均值配,高峰扛不住 5xx。Auto Scaling Group(ASG)让实例数量随指标自动伸缩,配合 ALB 既弹性又高可用。

9.1 ASG 三件套:Launch Template + ASG + Scaling Policy

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6'}}}%%
flowchart LR
    LT["Launch Template<br/>(AMI + 实例类型 + SG + IAM Role + user-data)"]
    ASG["Auto Scaling Group<br/>(min/desired/max + 子网 + 健康检查)"]
    SP["Scaling Policy<br/>(目标追踪 / 步进 / 计划)"]
    EC2["EC2 实例 × N"]

    LT --> ASG
    SP --> ASG
    ASG -->|"按需起/停"| EC2

    classDef lt fill:#FEF3C7,stroke:#D97706,color:#92400E
    classDef asg fill:#3B82F6,stroke:#2563EB,color:#fff
    classDef sp fill:#10B981,stroke:#059669,color:#fff
    classDef ec2 fill:#FEE2E2,stroke:#DC2626,color:#7F1D1D

    class LT lt
    class ASG asg
    class SP sp
    class EC2 ec2

简短解释三件套:

  • Launch Template 是 “ 实例长什么样 “ 的模板(详见第 4 章 4.2 节)
  • ASG(Auto Scaling Group)是 “ 我要 N 台实例分布在哪些子网 “ 的容器:min(最少)/ desired(期望)/ max(最多)
  • Scaling Policy 是 “ 什么时候改 desired” 的规则

ASG 自动维持 desired 数量的健康实例:有实例挂了自动起新的,触发扩容时自动起更多。

💰 ASG 本身不收费:只为底层 EC2 实例付费(按第 4 章实例族单价)。ASG 多起实例时,账单跟着加;缩到 0 时账单为 0(仅留 Launch Template / 空 ASG 不计费)。
来源:AWS Auto Scaling Pricing

9.2 与 ALB Target Group 自动注册

ASG 的关键价值之一:新起的实例自动注册到指定 Target Group,缩容时自动注销。这让 ALB 始终把流量指向当前活着的实例集合,无需人工运维。

配置方式:创建 ASG 时指定 --target-group-arns arn:...:targetgroup/my-tg。新实例 launch 后通过健康检查 → 自动加入 TG → 接收流量;scale-in 时反向流程。

9.3 扩缩策略选型

策略触发方式适用场景配置复杂度
Target Tracking(目标追踪)维持指标在目标值(如 CPU 平均 50%)通用首选——大部分应用⭐ 低
Step Scaling(步进)按指标阈值阶梯扩缩(CPU > 70% 加 2 台、> 90% 加 4 台)需对突发流量分级响应⭐⭐ 中
Scheduled(计划)按时间表(每天 9 点扩到 10 台、22 点缩到 2 台)已知流量模式(早高峰、定时批处理)⭐ 低
Predictive(预测)ML 预测未来 48 小时流量周期性强、ML 友好场景⭐⭐⭐ 高

实操原则:99% 项目用 Target Tracking——目标值设 50% 给突增留 buffer,简单稳健。Step Scaling 仅在已观察到 Target Tracking 反应不够及时时上。

9.4 EC2 健康检查 Vs ELB 健康检查

ASG 判定实例健康的两种方式:

类型检查内容失败后的动作
EC2 健康检查(默认)实例是否运行(pending / running / stopped 等)仅在实例本身停了才替换
ELB 健康检查ALB Target Group 健康检查的结果(应用响应是否健康)应用挂了即使实例还活着也会被替换

关键决策:生产强烈建议开 ELB 健康检查。典型场景:应用进程死了但 OS 还活着,EC2 检查觉得「实例 running 一切正常」;ELB 检查发现「应用 5xx」,触发实例替换。

CLI 启用:

1
2
3
4
5
aws autoscaling update-auto-scaling-group \
--auto-scaling-group-name my-app-asg \
--health-check-type ELB \
--health-check-grace-period 300 \
--region us-west-2

grace-period 300:实例新起后给应用 5 分钟冷启动时间再开始 ELB 健康检查,避免冷启动期被误判。

9.5 生命周期钩子:优雅上线与下线

生命周期钩子(Lifecycle Hook)让你在实例进入运行前或终止前插入自定义动作:

  • EC2_INSTANCE_LAUNCHING:新实例 pending 后、加入 TG 前——可用于预热缓存、注册到服务发现
  • EC2_INSTANCE_TERMINATING:缩容触发后、实例真正终止前——用于优雅下线(停接新连接、等老连接完成、上传日志)

典型用法:

1
2
3
4
5
6
7
aws autoscaling put-lifecycle-hook \
--lifecycle-hook-name shutdown-drain \
--auto-scaling-group-name my-app-asg \
--lifecycle-transition autoscaling:EC2_INSTANCE_TERMINATING \
--heartbeat-timeout 300 \
--default-result CONTINUE \
--region us-west-2

实例进入终止前会停在 Terminating:Wait 状态最多 5 分钟,期间应用可调 complete-lifecycle-action 主动告知 “ 我准备好了 “ 或自动 5 分钟后继续终止。

9.6 创建 ASG(CLI 示例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 前置:已有 Launch Template、跨 AZ 私有子网、Target Group
aws autoscaling create-auto-scaling-group \
--auto-scaling-group-name my-app-asg \
--launch-template "LaunchTemplateName=web-app-tpl,Version=\$Latest" \
--min-size 2 \
--desired-capacity 2 \
--max-size 10 \
--vpc-zone-identifier "subnet-pri-a,subnet-pri-b" \
--target-group-arns arn:aws:elasticloadbalancing:us-west-2:<account-id>:targetgroup/web-tg/abc123 \
--health-check-type ELB \
--health-check-grace-period 300 \
--region us-west-2

# 加一个 Target Tracking 策略(CPU 平均 50%)
aws autoscaling put-scaling-policy \
--auto-scaling-group-name my-app-asg \
--policy-name cpu-50 \
--policy-type TargetTrackingScaling \
--target-tracking-configuration '{
"PredefinedMetricSpecification": { "PredefinedMetricType": "ASGAverageCPUUtilization" },
"TargetValue": 50.0
}' \
--region us-west-2

注意 Version=\$Latest 让 ASG 自动跟随 Launch Template 最新版本(详见踩坑 3)。

💡 进阶 / 踩坑:扩容触发后从 “ 启动新实例 “ 到 “ 实例通过健康检查、加入 TG 接流量 “ 通常 2-5 分钟(拉镜像、跑 user-data、应用冷启动),这段时间叫冷启动延迟。瞬时大流量场景可能来不及响应。Warm Pool:预先起一批已完成 user-data 但停在 stopped 状态的实例,扩容时直接 start,把冷启动从分钟级降到秒级。代价是这些 stopped 实例仍按 EBS 存储付费。配置方式:aws autoscaling put-warm-pool --auto-scaling-group-name ... --max-group-prepared-capacity 5 --pool-state Stopped

9.7 常见踩坑

  1. 用 EC2 健康检查导致应用挂了但实例没替换

    • 现象:应用进程 OOM 退出几小时,ALB 5xx 暴增,但 ASG 没替换实例
    • 根因:默认 health-check-type 是 EC2,只看实例 running 状态;应用进程挂了实例还在 running,ASG 不动
    • 处理:update-auto-scaling-group --health-check-type ELB;同时把 ALB Target Group 的健康检查路径设为应用真实就绪端点(如 /readiness 检查依赖项)
  2. 缩容时丢失正在处理的请求

    • 现象:ASG 缩容后用户报 504 Gateway Timeout,日志显示部分请求处理一半被中断
    • 根因:实例直接 SIGKILL,没等 ALB 把流量切走;或缺生命周期钩子做 “ 优雅下线 “
    • 处理:开 EC2_INSTANCE_TERMINATING 钩子 + ALB Target Group 开 Connection Draining(默认 300 秒);应用监听 SIGTERM 信号,停接新请求等老请求完成再退出
  3. Launch Template 改了版本但 ASG 还引用旧版本

    • 现象:把 Launch Template 升到 v3(新 AMI、新 user-data),但 ASG 新起的实例还是 v2 的镜像
    • 根因:ASG 引用 Launch Template 时绑定了具体版本号 Version=2,不会自动跟随最新版
    • 处理:把 ASG 的 LaunchTemplate 引用改为 Version=$Latest;或显式 update-auto-scaling-group 更新到 v3,再触发 instance refresh(rolling update)

弹性伸缩之后,整套架构跑起来了。但跑得对不对、性能怎样、出错的地方在哪——这是最后一块基础设施:可观测性。

10. 可观测性:CloudWatch

CloudWatch 是 AWS 原生可观测三件套:Logs(日志)、Metrics(指标)、Alarms(告警)。跑生产应用的账号都该开起来——不开等于「应用挂了你才从用户工单里发现」。

10.1 三件套关系:Logs / Metrics / Alarms

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6'}}}%%
flowchart LR
    subgraph Sources ["数据源"]
        EC2["EC2 / ECS<br/>(CloudWatch Agent)"]
        ALB["ALB / RDS<br/>(自动发指标)"]
        App["应用代码<br/>(SDK 主动发)"]
    end

    Sources --> Logs["Logs<br/>(日志组 / 日志流)"]
    Sources --> Metrics["Metrics<br/>(数值时序)"]

    Logs -.->|"Logs Insights / EMF"| Metrics
    Metrics --> Alarms["Alarms<br/>(阈值 + 评估周期)"]
    Alarms --> SNS["SNS Topic"]
    SNS --> Email["邮件"]
    SNS --> Slack["Slack / Lambda"]

    classDef src fill:#FEF3C7,stroke:#D97706,color:#92400E
    classDef store fill:#3B82F6,stroke:#2563EB,color:#fff
    classDef alarm fill:#FEE2E2,stroke:#DC2626,color:#7F1D1D
    classDef out fill:#10B981,stroke:#059669,color:#fff

    class EC2,ALB,App src
    class Logs,Metrics store
    class Alarms alarm
    class SNS,Email,Slack out

简短解释:

  • Logs 存非结构化或结构化日志,可全文搜索 / SQL 风格查询
  • Metrics 存数值时序,自动按 1 分钟 / 5 分钟聚合
  • Alarms 监听 Metrics(或 Metrics 数学组合),命中阈值发送 SNS 通知

10.2 日志层级:Log Group → Log Stream → Log Event

CloudWatch Logs 三级层级:

  • Log Group(日志组):通常按应用/服务划分,如 /aws/ec2/my-app/aws/lambda/payment-fn
  • Log Stream(日志流):组内更细的分组,通常按实例 ID 或 hostname
  • Log Event(日志事件):具体一条日志

订阅过滤器(Subscription Filter):把匹配 pattern 的日志实时转出到 Lambda、Kinesis Data Stream 或 Firehose,实现日志告警、流式处理、转存到 S3。

10.3 Logs Insights:5 个常用查询模板

Logs Insights 用类 SQL 语法查日志,覆盖 90% 排查场景。5 个常用模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-- 1. 错误统计(每 5 分钟一个 bucket)
fields @timestamp, @message
| filter @message like /ERROR/
| stats count() by bin(5m)

-- 2. 慢请求 Top 10
fields @timestamp, latency, @message
| sort latency desc
| limit 10

-- 3. 5xx 来源 IP Top
fields @timestamp, status, sourceIp
| filter status >= 500
| stats count() as cnt by sourceIp
| sort cnt desc
| limit 20

-- 4. 用户级聚合
fields @timestamp, userId
| stats count() as cnt by userId
| sort cnt desc
| limit 20

-- 5. 时间序列(每小时请求数)
fields @timestamp
| stats count() by bin(1h)

@timestamp @message 是 CloudWatch 自动注入的字段;其它字段需要日志本身是 JSON 结构化(CloudWatch 自动解析顶层字段)。

10.4 EMF:从结构化日志直出指标

EMF(Embedded Metric Format,嵌入式指标格式)= 在结构化 JSON 日志里嵌入元数据。CloudWatch 自动从日志抽出对应数值生成 Metrics,省掉一次 PutMetricData API 调用,对高频指标场景大幅降本。

示例(应用打一条 EMF 日志):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"_aws": {
"Timestamp": 1714111111000,
"CloudWatchMetrics": [{
"Namespace": "MyApp",
"Dimensions": [["Service", "Endpoint"]],
"Metrics": [
{"Name": "Latency", "Unit": "Milliseconds"},
{"Name": "RequestCount", "Unit": "Count"}
]
}]
},
"Service": "checkout",
"Endpoint": "/order/create",
"Latency": 152,
"RequestCount": 1
}

写到 CloudWatch Logs 后,CloudWatch 自动按 MyApp/LatencyMyApp/RequestCount 生成指标,按 ServiceEndpoint 维度可分组查询。收益:每秒 1000 次请求场景,传统方式调 PutMetricData 每月 $30+;改 EMF 仅算日志摄取费 + 0 调用费。

10.5 告警链路:Metric → Alarm → SNS → 邮件/Slack

完整告警链路示例:监控 ALB 5xx 数量超阈值发邮件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Step 1 创建 SNS Topic
aws sns create-topic --name alarm-topic --region us-west-2

# Step 2 订阅邮件(用户会收到确认邮件,点确认才生效)
aws sns subscribe \
--topic-arn arn:aws:sns:us-west-2:<account-id>:alarm-topic \
--protocol email --notification-endpoint ops@example.com \
--region us-west-2

# Step 3 创建告警(5 分钟内 5xx > 10 触发)
aws cloudwatch put-metric-alarm \
--alarm-name alb-5xx-high \
--alarm-description "ALB 5xx > 10 in 5 min" \
--metric-name HTTPCode_ELB_5XX_Count \
--namespace AWS/ApplicationELB \
--statistic Sum \
--period 300 \
--evaluation-periods 1 \
--threshold 10 \
--comparison-operator GreaterThanThreshold \
--dimensions Name=LoadBalancer,Value=app/web-alb/abc123 \
--alarm-actions arn:aws:sns:us-west-2:<account-id>:alarm-topic \
--region us-west-2

告警三要素:metric(指标)、period × evaluation-periods(评估窗口)、threshold(阈值)。Slack 通知通过 SNS → Lambda → Slack Webhook 实现,Lambda 函数用官方模板 1 小时搞定。

💰 CloudWatch:Logs 摄取 $0.50/GB、存储 $0.03/GB- 月;自定义指标前 10,000 个 $0.30/月每个,10,001-250,000 $0.10/月每个;标准告警 $0.10/告警 - 月;Logs Insights 查询按扫描数据计费。这些是页面 example 措辞,未明示 us-west-2,请以 AWS Pricing Calculator 为准。
来源:AWS CloudWatch Pricing

💡 进阶 / 成本:CloudWatch Logs 默认保留期是 “Never Expire”——不改的话日志永久存,几个月后存储费可能比摄取费还贵。省钱优先级:(1) 每个 Log Group 都设保留期,按 RPO 选 30 天 / 90 天 / 1 年;(2) 高频低价值日志(健康检查、心跳)改 sample 或本地丢弃;(3) 用 Subscription Filter 转出冷数据到 S3,原 Log Group 设短保留。这 3 步通常能砍掉 50%+ 日志账单。

10.6 常见踩坑

  1. PutMetricData 高频调用账单暴涨

    • 现象:自定义指标账单从每月几十美元飙到几百
    • 根因:应用每次请求都调 PutMetricData,单价 $0.01/1000 次看似便宜,但 QPS 高时积少成多;且每个 dimension 组合算一个独立 Metric,high cardinality(如 user_id 当 dimension)一夜千万 metric
    • 处理:换 EMF(10.4 节);或客户端聚合(每分钟一次 PutMetricData);dimension 值的基数严格控制 < 1000
  2. 告警评估周期过短,瞬时尖峰频繁触发

    • 现象:CPU 偶尔瞬时尖到 90% 就发告警,运维被噪声淹没
    • 根因:Alarm 设了 period 60s, evaluation-periods 1,1 分钟超阈值就触发
    • 处理:改 period 300s, evaluation-periods 2(连续 10 分钟超阈值才报),或用 datapoints-to-alarm 3 of 5(5 个评估周期里有 3 次超阈值才报)
  3. 日志 Subscription Filter 触发 Lambda 死循环

    • 现象:Lambda 调用次数异常飙升,账单激增
    • 根因:Lambda 自身的日志写到 CloudWatch Logs,又被同一个 Filter 命中触发自己——形成自循环
    • 处理:Subscription Filter 的 Log Group 排除掉 Lambda 自己的 Log Group(如 /aws/lambda/my-fn);或在 Lambda 代码里检测到来自自己的日志直接返回

10 章的概念铺完了,接下来把它们串成一个真能跑的最小生产架构——30 分钟从零搭出来。

11. 实战配方:30 分钟搭建生产可用 Web 服务

本章独立可读:把前 10 章的概念串成一个能跑起来的最小生产架构。读完跟着做完,你就有了一个跨 AZ 高可用、HTTPS 域名访问、可弹性伸缩、有告警的 Web 服务。

11.0 前置与目标

必备前置:

  • AWS 账户已开通,账单告警已配置(实验欠费风险来自 NAT GW、RDS、ALB 长开机器)
  • 本机已 aws configure,当前 IAM 凭证拥有 EC2 / VPC / RDS / ELB / Auto Scaling / CloudWatch / Route 53 / ACM 完整权限
  • 一个已托管在 Route 53 的域名(或可手动加 CNAME 到第三方 DNS 的域名)
  • 本文统一基准 Region:us-west-2

目标架构:

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6'}}}%%
flowchart TB
    User["用户浏览器"]
    R53["Route 53<br/>app.example.com"]
    ACM["ACM 证书<br/>(us-west-2)"]

    subgraph VPC ["VPC 10.0.0.0/16 — us-west-2"]
        subgraph PubA ["公有子网 us-west-2a (10.0.1.0/24)"]
            ALBnodeA["ALB 节点 A"]
            NATa["NAT GW"]
        end
        subgraph PubB ["公有子网 us-west-2b (10.0.3.0/24)"]
            ALBnodeB["ALB 节点 B"]
        end
        subgraph PriA ["私有子网 us-west-2a (10.0.2.0/24)"]
            EC2a["EC2 (ASG)"]
        end
        subgraph PriB ["私有子网 us-west-2b (10.0.4.0/24)"]
            EC2b["EC2 (ASG)"]
        end
        subgraph IsoA ["隔离子网 us-west-2a (10.0.5.0/24)"]
            RDSa["RDS Primary"]
        end
        subgraph IsoB ["隔离子网 us-west-2b (10.0.6.0/24)"]
            RDSb["RDS Standby"]
        end
    end

    User -->|"DNS 解析"| R53
    User -->|"HTTPS"| ALBnodeA
    User -->|"HTTPS"| ALBnodeB
    ALBnodeA --> EC2a
    ALBnodeB --> EC2b
    EC2a -.->|"读写"| RDSa
    EC2b -.->|"读写"| RDSa
    RDSa -.->|"同步"| RDSb
    EC2a --> NATa

    classDef pub fill:#DBEAFE,stroke:#2563EB
    classDef pri fill:#FEF3C7,stroke:#D97706
    classDef iso fill:#FEE2E2,stroke:#DC2626
    classDef ext fill:#10B981,stroke:#059669,color:#fff

    class PubA,PubB,ALBnodeA,ALBnodeB,NATa pub
    class PriA,PriB,EC2a,EC2b pri
    class IsoA,IsoB,RDSa,RDSb iso
    class User,R53,ACM ext

最终成品:跨 2 AZ 高可用(任一 AZ 故障不影响服务);HTTPS 域名访问;ASG 弹性伸缩 min=2, max=4;RDS Multi-AZ 自动故障切换;CloudWatch 三个关键告警(5xx / CPU / DB 连接数);月成本约 $200-300(不含域名注册)。

架构思路一句话:流量从 Route 53 进 ALB,ALB 跨 AZ 把请求分到私有子网里的 EC2 集群,EC2 读写隔离子网里的 RDS Multi-AZ。三层各自独立 SG,数据库不暴露任何公网入口。下面 8 步把这套搭起来。

11.1 步骤 1:VPC + 双 AZ 子网(5 分钟)

为什么要分公有 / 私有 / 隔离三种子网?公有(Public)放需要被公网直连的组件——ALB 节点、NAT GW;私有(Private)放走 NAT 出网但不被公网直连的应用——EC2;隔离(Isolated)连 NAT 都没有,只接受私有子网内部流量——RDS。流量入站走 IGW → ALB → EC2,出站(如装包、调外部 API)走 NAT GW;数据库整层断网。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
VPC_ID=$(aws ec2 create-vpc --cidr-block 10.0.0.0/16 --region us-west-2 --query 'Vpc.VpcId' --output text)
aws ec2 create-tags --resources $VPC_ID --tags Key=Name,Value=demo-vpc --region us-west-2

# 6 个子网:每 AZ 公 / 私 / 隔离各 1
for AZ in a b; do
for TYPE in pub pri iso; do
case "$TYPE-$AZ" in
pub-a) CIDR=10.0.1.0/24 ;;
pri-a) CIDR=10.0.2.0/24 ;;
pub-b) CIDR=10.0.3.0/24 ;;
pri-b) CIDR=10.0.4.0/24 ;;
iso-a) CIDR=10.0.5.0/24 ;;
iso-b) CIDR=10.0.6.0/24 ;;
esac
aws ec2 create-subnet --vpc-id $VPC_ID --availability-zone us-west-2$AZ --cidr-block $CIDR \
--tag-specifications "ResourceType=subnet,Tags=[{Key=Name,Value=$TYPE-$AZ}]" --region us-west-2
done
done

IGW_ID=$(aws ec2 create-internet-gateway --region us-west-2 --query 'InternetGateway.InternetGatewayId' --output text)
aws ec2 attach-internet-gateway --vpc-id $VPC_ID --internet-gateway-id $IGW_ID --region us-west-2

# 公有子网路由表:0.0.0.0/0 → IGW,关联 pub-a / pub-b
# NAT GW 建在 pub-a,私有子网路由表:0.0.0.0/0 → NAT GW
# 隔离子网用 default route table,无 0.0.0.0/0 出口

⚠️ 完整 CLI 涉及路由表创建、关联、NAT GW + Elastic IP,命令较长。生产环境强烈建议用 Terraform / CloudFormation 管。本节给关键骨架,详见 AWS VPC 官方教程。NAT GW 一台每月固定 $32 + 流量费,是这套架构里仅次于 RDS 的固定成本头号——做实验就建一台跨 AZ 共用,正式生产再考虑每 AZ 一台规避 NAT 单点。

11.2 步骤 2:三件套安全组(3 分钟)

链式引用:Internet:443 → sg-alb → sg-ec2:80 → sg-rds:3306。每一层只放上一层的 SG 作 source,不写 CIDR。

1
2
3
4
5
6
7
8
9
10
11
12
# ALB SG:接公网 443(80 用于 80→443 重定向)
SG_ALB=$(aws ec2 create-security-group --group-name sg-alb --description "ALB" --vpc-id $VPC_ID --region us-west-2 --query 'GroupId' --output text)
aws ec2 authorize-security-group-ingress --group-id $SG_ALB --protocol tcp --port 443 --cidr 0.0.0.0/0 --region us-west-2
aws ec2 authorize-security-group-ingress --group-id $SG_ALB --protocol tcp --port 80 --cidr 0.0.0.0/0 --region us-west-2

# EC2 SG:仅接 ALB SG
SG_EC2=$(aws ec2 create-security-group --group-name sg-ec2 --description "EC2" --vpc-id $VPC_ID --region us-west-2 --query 'GroupId' --output text)
aws ec2 authorize-security-group-ingress --group-id $SG_EC2 --protocol tcp --port 80 --source-group $SG_ALB --region us-west-2

# RDS SG:仅接 EC2 SG
SG_RDS=$(aws ec2 create-security-group --group-name sg-rds --description "RDS" --vpc-id $VPC_ID --region us-west-2 --query 'GroupId' --output text)
aws ec2 authorize-security-group-ingress --group-id $SG_RDS --protocol tcp --port 3306 --source-group $SG_EC2 --region us-west-2

关键:链式 SG 引用(sg-alb → sg-ec2 → sg-rds)让流量路径单向受控——任何外部 IP 都无法直连 EC2 或 RDS,只能从 ALB 进。这里不要写 CIDR 当 source(如 --cidr 10.0.0.0/16),那样整个 VPC 内任何资源都能连 EC2,等于把内部边界拆了;引用 SG ID 才是真正的 “ 白名单 “。新手最常见的错配是给 sg-rds 开 --cidr 0.0.0.0/0:3306——配合公网子网就是把数据库直接挂到公网上,几小时就被扫到。

11.3 步骤 3:RDS Multi-AZ MySQL(8 分钟)

Multi-AZ 在另一 AZ 起一台同步副本(Standby),不对外提供读流量,只用作故障切换。主库挂了或被 AZ 级故障带走时,RDS 在 60-120 秒内把 DNS 端点切到副本,应用代码不用改。这是最便宜可靠的「数据库高可用」方案。读副本(Read Replica)是另一回事,本配方不用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# DB Subnet Group 用两个隔离子网
aws rds create-db-subnet-group \
--db-subnet-group-name demo-isolated --db-subnet-group-description "isolated subnets" \
--subnet-ids subnet-iso-a subnet-iso-b --region us-west-2

# 密码存 Secrets Manager,不进 shell history
DB_PASSWORD=$(openssl rand -base64 24)
aws secretsmanager create-secret --name demo/db/master --secret-string "$DB_PASSWORD" --region us-west-2

# Multi-AZ 实例(自动同步副本到另一 AZ,RPO≈0)
aws rds create-db-instance \
--db-instance-identifier demo-db \
--db-instance-class db.t3.medium \
--engine mysql --engine-version 8.0.35 \
--allocated-storage 100 --storage-type gp3 \
--multi-az \
--master-username admin --master-user-password "$DB_PASSWORD" \
--vpc-security-group-ids $SG_RDS \
--db-subnet-group-name demo-isolated \
--backup-retention-period 7 \
--region us-west-2

aws rds wait db-instance-available --db-instance-identifier demo-db --region us-west-2

11.4 步骤 4:Launch Template + user-data(5 分钟)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cat > /tmp/user-data.sh <<'EOF'
#!/bin/bash
yum update -y
yum install -y nginx
DB_ENDPOINT=$(aws rds describe-db-instances --db-instance-identifier demo-db --query 'DBInstances[0].Endpoint.Address' --output text --region us-west-2)
echo "DB: $DB_ENDPOINT" > /usr/share/nginx/html/index.html
systemctl enable --now nginx
EOF

aws ec2 create-launch-template --launch-template-name demo-tpl \
--launch-template-data "{
\"ImageId\": \"ami-0abcdef1234567890\",
\"InstanceType\": \"t3.medium\",
\"SecurityGroupIds\": [\"$SG_EC2\"],
\"IamInstanceProfile\": {\"Name\": \"ec2-app-role\"},
\"UserData\": \"$(base64 -w 0 /tmp/user-data.sh)\",
\"MetadataOptions\": {\"HttpTokens\": \"required\", \"HttpPutResponseHopLimit\": 1}
}" --region us-west-2

注意:HttpTokens=required 强制 IMDSv2,把 SSRF 取凭证的链路断掉(参见 4.3 节背景)。HttpPutResponseHopLimit=1 防止容器内进程多跳一次拿到元数据;如果你打算在这台 EC2 上跑 Docker / ECS Agent,按官方文档设为 2。user-data 在首次启动自动执行,把 nginx 装好——配合 ASG 弹性伸缩时,新机器从无到有上线只要 30-60 秒。

11.5 步骤 5:Auto Scaling Group(2 分钟)

1
2
3
4
5
6
7
aws autoscaling create-auto-scaling-group \
--auto-scaling-group-name demo-asg \
--launch-template "LaunchTemplateName=demo-tpl,Version=\$Latest" \
--min-size 2 --desired-capacity 2 --max-size 4 \
--vpc-zone-identifier "subnet-pri-a,subnet-pri-b" \
--health-check-type ELB --health-check-grace-period 300 \
--region us-west-2

health-check-type ELB 让 ASG 用 ALB 的 7 层健康检查替代默认的 EC2 状态检查——应用挂了但实例还活着的 “ 僵尸 “ 也会被踢出并替换。grace-period 300 给 user-data 留 5 分钟跑装包脚本,否则新实例还没起 nginx 就被判定不健康,循环 terminate / launch 烧钱。vpc-zone-identifier 必须填两个 AZ 的私有子网——只填一个 AZ 时,那个 AZ 一挂全部容量归零,Multi-AZ RDS 也救不了。

11.6 步骤 6:ALB + Target Group + HTTPS(5 分钟)

ALB 与 EC2 之间用 HTTP 80 而不是 HTTPS。TLS 在 ALB 终结,证书只装一份;EC2 上跑普通 nginx 就行(VPC 内流量走 AWS 骨干网,不存在中间人风险)。ALB 与公网之间一律 HTTPS,并把 80 端口做 301 重定向到 443——避免用户敲 http:// 时拿到不安全连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
TG_ARN=$(aws elbv2 create-target-group --name demo-tg --protocol HTTP --port 80 --vpc-id $VPC_ID \
--target-type instance --health-check-path /healthz --region us-west-2 --query 'TargetGroups[0].TargetGroupArn' --output text)

ALB_ARN=$(aws elbv2 create-load-balancer --name demo-alb --type application --scheme internet-facing \
--subnets subnet-pub-a subnet-pub-b --security-groups $SG_ALB --region us-west-2 \
--query 'LoadBalancers[0].LoadBalancerArn' --output text)

# ASG 关联 TG(ASG 拉起的实例自动注册到 TG)
aws autoscaling attach-load-balancer-target-groups \
--auto-scaling-group-name demo-asg --target-group-arns $TG_ARN --region us-west-2

# 80 → 443 重定向
aws elbv2 create-listener --load-balancer-arn $ALB_ARN \
--protocol HTTP --port 80 \
--default-actions 'Type=redirect,RedirectConfig={Protocol=HTTPS,Port=443,StatusCode=HTTP_301}' \
--region us-west-2

# 443 监听器在 11.7 拿到证书 ARN 后创建

11.7 步骤 7:Route 53 域名 + ACM 证书(2 分钟)

ACM(AWS Certificate Manager,证书管理器)签发的证书只能给同 Region 的 AWS 服务用——给 us-west-2 的 ALB 装证书,证书就必须在 us-west-2 申请。例外是 CloudFront:它要求证书在 us-east-1。本配方不涉及 CloudFront,所以同 Region 申就对了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ACM 证书必须与 ALB 同 Region
CERT_ARN=$(aws acm request-certificate --domain-name app.example.com \
--validation-method DNS --region us-west-2 --query 'CertificateArn' --output text)

# 控制台或 CLI 把 ACM 给的 CNAME 加进 Route 53;几分钟内状态变 ISSUED
# 创建 443 监听器,绑证书
aws elbv2 create-listener --load-balancer-arn $ALB_ARN \
--protocol HTTPS --port 443 \
--certificates "CertificateArn=$CERT_ARN" \
--ssl-policy ELBSecurityPolicy-TLS13-1-2-2021-06 \
--default-actions "Type=forward,TargetGroupArn=$TG_ARN" \
--region us-west-2

# Route 53 加 ALIAS A 记录指向 ALB
ALB_DNS=$(aws elbv2 describe-load-balancers --load-balancer-arns $ALB_ARN --query 'LoadBalancers[0].DNSName' --output text --region us-west-2)
# 控制台或 change-resource-record-sets 创建 A ALIAS:app.example.com → $ALB_DNS

11.8 步骤 8:CloudWatch 告警(3 分钟)

告警不是越多越好,越多越容易被忽略。最小可用告警三条线打底:

  • 入口异常:5xx 突增——用户已经看到错误页
  • 容量异常:CPU 持续高位——准备扩容或排查热点
  • 依赖异常:DB 连接数飙高——通常是连接池泄漏或慢查询堵塞

任何一条响,就值得人去看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
TOPIC_ARN=$(aws sns create-topic --name demo-alarms --region us-west-2 --query 'TopicArn' --output text)
aws sns subscribe --topic-arn $TOPIC_ARN --protocol email --notification-endpoint ops@example.com --region us-west-2

# 1) ALB 5xx:5 分钟内 > 10 个
aws cloudwatch put-metric-alarm --alarm-name demo-alb-5xx \
--metric-name HTTPCode_ELB_5XX_Count --namespace AWS/ApplicationELB \
--statistic Sum --period 300 --evaluation-periods 1 --threshold 10 \
--comparison-operator GreaterThanThreshold \
--dimensions Name=LoadBalancer,Value=app/demo-alb/abc123 \
--alarm-actions $TOPIC_ARN --region us-west-2

# 2) ASG 平均 CPU 持续 10 分钟 > 80%
aws cloudwatch put-metric-alarm --alarm-name demo-asg-cpu \
--metric-name CPUUtilization --namespace AWS/EC2 \
--statistic Average --period 300 --evaluation-periods 2 --threshold 80 \
--comparison-operator GreaterThanThreshold \
--dimensions Name=AutoScalingGroupName,Value=demo-asg \
--alarm-actions $TOPIC_ARN --region us-west-2

# 3) RDS 连接数 > 80
aws cloudwatch put-metric-alarm --alarm-name demo-rds-conn \
--metric-name DatabaseConnections --namespace AWS/RDS \
--statistic Average --period 300 --evaluation-periods 1 --threshold 80 \
--comparison-operator GreaterThanThreshold \
--dimensions Name=DBInstanceIdentifier,Value=demo-db \
--alarm-actions $TOPIC_ARN --region us-west-2

11.9 验证清单

部署完成「看着都对」不等于真的可用。下面 5 项是最低验证集,专门把架构里「高可用」和「可观测」两个承诺打一遍。不打就只是一堆资源,不是生产服务。逐项确认(任一不通就回到对应步骤排查):

  • 浏览器访问 https://app.example.com 返回 200,证书绿锁
  • [控制台手动 terminate 一台 ASG 实例,2-3 分钟内 ASG 自动起新实例补齐
  • RDS 控制台触发 “Reboot with failover”,应用短暂报错(约 60-120 秒)后恢复
  • aws sns publish --topic-arn $TOPIC_ARN --message test,邮箱能收到(确认订阅已生效)
  • CloudWatch Logs 控制台能看到应用日志流(如配了 CloudWatch Agent)

11.10 销毁脚本(避免账单意外)

⚠️ 本节最重要:实验做完一定要销毁。NAT GW + RDS Multi-AZ + ALB 三件套不停就是每天 $5-10 的烧。

按依赖反向顺序销毁(销毁前再确认一次资源名 / VPC ID):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 1. CloudWatch + SNS
aws cloudwatch delete-alarms --alarm-names demo-alb-5xx demo-asg-cpu demo-rds-conn --region us-west-2
aws sns delete-topic --topic-arn $TOPIC_ARN --region us-west-2

# 2. Route 53 ALIAS(控制台或 change-resource-record-sets DELETE)

# 3. ALB + ASG + TG + Launch Template
aws elbv2 delete-load-balancer --load-balancer-arn $ALB_ARN --region us-west-2
aws autoscaling delete-auto-scaling-group --auto-scaling-group-name demo-asg --force-delete --region us-west-2
aws elbv2 delete-target-group --target-group-arn $TG_ARN --region us-west-2
aws ec2 delete-launch-template --launch-template-name demo-tpl --region us-west-2

# 4. RDS(实验直接 skip 快照)
aws rds delete-db-instance --db-instance-identifier demo-db --skip-final-snapshot --delete-automated-backups --region us-west-2
aws rds delete-db-subnet-group --db-subnet-group-name demo-isolated --region us-west-2

# 5. ACM
aws acm delete-certificate --certificate-arn $CERT_ARN --region us-west-2

# 6. SG(反向依赖:rds → ec2 → alb)
aws ec2 delete-security-group --group-id $SG_RDS --region us-west-2
aws ec2 delete-security-group --group-id $SG_EC2 --region us-west-2
aws ec2 delete-security-group --group-id $SG_ALB --region us-west-2

# 7. NAT GW + EIP
# 8. 子网 + 路由表 + IGW + VPC

💡 进阶 / IaC:手动脚本一长就出错。生产配方建议用 Terraform / CloudFormation / CDK 管理整套资源——一份代码起一份环境,terraform destroy 一键回收,比 delete-* 命令链条稳得多。本章配方的全部资源用 Terraform 写出来约 200 行,作为下一步练习刚好。

正文结束。最后附上 CLI 速查表和延伸阅读,留作日常翻阅。

12. 附录

本附录提供两块内容:常用 CLI 速查(按服务分组)和推荐进阶阅读。

12.1 常用 CLI 速查

所有命令前置:已配置 awscli + IAM 权限 + 默认 Region us-west-2。

EC2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 列出运行中实例(ID + AZ + 私网 IP)
aws ec2 describe-instances \
--filters "Name=instance-state-name,Values=running" \
--query "Reservations[].Instances[].[InstanceId,Placement.AvailabilityZone,PrivateIpAddress]" \
--output table --region us-west-2

# 启动 / 停止 / 重启 / 终止
aws ec2 start-instances --instance-ids i-xxx --region us-west-2
aws ec2 stop-instances --instance-ids i-xxx --region us-west-2
aws ec2 reboot-instances --instance-ids i-xxx --region us-west-2
aws ec2 terminate-instances --instance-ids i-xxx --region us-west-2

# Session Manager 登录(无需 22 端口 + SSH key)
aws ssm start-session --target i-xxx --region us-west-2

# 强制 IMDSv2
aws ec2 modify-instance-metadata-options --instance-id i-xxx \
--http-tokens required --http-put-response-hop-limit 1 --region us-west-2

# 修改卷大小
aws ec2 modify-volume --volume-id vol-xxx --size 100 --region us-west-2

S3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 列桶
aws s3 ls --region us-west-2

# 上传 / 下载(增量,推荐)
aws s3 sync ./local-dir s3://my-bucket/path/ --region us-west-2
aws s3 sync s3://my-bucket/path/ ./local-dir --region us-west-2

# 单文件
aws s3 cp file.txt s3://my-bucket/path/ --region us-west-2

# 删除(含 --delete 镜像)
aws s3 sync ./local-dir s3://my-bucket/path/ --delete --region us-west-2

# 看桶内对象大小汇总
aws s3 ls s3://my-bucket/ --recursive --human-readable --summarize --region us-west-2

# 设置桶生命周期
aws s3api put-bucket-lifecycle-configuration --bucket my-bucket \
--lifecycle-configuration file://lifecycle.json --region us-west-2

RDS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 列实例(ID + endpoint + 端口 + master 用户)
aws rds describe-db-instances \
--query "*[].[DBInstanceIdentifier,Endpoint.Address,Endpoint.Port,MasterUsername]" \
--output table --region us-west-2

# 创建快照
aws rds create-db-snapshot --db-snapshot-identifier my-db-snap-$(date +%Y%m%d) \
--db-instance-identifier my-db --region us-west-2

# PITR 恢复(恢复到 5 分钟前)
aws rds restore-db-instance-to-point-in-time \
--source-db-instance-identifier my-db \
--target-db-instance-identifier my-db-restore \
--restore-time $(date -u -v-5M +%Y-%m-%dT%H:%M:%SZ) --region us-west-2

# 修改参数组并重启生效
aws rds modify-db-parameter-group --db-parameter-group-name my-pg \
--parameters "ParameterName=max_connections,ParameterValue=500,ApplyMethod=immediate" --region us-west-2
aws rds reboot-db-instance --db-instance-identifier my-db --region us-west-2

CloudWatch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 列告警当前状态
aws cloudwatch describe-alarms --state-value ALARM \
--query "MetricAlarms[].[AlarmName,StateReason]" --output table --region us-west-2

# 触发 Logs Insights 查询(拿 query ID)
aws logs start-query --log-group-name /aws/ec2/my-app \
--start-time $(date -u -v-1H +%s) --end-time $(date -u +%s) \
--query-string 'fields @timestamp, @message | filter @message like /ERROR/ | stats count() by bin(5m)' \
--region us-west-2

# 拿查询结果
aws logs get-query-results --query-id <query-id> --region us-west-2

# 创建 SNS Topic + 邮件订阅
aws sns create-topic --name alarm-topic --region us-west-2
aws sns subscribe --topic-arn arn:... --protocol email --notification-endpoint ops@example.com --region us-west-2

ELB

1
2
3
4
5
6
7
8
9
# 列 Load Balancer
aws elbv2 describe-load-balancers \
--query "LoadBalancers[].[LoadBalancerName,DNSName,Scheme,Type]" --output table --region us-west-2

# 看 Target Group 后端健康
aws elbv2 describe-target-health --target-group-arn arn:... --region us-west-2

# 触发缓存失效(CloudFront)
aws cloudfront create-invalidation --distribution-id E1ABCDEF12345 --paths "/static/*" --region us-east-1

IAM / Secrets Manager

1
2
3
4
5
6
7
8
9
# 看当前调用方身份
aws sts get-caller-identity

# 列 IAM Role
aws iam list-roles --query "Roles[].RoleName" --output table

# 拉 Secret(应用启动时用)
aws secretsmanager get-secret-value --secret-id demo/db/master \
--query SecretString --output text --region us-west-2

12.2 推荐进阶阅读

  • AWS Well-Architected Framework(六大支柱:卓越运营 / 安全 / 可靠性 / 性能 / 成本 / 可持续):官方门户
  • AWS 价格计算器:calculator.aws — 任何价格疑问的第一站
  • AWS 公开定价 API(机器可读 JSON,文件较大但是最权威的源):https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/<service>/current/<region>/index.json
  • AWS 中国官网博客:aws.amazon.com/cn/blogs/china/ — 中文实战案例

12.3 价格声明

文中价格抓取自 AWS 官方页(基准 Region us-west-2,抓取日期 2026-04-26),每张价格卡片带「来源」链接。价格随时调整,生产决策请以 AWS Pricing Calculator 实时查询为准。