---
url: /courses/cloud-platform-build-management/14-compose-standard-delivery/index.md
---
# 第十四次课：Compose 标准化交付

## 进入本课环境

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

第 6 次课已经用 Compose 组织过 Web 与 API。今天不再追求“多启动一个容器”，而是把项目整理成其他人拿到后也能看懂、部署、验收和清理的标准包。

::: tip 关键提醒
标准化交付不是只有 compose.yaml。完整发布包还要说明配置来源、运行边界、健康条件、验收路径、故障恢复、清理方法和不能打包的敏感文件。
:::

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

1. 解释标准 Compose 发布包中各文件的职责。
2. 使用 `docker compose config` 查看变量插值后的真实配置。
3. 区分容器 `running`、`healthy` 和用户路径可用。
4. 从一份完整目录运行 `deploy.sh`、`verify.sh` 和 `cleanup.sh`。
5. 通过第二次部署验证重复执行不会制造重复资源。

## 14.1 交付包要回答哪些问题

拿到目录的人应该能直接找到配置入口、公开变量模板、部署脚本、验收脚本和清理方法。实际变量留在本地，敏感文件不进入发布包。

接下来的检查围绕四件事展开：配置能否完整展开、服务是否真正健康、相同输入能否安全重复部署、清理是否只移除本项目。

## 14.2 标准包要让目录结构自己说话

本课的交付工程规范结构如下：

```txt
lab-14/
├── compose.yaml
├── .env.example
├── .gitignore
├── deploy.sh
├── verify.sh
├── cleanup.sh
├── api/status.json
├── web/index.html
└── nginx/default.conf
```

| 文件 / 目录 | 承担职责 | 准入交付包规范 |
|---|---|---|
| `compose.yaml` | 声明服务定义、依赖关系、健康检查、端口与挂载目录 | 是 |
| `.env.example` | 提供公开的变量占位符与教学默认值 | 是 |
| `.env` | 保存当前部署环境的实际变量值 | 否，仅在部署时由本地复制生成 |
| `.gitignore` | 防止 `.env` 与日志/证据材料被提交至代码库 | 是 |
| `deploy.sh` | 自动化脚本：先做配置检查，再启动并等待健康就绪 | 是 |
| `verify.sh` | 自动化脚本：分层校验静态配置、运行态、端口与 HTTP 接口 | 是 |
| `cleanup.sh` | 自动化脚本：仅清理本项目专属的容器与网络 | 是 |
| `api/`、`web/`、`nginx/` | 提供项目基础数据与代理入口配置文件 | 是 |

如果代码仍依赖“本地电脑里某个没有打包的隐藏文件”，接收者就无法复现部署。私钥、`.env` 实际值、数据库原始卷和全局日志不应进入交付包。

## 14.3 先展开，再相信 Compose 文件

Compose 支持 `${VARIABLE}` 插值。最终值可能来自 shell、`--env-file` 或项目 `.env`，因此只读 `compose.yaml` 还看不到真实模型。

```yaml
name: ${COMPOSE_PROJECT_NAME:-cloud-course-lab-14}

ports:
  - 127.0.0.1:${COURSE_PORT:?copy .env.example to .env}:80
```

第一行给项目名一个默认值。端口使用 `:?` 必填形式：没有 `COURSE_PORT` 时直接报错，避免空值悄悄变成错误配置；`127.0.0.1` 把入口限制在宿主机回环地址。

进入 **ECS** 本课目录：

```bash
[ECS] cd /opt/xpk-course-demos/lesson-14/release
```

进入课程专用发布目录。使用个人环境时，换成课程分配的路径。

```bash
[ECS] cp .env.example .env
```

创建当前部署的实际变量文件。复制后先检查项目名和端口，避免与同学或已有服务冲突。

```bash
[ECS] docker compose --env-file .env config --environment \
  | grep -E '^(COMPOSE_PROJECT_NAME|COURSE_PORT)='
```

只保留本课允许展示的两个插值变量。不要直接截图未经筛选的 `config --environment` 完整输出：它还可能带出当前 Shell 的 `SSH_CLIENT`、`SSH_CONNECTION` 或其他环境变量。

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

合并文件、解析变量并展开简写，输出 Docker Engine 将接收的规范化模型。官方文档把它定义为“parse, resolve and render”。

2026-07-28 在云主机 B 的隔离项目中实际展开：

{{guided-demo:lesson-14-package-walkthrough}}

## 14.4 running、healthy 和可用是三道检查

Compose 中两个服务都定义健康检查：

```yaml
healthcheck:
  test:
    - CMD-SHELL
    - wget -q -O - http://127.0.0.1/healthz | grep -q web-ok
  interval: 5s
  timeout: 3s
  retries: 6
  start_period: 5s
```

容器内每 5 秒访问一次 `/healthz`；3 秒无结果算失败，启动前 5 秒为宽限期，连续失败会进入 unhealthy。检查命令必须真实覆盖应用就绪条件，不能只写永远返回 0 的占位命令。

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

web 等待 api 进入 healthy 后再启动。它优化了启动顺序，但不意味着运行期间 api 故障时 web 会自动重启或业务一定可用。

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

脚本先执行 `config --quiet`，随后使用 `up -d --wait --wait-timeout 60`。Docker 官方说明 `--wait` 会等待服务达到 running 或 healthy，并自动使用后台模式。

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

查看容器状态和端口。`Up (healthy)` 是容器健康的证据，还要继续验证用户入口。

```bash
[ECS] curl --max-time 5 -fsS http://127.0.0.1:18014/
```

检查主页是否返回预期标题。`--max-time` 限制等待，`-f` 让 HTTP 错误返回失败，`-sS` 保持输出简洁但显示错误。

```bash
[ECS] curl --max-time 5 -fsS http://127.0.0.1:18014/api/status.json
```

通过 web 代理访问 API。预期包含 `"status":"ok"`，它覆盖了入口 Nginx、项目网络、API 容器和静态状态文件。

## 14.5 第二次部署回答“能否安全重复”

部署脚本不是只能成功一次。第二次运行前，先记录容器 ID：

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

显示 api 容器完整 ID。对 web 再执行一次并记录前 12 位即可，报告不需要提交完整 ID。

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

再次执行同一个部署脚本。配置和镜像没有变化时，Compose 保持现有容器并等待健康；若配置发生变化，`up` 可能重建受影响的服务。

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

按第一次的路径再次验收。重复部署通过的判断包括：

* 项目名与端口没有变化；
* 没有出现第二套同名服务；
* 容器保持健康；
* 主页和 API 仍返回同一发布版本；
* 自动验收再次通过。

## 14.6 可恢复故障：缺少 `.env`

本课故障只操作发布目录中的变量文件：

```bash
[ECS] mv .env .env.saved
```

把当前 `.env` 保存为 `.env.saved`，保留原值以便恢复。不要打开后随意改端口，也不要删除文件。

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

预期脚本立即提示“缺少 .env”并退出，不会改变正在运行的容器。这是“尽早失败”：在配置边界不清楚时拒绝继续。

```bash
[ECS] mv .env.saved .env
```

恢复原变量文件。

```bash
[ECS] ./deploy.sh && ./verify.sh
```

重新部署并按原路径验收。预期容器健康、主页和 API 通过。

::: warning 安全边界
不要通过创建空 `.env`、临时改成已有业务端口或删除未知容器来“消除报错”。先恢复经过验证的原文件，再重新展开配置。
:::

## 14.7 发布包检查清单

打包前按顺序检查：

| 检查项 | 合格证据 | 常见问题 |
|---|---|---|
| 目录完整 | `find` 能列出 Compose、配置、脚本和应用文件 | 漏掉 Nginx 配置或状态文件 |
| 配置可展开 | `docker compose config --quiet` 退出 0 | 变量缺失、路径失效 |
| 敏感文件排除 | `.gitignore` 与压缩包清单都不含 `.env` | 把实际端口或密钥当示例提交 |
| 健康检查有效 | api/web 均 healthy | 检查命令永远成功 |
| 入口最小暴露 | `host_ip: 127.0.0.1` | 写成 `0.0.0.0` |
| 重复部署 | 第二次部署和验收通过 | 脚本只适合首次运行 |
| 清理可控 | 只 `down` 当前项目 | 按镜像名或全局删除容器 |

{{chat-lab:lesson-14-delivery-evidence}}

## 14.8 清理和提交

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

脚本读取本目录 `.env`，只执行当前 Compose 项目的 `down --remove-orphans`，随后确认没有项目容器。它不删除镜像、命名卷或其他项目。

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

检查本课回环端口是否释放。预期只有表头，没有监听行。

```bash
[ECS] docker ps --format '{{.Names}} {{.Ports}}'
```

复核既有业务容器仍在运行。只核对本人环境或课程指定范围，不停止未知容器。

完整步骤见[实训 14：Compose 标准发布包与重复部署](./lab-14.md)。

提交：

```txt
班级_学号_姓名_第14次课_Compose标准化交付报告.docx
```

只提交一个 Word 到智慧职教“第14次课”。不提交 `.env`、日志全集、容器导出包、密钥或服务器地址。

{{reflection-checkpoint:lesson-14-delivery-ready}}

## 14.9 第十四次课小测

{{assessment:lesson-14-check}}

## 14.10 小结

* 标准发布包包含配置、示例变量、应用文件、部署、验收和清理脚本。
* `docker compose config` 展示变量与简写解析后的真实模型。
* running、healthy 和用户路径可用是不同证据。
* 第二次部署用于验证脚本可重复，不表示所有升级都不会重建。
* 缺少 `.env` 时应尽早失败，恢复原值后重新展开和验收。
* 清理只针对当前项目，结束后检查端口和既有业务。

下一次课将把声明式思想带到 K3s，认识节点、Pod、Deployment 与 Service。

## 14.11 资料来源

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

* [Docker：docker compose config](https://docs.docker.com/reference/cli/docker/compose/config/)
* [Docker：Compose 变量插值](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/)
* [Docker：环境变量优先级](https://docs.docker.com/compose/how-tos/environment-variables/envvars-precedence/)
* [Docker：Compose services 与 healthcheck](https://docs.docker.com/reference/compose-file/services/)
* [Docker：docker compose up 与 --wait](https://docs.docker.com/reference/cli/docker/compose/up/)
* [Docker：Compose CLI 与项目名](https://docs.docker.com/reference/cli/docker/compose/)

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