最近,部署在 OCI ARM 实例上的 hermes-agent 容器突然出了状况:Web 端的某些子路由会话失灵,Telegram 机器人也彻底没了反应,发送命令没有任何回显。
但奇怪的是,宿主机自身的网络、博客网站以及 Hermes 默认网页端却一切正常。
这场看似毫无头绪的“局部瘫痪”,经过一番排查,最后发现竟是一次容易被忽略的误操作引起的。真正找到问题根源后,修复反而很简单:重启 Docker,网络规则自动恢复,容器重新获得了正常的出网能力。
这里把整个过程记录下来,既作为一次故障复盘,也给以后维护服务器留个备忘。
一、故障现象:宿主机正常,容器却无法出网
最开始的表现非常迷惑,主要有以下几个方面。
1. 宿主机网络正常
在 ARM 宿主机上执行外网访问测试,结果正常。博客网站可以访问,宿主机自身访问外部 API 也没有明显问题。
这说明宿主机的基础网络连接并没有整体瘫痪。
2. 容器内 DNS 解析失败
进入 hermes-agent 容器进行测试时,却出现了:
Temporary failure in name resolution
即临时域名解析失败。
继续检查发现,问题并不局限于 DNS。容器尝试连接外网 IP 地址 1.1.1.1 的 443 端口时,也出现连接超时。
这就说明,除了域名解析异常,容器本身的出站网络连接也存在问题。
3. Hermes 的部分功能失灵
由于容器无法正常访问外部网络,依赖外部大模型 API 的请求无法正常完成,Telegram 机器人的消息处理也受到影响。
与此同时,Hermes 自带的 Web 聊天页面仍然能够打开和使用部分功能。
这也是本次故障最容易让人误判的地方:网页可以打开,并不代表容器的出站网络正常。反向代理、宿主机访问容器的端口,以及容器主动连接外部服务,是不同的网络路径。
二、曲折的排查过程:几度迷失方向
弯路一:怀疑 Hermes 代码或 Telegram 网关逻辑
一开始,我们曾怀疑是不是命令没有注册好、Telegram 网关出现异常,或者 Hermes 的程序配置出了问题。
但随着检查深入,发现问题不只是某条命令没有响应,容器内部连基本的外网连接都无法建立。
既然连网络层面的基础通信都存在问题,继续围绕业务代码排查就不是最优先的方向了。
弯路二:怀疑 DNS 配置
接着,我们检查了容器内的 DNS 配置,并通过 Python 测试域名解析。
结果发现,容器内无法正常解析 generativelanguage.googleapis.com,而宿主机的 DNS 解析却没有明显异常。
不过,仅凭 DNS 解析失败,还不能直接认定问题就是 DNS 服务器配置错误。因为容器的 DNS 查询本身也需要通过相应的网络路径完成通信。
后来,容器内连接 1.1.1.1:443 同样失败,这让排查方向进一步转向 Docker 网络和数据包转发。
弯路三:怀疑 HE 隧道和防火墙策略
由于此前曾经调整过网络,并配置过 HE IPv6 隧道,我们一度怀疑是不是相关网络操作影响了 ARM 实例的路由或防火墙。
检查过程中,我们没有发现足以直接证明宿主机整体网络被阻断的证据。
但这并不意味着防火墙规则没有问题。Docker 容器的网络转发与宿主机自身的网络通信并不完全相同,即使宿主机可以正常访问外网,容器仍然可能因为 NAT 或转发规则缺失而无法出网。
真正的线索,最终来自对历史操作记录的重新检查。
三、真相大白:被清空的 iptables 规则
在重新梳理此前的操作,并对照 .bash_history 中的历史命令时,我们终于发现了关键证据。
此前排查其他网络问题时,曾执行过下面这组命令:
sudo iptables -F && \
sudo iptables -X && \
sudo iptables -t nat -F && \
sudo iptables -t mangle -F && \
sudo iptables -P INPUT ACCEPT && \
sudo iptables -P FORWARD ACCEPT && \
sudo iptables -P OUTPUT ACCEPT
这组命令的影响范围很大。
其中:
iptables -F:清空默认filter表中的规则。iptables -X:删除不再被引用、且符合删除条件的自定义链。iptables -t nat -F:清空nat表中的规则。iptables -t mangle -F:清空mangle表中的规则。iptables -P ... ACCEPT:将相应内置链的默认策略设置为允许。
需要注意,iptables -F 默认针对 filter 表,并不是清空所有表;上面的命令则分别操作了多个表。
问题在于,Docker 的容器网络依赖一系列自动维护的防火墙规则。这些规则并非普通的装饰性配置,而是容器网络正常运行的重要组成部分。
1. Docker 为什么需要 NAT 规则?
以默认 bridge 网络为例,容器通常使用独立的私有 IP 地址,例如 172.17.0.0/16 网段中的地址。
容器访问外部网络时,Docker 通常需要通过源地址伪装(MASQUERADE)等 NAT 规则,让容器的流量能够通过宿主机对外通信。
例如,正常情况下,nat 表的 POSTROUTING 链可能包含类似下面的规则:
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
这条规则的含义是:来自 172.17.0.0/16 网段、且从 docker0 以外的网络接口发出的流量,执行源地址伪装。
如果相关规则丢失,容器的出站流量就可能无法按照预期进行地址转换,进而无法正常访问外网。
2. 为什么博客仍然正常?
这是因为宿主机自身的网络连接与 Docker 容器的出站连接并不是同一条网络路径。
博客网站通过宿主机的 Nginx 监听端口对外提供服务。只要相关服务、监听端口、云端安全规则及必要的网络路径仍然正常,博客就可能继续访问正常。
而容器主动访问外部 API 时,需要经过 Docker 的网络转发和相关 NAT 处理。
因此,出现“宿主机正常、容器出网失败”的现象,并不矛盾。
此外,容器的端口映射也依赖 Docker 创建的相关规则。因此,清空规则可能同时影响容器出站连接和某些入站访问。不过,具体影响取决于实际网络模式、规则和流量路径,不能简单认为所有容器都会以完全相同的方式失效。
四、解决办法:重启 Docker,自动恢复网络规则
找到问题根源后,我们没有继续手工拼接复杂的 iptables 规则,也没有删除或重建容器,而是先确认容器和 Docker 网络配置仍然存在,再采用了一个相对简单的恢复方法:
sudo systemctl restart docker
注意:重启 Docker 会短暂中断容器服务。 执行前应确认业务影响范围,并选择合适的维护时段。
这次重启后,Docker 容器重新启动,原有的容器和端口映射均仍然存在,没有删除容器,也没有清除挂载的数据目录。
更重要的是,Docker 自动重新生成了所需的网络规则。
1. 检查容器状态
执行:
docker ps
检查 hermes-agent、onenav 和 heimdall-backup 等容器是否正常运行,以及端口映射是否仍然存在。
2. 检查 NAT 规则
执行:
iptables -t nat -S
重启后,我们看到 Docker 的相关规则已经恢复,其中包括:
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
以及对应容器的端口映射规则,例如:
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 9119 -j DNAT --to-destination 172.17.0.3:9119
这表明 Docker 已经重新建立了相应的 NAT 和端口映射规则。
3. 检查转发规则
执行:
iptables -S FORWARD
检查 Docker 的转发链是否重新建立。
本次检查结果包括:
-P FORWARD ACCEPT
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-FORWARD
说明转发链的相关入口已经恢复。需要注意,完整的转发规则还应结合 DOCKER-FORWARD 等链中的具体内容判断,不能只凭这三行就认定所有转发策略都完全正常。
4. 再次测试容器 DNS 和外网连接
DNS 测试:
docker exec hermes-agent python3 -c \
"import socket; print(socket.getaddrinfo('generativelanguage.googleapis.com',443))"
这次成功返回了 Google API 域名对应的 IP 地址,说明容器 DNS 解析已经恢复。
然后测试 TCP 连接:
docker exec hermes-agent python3 -c \
"import socket; s=socket.create_connection(('1.1.1.1',443),timeout=5); print('TCP连接成功'); s.close()"
终端返回:
TCP连接成功
这证明容器已经能够建立到外部 IP 的 TCP 连接。
最后,再实际检查 Hermes 的大模型调用、Telegram 机器人以及相关 Web 功能,确认业务是否全部恢复。
本次故障最终通过重启 Docker 恢复了网络规则。 但这并不意味着所有 Docker 网络故障都可以通过重启解决。如果网桥配置、Docker 网络定义、宿主机路由或其他防火墙策略也已经损坏,就可能需要进一步排查。
五、经验与教训
1. 容器与宿主机表现不一致,优先检查网络路径
当宿主机可以正常访问外网,但容器无法连接外部服务时,应重点检查:
- 容器 DNS 解析是否正常;
- 容器能否建立外网 TCP 连接;
- Docker bridge 网桥和容器 IP 是否正常;
nat表中的 MASQUERADE 和端口映射规则是否存在;FORWARD、DOCKER-USER、DOCKER-FORWARD等链是否正常;- 是否存在其他防火墙或路由策略影响流量。
这样能够较快地区分应用层故障与网络层故障,避免在代码和配置上反复绕圈。
2. 修改 iptables 前,一定要确认影响范围
iptables -F、iptables -t nat -F 等命令看似简单,却可能影响多个服务。
尤其是在同一台服务器上运行 Docker、网站、反向代理、VPN 或 IPv6 隧道时,不应该为了排查单个问题就全局清空防火墙规则。
以后需要修改网络策略时,应先查看现有规则、保存配置,并针对明确的问题进行精准调整。
不要把全局清空防火墙规则当成常规排障手段。
3. 不要只看 DNS,要同时测试 IP 连接
如果域名解析失败,确实应该检查 DNS。但本次故障还出现了容器连接 1.1.1.1:443 超时的现象。
这条线索非常关键:如果直接访问 IP 也不通,就不能把问题简单归结为 DNS 服务器故障。
因此,排查时最好分别测试域名解析和 IP 连接。
4. 保留操作历史,能显著缩短排查时间
这次能够找到真正的原因,很大程度上得益于对过去操作的回忆,以及 .bash_history 中留下的命令记录。
服务器故障发生时,当前状态往往只能告诉我们“哪里不正常”,而历史操作可能直接揭示“为什么会变成这样”。
尤其是涉及防火墙、路由、网卡、Docker 网络和隧道配置的修改,最好记录修改前后的状态和具体命令,方便日后追溯。
六、留给下次排查的快速检查清单
如果以后再次遇到“宿主机网络正常,但 Docker 容器无法访问外网”的情况,可以按照下面的顺序检查。
第一步:检查容器是否运行
docker ps
第二步:检查容器 DNS 解析
docker exec hermes-agent python3 -c \
"import socket; print(socket.getaddrinfo('generativelanguage.googleapis.com',443))"
第三步:检查容器外网 TCP 连接
docker exec hermes-agent python3 -c \
"import socket; s=socket.create_connection(('1.1.1.1',443),timeout=5); print('TCP连接成功'); s.close()"
第四步:检查 NAT 和转发规则
iptables -t nat -S
iptables -S FORWARD
如果发现 Docker 所需的 NAT、端口映射或转发规则缺失,不要立即清空或手工重建所有规则。先保存当前状态,确认故障范围,再选择恢复方式。
如果问题确实是 Docker 自动维护的规则被清空,而容器、网络定义及网桥配置仍然存在,那么在确认服务中断影响后,可以考虑:
sudo systemctl restart docker
重启后应重新检查容器状态、网络规则和实际连接情况,不能只凭容器显示为 Up 就认定网络已经恢复。
特别提醒: 上述命令主要用于诊断。不要在不了解现有策略的情况下直接执行 iptables -F、iptables -t nat -F,也不要全局清空 nftables 规则。这些操作可能影响网站、容器、端口映射和其他网络服务。
七、总结
这次 Docker 容器网络故障,表面上看是 Hermes 的 Web 会话和 Telegram 机器人突然失灵,实际上却是更底层的网络规则出了问题。
宿主机可以访问外网,不代表容器也一定可以;网页能够打开,也不代表容器的出站连接正常。
最终,借助历史操作记录,我们找到了之前清空 iptables 规则的关键命令。重启 Docker 后,相关 NAT 和转发规则重新建立,容器 DNS 解析与外网 TCP 连接均恢复正常。
这次没有删除容器、重建服务或重新安装系统,也没有继续手工拼接防火墙规则,而是采用了范围较小、便于验证的恢复方法。
这次故障给我的最大提醒是:服务器出现异常时,先回忆并核对最近做过什么,再根据证据逐步检查;找到根源后,优先选择影响范围最小、能够验证结果的修复方式。
对个人维护的服务器来说,保住现有数据和正常运行的服务,比为了尽快解决问题而贸然进行大范围修改更重要。

发表回复