---
url: /courses/cloud-platform-build-management/08-nginx-load-balancing/index.md
---
# 第八次课：Nginx 反向代理与负载均衡

## 进入本课环境

* `[Windows PowerShell] wsl ~`：打开本机默认的 WSL2 Ubuntu；出现 `用户名@主机名:~$` 后再执行标为 `[WSL]` 的命令。
* `[WSL] ssh root@你的ECS公网IP`：从 WSL 连接自己的 ECS；如果登录用户不是 `root`，请换成控制台显示的实际用户，看到远端提示符后再执行标为 `[ECS]` 的命令。

前几次课里，浏览器始终连接某一个 Web 服务。如果同一站点有两个后端实例，需要在它们前面增加一个稳定入口：客户端只认识负载均衡器，负载均衡器再选择后端。

本课运行三个隔离容器：

* `lb`：Nginx 负载均衡入口，只绑定 ECS 的 `127.0.0.1:18008`；
* `backend-a`：返回页面标识 `backend-a`；
* `backend-b`：返回页面标识 `backend-b`。

连续请求看到两个不同标识时，说明请求确实到过不同后端。停止其中一个后端，入口仍能返回另一个后端的页面；恢复后再次观察到两个标识，才算走完一轮完整的故障处理验证。

::: warning 安全边界
只允许停止 `cloud-course-lab-08` 中的 `backend-b`。不要停止服务器现有 Nginx、所有容器或其他 Compose 项目，也不要把 18008 改为公网绑定。
:::

## 8.1 反向代理站在谁的角度

**反向代理**（代表后端服务接收客户端请求，再把请求转发到内部服务的中间层） 对浏览器暴露一个统一入口。用户访问时只认这个公开地址，通常不必知道后端有多少台机器、内部 IP 是什么。

完整请求路径如下：

```txt
Windows 浏览器
  -> WSL SSH 隧道
  -> ECS 127.0.0.1:18008
  -> lb:80
  -> backend-a:80 或 backend-b:80
```

正向代理替客户端去访问外网资源，反向代理则是替服务端接收并分发外部请求。本课聚焦在反向代理场景。

::: tip 关键提醒
负载均衡器本身存活，后端不一定正常；配置了两个后端，流量也不一定平摊过去。必须结合静态配置、后端直连测试与入口访问日志综合判断。
:::

## 8.2 `upstream` 定义后端组

本课的核心 Nginx 配置：

```nginx
upstream course_backends {
    server backend-a:80 max_fails=1 fail_timeout=5s;
    server backend-b:80 max_fails=1 fail_timeout=5s;
}
```

`upstream course_backends` 为后端服务组命名。两条 `server` 指向 Compose 服务的名称，避免写死容器的动态 IP。

在未指定额外调度算法时，Nginx 默认采用轮询（Round Robin）。`max_fails=1 fail_timeout=5s` 规定在 5 秒内如果某后端连续失败 1 次，就暂时将其剔除；这属于请求驱动的被动熔断，不是主动探针检测。

入口代理配置：

```nginx
location / {
    proxy_pass http://course_backends;
    proxy_connect_timeout 2s;
    proxy_read_timeout 5s;
    proxy_next_upstream error timeout http_502 http_503 http_504;
    proxy_next_upstream_timeout 6s;
    proxy_next_upstream_tries 2;
}
```

`proxy_pass` 把请求转发给后端组。`proxy_connect_timeout 2s` 限制连接单个后端的等待时间，`proxy_next_upstream_timeout 6s` 限制本次请求在候选后端之间重试的总时间。没有这两个边界时，停止后端后的第一条请求可能等待很久，终端看上去像“卡住了”。

## 8.3 启动前先检查配置和端口

进入 **ECS** 上的项目目录：

```bash
[ECS] mkdir -p ~/cloud-course/lab-08
[ECS] cd ~/cloud-course/lab-08
[ECS] cp -r starter/* .
```

实训包包含 `compose.yaml`、`lb.conf` 和两个后端目录（从智慧职教课程资源区下载 `lab-08-starter.zip` 解压）。

解析 Compose 配置：

```bash
[ECS] docker compose config --services
```

输出应包含 `backend-a`、`backend-b`、`lb`。这能证明 service 定义正确。

```bash
[ECS] docker compose config --quiet
```

命令静默无输出说明 YAML 语法合法。而 Nginx 内部的配置文件语法，等容器启动后再用 `nginx -t` 进一步校验。

```bash
[ECS] ss -lnt 'sport = :18008'
```

启动前预期无输出。如果端口已被占用，请先暂停，并按课程分配重新确认端口。

启动容器服务：

```bash
[ECS] docker compose up -d
[ECS] docker compose ps
```

`backend-a`、`backend-b` 只显示内部 80；`lb` 显示 `127.0.0.1:18008->80/tcp`。

检查 Nginx 配置：

```bash
[ECS] docker compose exec -T lb nginx -t
```

预期出现 `syntax is ok` 和 `test is successful`。这能证明语法和引用文件可加载，后端页面能不能访问还得接着看。

分别从负载均衡器直连两个后端：

```bash
[ECS] docker compose exec -T lb wget -qO- http://backend-a/
```

输出应包含 `backend-a`。

```bash
[ECS] docker compose exec -T lb wget -qO- http://backend-b/
```

输出应包含 `backend-b`。两次直连把“后端本身不可达”和“负载算法异常”分开。

{{guided-demo:lesson-08-request-path}}

## 8.4 用多次请求观察轮询

一次请求只能证明”某个后端返回了响应”，请求是不是真的分到了不同后端还得继续验证。

Bash `for` 循环的格式是 `for 变量 in 列表; do 命令; done`——`i` 依次取列表中的每个值，`$i` 在循环体内展开为当前值。下面的循环把 `i` 从 1 走到 6，每次请求入口并提取后端标识：

```bash
[ECS] for i in 1 2 3 4 5 6; do
  printf 'request-%s -> ' “$i”
  curl -sS http://127.0.0.1:18008/ | grep -o 'backend-[ab]'
done
```

预期交替或近似交替出现 `backend-a`、`backend-b`。默认轮询表示按请求选择后端，但连接复用、失败重试和配置变化都会影响实际序列；验收重点是两个标识都出现，不要求死记固定顺序。

```bash
[ECS] curl -sSI http://127.0.0.1:18008/ | grep -i '^X-Course-Upstream:'
```

本课配置额外返回 `X-Course-Upstream`，用于教学观察。生产环境是否公开上游信息要根据安全策略决定。

## 8.5 单后端停止时入口会怎样

故障前保存基线：

```bash
[ECS] docker compose ps
```

只停止本项目的 `backend-b`：

```bash
[ECS] docker compose stop backend-b
```

再次连续请求：

```bash
[ECS] for i in 1 2 3 4; do
  curl --max-time 8 -sS http://127.0.0.1:18008/ | grep -o 'backend-[ab]'
done
```

预期请求仍返回 `backend-a`。`--max-time 8` 是客户端总等待上限；首次命中故障后端时，Nginx 会在配置的连接和重试时间内转向下一项可用后端。

查看最近日志：

```bash
[ECS] docker compose logs --since 30s --tail 20 lb
```

重点寻找 `connect() failed`、`upstream` 地址和最终 HTTP 状态。日志能说明负载均衡器怎样处理请求，不要因为看到一行错误就认定整个站点已经不可用。

恢复后端：

```bash
[ECS] docker compose start backend-b
```

先确认容器健康：

```bash
[ECS] docker compose ps
```

等待 `backend-b` 为 `healthy`，超过 `fail_timeout` 后再次执行多次请求。重新看到 `backend-a` 与 `backend-b`，表示后端已经回到请求链。

完整记录按“停止后端—重复请求—查看日志—启动后端—再次请求”的顺序展开。日志中出现 `connect() failed`，描述的是 Nginx 访问上游时遇到的故障，不代表 `docker compose logs` 这条查询命令执行失败。

四行 `backend-a` 与上方同一条循环命令直接对应，说明捕获时这些只读请求仍得到响应。它没有说明 Nginx 怎样完成切换，下一张日志才补上内部证据。

日志中的 `connect() failed` 是第一次上游连接失败，`upstream_status=502, 200` 表示同一请求先遇到失败，随后从另一候选后端取得成功响应。内部地址已经遮盖，这段日志也只对应捕获时的 GET 请求。

输出重新同时出现 `backend-a` 与 `backend-b`，证明恢复后的后端已经回到请求链。这个短序列只能证明两个后端都被选中过，不能用来比较性能或推断长期分配比例。

{{chat-lab:lesson-08-load-balancer-evidence}}

## 8.6 被动故障判断不是完整高可用

开源 Nginx 的上述配置依赖真实请求发现连接错误。它没有解决所有高可用问题：

* `lb` 仍然是单个入口，若它停止，两个后端都无法通过该入口访问；
* 两个后端必须共享或同步必要数据，否则用户可能看到不一致内容；
* 带副作用的请求不能随意重试，需要结合 HTTP 方法和应用幂等性；
* 主动健康检查、连接排空、会话保持和跨机故障切换需要进一步设计；
* 两个容器在同一台 ECS 上，不能抵御整台主机故障。

因此，本课结果应表述为“单个后端连接失败时，入口仍能把本次只读请求交给另一个后端”，而不是“系统已经永不宕机”。

## 8.7 常见现象的检查顺序

**`nginx -t` 失败**

先根据错误行号检查 `upstream`、分号和 `proxy_pass`。不要反复重启，配置无法加载时重启只会重复失败。

**两个后端直连都成功，入口返回 502**

查看 `lb` 日志和 `proxy_pass` 的组名，确认入口 location 使用的是 `course_backends`，再检查 Nginx 是否加载了修改后的配置。

**多次请求始终只有一个后端**

先确认另一后端健康并能直连，再检查是否存在 `ip_hash`、权重、失败计数或浏览器缓存。不要仅凭两次请求断定轮询失效。

**停止一个后端后入口也失败**

检查另一后端能否直连、`proxy_next_upstream` 条件和错误日志。若两个后端同时不可达，负载均衡器无法凭空生成业务响应。

## 8.8 分层实训与提交

完整步骤见[实训 08：负载均衡与后端故障恢复](./lab-08.md)。

**保底任务**

* 从配置中标出入口、upstream 组和两个后端；
* 区分配置检查、后端直连和入口请求三类证据；
* 根据给定日志判断请求停在哪一层。

**标准任务**

* 启动三服务项目并通过 `nginx -t`；
* 用连续请求观察两个后端；
* 停止 `backend-b`，记录入口、日志与恢复结果；
* 运行 `verify.sh`，清理项目并确认 18008 释放。

**挑战任务**

* 为 `backend-a` 设置权重 2，记录 12 次请求的分布；
* 改用 `least_conn`，说明它与轮询关注的指标差异；
* 画出“负载均衡器自身故障”还需要增加的组件。

提交前清理实验环境：

```bash
[ECS] docker compose down
[ECS] ss -lnt 'sport = :18008'
[ECS] rm -rf ~/cloud-course/lab-08
```

最后一条确认 18008 端口已释放。

提交：

```txt
班级_学号_姓名_第08次课_Nginx负载均衡报告.docx
```

只上传一个 Word 到智慧职教“第08次课”，不上传公网地址、SSH 配置、容器日志全集或其他项目信息。

{{reflection-checkpoint:lesson-08-load-balancer-ready}}

### 8.8.1 前八次课回归测评

这组题不重复本课术语，而是检查你能否把前八次课的环境定位、Web 链路、备份恢复、Compose 依赖和负载均衡证据重新调动起来。先独立作答；错题回到对应证据层复查，不靠背选项。

{{assessment:stage-08-regression}}

## 8.9 第八次课小测

{{assessment:lesson-08-check}}

## 8.10 小结

* 反向代理为客户端提供稳定入口，内部再选择后端。
* `upstream` 定义后端组，默认算法为轮询。
* 配置检查、后端直连、多次入口请求和日志回答不同问题。
* 单后端停止后，Nginx 可以把符合条件的请求交给另一后端。
* 恢复后必须重新观察两个后端，而不是只看容器启动。
* 单机三容器只能演示后端故障处理，不能替代跨主机高可用设计。

下一次课将进入阶段考核：从环境基线出发，独立定位并修复一组匿名化故障。

带入第 9 次课的不是某一条“万能修复命令”，而是一张简短工单：正常基线是什么、故障时哪一层先异常、你用了什么最小修改、修复后跑了哪些同路检查。阶段考核只提供现象，不提示故障层级，这张工单就是你的排查路线。

## 8.11 资料来源

以下资料在 2026-07-28 核对：

* [NGINX：Using nginx as HTTP load balancer](https://nginx.org/en/docs/http/load_balancing.html)
* [NGINX：ngx\_http\_upstream\_module](https://nginx.org/en/docs/http/ngx_http_upstream_module.html)
* [NGINX：ngx\_http\_proxy\_module](https://nginx.org/en/docs/http/ngx_http_proxy_module.html)

{{assistant-invite:lesson-08-finish}}
