本章记录一次真实的 Docker Web 项目故障排查过程。

项目环境:

  • Ubuntu
  • Docker
  • Docker Compose
  • Nginx
  • Tornado
  • MySQL

故障现象:

浏览器访问 http://127.0.0.1 无法显示自己的 HTML 页面,Nginx 最终返回 403 Forbidden。


一、项目架构

我们的项目大致是:

用户浏览器

用户浏览器

│ HTTP :80

┌─────────────┐
│ Nginx │
│ :80 │
└──────┬──────┘

┌───────────┴──────────┐
│ │
↓ ↓
静态 HTML /api/
│ │
│ ↓
│ ┌───────────┐
│ │ Tornado │
│ │ :8888 │
│ └─────┬─────┘
│ │
│ ↓
│ ┌───────────┐
│ │ MySQL │
│ │ :3306 │
│ └───────────┘

我们希望最终实现:

http://127.0.0.1
        ↓
Nginx
        ↓
index.html
        ↓
显示自己的项目首页

但是

http://127.0.0.1/api/

Nginx

Tornado :8888

返回 API 数据

http://127.0.0.1

Nginx

index.html

显示自己的项目首页


二、最开始的问题

之前访问:

curl http://127.0.0.1

得到:

{"status": "success", "message": "Docker Tornado Running"}

一开始我们以为:

为什么不是 HTML?

于是开始排查。


三、第一步:确认端口是否正常

首先使用

ss -lntp

看到:

LISTEN 0 4096 0.0.0.0:80
LISTEN 0 4096 0.0.0.0:8888
LISTEN 0 4096 0.0.0.0:3306

说明:

80 → Nginx
8888 → Tornado
3306 → MySQL

端口都在监听。

所以暂时排除:

服务没有启动
端口没有监听

四、第二步:直接访问 Tornado

执行:

curl -i http://127.0.0.1:8888

得到:

HTTP/1.1 200 OK
Server: TornadoServer/6.5.7
Content-Type: application/json; charset=UTF-8

{"status": "success", "message": "Docker Tornado Running"}

这说明:

Tornado 本身正常。

而且返回的是:

application/json

不是:

text/html

所以我们进一步确认:

Tornado 是 API 服务,不负责直接给我们显示 HTML 首页。


五、第三步:访问 Nginx

执行:

curl -i http://127.0.0.1

当时得到:

HTTP/1.1 200 OK
Server: nginx/1.31.3
Content-Type: application/json

返回:

{"status": "success", "message": "Docker Tornado Running"}

这说明:

浏览器
↓
Nginx :80
↓
Tornado :8888

已经打通了。

也就是说:

Nginx → Tornado 的反向代理是工作的。


六、第四步:检查 Nginx 配置

执行:

docker exec nginx-web nginx -T

发现:

http {

server {

listen 80;

location / {

proxy_pass http://app:8888;

proxy_set_header Host $host;

proxy_set_header X-Real-IP $remote_addr;

}

}

}

这里就发现了一个非常重要的问题。

当前配置:

location / {
proxy_pass http://app:8888;
}

意味着:

所有 / 请求都转发给 Tornado。

所以:

http://127.0.0.1/

并不会寻找:

index.html

而是:

Nginx
↓
Tornado
↓
JSON

因此浏览器看到 JSON 是完全正常的


七、第五步:我们希望实现什么?

我们真正需要的是:

/ --- HTML

而:

/api/----Tornado

因此 Nginx 应该改成:

location / {

root /usr/share/nginx/html;

index index.html;

}

location /api/ {

proxy_pass http://app:8888;

proxy_set_header Host $host;

proxy_set_header X-Real-IP $remote_addr;

}

也就是说:/ → 静态 HTML /api/ → Tornado


八、第六步:发现 HTML 文件问题

修改之后,访问:

curl -I http://127.0.0.1

结果:

HTTP/1.1 403 Forbidden
Server: nginx/1.31.3

这时候非常关键。

因为现在:

Nginx 正常
配置正常
端口正常

但是:

403 Forbidden

说明:

Nginx 收到了请求,但是没有权限/没有合适的资源可以返回。

于是继续检查:

docker exec nginx-web cat /usr/share/nginx/html/index.html

一开始看到的是:

<title>Welcome to nginx!</title>

这是:

Nginx 官方镜像自带的默认 HTML。


九、第七步:检查 Docker Compose

继续查看:

grep -A15 -B5 "nginx:" docker-compose.yml

发现:

nginx:

image: nginx:latest

container_name: nginx-web

restart: always

ports:- "80:80"

volumes:

- ./nginx/nginx.conf:/etc/nginx/nginx.conf

这里出现了真正的问题。

我们只挂载了:

nginx.conf

但是没有挂载:

HTML

也就是说:

宿主机挂载./nginx/nginx.conf

容器挂载-----/etc/nginx/nginx.conf

但是:

宿主机挂载到了./nginx/html/index.html

容器和/usr/share/nginx/html/index.html

两者没有关系。


十、Docker Volume 到底是什么?

这里一定要理解。

例如:

volumes:

- ./nginx/html:/usr/share/nginx/html

意思是:

宿主机 容器

./nginx/html

│ volume

/usr/share/nginx/html

也就是说:

宿主机目录直接映射到容器目录。

因此:

vim nginx/html/index.html

修改宿主机文件之后:

容器里的

/usr/share/nginx/html/index.html

也会立即看到这个文件。


十一、第八步:添加 HTML Volume

修改:

volumes:

- ./nginx/nginx.conf:/etc/nginx/nginx.conf

- ./nginx/html:/usr/share/nginx/html

完整 Nginx:

nginx:

image: nginx:latest

container_name: nginx-web

restart: always

ports:

- "80:80"

volumes:

- ./nginx/nginx.conf:/etc/nginx/nginx.conf

- ./nginx/html:/usr/share/nginx/html

depends_on:

- app


十二、第九步:创建 HTML 目录

执行:

mkdir -p nginx/html

然后:

nano nginx/html/index.html

写入我们的网页。

例如:

location / {

root /usr/share/nginx/html;

index index.html;

}

location /api/ {

proxy_pass http://app:8888;

proxy_set_header Host $host;

proxy_set_header X-Real-IP $remote_addr;

}

十三、第十步:检查宿主机文件

执行:

ls -l nginx/html/

应该看到:

index.html

然后:

head nginx/html/index.html

确认:

<!DOCTYPE html>

<html lang="zh-CN">


十四、第十一步:重新创建 Nginx

因为修改了 Compose 的挂载关系,所以执行:

docker compose up -d --force-recreate nginx

然后:

docker ps

确认:

nginx-web Up


十五、第十二步:进入容器检查

这是非常重要的一步。

执行:

docker exec nginx-web ls -l /usr/share/nginx/html/

应该看到:

index.html

然后:

docker exec nginx-web head -10 /usr/share/nginx/html/index.html

应该看到:

<!DOCTYPE html>

<html lang="zh-CN">

<head>

<meta charset="UTF-8">

这就证明:

宿主机 HTML

Docker Volume

Nginx 容器

/usr/share/nginx/html/index.html

整个链路已经打通。


十六、第十三步:检查 Nginx 配置

执行:

docker exec nginx-web nginx -t

正常:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok

nginx: configuration file /etc/nginx/nginx.conf test is successful

说明:

Nginx 配置文件语法没有问题。


十七、第十四步:测试 HTTP

执行:

curl -I http://127.0.0.1

应该得到:

HTTP/1.1 200 OK

Content-Type: text/html

注意这里:

之前是:

Content-Type: application/json

现在应该变成:

Content-Type: text/html

这说明:

Nginx 已经开始返回 HTML。


十八第十五步:浏览器访问

浏览器输入:

http://127.0.0.1

就应该看到我们自己的网页。

更多推荐