开源项目 QEMU、KVM、libvirt 实现了创建虚拟机,启动虚拟机,监控虚拟机。我们解决了从无到有的问题,这时就该考虑从有到优了。尽管我们能使用 SSH 的方式来登录使用虚拟机,但这种方式从感觉欠缺点什么,用户往往会更喜欢绚丽多彩的东西。

事实上 VNC 的客户端很多,诸如 VNC Viewer,TightVNC,RealVNC 等。然而我们需要的是 web 版的 VNC,自然而然我选择了 noVNC。


noVNC:HTML5 技术的 VNC 客户端

noVNC 是一个可以运行在众多浏览器的 HTML5 VNC 客户端,包括手机浏览器(iOS 和 Android)。

诸如 Ganeti Web Manager,OpenStack,OpenNebula,LibVNCServer 和 ThinLinc 等众多厂商、项目和产品整合了 noVNC。


VNC Server

除非你使用的 VNC Server 支持 WebSockets 连接(比如 x11vnc/libvncserver、QEMU 或者 MobileVNC),否则你需要使用一个 WebSockets 和 TCP socket 之间相互转换的代理。

幸运的是 noVNC 提供了一个代理器 websockify。

尽管官方说 QEMU 支持 WebSockets 连接,但我仍然不知道如何在不使用 websockify 的情况下连接到 QEMU,如果有知道的朋友,分享出来吧。


Linux 系统环境

物理机系统版本:Windows7 64 位

宿主机:VMware 12.1.0 
宿主机系统版本:Ubuntu 16.04 64 位

客户机(虚拟机):QEMU 2.5.0 libvirt 1.3.1 
客户机(虚拟机)系统版本:Deepin 15.4 64 位

一眼看去,可能关系很复杂,其实很简单,因为笔者比较穷,只有一台物理台式机,宿主机是在该物理机上通过 VMware 虚拟出来,而客户机(虚拟机)是在宿主机上通过 QEMU、KVM 虚拟出来的。

测试环境的拓扑关系

宿主机有两台,分别命名为 Node1 和 Node2,这两台宿主机的系统环境完全一致。Node1 的 IP 为:192.168.10.231,Node2 的 IP 为:192.168.10.230。客户机(虚拟机)有一台,名为 Guest1,位于宿主机 Node1,也就是 VNC Server 位于 Node1。要用 noVNC 连接的远程机器就是客户机(虚拟机)Guest1,noVNC 和 websockify 位于宿主机 Node2。它们之间的关系如图所示。
这里写图片描述
图1

由于 QEMU、KVM 本身对 VNC Server 支持很友好,因此不需要额外在宿主机 Node1 安装 tightvnc 或 x11vnc 。如果你要连接的机器没有 VNC Server,可直接安装 tightvnc 和 x11vnc 的任意一个。


准备工作

修改 /etc/libvirt/qemu.conf 配置文件

sudo gedit /etc/libvirt/qemu.conf

打开后有如下文件内容(仅截取部分)展示。

# Master configuration file for the QEMU driver.
# All settings described here are optional - if omitted, sensible
# defaults are used.

# VNC is configured to listen on 127.0.0.1 by default.
# To make it listen on all public interfaces, uncomment
# this next option.
#
# NB, strong recommendation to enable TLS + x509 certificate
# verification when allowing public access
#
#vnc_listen = "0.0.0.0"

# Enable this option to have VNC served over an automatically created
# unix socket. This prevents unprivileged access from users on the
# host machine, though most VNC clients do not support it.
#
# This will only be enabled for VNC configurations that do not have
# a hardcoded 'listen' or 'socket' value. This setting takes preference
# over vnc_listen.
#
#vnc_auto_unix_socket = 1

# Enable use of TLS encryption on the VNC server. This requires
# a VNC client which supports the VeNCrypt protocol extension.
# Examples include vinagre, virt-viewer, virt-manager and vencrypt
# itself. UltraVNC, RealVNC, TightVNC do not support this
#
# It is necessary to setup CA and issue a server certificate
# before enabling this.
#
#vnc_tls = 1

将 vnc_listen = “0.0.0.0” 解禁,如图所示。

这里写图片描述
图2

默认情况下 VNC 监听 127.0.0.1,修改为 0.0.0.0 后适应性更高,如果你使用 libvirt 创建虚拟机,那么虚拟机 xml 配置文件可以向下面这样添加一个 graphics。

        <graphics type='vnc' autoport='yes' keymap='en-us'
                  listen='0.0.0.0'/> <!--VNC配置,autoport="yes"表示自动分配VNC端口,推荐使用,listen="0.0.0.0"表示监听所有IP-->

取得客户机(虚拟机)Guest1 的端口号

获得客户机(虚拟机)Guest1 的 port(端口号),有两种方式获得,可通过调用 libvirt Java api 获得客户机(虚拟机)Guest1 的 xml 配置描述文件,然后从中取出 port,还可通过 virsh 控制台命令获得端口号,命令如下。

sudo virsh vncdisplay kvmdemo

kvmdemo 是客户机(虚拟机)Guest1 的名称,得到的结果如图所示。

这里写图片描述

自动分配的 VNC 端口(自增)默认从 5900 开始,因此 kvmdemo 的 port 是 5900。


noVNC 快速开始

首先下载 noVNC,可通过 git 下载,也可到官网下载压缩包。
按照前面的拓扑关系,我们将 noVNC 下载到宿主机 Node2。

sudo git clone https://github.com/novnc/noVNC.git

下载完毕后,进入 noVNC 文件夹,执行如下命令。

sudo ./utils/launch.sh --vnc 192.168.10.231:5900

注意: 这里填写的是宿主机 Node1 的 ip,而不是客户机(虚拟机)Guest1 的 ip,VNC Server 通过端口映射的方式找到位于宿主机上的客户机(虚拟机)。

如果不指定端口,noVNC 默认的访问端口是 6080。执行过程中,noVNC 会去 GitHub 下载 websockify,如果觉得下载太慢,可先将 websockify 下载下来后,解压到 utils 文件夹下。
如此,一个 noVNC 就启动起来了。

这里写图片描述
图4

oh,it’s work.

现在你可以在浏览器输入:

http://192.168.10.230:6080/vnc.html?host=192.168.10.230&port=6080

就可以访问到客户机(虚拟机)Guest1 了。

这里写图片描述

我们来梳理一下用户发出请求到得到响应的流程:

PC Chrome(192.168.10.100) => Node1(192.168.10.230:6080) => websockify => Node2(192.168.10.231:5900) => websockify => Node1 => Chorme

PC Chrome 请求 Node1,websockify 将请求转发到指定的 Node2(192.168.10.231:5900),Node2收到请求返回 TCP socket 响应,在 Node1 websockify 代理器这里被转成 Web socket 的响应。

整个过程 websockify 代理器是关键,noVNC 可以被放在浏览器端。从流程来看 websockify 可以被部署在任何地方,下一节就将实现 websockify 与 noVNC 分离。


noVNC 进阶,独立 websockify 实现一个端口,多个代理

上一节,我们已经通过 noVNC 连上了 KVM,然而这种连接方式并不实用。在实际应用中,不可能为每台虚拟机都架一个代理,这种方式对端口号的消耗也是巨大的,同时 noVNC 通常是集成在前端页面。那有没有可能仅开一个端口,而实现代理多台虚拟机呢,答案自然是可以。

一个端口,多个代理原理,引入 token 文件

在 websockify 项目的 Wiki 主页介绍了实现一个端口,多个代理的方法。
实现的原理就是 websocketproxy.py 这个代理从一个指定的 token 目录读取 token 文件,一个 token 文件通常对应一台客户机(虚拟机)。token文件内容形如 token1:host1:port1 ,这里的 token1 是全局唯一的一个字符串标识,host1 是客户机(虚拟机)所在的宿主机的 ip 地址,本例中就是 Node1 的 ip,而 port1 是客户机(虚拟机) VNC Server 的端口号,本例中就是 Guest1 的 VNC Server 的端口号。因此,本例中名为 generic 的客户机(虚拟机)Guest1 的 token 文件内容为:

generic:192.168.10.231:5901

注意: 一个 token 文件可以对应一台客户机(虚拟机),一个 token 文件也可以对应多台客户机(虚拟机)。为了方便编程,通常是一对一的关系。


分离 noVNC 与 websockify

在 Github 上 noVNC 和 websockify 本来就是独立的两个项目。

首先下载 websockify,可通过 git 下载。
按照前面的拓扑关系,我们将 websockify 下载到宿主机 Node2。

sudo git clone https://github.com/novnc/websockify.git

下载完毕后,进入 websockify 文件夹,将上面的 generic 的 token 文件移动到 ./token/目录下,然后执行如下命令。

sudo python2.7 ./run –token-plugin TokenFile –token-source ./token/ 6080
1
这里的 6080 就是 websockify 代理器的端口号。

这里写图片描述

现在可以打开 noVNC 的 vnc_lite.html,并在末尾加上
?host=192.168.10.230&port=6080&path=websockify/?token=generic 就可以访问远程客户机(虚拟机)Guest1 了。

这里写图片描述

如果要连接其他客户机(虚拟机)只需往 ./token/ 目录下添加对应的 token 文件,然后改变 url 的 token 就可以通过 noVNC 访问客户机(虚拟机)。

这里写图片描述


容器化 websockify

微服务现在是一个很火的概念,提到微服务,自然少不了 docker。下面就提供一种将 websockify 去状态并容器化运行的方案。

1.编写 Dockerfile 文件

 FROM python

 MAINTAINER kyyee "kyyee.com"

 RUN apt-get update -y

 RUN apt-get upgrade -y

# Installing the fundamental package for scientific computing with Python: numpy
 RUN apt-get install -y python-numpy

# clean the backup
 RUN apt-get clean

# Copy the files into the container
 ADD ./websockify/ /websockify/

# start the java application
 CMD ["python2.7", "/websockify/run", "--token-plugin", "TokenFile", "--token-source", "/websockify/token/", "6080"]

# usage volume
# VOLUME ["/websockify/token/"]

2.生成 docker 镜像

sudo docker build -t websockify .

3.运行 docker 镜像

sudo docker run -p 6080:6080 --name websockify -it websockify -v /home/kyyee/websockify/token/:/websockify/token/

-v 为 docker 挂载命令,: 前是宿主机上的目录,: 后是 docker 容器中的目录。这里将 /websockify/token/ 目录挂载到宿主机上的 /home/kyyee/websockify/token/ 目录,在 /home/kyyee/websockify/token/ 目录操作与在 /websockify/token/ 目录操作没有区别。

可能存在的问题

某些情况下,你可能连不上 VNC Server,如图所示。

这里写图片描述

这个时候请查看启动 websockify 的控制台,如果是 handler exception: [Errno 111] Connection refused,通常是由于远端要连接的宿主机 ip 或映射 port 填写错误,或者你远端要连接的客户机(虚拟机)压根没开机。如果没有错误提示,但仍然无法连接,那么可能是远端要连接的客户机(虚拟机)在 virt-manager 或者其他工具中已经打开。

未完待续,后续将讲解 noVNC 加密传输。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐