開源項目 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。它們之間的關系如圖所示。
由於 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” 解禁,如圖所示。
默認情況下 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 就啟動起來了。
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
這里的 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 加密傳輸。