AWS核心服务实战技巧

新手在 AWS 上最容易踩两类坑:一类是凭证 / 账单失控(root key 泄露、NAT GW 忘关、僵尸 EBS),另一类是架构基础没打稳(公私子网混用、SG 写 CIDR、Multi-AZ 当读写分离用)。这篇把这两类都覆盖:第 0 章先把账户安全和账单看护配齐,1 到 9 章依次过 VPC / ELB / NAT / EC2 / S3 / CloudFront / RDS / 缓存 / 弹性与告警,第 10 章用 30 分钟搭出一套跨 AZ 高可用的 Web 服务,第 11 章交代怎么从手动 CLI 走向 IaC 与容器化。

环境准备:本文示例统一基准 Region 为 us-west-2(俄勒冈),CLI 命令均带 --region us-west-2。价格抓取自 AWS 官方页(截至 2026-04),生产决策请以 AWS Pricing Calculator 为准。

0. 入门必做:账户安全与账单看护

第一次开 AWS 账户,先别着急建 EC2。下面四件事不做,后面都白搭:root 凭证一旦泄露整个账号一起没;账单不看着,一个忘关的 NAT GW 一个月就能烧掉 $30。

0.1 根账户与 MFA

注册 AWS 时填的邮箱 + 密码就是 root 用户。它能干掉账号本身、改账单地址、解绑 MFA,权力远超日常工作所需。规则就两条:

  • root 立刻开 MFA(控制台右上角用户名 → My Security Credentials → Multi-factor authentication)
  • root 永远不创建 access key;日常一律用 IAM 用户

如果之前已经为 root 建过 access key,现在去 IAM 控制台删掉,没有例外。

0.2 IAM 用户、Role、最小权限

IAM(Identity and Access Management,身份与访问管理)的三个常用对象:

  • IAM 用户(User):一个真人或一个长期凭证,对应一组 access key
  • IAM 角色(Role):可被临时扮演的身份,常给 AWS 服务(如 EC2、Lambda)用
  • IAM 策略(Policy):JSON 描述的权限规则,挂到用户或角色上

新手常见错误是图省事给 IAM 用户挂 AdministratorAccess,相当于复制了一份 root。正确做法分两步:

  1. 给自己建一个 IAM 用户(开 MFA),日常控制台登录用它,不用 root
  2. 给应用 / EC2 / Lambda 用 Role,不用长期 access key

为什么应用要用 Role?Role 给出来的是 15 分钟到 12 小时滚动失效的临时凭证,泄露窗口短;access key 一旦写进 git 仓库或日志,可以被滥用到你 revoke 为止。

最小权限的实操原则:先给只读策略跑通流程(如 AmazonEC2ReadOnlyAccess),需要写操作时再具体加,不要先开全集再收紧。IAM 服务本身不收费。

0.3 凭证安全:access Key 不进 Git

access key 泄露是 AWS 用户被攻击的头号路径。GitHub 上每分钟都有扫描器在扒公开仓库里的 AKIA 开头字符串。三条底线:

  • access key 不写进配置文件、不进 git、不发 Slack
  • 本机用 aws configure 把 key 存到 ~/.aws/credentials,加 chmod 600
  • 怀疑泄露立刻在 IAM 控制台 Deactivate + Delete,再生成新 key

CI / 服务器上跑的脚本,能用 IAM Role(OIDC、EC2 Instance Profile)就不用 access key。

0.4 账单看护:Budgets + Cost Explorer

AWS 不会主动通知你「快花超了」,账单警觉得自己开。两件事一起配:

  • AWS Budgets(预算):设置月度阈值,超过 80% / 100% 发邮件。前两个 budget 免费,足够日常用
  • AWS Cost Explorer(成本浏览器):可视化看每天每个服务花多少,按 tag 拆分。免费

新建账户后立刻去 Billing → Budgets 建一个月度预算(如 $50),收件人填自己邮箱。再开 Cost Explorer(默认关闭,Billing → Cost Explorer 一键启用)。这两步不到五分钟,能把「账单意外」从大概率事件压到几乎不会发生。

进阶:如果有团队多账户,用 AWS Organizations + Consolidated Billing 把所有子账户账单合并,配合 Cost Allocation Tags(成本分配标签)按项目 / 环境 / 团队分摊;单账户场景不用上这套。

账户安全和账单看护配齐后,下一步开始动结构:先把资源放在地球的哪里、网络边界划在哪两件事定下来。

1. 网络基础:Region / AZ / VPC

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

1.1 概念三件套

%%{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"]
        end
        subgraph AZ3 ["可用区 C"]
            DC5["数据中心 4"]
        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,DC5 dc
概念定义类比
Region(区域)AWS 在全球的物理地理区域(如 us-east-1ap-northeast-1城市
AZ(可用区)Region 内物理隔离的数据中心集群,独立电力 / 冷却 / 网络城市内的独立园区
VPC(虚拟私有云)Region 内逻辑隔离的私有网络,由你掌控 IP / 子网 / 路由园区里你租下的那栋楼

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

1.2 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 通常最贵

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

💰 同机型 m6i.large(按需)月度价格对照(按官方 API 抓取):us-east-1 约 $63、us-west-2 约 $69、ap-northeast-1 约 $86。三地年度成本差能到 30%。
来源:AWS EC2 On-Demand Pricing

1.3 多可用区架构与跨 AZ 流量费

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6'}}}%%
flowchart TB
    subgraph VPC ["VPC: my-vpc (10.0.0.0/16)"]
        subgraph AZA ["us-west-2a"]
            PubA["public-subnet-a 10.0.1.0/24"]
            PriA["private-subnet-a 10.0.2.0/24"]
        end
        subgraph AZB ["us-west-2b"]
            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):路由表含 0.0.0.0/0 → IGW,子网内资源可双向访问公网。放对外暴露的组件:ALB、NAT Gateway、堡垒机
  • 私有子网(Private):路由表含 0.0.0.0/0 → NAT GW,能访问公网(拉镜像、调外部 API),但公网无法主动连入。放业务应用层:EC2、ECS、Lambda 的 VPC ENI
  • 隔离子网(Isolated):路由表无 0.0.0.0/0 默认路由,与公网完全断开。放数据层:RDS、ElastiCache、自建数据库

为什么这么分?最小爆破半径加安全分层。万一应用层被攻陷,攻击者也无法从私有子网直接 SSH 出去外联 C2 服务器;万一应用层 SSRF 被利用,隔离子网的数据库也连不上外网,无法把数据偷偷传出去。每多一层路由隔离,就多一道兜底防线。

1.5 路由表与 IGW

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

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

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

1.6 安全组:实例级白名单

VPC 里有两层防火墙:Security Group(安全组,作用在实例网卡)和 Network ACL(NACL,作用在子网)。新手 99% 时间只用 SG 就够:SG 之间可以互相引用(如 ALB-SG 作为 EC2-SG 的 source),表达力强且有状态省心。NACL 通常只在阻挡某段恶意 IP 或合规要求子网级强制隔离时用,其他时候保持默认全开。

理解了网络隔离边界,下一步看流量怎么进入 VPC:ALB 与 HTTPS 终结。

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
NLB(Network Load Balancer)L4TCP / UDP / TLS高并发长连接、游戏、DB 代理、需要静态 IP(每 AZ 一个 EIP)、TLS 透传需要 HTTP 路由 / Host / Path
CLB(Classic Load Balancer)L4 / L7HTTP / TCP仅老系统兼容新项目一律不选

实操原则:99% 的新项目从 ALB 起手;只有当应用是纯 TCP/UDP 或要求静态入口 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 健康检查关键决策

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

2.4 创建 ALB

1
2
3
4
5
6
7
8
9
# 前置:已有 VPC、跨 AZ 公有子网、ALB 安全组、Target Group
# 配置 awscli,IAM 拥有 elasticloadbalancing:CreateLoadBalancer
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

监听器要点:80 端口默认 action 用 redirect → HTTPS:443(HTTP_301 永久重定向)而不是 forward,避免用户敲 http:// 时拿到不安全连接。

2.5 HTTPS 与 ACM 证书

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

  • 证书集中在 ALB 管理,无需在每台 EC2 配置和续签
  • TLS 解密耗 CPU,由 ALB 集中处理,后端 EC2 节省算力
  • VPC 内部网络已是受信环境,HTTP 足够

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 服务完全免费

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 原生支持:

1
2
3
4
5
6
7
8
9
10
# 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 } # 重定向用

# EC2 SG(关键:source 写 sg-alb 而不是 CIDR)
sg-ec2:
inbound:
- { protocol: tcp, port: 80, source: sg-alb }

效果:即使 EC2 有公网 IP,外部也无法绕过 ALB 直连。这是 VPC 内部「零信任」模式的最小落地。

入站流量被 ALB 收齐后,反方向问题接踵而来:私有子网里的应用怎么对外发起请求?

3. 出站流量:NAT 与 VPC Endpoint

私有子网里的 EC2 想拉镜像、调外部 API、装系统包,需要一条到公网的出口,但又不能让公网直接连进来。AWS 给了三条路:NAT Gateway、NAT Instance、VPC Endpoint。

3.1 三条路怎么选

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

    EC2 --> |"路径 1: 任意公网"| NATGW
    EC2 --> |"路径 2: AWS 服务"| VPCE
    NATGW --> 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

    class EC2 ec2
    class NATGW,IGW,Backbone gw
    class VPCE,AWSSvc ok
  • 想访问任意公网(GitHub、外部 API、包管理器仓库):生产用 NAT Gateway,AWS 全托管。NAT Instance 是自建 EC2 当 NAT 用,看似省钱但单点故障 + 自维护成本远超那点节省,新项目不要碰
  • 只想访问特定 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(小时费)+ $135(数据处理)= $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。每月有 100 GB 免费额度(聚合所有 AWS 服务和 Region)。
来源:AWS Data Transfer Pricing API

3.3 VPC Endpoint:S3 / DynamoDB 走 Gateway 省钱

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

类型支持服务计费
Gateway Endpoint仅 S3、DynamoDB免费
Interface Endpoint(PrivateLink)大部分 AWS 服务(SQS / SNS / KMS / Secrets Manager / ECR / Lambda 等)按小时(每 AZ 一个 ENI)+ 数据处理 $0.01/GB

举一个常见省钱场景:应用频繁向 S3 上传日志,月流量 1 TB。

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

仅靠一个免费 Gateway Endpoint,每月省 $138。实战要点:在路由表里给 S3 / DynamoDB 加一条目标为 vpce-xxx 的路由,应用代码无需改动,SDK 走默认 endpoint 即可被路由命中。Interface Endpoint 别忘了勾「启用私有 DNS」,否则 SDK 仍解析到公共域名,命中默认路由走 NAT GW。

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

网络通了,接着把跑应用的「机器」放进私有子网里。

4. 计算:EC2 与存储

EC2(Elastic Compute Cloud,弹性计算云)是 AWS 上跑应用的根基。这一章只讲 80% 场景必备的部分:选机型、Launch Template、IMDSv2、user-data、Session Manager、EBS 扩容。

4.1 实例族速查

族系类型典型场景代表机型
T(Burstable)突发型低流量 Web、开发测试、CI Runnert3.medium、t4g.medium
M(General)通用型Web 应用、微服务、中等数据库m6i.large、m7g.large
C(Compute)计算优化视频编码、批量计算、HPCc7g.large、c7i.large
R(Memory)内存优化内存数据库、缓存、Sparkr6i.large、r7g.large

带 g 后缀的是 ARM(Graviton),性价比比 Intel / AMD 同级别高 20% 至 40%,绝大多数 Linux 应用可无痛迁移。GPU、超大内存、本地 SSD 等专用族系新手用不到,需要时再查文档。

💰 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

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

4.2 AMI 与 Launch Template

AMI(Amazon Machine Image)= 操作系统 + 预装软件 + 启动配置的快照。Launch Template(启动模板)= AMI + 实例类型 + SG + IAM Role + user-data 等所有启动参数的封装,是 Auto Scaling Group(第 9 章)的前置基础。

为什么不直接「Launch Instance」?Launch Template 可版本化(v1 → v2 → vN),方便回滚;ASG 必须基于 Launch Template 才能批量起实例;团队协作时一份模板共享,避免每人手动凑参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 前置:已有 AMI ID、SG、IAM Instance Profile
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>",
"MetadataOptions": {"HttpTokens": "required", "HttpPutResponseHopLimit": 1}
}' \
--region us-west-2

ASG 引用 Launch Template 时如果绑死具体版本号,更新模板后新实例还是旧版。改成 Version=$Latest 让 ASG 自动跟随最新版。

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,从根上断了路径。

1
2
3
4
5
6
# 强制实例只允许 IMDSv2
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.2 节示例已含)。

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 默认只在首次启动执行,后续重启不会再跑。

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 的实例」。

4.6 EBS 扩容三步走

EBS 扩容是新手最常踩的「操作分两层」问题:先在 AWS 控制面把卷扩大,再在 OS 内部把分区和文件系统扩大。

1
2
3
4
5
6
7
8
9
10
11
# Step 1: AWS 控制面扩卷
aws ec2 modify-volume --volume-id vol-0abcdef1234567890 --size 100 --region us-west-2
aws ec2 describe-volumes-modifications --volume-id vol-0abcdef1234567890 --region us-west-2

# Step 2: 在实例内扩展分区
sudo growpart /dev/nvme0n1 1
df -hT

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

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

EBS 类型 99% 选 gp3,比 gp2 便宜约 20%,IOPS 和 Throughput 可独立加量,不用「为了 IOPS 多买容量」。OLTP 数据库受 IOPS 制约(小事务多),视频处理 / 日志归档受 Throughput 制约(大文件少),gp3 两边都能调。

💰 EBS gp3:$0.08/GB-月 + $0.005/IOPS- 月(超出 3,000)+ $0.06/MB·s- 月(超出 125)。
来源:AWS EBS Pricing

4.7 EC2 Vs ECS Fargate Vs Lambda

文章一路讲 EC2 + ASG,但 2026 年的「新项目起手」早已不是单一选项。简单的判断:

工作负载
长跑 Web 服务,团队懂 Linux 运维,需要自定义内核 / GPUEC2 + ASG
容器化应用,不想管 OS 和补丁,按容器粒度计费ECS Fargate(推荐起手)
事件驱动、按调用计费、毫秒级冷启动可接受Lambda

新项目如果只是跑普通 Web / API,强烈建议 ECS Fargate。它比 EC2 + ASG 少了 OS 维护、AMI 更新、user-data 调试。这篇为了讲清楚网络和弹性的基础原理选了 EC2 路径,理解之后迁移到 Fargate 大概率更省心。

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

计算节点的本地盘只装系统和短期数据;用户上传、日志、备份这些「不能丢、要长留」的东西归到对象存储。

5. 对象存储:S3 进阶

S3(Simple Storage Service)几乎每个 AWS 账户都开过一个 bucket。但「会用 S3」和「用好 S3」差距很大,存储类、生命周期、桶策略、签名 URL 每一项都有讲究。

5.1 6 种存储类决策

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

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

实操原则:写完后 30 天内会再访问的留 Standard;写完后多半不会再读的转 Standard-IA;仅合规留存的转 Glacier Flexible 或 Deep Archive。用生命周期规则自动转储,不要手动搬。

5.2 Cp Vs Sync

1
2
aws s3 sync s3://bucket-name/path/ . --region us-west-2          # 下载整个目录
aws s3 sync . s3://bucket-name/path/ --region us-west-2 # 上传当前目录
命令行为适用场景
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 自动按访问模式分层。

5.4 桶策略 Vs IAM 策略 Vs ACL

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 上传签名 URL(Go SDK v2)
obj, err := s.preSignClient.PresignPutObject(ctx, &s3.PutObjectInput{
Bucket: &bucket,
Key: &key,
}, func(po *s3.PresignOptions) {
po.Expires = 15 * time.Minute
})

// 下载签名 URL
obj, err = s.preSignClient.PresignGetObject(ctx, &s3.GetObjectInput{
Bucket: &bucket,
Key: &key,
}, func(po *s3.PresignOptions) {
po.Expires = 5 * time.Minute
})

安全要点:时效尽量短(上传链接 ≤ 15 分钟、下载链接 ≤ 5 分钟);不要把签名 URL 写日志;私密文件下载建议每次访问重新签发而非缓存。

5.6 私有桶 + 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 签名访问 S3,Condition 限定来源 distribution 防止其他 CloudFront 分发借桥。

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

S3 把对象稳稳存下来,但用户从地球另一端取一张图仍要走半个地球——这正是 CDN 解决的问题。

6. 内容分发:CloudFront

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

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 域名解析到离用户最近的边缘节点。命中时直接返回,未命中才回源拉取并顺带缓存到本节点。典型静态站点接入后缓存命中率通常 80% 至 95%,源站只扛 5% 至 20% 的请求。

6.2 缓存策略三件套

CloudFront 的缓存键由三件套 Policy 共同决定,是命中率的关键:

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

默认起手:Cache Policy 选 AWS 托管的 Managed-CachingOptimized(不带 Cookie / Query String,TTL 24 h),Origin Request Policy 默认即可,Response Headers Policy 加一个 SecurityHeadersPolicy

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

6.3 ACM 证书必须在 Us-east-1

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

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

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

生产实践通常是同一个域名在 us-east-1 与 ALB 所在 Region 各申一张,DNS 验证共用同一组 CNAME 记录。

6.4 缓存失效

更新源站资源后,需要强制让 CloudFront 重新回源拉取:

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 万次免费。
来源:AWS CloudFront Pricing

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

静态资源走完了「就近加速」的路径,接下来的难题在动态数据:怎么把数据库托管起来、又不丢可用性。

7. 关系数据库:RDS

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

7.1 引擎选择

引擎兼容性性能存储扩展起步成本
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 略高

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

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

7.2 创建数据库实例

1
2
3
4
5
6
7
8
9
10
11
12
13
# 前置:已有 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 备份机制

类型触发保留期恢复粒度
自动备份每天一次完整备份 + 持续事务日志1 至 35 天(实例删除后默认清除)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。快照只包含数据,参数组关联和 SG 关联是实例级配置不在快照里,恢复后立即重新关联。

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 秒手动提升为主库
解决的问题高可用读写分离、降低主库压力

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

7.6 应用层容错配套

Multi-AZ 自动切换 60 至 120 秒期间,应用拿到的是 connection refused 或读超时。如果应用层没重试,用户照样看到 5xx。三件事一起配:

  • 连接池配 connection timeout ≤ 5 秒、max retries ≥ 3,指数退避
  • 写操作幂等设计(用幂等 key 或唯一约束),切换中重试不会重复扣款 / 重复下单
  • 健康检查端点 /readiness 真探一次 DB(如 SELECT 1),DB 不可达就返回 503,让 ALB 把流量切到其他实例

不做这套配套,Multi-AZ 给你的是「数据不丢」,但不是「用户无感」。

7.7 Performance Insights 与用户管理

Performance Insights 是 RDS 内置的性能分析工具:Top SQL 按等待时间排名当前最耗资源的 SQL;等待事件展示 CPU、IO、锁各维度时间分布;保留期免费 7 天,扩展到 2 年另收费。新手用法是控制台 RDS → 实例 → Performance Insights,鼠标点点就能定位「哪条 SQL 占了 60% CPU」。

用户管理建议按读 / 写 / 管理分至少 3 套账号,master 账号只用来建库建用户,应用一律用业务账号:

1
2
3
4
5
6
7
8
9
10
-- 读写
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'@'%';

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

FLUSH PRIVILEGES;

密码放 Secrets Manager 而非配置文件,应用启动时通过 SDK 拉密码(带缓存)。Secrets Manager 可配置每 30 天自动轮换:调用 Lambda 函数生成新密码 → 更新到 RDS → 更新到 Secret,应用层无感。

数据库扛得住事务但抗不住并发热点;缓存层把读请求挡在 DB 之前。

8. 缓存:ElastiCache

缓存层把热点数据从 DB 抬到内存,挡掉 80% 的读。AWS 自家用 ElastiCache 覆盖这块,支持 Redis(含 Valkey 这个开源 fork)和 Memcached。

8.1 三种形态

形态自动扩缩数据分片适用
Serverless自动自动不想管节点;流量波动大
Cluster Mode Disabled手动单分片 + 副本小数据集、需 Redis 全部命令
Cluster Mode Enabled手动多分片(slot 分布)大数据集、高吞吐;客户端必须 cluster-aware

💰 ElastiCache:Serverless 按 ECPU $0.0023/百万次(Valkey/Redis OSS)+ 数据存储 $0.084/GB·h;节点形态按实例小时费 + 备份存储 $0.085/GiB- 月。
来源:AWS ElastiCache Pricing

8.2 Redis Vs Memcached

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

99% 新项目选 Redis。AWS 推荐用 Valkey(Redis 7.x 的开源 fork,与 Redis OSS 协议兼容,价格更低)。

1
2
3
4
5
6
7
# Serverless 创建:前置已有 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

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

存储和缓存就位后,剩下两件事决定能不能扛住流量:实例数量随负载自动伸缩、异常发生时第一时间被看到。

9. 弹性伸缩与可观测性

固定数量 EC2 撑不起波动的流量。Auto Scaling Group(ASG)让实例数量随指标自动伸缩;CloudWatch 把日志、指标、告警串起来,让你在用户工单到来之前就看到异常。这两件事放一章讲,因为 ASG 离不开 CloudWatch 指标驱动。

9.1 ASG 三件套

%%{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.2)
  • ASG 是「我要 N 台实例分布在哪些子网」的容器:min(最少)/ desired(期望)/ max(最多)
  • Scaling Policy 是「什么时候改 desired」的规则

ASG 自动维持 desired 数量的健康实例:有实例挂了自动起新的,触发扩容时自动起更多。新起的实例自动注册到指定 Target Group,缩容时自动注销。ASG 本身不收费,只为底层 EC2 实例付费。

9.2 扩缩策略与健康检查

策略触发方式适用
Target Tracking(目标追踪)维持指标在目标值(如 CPU 平均 50%)通用首选
Step Scaling(步进)按指标阈值阶梯扩缩需对突发流量分级响应
Scheduled(计划)按时间表(每天 9 点扩到 10 台、22 点缩到 2 台)已知流量模式

99% 项目用 Target Tracking,目标值设 50% 给突增留 buffer,简单稳健。

ASG 判定实例健康的两种方式:EC2 健康检查(默认)只看实例 running 状态,应用进程死了 OS 还活着也判活;ELB 健康检查跟随 ALB Target Group 的 7 层探活,应用挂了即使实例还活着也会被替换。生产强烈建议开 ELB 健康检查

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.3 创建 ASG

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 前置:已有 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 最新版本。绑死具体版本号是新手最常见的踩坑:升级模板后新实例还是旧版。

9.4 CloudWatch 三件套

%%{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/>(数值时序)"]

    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 命中阈值发送 SNS 通知

CloudWatch Logs 三级层级:Log Group(按应用 / 服务划分)→ Log Stream(通常按实例 ID 或 hostname)→ Log Event(具体一条日志)。

9.5 Logs Insights:5 个常用查询

Logs Insights 用类 SQL 语法查日志,覆盖 90% 排查场景:

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
-- 错误统计(每 5 分钟一个 bucket)
fields @timestamp, @message
| filter @message like /ERROR/
| stats count() by bin(5m)

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

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

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

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

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

9.6 告警链路:Metric → Alarm → SNS → 邮件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 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 \
--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

Slack 通知通过 SNS → Lambda → Slack Webhook 实现,Lambda 用官方模板 1 小时搞定。

💰 CloudWatch:Logs 摄取 $0.50/GB、存储 $0.03/GB- 月;自定义指标前 10,000 个 $0.30/月每个;标准告警 $0.10/告警 - 月。
来源:AWS CloudWatch Pricing

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

零散的服务讲到这里,前 9 章的概念该串起来了——下一章用一套可跑的最小架构把它们落地。

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

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

10.0 前置与目标

必备前置:

  • AWS 账户已开通,账单告警已配置(参见第 0 章)
  • 本机已 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,数据库不暴露任何公网入口。

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

公有放需要被公网直连的组件(ALB 节点、NAT GW),私有放走 NAT 出网但不被公网直连的应用(EC2),隔离连 NAT 都没有只接受私有子网内部流量(RDS)。

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 管,参见第 11 章。本节给关键骨架,详见 AWS VPC 官方教程。NAT GW 一台每月固定 $32 + 流量费,是这套架构里仅次于 RDS 的固定成本头号。做实验就建一台跨 AZ 共用,正式生产再考虑每 AZ 一台规避 NAT 单点。

10.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 引用让流量路径单向受控,任何外部 IP 都无法直连 EC2 或 RDS,只能从 ALB 进。这里不要写 CIDR 当 source(如 --cidr 10.0.0.0/16),那样整个 VPC 内任何资源都能连 EC2,等于把内部边界拆了。新手最常见的错配是给 sg-rds 开 0.0.0.0/0:3306,配合公网子网就是把数据库直接挂到公网上,几小时就被扫到。

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

Multi-AZ 在另一 AZ 起一台同步副本(Standby),不对外提供读流量,只用作故障切换。主库挂了或被 AZ 级故障带走时,RDS 在 60 至 120 秒内把 DNS 端点切到副本,应用代码不用改。

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

10.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 在首次启动自动执行,配合 ASG 弹性伸缩时新机器从无到有上线只要 30 至 60 秒。

10.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 也救不了。

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

ALB 与 EC2 之间用 HTTP 80 而不是 HTTPS。TLS 在 ALB 终结,证书只装一份;EC2 上跑普通 nginx 就行。ALB 与公网之间一律 HTTPS,并把 80 端口做 301 重定向到 443。

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 监听器在 10.7 拿到证书 ARN 后创建

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

ACM 签发的证书只能给同 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

10.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

10.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)

10.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

「能跑」之后还差一步:把这套手工 CLI 换成可重复、可审计的代码,把僵尸资源管起来。

11. 走向生产

10 章配方让你能跑起来,但离「生产可用」还差最后一公里:手动 CLI 不可重复、僵尸资源会偷偷烧钱、容器化路径才是 2026 年的主流起手。这一章交代怎么从「能跑」走到「能交付」。

11.1 IaC:从 CLI 走到 Terraform

10 章的 CLI 脚本是为了让你看懂每个组件做了什么,不是给生产用的。手工命令链有三个问题:环境差异(你本地能跑,同事的机器跑不通)、不可重复(删错一个资源没法 undo)、不可审计(改了什么进了 git 没?)。

生产正解是 IaC(Infrastructure as Code,基础设施即代码):

工具语言适用
TerraformHCL多云、社区生态最广,强烈推荐起手
AWS CDKTypeScript / Python / Go只用 AWS、想用真编程语言写基础设施
CloudFormationYAML / JSONAWS 原生、零依赖;CDK 编译产物也是它

10 章那套配方用 Terraform 写大约 200 行,terraform apply 一键起,terraform destroy 一键回收。改资源也是改代码进 PR、code review、自动 plan 出 diff,比 CLI 链条稳得多。新手起步建议直接学 Terraform,把配方先用代码复刻一遍。

11.2 资源回收:找出在偷偷烧钱的东西

大账单 80% 来自被遗忘的资源:忘释放的 Elastic IP、孤立的 EBS 卷、没删的 NAT GW、僵尸 RDS。每月一次扫一遍,能省下肉眼可见的钱。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1. 未关联实例的 Elastic IP(每个 $3.6/月空挂)
aws ec2 describe-addresses --query "Addresses[?AssociationId==null].[PublicIp,AllocationId]" \
--output table --region us-west-2

# 2. 未挂载实例的 EBS 卷
aws ec2 describe-volumes --filters Name=status,Values=available \
--query "Volumes[].[VolumeId,Size,VolumeType,CreateTime]" \
--output table --region us-west-2

# 3. 旧版本的 EBS 快照
aws ec2 describe-snapshots --owner-ids self \
--query "Snapshots[?StartTime<'2025-01-01'].[SnapshotId,VolumeSize,StartTime]" \
--output table --region us-west-2

# 4. 当前运行的 NAT GW(每个 $32/月起 + 流量费)
aws ec2 describe-nat-gateways --filter Name=state,Values=available \
--query "NatGateways[].[NatGatewayId,VpcId,SubnetId]" \
--output table --region us-west-2

# 5. 未停的 RDS 实例
aws rds describe-db-instances \
--query "DBInstances[?DBInstanceStatus=='available'].[DBInstanceIdentifier,DBInstanceClass,MultiAZ]" \
--output table --region us-west-2

逐项核对:还在用就留,不在用就删。设个 cron 每月 1 号跑一次,结果发邮箱。

11.3 Trusted Advisor:免费的成本与安全自检

Trusted Advisor 是 AWS 自带的自动检查工具,免费版覆盖 6 项核心:低利用率 EC2、未关联 EIP、IAM 用户没开 MFA、Security Group 开 0.0.0.0/0、S3 桶公开访问、Service Limits 接近上限。控制台 → Trusted Advisor 一眼看到红黄绿。完整套件(涵盖 100+ 检查项)需要 Business Support 计划。

新建账户后立刻去看一眼 Trusted Advisor,把红色项处理掉。这一步常常能挖出注册时图省事开了的 root key、0.0.0.0/0 的 SG 等明显问题。

11.4 域名注册与 Route 53

10.0 假设你已有 Route 53 托管的域名。如果还没有,新人最快路径:

  1. AWS 控制台 → Route 53 → Domains → Register Domain,选域名(.com 约 $13/年)
  2. 注册成功 AWS 自动建好一个 Hosted Zone($0.50/月每个),并把 NS 指向它
  3. 在 Hosted Zone 里加 ALIAS A 记录指向 ALB DNS

域名也可以注册在第三方(Namecheap / Cloudflare),把 NS 改到 Route 53 即可。Hosted Zone 必要原因是:ALIAS A 记录是 Route 53 特有的,能直接指向 ALB / CloudFront 而不收 DNS 查询费,第三方 DNS 只能用 CNAME,根域名 example.com(无前缀)不能 CNAME,必须 ALIAS。

11.5 容器化路径:什么时候离开 EC2 + ASG

把 10 章的 EC2 + ASG 跑稳之后,下一步通常是迁到容器。判断信号:

  • 一个团队维护多个微服务,AMI 数量爆炸、Launch Template 难管 → ECS Fargate
  • 应用启动 < 5 秒、流量极不规律(白天高夜里几乎为 0) → Lambda
  • 用 Kubernetes 已经是公司标准 → EKS

ECS Fargate 是最容易的迁移路径:现有 Dockerfile 直接用,把 EC2 + ASG 换成 ECS Service + Fargate Task,ALB Target Group 改 target type 为 IP,其他几乎不变。Fargate 计费按 vCPU + 内存按秒算,跑稳几周后用 Compute Savings Plan 同样能省 30%+。

正文路径走完。附录把 10 个最高频的踩坑和日常用得最多的 CLI 集中放一起,方便事后回查。

附录

高频踩坑 Top 10

10 章的内容里散落着不少踩坑场景。下面 10 条是新手账户里最高频的,照着排查能解决 80% 的「为什么不通」「为什么这么贵」。

  1. 私有子网实例 apt update 卡死:路由表 0.0.0.0/0 没指 NAT GW,或 NAT GW 建在了私有子网(NAT GW 必须在公有子网,它自己要走 IGW 出网)
  2. EC2 SG source 写 CIDR 而非 SG,绕过 ALB:把 source 改成 ALB 的 SG ID,让 EC2 只接受 ALB 来的流量
  3. ACM 证书在 ALB 列表里看不到:证书 Region 错了。ALB 用同 Region 证书,CloudFront 必须用 us-east-1
  4. EBS 扩卷后 df -h 不变:只扩了控制面,没扩 OS。growpart + resize2fsxfs_growfs 三步走
  5. Multi-AZ 备库当读副本用:备库不可读,只用作故障切换。读写分离用只读副本(Read Replica)
  6. CloudFront 命中率长期 < 5%:Cache Policy 把 Cookie 全转发了,缓存键随用户爆炸。改用 Managed-CachingOptimized
  7. NAT GW 跨 AZ 流量费暴涨:单一 NAT GW 服务多 AZ,私有子网出网先跨 AZ。每 AZ 独立部署一个 NAT GW
  8. ASG 用 EC2 健康检查导致僵尸实例:应用挂了但 OS 还在,ASG 不替换。--health-check-type ELB
  9. Launch Template 改了版本但 ASG 用旧版:版本号绑死。Version=$Latest 让 ASG 自动跟随
  10. S3 签名 URL 时效设太长被泄露:上传 ≤ 15 分钟、下载 ≤ 5 分钟;前端「下载」动作每次重新请求后端签发

常用 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
# 列出运行中实例
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
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

RDS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 列实例
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

CloudWatch

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

# 触发 Logs Insights 查询
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

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

推荐进阶阅读

价格声明

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