文章背景图

记一次自建博客的网络排错与内网优化

2026-06-04
0
-
- 分钟
|

今天,我的博客突然毫无征兆地无法访问了,浏览器直白地吐出了一个 502 Bad Gateway

作为一个正在摸索和学习服务器运维与 Docker 配置的开发者,遇到这种服务挂掉的情况确实有些手忙脚乱,但每一次“踩坑”其实都是极佳的学习机会。这次排错的过程,以及最终的解决方案,让我对 Docker 容器网络和反向代理的底层逻辑有了更清晰的认识。

为了总结经验,同时也为了梳理和巩固排错思路,我决定把今天的排查全过程以及架构优化记录下来。在这篇文章中,我将还原这起“事故”的诊断过程,并聊聊为什么我最终选择弃用旧的方案,转而全面拥抱 Docker 容器局域网架构。


1. 现场还原:发现博客无法访问

当时打开网页转了半天圈,最后返回 502 Bad Gateway。我的第一反应是:博客容器挂了?

立刻 SSH 登录云服务器,敲下最常用的查看指令:

sudo docker ps -a

然而,输出结果却让我有些意外:

  • 博客容器 standalone-halo 状态是 Up X hours,处于正常运行状态。
  • 网关容器 global_caddy 也是 Up 状态。
  • 宿主机内存、CPU 均有富余,没有发生 OOM(内存溢出)导致的闪退。

既然博客服务本身没死,网关也活着,那为什么流量进不去?


2. 深入日志:定位 502 的蛛丝马迹

既然容器都在运行,那必然是“中间的网路断了”。我决定看一看网关 Caddy 的日志,看看它是怎么转发流量的:

sudo docker logs -f --tail 50 global_caddy

在滚动的日志中,我抓到了下面这行报错:

{"level":"error","ts":...,"logger":"http.log.error","msg":"dial tcp: lookup host.docker.internal on 127.0.0.11:53: no such host","request":{"host":"blog.ownd.cc", ...}}

lookup host.docker.internal on 127.0.0.11:53: no such host —— 这行报错透露了关键信息。

  • 127.0.0.11:53 是 Docker 容器内部默认的 DNS 解析服务器。
  • Caddy 试图将博客的域名请求转发给 host.docker.internal:8090,但是 Docker 的 DNS 解析服务表示:找不到 host.docker.internal 这个主机。

DNS 解析失败,导致网关无法把流量送达博客服务,最终向浏览器返回了 502。


3. 问题根源:沙箱隔离与临时的“跳板”

为什么 Caddy 会去解析 host.docker.internal?这得从我之前的部署架构说起。

之前为了省事,我的博客是用独立的 docker-compose.yaml 启动的,它运行在 Docker 默认的 Bridge 网络中,并且把端口映射到了宿主机:

ports:
  - '8090:8090'  # 将容器的 8090 映射到宿主机的 8090

而网关 Caddy 运行在另一个网络中。因为两个容器处于不同的虚拟网络(网络隔离),Caddy 无法直接通过容器名访问到博客。

为了让 Caddy 能够“跨网络”找到博客,我当时配置了 host.docker.internal,试图让 Caddy “跳出沙箱”,先访问宿主机的网关 IP,再通过宿主机映射的 8090 端口绕回博客容器。

但这种方案存在两个致命的缺陷:

  1. 可靠性极差:在 Linux 环境下,host.docker.internal 的解析需要依赖 Caddy 容器配置 extra_hosts 强行映射。一旦 Docker 守护进程重启、网卡配置变动或 DNS 缓存失效,这个映射就会断开,导致 502。
  2. 严重的安全性隐患:为了让 Caddy 通过宿主机绕回来,我必须在宿主机上暴露 8090 端口。这意味着任何人都可以通过 http://服务器公网IP:8090 绕过 Caddy 网关,直接裸奔访问我的博客后台

4. 架构重构:全面拥抱容器局域网

找到了根源,解决思路就很清晰了:不应该让流量在宿主机绕圈子,而应该让 Caddy 和博客直接在 Docker 内部局域网进行安全互联。

我决定对博客的网络架构进行重构。

第一步:关闭外网端口,加入统一共享网络

我修改了博客的 docker-compose.yaml
首先,删除了 ports: - '8090:8090',改为仅在容器网内暴露的 expose: - '8090'
然后,在 Compose 文件末尾声明,将博客服务加入 Caddy 所在的外部共享虚拟网络 ownd-api_default

services:
  halo:
    image: halohub/halo:2.24
    container_name: standalone-halo
    expose:
      - '8090'  # 仅在 Docker 内网暴露,外网无法直接探测
    # ... 其他配置 ...

networks:
  default:
    name: ownd-api_default  # 接入统一的共享网桥
    external: true          # 声明是外部已存在的网络

第二步:修改网关 Caddyfile 路由

既然大家都在同一个虚拟局域网了,Caddy 就可以直接通过博客的容器名进行内网通信,不再需要任何 host.docker.internal 的网关桥接:

# Caddyfile
blog.ownd.cc {
	reverse_proxy standalone-halo:8090  # 直接使用容器名加内部端口
}

第三步:内网“借道测试”验证

在服务器上应用新配置并重启容器后,我没有直接去浏览器刷新,而是采用了一个更稳妥的调试手法——跳入网关容器内部直接 curl 博客服务

sudo docker exec -it global_caddy curl -I http://standalone-halo:8090

输出结果令人振奋:

HTTP/1.1 200 OK
content-type: text/html;charset=UTF-8
...

这表明,在 Docker 内部局域网中,网关 Caddy 已经可以通过容器名完美地解析并连接到博客服务。外部浏览器再次刷新,博客也瞬间恢复了访问,且响应速度有了明显的提升。


5. 总结与技术收获

经历这次 502 事故和架构重构,我梳理出了自建微服务时关于 Docker 网络的核心收获:

  1. 物理隔离,逻辑互通:在管理服务时,我们在磁盘物理目录上应该保持高内聚低耦合(博客、主站、网关各自拥有独立的 Compose 项目和数据卷);但在网络层面上,应该通过 external 虚拟网桥将它们逻辑上连接在一起,交给统一的网关进行反向代理。
  2. 安全第一:除了网关(如监听 80/443 的 Caddy/Nginx),其他业务容器(包括数据库、API 后端)绝对不要在 Compose 中使用 ports 映射端口到宿主机。只用 expose 保证内网互通,能挡掉互联网上 99% 的恶意端口扫描。
  3. 善用内网工具诊断:当遇到网络问题时,尽量把诊断链路缩短。使用 docker exec 进入网关容器,使用 curl -I http://<容器名>:<内网端口> 进行直接测试,能帮你瞬间排查出是 DNS 解析问题、容器网络未加通,还是业务程序自身崩溃。
原创

记一次自建博客的网络排错与内网优化

本文链接: 记一次自建博客的网络排错与内网优化

本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

评论交流

文章目录