这个项目是一个简单的用来验证 Google Cloud Run 的 Load Balancer 在就绪探针(Readiness Probe)失败时的流量切断行为的微服务 Demo。
main.py: FastAPI 应用源代码。Dockerfile: 用于构建容器镜像。requirements.txt: Python 依赖。
应用启动时会自动尝试从 Google Cloud Metadata Server 获取真实的 Instance ID。
- Cloud Run 环境:返回真实的 32 位实例 ID。
- 本地环境:自动降级为
local-dev-[suffix],带有随机后缀,方便本地多进程调试。
我们推荐使用 Python 虚拟环境来运行项目。
# 创建并激活虚拟环境 (如果尚未创建)
python3 -m venv venv
source venv/bin/activate
# 安装依赖
pip install -r requirements.txt
# 启动应用
# 方式 A: 直接运行 Python 文件
python main.py
# 方式 B: 使用 Uvicorn 命令 (支持热重载)
uvicorn main:app --reload --port 8080# 构建镜像
docker build -t readiness-demo .
# 运行容器
docker run -p 8080:8080 readiness-demo这是一个**“破坏实验”。 我们将模拟一个服务实例突然“生病”(就绪探针失败),然后观察 Cloud Run 的负载均衡器(Load Balancer)是否足够聪明,能够迅速切断**发往该实例的流量,保护业务不受影响。
sequenceDiagram
participant User as 👤 用户
participant LB as ⚖️ Cloud Run LB
participant Instance as 📦 服务实例
Note over Instance: 🟢 状态: Healthy
User->>LB: 请求 (GET /)
LB->>Instance: 转发请求
Instance-->>LB: 200 OK
LB-->>User: 200 OK
Note over Instance: 🤢 突然生病! (装病返回 503)
User->>LB: 请求 (GET /)
Note right of LB: 🚫 保安: "这人病了"<br/>停止派单 (切断流量)
LB--xInstance: (流量被切断)
LB-->>User: 503 Service Unavailable<br/>(或转发给其他健康实例)
Note over Instance: � 药效过了 (满血复活 200)
User->>LB: 请求 (GET /)
LB->>Instance: 恢复转发
Instance-->>LB: 200 OK
首先,我们要确保服务正在正常运行。
# 1. 检查业务接口 (应该返回 200)
curl http://localhost:8080/
# ✅ 预期输出: {"status":"ok", "served_by":"..."}
# 2. 检查就绪探针 (应该返回 200)
curl http://localhost:8080/health/ready
# ✅ 预期输出: {"status":"ready", ...}我们要人为制造一个“事故”。调用这个接口,让当前实例在接下来的 30秒 内假装自己“坏掉了”(Readiness Probe 返回 503)。
curl -X POST "http://localhost:8080/admin/fail?duration=30"现象预告:这就好比告诉门口的保安(Load Balancer):“我肚疼,别给我派活了!”
这时候,如果我们直接问这个实例“你还好吗?”,它会说“不好”。
curl -v http://localhost:8080/health/ready
# ⚠️ 预期输出: HTTP/1.1 503 Service Unavailable关键点来了: 在 Cloud Run 的真实环境中,一旦 Load Balancer 收到这个 503 信号,它就会立刻停止把外部的用户请求转发给这个实例。 如果这是唯一的实例,用户会看到 503;如果有多个实例,用户流量会被自动导向其他健康的实例,实现无感故障转移。
耐心等待 30 秒(就像等待药效过去)。再次检查,服务应该已经生龙活虎了。
curl -v http://localhost:8080/health/ready
# ✅ 预期输出: HTTP/1.1 200 OK除了直接“装病”(503),另一种常见的故障是**“反应迟钝”**。 我们将模拟服务响应变慢,导致 Readiness Probe 超时。
sequenceDiagram
participant User as 👤 用户
participant LB as ⚖️ Cloud Run LB
participant Instance as 📦 服务实例
Note over Instance: 🟢 状态: Healthy
User->>LB: 请求 (GET /)
LB->>Instance: 转发请求
Instance-->>LB: 200 OK
Note over Instance: 💤 喝了安眠药! (响应延迟 > Timeout)
LB->>Instance: 发送探针 (GET /health/ready)
Note right of LB: ⏱️ LB 等得花都谢了 (Timeout)<br/>标记实例为 Unhealthy
LB--xInstance: (流量被切断)
User->>LB: 请求 (GET /)
LB-->>User: 503 Service Unavailable<br/>(或转发给其他健康实例)
Note over Instance: 🟢 药劲过了 (恢复秒回)
LB->>Instance: 发送探针
Instance-->>LB: 200 OK
LB->>Instance: 恢复业务流量转发
我们要让这位实例变得反应迟钝。调用接口,强制它在接下来的 30秒 内,每次回答 Readiness Probe 都要思考 5秒 人生。
# 模拟延迟 5秒,持续 30秒
curl -X POST "http://localhost:8080/admin/delay?delay=5&duration=30"现象预告:这就像门口的保安(LB)问:“在吗?”,实例半天不回话(超时),保安只能当它不在了。
如果 Cloud Run 上配置的 Readiness Probe Timeout 设置为 1秒(默认值通常较小),那么 5秒 的延迟对保安来说就是**“失联”**。
保安会毫不留情地切断发往该实例的流量。
等待 30秒后,“安眠药”失效,实例恢复秒回状态,保安确认它又活过来了,流量恢复。
为了方便排查问题,应用已经在关键节点添加了结构化日志。您可以在 Cloud Run 的 Logs 标签页或使用 Cloud Logging 查询。
| 场景 | 日志级别 | 关键词 | 说明 |
|---|---|---|---|
| 应用启动 | INFO | 🚀 应用启动 |
包含真实的 Instance ID |
| 故障注入 | WARNING | 💣 管理操作 |
记录 503 注入操作 |
| 延迟注入 | WARNING | 🐢 管理操作 |
记录延迟注入操作 |
| 探针失败 | WARNING | 🚫 就绪探针 |
记录当前正在返回 503 |
| 探针延迟 | INFO | 🐢 就绪探针 |
记录当前正在 Sleep |
resource.type="cloud_run_revision"
textPayload:"Readiness Probe"
jsonPayload.instanceId="YOUR_INSTANCE_ID"通过日志,您可以精确对应的知道是哪个实例在什么时间点开始由于什么原因被标记为了不健康。