因为开发需要使用带界面的仿真软件,而以往的使用习惯基本都是 SSH 登录虚拟机即可满足需求。
但这次遇到两个现实问题:
- 现有虚拟机系统为Ubuntu 18.04
- 仿真软件要求Ubuntu 22.04 + GUI
最终选择的方案是:
- 基于 Docker 构建一套Ubuntu 22.04 + 仿真软件的镜像
- 运行在原有虚拟机中
- 结合VMware Horizon,通过浏览器访问桌面,实现软件可视化操作
也正是在这个组合下,踩到了Docker 与 Horizon 的一个隐藏但非常典型的坑。
也尝试过使用 MobaXterm 弹窗,根本弹不了远程虚拟机上的容器程序,AI 说可以是骗人的。
一、Horizon 是什么
VMware Horizon 本质上是一套VDI(虚拟桌面基础架构)解决方案。
简单理解就是:
- 后端运行着大量云服务器 / 虚拟机
- 虚拟机具备完整桌面环境(Linux / Windows)
- 用户可通过浏览器(Web)或客户端访问远程桌面
在此前的实际使用中:
- 日常运维、开发:SSH 完全够用
- Horizon 桌面更多是“存在但几乎不用”的状态
但当GUI 仿真工具加入后,情况就完全不同了。
二、问题现象:安装 Docker 后,Horizon Web 桌面不可用
在虚拟机中安装 Docker 并启动后,出现了一个非常反直觉的现象:
- SSH 登录、命令行操作一切正常
- Horizon Web 桌面无法打开
浏览器直接报错:
An error has occurred: {"errno":"ETIMEDOUT","code":"ETIMEDOUT","syscall":"connect","address":"172.17.0.1","port":22443}关键点非常集中:
172.17.0.1:22443
而172.17.0.1,正是 Docker 默认创建的docker0网桥地址。
三、问题根因:Docker 默认网桥优先级与 Horizon 网络路径冲突
1️⃣ 先厘清:Horizon Web 桌面是如何建立连接的?
Horizon Web(Blast / HTML Access)并不只有一种连接模式,常见有两种:
模式一:浏览器直连虚拟机(Direct Connection)
- 浏览器直接访问:
虚拟机IP:22443 - Blast 服务监听地址为:
0.0.0.0:22443- 网络路径:
浏览器 → 虚拟机 → Blast Server模式二:通过中转服务器访问(Blast Secure Gateway / Unified Access Gateway)
- 浏览器先连接 Horizon 中转服务器
- 由中转服务器回连虚拟机的 Blast 服务端口
- 网络路径:
浏览器 → Horizon 中转服务器 → 虚拟机 → Blast Server本次问题环境使用的是第二种(中转模式),该模式可在 Horizon 管理台中配置。
2️⃣ 为什么会“连到” 172.17.0.1?
需要特别强调的是:
这并不是 Blast 监听地址配置错误,而是 Linux 系统选路问题。
Blast 服务本身监听的是:
0.0.0.0:22443这意味着:
- Blast 并不绑定某个具体 IP
- 实际使用哪个 IP,对它来说取决于操作系统的网络选路结果
真正出问题的地方在于:
Linux 在选择“对外通信地址”时,选错了网卡。
3️⃣ Docker 实际造成了什么影响(真正的根因)
Docker 启动后会自动完成以下操作:
- 创建
docker0网桥 - 默认分配地址:
172.17.0.1/16- 同时调整系统路由表与 iptables 规则
在部分环境中,会出现以下情况:
docker0网卡在路由或接口优先级上
高于真实业务网卡- 系统在选择“回包路径”或“源地址”时
优先选用了 docker0 的地址(172.17.0.1)
于是,实际发生的网络行为变成了:
Horizon 中转服务器 → 虚拟机 ↳ 系统选路:docker0 (172.17.0.1) ↳ Blast :22443结果就是:
- Horizon 中转服务器尝试连接
172.17.0.1:22443 - 该地址仅在虚拟机本地 Docker 网桥存在
- 对外部网络完全不可达
最终表现为:
ETIMEDOUT4️⃣ 补充:虚拟机访问地址的来源
需要注意的是:
虚拟机的访问地址是由 Horizon Agent 上报给 Connection Server / Gateway 的。
当系统在某些场景下:
- 选用了 docker0 对应的地址作为“对外地址”
那么 Agent 上报的地址本身就是不可达的,这也是问题产生的根源之一。
5️⃣ 结论
这并不是:
“Docker 抢占了 Blast 的端口”
而是:
Docker 创建的 docker0 网桥,在系统选路/接口优先级上高于真实网卡,
导致 Horizon 中转回连流量被引导到一个外部不可达的地址。
这也解释了为什么:
- SSH 完全正常
- 虚拟机本身运行无异常
- 只有Horizon Web 桌面无法访问
因为只有 Horizon(尤其是中转模式)
👉强依赖“系统最终选择哪个 IP 作为对外通信地址”。
四、解决方案(可行但偏工程化)
最终采用的解决方式并不优雅,但非常稳定:
删除 docker0 → 登录 Horizon 桌面 → 重启 Docker
iplinksetdocker0 downiplinkdelete docker0# 用户成功登录 Horizon 桌面后systemctl restartdocker# docker0 会被重新创建后续断开并重新连接 Horizon 桌面也可以正常访问(可能与会话 / 地址缓存有关)。
并不是所有虚拟机都会触发该问题
且实际需要桌面(GUI 仿真)的用户数量有限
同时虚拟机 IP 均为 DHCP 分配,无法批量在 Horizon Agent 中固定地址
因此最终选择了这种最小改动、可控范围内的工程方案。
关于修改 Docker 网桥地址的误区
曾尝试通过修改 Docker 网关配置:
"bip":"10.99.99.1/24"来规避172.17.0.1,但实践发现:
- 问题并未彻底解决
- 根因并非“占用了 172.17.0.1 本身”
- 而是docker0 作为接口参与系统选路时的优先级问题
另外还踩过一个小坑:
- Horizon 服务未停止时
- Docker 启动阶段可能仍会受到影响
最终采用的顺序是:
- 停止 Horizon Web / Agent 服务
- 启动 Docker
- 再启动 Horizon
五、另一类常见报错的排查思路
如果不是 172.17.0.1 相关报错,而是:
“无法进入桌面,请联系管理员”
则可以按以下顺序排查。
检查 Horizon Agent 服务
systemctl status viewagent必要时重启:
systemctl restart viewagent检查 Blast 服务端口
netstat-tlnp|grep22443期望看到:
tcp 0 0 0.0.0.0:22443 LISTEN xxx/VMwareBlastS检查 iptables 影响
Docker 会自动修改 iptables,可能影响 Blast 通信。
可临时验证:
iptables -F iptables -L仅用于排错验证,生产环境请谨慎
如果最终浏览器访问 Horizon 出现:
ETIMEDOUT 172.17.0.1:22443基本可以确认仍是 docker0 选路问题,按前述方式处理即可。
六、Docker + GUI:DISPLAY
桌面能正常访问,并不代表容器里的 GUI 程序就能显示。
宿主机(虚拟机)准备
echo$DISPLAY例如:
:102并执行:
xhost +仅限内网 / 临时调试使用
Docker 启动示例
dockerrun --net=host --rm -it\-eDISPLAY=$DISPLAY镜像bash测试
aptinstall-y x11-apps xclock能够弹窗即说明 GUI 显示链路打通。
七、批量环境治理
既然 Docker 在后续场景中是必需组件,那就统一在虚拟机上提前安装,并批量为现有用户授予 Docker 使用权限,避免后续重复配置和权限问题。
ansible 虚拟机组 -m shell -a'apt install -y docker.io'ansible 虚拟机组 -m shell -a\'for user in $(ls /home | grep -vE "^(public|gerrit|lost\+found)$"); do usermod -aG docker "$user" && echo "用户 $user 已加入 docker 组" done'总结
本文问题本质并非端口或服务异常,而是 Docker 默认网桥参与系统选路,导致 Horizon 回连路径选错接口。
在依赖回连机制的 VDI 场景中,Docker 网络与桌面访问路径必须明确隔离或有序控制。