---
url: /courses/cloud-platform-build-management/05-lnmp-wordpress/index.md
---
# 第五次课：用 LNMP 部署 WordPress

## 进入本课环境

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

第四次课备份的是一组静态文件。静态页面只要 Nginx 找到文件就能返回；WordPress 还要执行 PHP、读取数据库，任何一环断开都可能让浏览器只看到空白页或 502。

本课把“云端课程学习站”升级为 WordPress 版，并留下三类证据：

* Nginx、PHP-FPM、MariaDB 的运行状态和端口关系；
* WordPress 安装页面或首页的浏览器结果；
* 一次 PHP 服务中断后的日志、修复和回归验证。

::: warning 安全边界
数据库只在 Compose 内部网络中提供服务，不映射宿主机 3306 端口。实验 Web 端口默认绑定 ECS 的 127.0.0.1，并通过 SSH 隧道访问；不要为了方便把数据库或管理页面直接暴露到公网。
:::

## 5.1 动态页面多走了哪几步

**LNMP**（Linux、Nginx、MariaDB/MySQL 与 PHP 的组合，用于运行动态 Web 应用） 中各组件承担不同职责：

* **Linux** 提供进程、文件、网络和权限环境。
* **Nginx** 接收 HTTP 请求，直接返回图片、CSS 等静态文件，并把 `.php` 请求交给 PHP-FPM。
* **PHP-FPM** 执行 WordPress 的 PHP 代码。
* **MariaDB/MySQL** 保存文章、用户、配置等结构化数据。
* **WordPress** 是运行在这组环境上的应用，不是单独的 Web 服务器。

一次首页请求大致经过：

```txt
浏览器
  -> SSH 隧道或安全组允许的 HTTP 路径
  -> Nginx :80
  -> PHP-FPM :9000
  -> MariaDB :3306
  -> PHP 生成 HTML
  -> Nginx 返回响应
```

这里的 9000 和 3306 是容器网络内部端口。学生从浏览器访问的只有 Web 入口，不需要、也不应该直接连接它们。

::: tip 关键提醒
502 通常表示 Nginx 已经收到请求，却没能从上游得到有效响应。排查应转向 PHP-FPM 地址、端口、状态和日志，而不是先重装全部组件。
:::

## 5.2 用隔离容器保护共享服务器

LNMP 既可以直接装在 Ubuntu 系统里，也能用容器拆开。本课选择 Docker Compose，倒不是因为“容器更高级”，而是出于实训环境考虑：

* 避免污染宿主机既有业务；
* 明确限定各组件的资源上限；
* 用项目名隔离容器、网络与数据卷；
* 实训结束后能一键清理，不留垃圾。

在学生个人的 ECS 上，这也是推荐做法。即使运行在容器里，Nginx、PHP-FPM 和 MariaDB 的实际服务、端口占用、日志排查与数据持久化逻辑依然完全一致。

动手前，先在 **ECS** 跑一轮只读检查：

* `free -h`：看 `available` 剩余内存，确认够不够跑数据库和 PHP；
* `df -hT /`：确认磁盘空间，防止拉取镜像或写入日志时爆满；
* `docker ps`：核对已有容器，别误停了其他服务；
* `ss -lnt 'sport = :18005'`：确认课程 Web 入口端口没被占用。

```bash
[ECS] free -h
[ECS] df -hT /
[ECS] docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
[ECS] ss -lnt 'sport = :18005'
```

如果最后一条命令没有任何输出，说明 18005 端口此时是干净的。

## 5.3 读懂 Compose 中的三项服务

实训包准备好了 `compose.yaml` 和 `nginx-wordpress.conf`。先在 **ECS** 创建工作目录：

```bash
[ECS] mkdir -p ~/cloud-course/lab-05
[ECS] cd ~/cloud-course/lab-05
```

上传 starter 文件后，复制一份配置文件：

```bash
[ECS] cp .env.example .env
[ECS] chmod 600 .env
[ECS] nano .env
```

`.env` 负责保存本次实验的随机密码，属于敏感凭据。不要上传到平台，也不要提交到 Git 仓库。`.env.example` 只留变量名和占位符，用来说明需要准备哪些配置项。

接着让 Compose 展开变量并检查结构：

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

命令静默退出且返回码为 0，说明 Compose 格式合法。至于镜像能否下载、数据库能否正常初始化，还需要下一步启动验证。

三个服务之间的关系如下：

```txt
web（Nginx）
  -> 读取 wordpress-data 中的静态文件
  -> 把 PHP 请求发给 php:9000

php（WordPress PHP-FPM）
  -> 读取和执行 wordpress-data 中的 PHP 文件
  -> 使用服务名 db 访问数据库

db（MariaDB）
  -> 把数据写入 db-data
  -> 不映射宿主机端口
```

`wordpress-data` 让 Nginx 与 PHP 看到同一套应用文件；`db-data` 让数据库容器被重新创建后仍能读取实验数据。数据卷不是备份，误删和卷损坏仍然需要独立恢复材料。

## 5.4 启动并观察健康状态

在 **ECS** 启动服务：

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

`-d` 让容器在后台运行。第一次启动会拉取镜像并初始化数据，耗时取决于网络和磁盘。

随后查看服务状态：

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

重点核对：

* `db` 是否变为 `healthy`（健康状态）；
* `php` 是否保持运行；
* `web` 是否绑定 `127.0.0.1:18005->80/tcp`；
* 是否出现反复重启或退出。

再用容器网络和宿主机端口两种视角检查：

```bash
[ECS] docker compose exec web getent hosts php db
[ECS] ss -lnt 'sport = :18005'
[ECS] curl -I http://127.0.0.1:18005/
```

`getent` 能证明 `php`、`db` 两个服务名可以在 Compose 网络中解析；`ss` 证明宿主机回环地址正在监听；`curl` 出现 `200` 或跳转到安装页面，才说明 HTTP 请求走通。

{{guided-demo:lesson-05-request-chain}}

## 5.5 通过 SSH 隧道打开安装页

因为远端 Web 端口只绑定 `127.0.0.1`，Windows 浏览器不能直接访问。需要在 **WSL** 建立本地端口转发：

```bash
[WSL] ssh -N -L 18005:127.0.0.1:18005 你的ECS登录目标
```

这条命令保持运行时，浏览器访问：

```txt
http://127.0.0.1:18005/
```

连接关系是：

```txt
Windows 浏览器 :18005
  -> WSL SSH 客户端
  -> ECS SSH 服务 :22
  -> ECS 127.0.0.1:18005
  -> Nginx 容器 :80
```

隧道复用了已有 SSH 入口，没有新增公网端口。终止这条 SSH 命令只会关闭转发，不会停止远端 WordPress。

安装页面中的站点标题可填“云端课程学习站”。管理员密码使用密码管理器生成，不能使用数据库密码，也不能出现在报告截图中。若只做链路验收，可以停在安装页面，不必创建正式管理员账号。

## 5.6 日志要跟着请求看

下面三条命令分别回答不同问题：

* `docker compose logs web`：Nginx 是否收到请求、是否连接上 PHP。
* `docker compose logs php`：PHP-FPM 是否启动，以及执行请求时是否报错。
* `docker compose logs db`：数据库初始化、登录和存储是否出现问题。

```bash
[ECS] docker compose logs --tail 40 web
[ECS] docker compose logs --tail 40 php
[ECS] docker compose logs --tail 40 db
```

日志只截取与本次请求时间相近的必要行。数据库日志中的账号、客户端地址和其他业务信息应脱敏。

常见现象可以这样判断：

**浏览器显示 502 或 504**

Nginx 已接收请求，但 `fastcgi_pass php:9000` 对应的 PHP-FPM 不可达或响应超时。先检查 `php` 容器状态、服务名解析和 Web 日志。连接被拒绝常见 502，等待上游超时则可能返回 504，具体原因以日志为准。

**页面显示 Error establishing a database connection**

PHP 已经执行到数据库连接阶段。先核对 `db` 是否健康、数据库名与用户名是否一致，以及两个服务是否在同一 Compose 网络。

**页面空白或 500**

请求可能已经进入 PHP。保留 HTTP 状态，查看 PHP 日志和 WordPress 配置，不要用反复刷新代替取证。

## 5.7 制造一次可恢复的 PHP 故障

这次故障演练只在自己的课程项目中进行。先保存正常响应：

```bash
[ECS] curl -sS -o /dev/null -w 'before=%{http_code}\n' http://127.0.0.1:18005/
```

这条命令组合了四个 curl 参数：`-s` 隐藏进度条，`-S` 在静默时仍显示错误，`-o /dev/null` 丢弃响应正文（只需要状态码），`-w '…'` 按自定义格式输出——`%{http_code}` 展开为 HTTP 状态码。后面故障演练中反复使用同一组参数来对比状态码变化。

停止 PHP 服务：

```bash
[ECS] docker compose stop php
```

再次请求并查看 Nginx 日志：

```bash
[ECS] curl -sS -o /dev/null -w 'during=%{http_code}\n' http://127.0.0.1:18005/
[ECS] docker compose logs --tail 12 web
```

预期出现 502 或 504，并在 Web 日志中看到连接上游失败或超时。恢复 PHP 后再验证：

```bash
[ECS] docker compose start php
[ECS] docker compose ps
[ECS] curl -sS -o /dev/null -w 'after=%{http_code}\n' http://127.0.0.1:18005/
```

恢复后的状态码应回到安装页或首页对应的成功/跳转结果。

{{chat-lab:lesson-05-lnmp-evidence}}

## 5.8 配置和数据分别怎样保护

WordPress 的恢复材料至少分三类：

* `compose.yaml`、Nginx 配置和 `.env.example`：描述怎样重建服务，不含真实密码。
* WordPress 应用目录：包含插件、主题和上传文件；课程使用 `wordpress-data`。
* MariaDB 数据：保存文章、用户和站点配置；需要数据库逻辑备份和恢复验证。

查看数据卷：

```bash
[ECS] docker volume ls --filter label=com.docker.compose.project=cloud-course-lab-05
```

它能证明卷存在，不能证明其中数据完整。第七次课会使用导出、删除测试库、恢复和数据条数比较来验证数据库备份。

停止服务但保留数据：

```bash
[ECS] docker compose down
```

普通 `down` 不删除命名卷。只有确认不再需要实验数据、完成备份并通过验收后，才考虑带 `--volumes` 的清理；本课默认不执行。

## 5.9 分层实训与提交

完整步骤见[实训 05：隔离部署 LNMP 与 WordPress](./lab-05.md)，其中可以下载 `compose.yaml`、`nginx-wordpress.conf` 和 `.env.example`。

**保底任务**

* 读懂三项服务及端口关系；
* 运行 `docker compose config --quiet`；
* 根据给定 `ps`、`curl` 和日志材料判断请求停在哪一层；
* 在资源暂不可用时，不要把给定材料写成自己的 ECS 运行结果。

**标准任务**

* 在个人 ECS 启动隔离 LNMP；
* 通过 SSH 隧道访问 WordPress 安装页；
* 完成 PHP 中断、日志取证和恢复验证；
* 说明配置、应用文件与数据库三类恢复材料。

**挑战任务**

* 完成 WordPress 初始化并发布一篇“课程学习站上线记录”；
* 解释为什么数据卷不等于备份；
* 设计一次数据库逻辑备份，但不提前执行第七次课的删除恢复任务。

提交文件名：

```txt
班级_学号_姓名_第05次课_LNMP与WordPress部署报告.docx
```

报告每张截图都写清运行位置、关键输出和结论。只上传一个 `.docx` 到智慧职教“第05次课”作业收集，不上传 `.env`、数据库文件、服务器地址或管理账号。

{{reflection-checkpoint:lesson-05-lnmp-ready}}

## 5.10 第五次课小测

小测关注组件职责、端口边界、502 判断、日志和持久化：

{{assessment:lesson-05-check}}

## 5.11 小结

* Nginx 接收 HTTP，PHP-FPM 执行 WordPress，MariaDB 保存动态数据。
* 宿主机入口端口与容器内部端口承担不同角色。
* `docker compose ps`、`curl` 和分服务日志共同构成证据链。
* 502 说明上游链路异常，不能直接推出“必须重装”。
* 回环绑定加 SSH 隧道可以完成远程演示，而不扩大公网入口。
* 配置、应用文件和数据库需要分别保护，数据卷本身不是备份。

下一次课会把三项服务的依赖关系写成更规范的 Compose 项目，并观察一个服务失败时其余服务如何表现。

## 5.12 资料来源

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

* [WordPress 官方运行要求](https://wordpress.org/about/requirements/)
* [WordPress 官方 Docker 镜像说明](https://hub.docker.com/_/wordpress)
* [Nginx FastCGI 模块文档](https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html)
* [MariaDB 容器镜像说明](https://hub.docker.com/_/mariadb)
* [Docker Compose 服务说明](https://docs.docker.com/reference/compose-file/services/)

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