---
url: >-
  /courses/cloud-platform-build-management/18-integrated-operations-project/index.md
---
# 第十八次课：综合项目——交付、验收、备份与故障恢复

## 进入本课环境

* `[Windows PowerShell] wsl ~`：打开本机默认的 WSL2 Ubuntu；先在这里检查交付目录和脚本，再进入远程环境。
* `[WSL] ssh root@你的ECS公网IP`：从 WSL 连接自己的 ECS；如果登录用户不是 `root`，请换成控制台显示的实际用户，看到远端提示符后再执行部署、验收、恢复和清理命令。

前十七次课分别练过 Linux 巡检、Nginx、备份恢复、Compose、数据库、负载均衡、云平台、Terraform、Kubernetes 和 Ansible。最后一次课不再追求“再认识一个新工具”，而是把已经学过的动作连成一个可以交付、可以验收、可以恢复、也可以安全撤场的小项目。

项目由两个服务组成：`web` 提供主页并反向代理 `/api/`，`api` 返回状态和发布版本。它不大，却故意包含运维交付最重要的几件事：明确边界、自动健康检查、用户路径验证、带校验和的备份、可恢复故障、同路回归和范围化清理。

::: tip 关键提醒
综合项目的完成标志不是“页面曾经打开过”，而是交付目录完整、配置可解释、服务达到健康、用户路径通过、备份能够验证、故障能够恢复、课程资源能够单独清理，并且每一步都有证据。
:::

学完这一课，做到六件事：

1. 读懂 Web、API、反向代理、健康检查和回环端口组成的项目边界。
2. 用 `docker compose config`、`up --wait`、`ps`、`curl` 和 `verify.sh` 分层验收。
3. 生成带 SHA-256 校验文件的站点备份，并在恢复前检查校验和与归档内容。
4. 处理页面内容漂移和 API 中断两类可恢复故障。
5. 用同一条用户路径完成回归，再只清理本课容器、网络和端口。
6. 把操作、截图、证明范围和限制写入 Word 报告并提交智慧职教。

## 18.1 项目完成标准

先审查交付目录，再部署并运行自动验收。主页和 API 都通过后，分别完成站点备份、页面漂移恢复和 API 中断恢复，最后只清理本项目。

整个过程使用同一组项目名、目录和回环端口。每次故障都要保留原始现象，恢复后沿原用户路径复测，不能用重建全部环境代替定位。

## 18.2 先划清系统边界

本课综合项目的完整请求链路如下：

```txt
Windows 浏览器
      │
      │ SSH 隧道
      ▼
ECS 127.0.0.1:18018
      │
      ▼
web（Nginx）
      ├── /              → 静态项目主页
      ├── /healthz       → Web 健康检测标记
      └── /api/status.json → api:80/status.json
                                 │
                                 └── 返回 status、service、release JSON
```

组件角色与边界划分：

| 实体对象 | 本课命名 | 网络暴露边界 | 承担职责 |
|---|---|---|---|
| Compose 项目 | `cloud-course-lab-18` | 课程专用项目 | 统一标识项目容器与网络 |
| Web 服务 | `web` | 绑定宿主机回环 18018 | 提供主页、健康端点与 API 反向代理 |
| API 服务 | `api` | 仅位于 Compose 内部网络 | 返回 JSON 状态数据 |
| 站点数据 | `data/site/` | 只读挂载给 Web | 可备份、可恢复的内容文件 |
| 备份目录 | `backup/` | 位于课程目录内部 | 保存归档压缩包与 SHA-256 校验和 |
| 既有业务 | 8000、18443 等端口 | 外部已有服务 | 仅排查端口冲突，切勿停止或修改 |

::: warning 安全边界
课程项目仅允许在 `/opt/xpk-course-demos/lesson-18`、`cloud-course-lab-18` 以及 `127.0.0.1:18018` 范围内操作。绝对禁止停止未知容器，不要暴露公网端口，切勿修改全局安全组，严禁全局删除容器、卷或镜像。
:::

## 18.3 从目录结构审查交付物

实训包包含 `compose.yaml`、`.env.example`、Nginx 配置、Web/API 应用文件和配套脚本（从智慧职教课程资源区下载 `lab-18-starter.zip` 解压到工作目录）。

```bash
[WSL] mkdir -p ~/cloud-course/lab-18
[WSL] cd ~/cloud-course/lab-18
[WSL] cp -r /path/to/lab-18-starter/* .
[WSL] find starter -maxdepth 3 -type f -print | sort
```

打印交付工程目录的文件树。预期可以看到 Compose 文件、环境变量模板、Nginx 配置、静态页面、API 模板，以及配套的部署、验收、备份与清理脚本。

```bash
[WSL] bash -n starter/*.sh
```

`bash -n` 只对 Shell 脚本做语法检查，不实际运行。它能帮你挑出拼写和语法错误，但不能代替真实的 Docker 运行测试。

```bash
[WSL] cp starter/.env.example starter/.env
```

从模板复制出一份本地运行环境变量。`.env.example` 可以随代码交付，而包含具体配置的 `.env` 切勿打入报告或提交。

```bash
[WSL] docker compose --env-file starter/.env \
  -f starter/compose.yaml config --quiet
```

校验 Compose 配置的变量展开与语法有效性。若静默退出且返回码为 0，说明 YAML 格式无误；配置合法，容器还没拉起，下一步才是 `up`。

## 18.4 Compose 怎样等待依赖真正就绪

API 定义健康检查：

```yaml
healthcheck:
  test:
    - CMD-SHELL
    - wget -q -O - http://127.0.0.1/status.json | grep -q '"status":"ok"'
  interval: 5s
  timeout: 3s
  retries: 6
```

检查容器内部的实际 JSON，而不是只检查进程存在。只有 `status=ok` 才算 API healthy。

Web 对 API 的依赖写成：

```yaml
depends_on:
  api:
    condition: service_healthy
```

Docker 官方文档说明，普通启动顺序只保证容器进入 running，不保证应用已经 ready；`service_healthy` 会让依赖方等待健康检查通过。

```yaml
ports:
  - 127.0.0.1:${COURSE_PORT}:80
```

宿主机入口明确绑定 `127.0.0.1`。从 Windows 浏览器访问时使用 SSH 隧道，不为临时演示扩大公网暴露。

```yaml
volumes:
  - ./data/site:/usr/share/nginx/html:ro
```

站点目录通过只读 bind mount 进入容器。容器可以读取页面，却不能从容器内改写宿主机课程内容；恢复操作在受控脚本中完成。

{{guided-demo:lesson-18-delivery-chain}}

## 18.5 部署后要经过四层验收

```bash
[ECS] bash deploy.sh
```

脚本先运行 Compose 配置检查，再执行 `docker compose up -d --wait --wait-timeout 60`。`--wait` 会等服务达到 running 或 healthy，超时则返回失败。

```bash
[ECS] docker compose --env-file .env ps
```

查看两个服务的状态。预期 `api` 与 `web` 均为 healthy，只有 Web 显示 `127.0.0.1:18018->80/tcp`。

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

检查宿主机监听。预期地址是 `127.0.0.1:18018`，不是 `0.0.0.0:18018`。

```bash
[ECS] curl -fsS http://127.0.0.1:18018/api/status.json
```

沿 Web 反向代理访问 API。预期 JSON 同时包含 `"status":"ok"` 和 `"release":"lesson18-v1"`。

```bash
[ECS] bash verify.sh
```

自动验收继续检查项目结构、回环绑定、容器健康、主页标题、健康契约和 API 发布标记。任何关键项失败都返回非 0。

在 WSL 中建立 SSH 隧道，把远端回环端口映射到本地：

```bash
[WSL] ssh -N -L 18018:127.0.0.1:18018 root@你的ECS公网IP
```

这条命令保持运行期间，Windows 浏览器访问 `http://127.0.0.1:18018/` 即可打开远端项目主页。终止 SSH 命令只会关闭隧道，不会停止远端容器。真实环境打开后的效果如下：

页面打开只是其中一层证据。图中 `API ready` 来自浏览器实际请求 `/api/status.json`；如果 API 依赖中断，这一张旧截图不能代替重新检查。

## 18.6 备份为什么还要有校验和

```bash
[ECS] bash backup.sh
```

脚本把 `data/site/` 打成时间戳归档，并在同一目录生成 `.sha256` 文件。输出中的 `OK` 表示刚生成的归档与校验记录一致。

```bash
[ECS] tar -tzf backup/lesson18-site-时间戳.tar.gz
```

`-t` 列出归档内容，`-z` 处理 gzip，`-f` 指定归档文件。预期只有 `site/` 和 `site/index.html`；恢复前先看清“里面是什么”。

```bash
[ECS] cd backup
[ECS] sha256sum -c lesson18-site-时间戳.tar.gz.sha256
[ECS] cd ..
```

校验文件记录的是归档的相对文件名，因此先进入 `backup/` 再验证。输出 `OK` 能发现传输或存储后的字节变化，但不能说明备份内容没有遗漏，也不能保证恢复流程可用。

::: tip 关键提醒
备份是“保存了什么”，恢复是“能否把目标状态带回来”。只有归档、校验和、恢复过程和恢复后的用户路径都留下证据，才能构成可验证的恢复流程。
:::

## 18.7 页面仍返回 200，也可能已经漂移

```bash
[ECS] bash inject-fault.sh
```

脚本只替换本课 `data/site/index.html`，并把原文件保存在课程目录中。漂移页面仍有 HTML 和标题，Nginx 也继续运行。

```bash
[ECS] bash verify.sh
```

此时 API 与 Web 仍 healthy，但主页缺少 `lesson18-project-ok`，因此出现 `[FAIL] 主页内容不符合预期`，退出码为 1。

```bash
[ECS] bash restore.sh \
  backup/lesson18-site-时间戳.tar.gz
```

恢复脚本只接受本项目 `backup/` 内的归档，先验证 SHA-256，再检查归档路径没有越界，在临时目录解包并确认健康契约，最后安装页面并自动运行 `verify.sh`。

真实备份和恢复结果：

完整记录依次展示归档、查看内容、注入页面漂移、验收失败和恢复回归。时间戳来自本次真实捕获，下面三张单命令卡用于放大其中的校验、漂移和恢复结果。

`OK` 表示刚生成的归档与校验记录一致，后两行给出归档和校验文件的位置。此时只能确认“文件已经生成且字节一致”，还不能确认它能恢复页面。

输出清晰地显示了故障边界：API 和 Web 容器仍为 healthy，API 也返回正确版本，只有主页内容检查失败。因此修复对象应是站点页面，而不是 Docker 服务或 API。

恢复命令先再次得到校验 `OK`，随后报告页面恢复，最后主页与 API 同时通过。这里完成的是本课静态页面恢复流程，不包含数据库、镜像或整机恢复。

## 18.8 API 中断怎样从 504 缩小范围

```bash
[ECS] docker compose --env-file .env stop api
```

只停止本课 API 容器。Web 容器继续运行，便于观察依赖中断后的真实用户现象。

```bash
[ECS] curl --max-time 8 -sS -o /dev/null \
  -w 'http=%{http_code}\n' \
  http://127.0.0.1:18018/api/status.json
```

`--max-time 8` 防止无限等待，`-o /dev/null` 丢弃响应体，`-w` 只打印状态码。此时会看到 `http=504`：Web 收到了请求，但在等待上游 API 时超时。

```bash
[ECS] docker compose --env-file .env \
  logs --since 30s --tail 10 web
```

查看 Web 最近日志。真实日志包含 `upstream timed out while connecting to upstream`，把范围从“浏览器打不开 API”缩小到 Web 到 API 的上游连接。

```bash
[ECS] bash verify.sh
```

此时脚本同时报告 API 容器缺失和 API 内容不符合预期。Web 自身 healthy、主页仍可读，但完整用户路径还没恢复。

```bash
[ECS] docker compose --env-file .env start api
```

只恢复故障对象。不要重启 Docker 服务，也不要重建正常 Web。

```bash
[ECS] bash verify.sh
```

等待 API healthy 后，沿原路径重新验收。只有 API 状态、主页和代理请求全部通过，才能宣布恢复。

真实故障、日志、恢复与清理结果：

这组记录从停止 API 一直保留到 `cleanup.sh`。HTTP 504 和上游超时日志描述服务链路的现象；查询日志的命令本身是否返回非 0，原始记录没有单独保存，因此卡片只标注“未记录退出码”。

这一条 `curl` 只证明用户路径返回 504：Web 入口仍能接受请求，但没有及时从上游得到响应。仅凭状态码还无法判断是 API 停止、内部网络异常还是代理配置错误。

`while connecting to upstream` 把范围缩小到 Web 与内部 API 之间；请求路径与上一张图相同，使状态码和内部日志可以相互印证。内部地址已遮盖，日志本身仍不能解释 API 为何停止。

恢复后没有换一条更容易通过的检查，而是再次运行同一个 `verify.sh`。四项 PASS 覆盖两项容器、主页和代理 API；它证明本次回归成功，不替代持续监控。

{{chat-lab:lesson-18-incident-review}}

## 18.9 故障处理记录应该怎样写

| 阶段 | 本课证据 | 写法 |
|---|---|---|
| 范围 | 项目名、目录、端口、已有业务 | 只处理 lesson-18 |
| 基线 | 两服务 healthy，主页/API 通过 | 记录故障前状态 |
| 现象 | 页面契约失败或 API 504 | 保留原始退出码 |
| 假设 | 内容漂移；API 上游不可达 | 必须可以被下一条检查证伪 |
| 定位 | 页面 marker；Web upstream 日志 | 不只写“网络问题” |
| 修复 | 从已验证归档恢复；启动 API | 只改变故障对象 |
| 回归 | 再运行同一 `verify.sh` | 与故障前证据可比 |
| 清理 | Compose 项目为 0，18018 释放 | 同时核对既有业务仍在 |

“重启后好了”不是完整报告，因为它没有说明故障对象、证据、修改范围和回归路径。

## 18.10 清理也是交付的一部分

```bash
[ECS] bash cleanup.sh
```

脚本在本项目目录执行 `docker compose down --remove-orphans`，只删除本课容器和默认网络，并检查 `18018` 已释放。

```bash
[ECS] docker ps -a \
  --filter label=com.docker.compose.project=cloud-course-lab-18
```

按 Compose 项目标签检查残留。预期没有本课容器。

```bash
[ECS] ss -lnt | grep -E ':(8000|18443) '
```

复核预先标记的既有业务端口，确认其仍保持原状态。这条命令不能证明业务功能完整，但能发现本课清理是否误停明显监听。

::: warning 安全边界
不要把清理改成 `docker system prune -a`、删除所有容器、删除全部网络或停止 Docker。课程资源已经有项目名、目录和端口，清理范围也必须使用这些边界。
:::

## 18.11 报告和课程结束检查

完整步骤见[实训 18：综合运维交付与恢复](./lab-18.md)。

提交文件名：

```txt
班级_学号_姓名_第18次课_综合运维项目报告.docx
```

只上传一个 Word 到智慧职教“第18次课”。报告以操作记录和截图为主，每张图旁边写清环境、命令、关键结果、能够证明什么、不能证明什么。不要提交 `.env`、公网地址、SSH 配置、密钥、验证码、完整容器 ID、原始备份、完整日志、账号、订单或余额。

{{reflection-checkpoint:lesson-18-project-ready}}

### 18.11.1 结课回归测评

这组题跨越 Compose 标准化、Kubernetes Service 与发布、Ansible 幂等和综合恢复。重点不是回忆命令拼写，而是判断一条证据能把故障范围缩小到哪里，以及恢复后应沿哪条路径复测。

{{assessment:stage-18-regression}}

## 18.12 第十八次课小测

{{assessment:lesson-18-check}}

## 18.13 小结

* 目录、配置、运行状态、健康和用户路径是不同层次的交付证据。
* `service_healthy` 与 `up --wait` 用健康检查约束依赖启动，但仍需用户路径验收。
* 回环端口配合 SSH 隧道能完成临时访问，不必扩大公网暴露。
* SHA-256 能发现归档字节变化，不能替代恢复演练。
* 页面 HTTP 200 仍可能内容漂移，业务标记用于识别预期版本。
* API 中断时，504 和 Web 上游日志能把范围缩小到依赖链。
* 恢复后要沿原路径回归；清理只处理本项目并复核既有业务。
* 运维交付的最后成果不是命令数量，而是一条他人可以复查的证据链。

## 18.14 资料来源

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

* [Docker Docs：Control startup and shutdown order in Compose](https://docs.docker.com/compose/how-tos/startup-order/)
* [Docker Docs：docker compose up](https://docs.docker.com/reference/cli/docker/compose/up/)
* [Docker Docs：docker compose down](https://docs.docker.com/reference/cli/docker/compose/down/)
* [Docker Docs：Bind mounts](https://docs.docker.com/engine/storage/bind-mounts/)
* [NGINX 官方文档：ngx\_http\_proxy\_module](https://nginx.org/en/docs/http/ngx_http_proxy_module.html)
* [curl 官方手册](https://curl.se/docs/manpage.html)
* [GNU tar manual](https://www.gnu.org/software/tar/manual/tar.html)

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