因为开发需要使用 带界面的仿真软件,而以往的使用习惯基本都是 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 网桥存在
- 对外部网络完全不可达
最终表现为:
ETIMEDOUT
4️⃣ 补充:虚拟机访问地址的来源
需要注意的是:
虚拟机的访问地址是由 Horizon Agent 上报给 Connection Server / Gateway 的。
当系统在某些场景下:
- 选用了 docker0 对应的地址作为"对外地址"
那么 Agent 上报的地址本身就是不可达的,这也是问题产生的根源之一。
5️⃣ 结论
这并不是:
"Docker 抢占了 Blast 的端口"
而是:
Docker 创建的 docker0 网桥,在系统选路/接口优先级上高于真实网卡,
导致 Horizon 中转回连流量被引导到一个外部不可达的地址。
这也解释了为什么:
- SSH 完全正常
- 虚拟机本身运行无异常
- 只有 Horizon Web 桌面无法访问
因为只有 Horizon(尤其是中转模式)
👉 强依赖"系统最终选择哪个 IP 作为对外通信地址"。
四、解决方案(可行但偏工程化)
最终采用的解决方式并不优雅,但非常稳定:
删除 docker0 → 登录 Horizon 桌面 → 重启 Docker
bash
ip link set docker0 down
ip link delete docker0
# 用户成功登录 Horizon 桌面后
systemctl restart docker
# docker0 会被重新创建
后续断开并重新连接 Horizon 桌面也可以正常访问(可能与会话 / 地址缓存有关)。
并不是所有虚拟机都会触发该问题
且实际需要桌面(GUI 仿真)的用户数量有限
同时虚拟机 IP 均为 DHCP 分配,无法批量在 Horizon Agent 中固定地址
因此最终选择了这种 最小改动、可控范围内的工程方案。
关于修改 Docker 网桥地址的误区
曾尝试通过修改 Docker 网关配置:
json
"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 服务
bash
systemctl status viewagent
必要时重启:
bash
systemctl restart viewagent
检查 Blast 服务端口
bash
netstat -tlnp | grep 22443
期望看到:
tcp 0 0 0.0.0.0:22443 LISTEN xxx/VMwareBlastS
检查 iptables 影响
Docker 会自动修改 iptables,可能影响 Blast 通信。
可临时验证:
bash
iptables -F
iptables -L
仅用于排错验证,生产环境请谨慎
如果最终浏览器访问 Horizon 出现:
ETIMEDOUT 172.17.0.1:22443
基本可以确认仍是 docker0 选路问题,按前述方式处理即可。
六、Docker + GUI:DISPLAY
桌面能正常访问,并不代表容器里的 GUI 程序就能显示。
宿主机(虚拟机)准备
bash
echo $DISPLAY
例如:
:102
并执行:
bash
xhost +
仅限内网 / 临时调试使用
Docker 启动示例
bash
docker run --net=host --rm -it \
-e DISPLAY=$DISPLAY 镜像 bash
测试
bash
apt install -y x11-apps
xclock
能够弹窗即说明 GUI 显示链路打通。
七、批量环境治理
既然 Docker 在后续场景中是必需组件,那就统一在虚拟机上提前安装,并批量为现有用户授予 Docker 使用权限,避免后续重复配置和权限问题。
bash
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 网络与桌面访问路径必须明确隔离或有序控制。