http://blog.csdn.net/dn_echo/article/details/45842897
docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app)。几乎没有性能开销,可以很容易地在机器和数据中心中运行。最重要的是,他们不依赖于任何语言、框架或包括系统。
为了能够在docker中运行GUI程序,本文研究了几种常用的GUI方案,介绍其使用的技术,并分析各种方案的优缺点。主要有以下四种方案:
1、基于VNCServer + noVNC构建Docker桌面系统;
2、通过ssh+Xpra + Xephyr构建Docker桌面系统;
3、容器与宿主机器共用X11的socket运行GUI apps;
4、通过ssh转发X窗口运行GUI apps。
由于运行的时GUI程序,这四种方案都离不开X window协议,所以本文首先简单介绍X window协议,然后依次介绍4种方案以及该方案用到的关键技术,最后进行总结。
一、Xwindow协议
XWindow即X Window图形用户接口,是一种计算机软件系统和网络协议,提供了一个基础的图形用户界面(GUI)和丰富的输入设备能力联网计算机。其中软件编写使用广义的命令集,它创建了一个硬件抽象层,允许设备独立性和重用方案的任何计算机上实现。
XWindow由3个相关的部分(X Server、XClient和通道)组合起来(如图1所示):
图1 X window架构图
1.1服务端(Server):
Server是控制显示器和输入设备(键盘和鼠标)的软件。Server可以创建视窗,在视窗中绘图和文字,回应Client程序的“需求”(requests),但它不会自己完成,只有在Client程序提出需求后才完成动作。每一套显示设备只对应惟一的Server,而Server一般由系统供应商提供,通常无法被用户修改。对操作系统而言,Server只是一个普通的用户程序而已,因此很容易更换新版本,甚至更换成第三方提供的原始程序。
1.2客户端(Client):
Client是使用系统视窗功能的一些应用程序。在X下的应用程序称做Client,原因是它是Server的客户,要求Server回应它的请求完成特定动作。
Client无法直接影响视窗行为或显示效果,它们只能送一个请求(request)给Server,由Server来完成这些的请求。典型的请求通常是“在某个视窗中写‘Hello World’的字符串”,或者从A到B划一条直线。
Client的功能大致可分为两部分:向Server发出“需求”只是它的一部分功能,其他的功能是为用户执行程序而准备的。例如输入文字信息、作图、计算等等。通常,Client程序的这一部分是和X独立的,它对于X几乎不需要知道什么。通常,应用程序(特别是只大型的标准绘图软件、统计软件等)对许多输出设备具有输出的能力,而在X视窗中的显示只是Client程序许多输出中的一种,所以,Client程序中和X相关的部分只占整个程序中很小的一部分。
用户可以通过不同的途径使用Client程序:通过系统提供的程序使用;通过第三方的软件使用;或者用户为了某种特殊应用而自己编写的Client程序来使用。
1.3通讯通道 (communication channel):
client藉著它送"需求" 给server,而server藉著它回送状态 (status) 及一些其它的资讯 (information)。
只要client 和 server 都知道如何使用通道,通道的本身并不是很重要,在系统或网路上支援通讯型态的需求是内建於系统基本的X视窗函数馆(library),所有和通讯型态有关的事都从函数馆独立出来,client和server之间的通讯只要藉著使用这函数馆(在标准X版为xlib)。
Server和Client通信的方式大致有两类,对应于X系统的两种基本操作模式。
第一类,Server和Client在同一台机器上执行,它们可以共同使用机器上任何可用的通信方式做互动式信息处理。在这种模式下,X可以同其他传统的视窗系统一样高效工作。
第二类,Client在一台机器上运行,而显示器和Server则在另一台机器上运行。因此两者的信息交换就必须通过彼此都遵守的网络协议进行,最常用的协议为TCP/IP。这种通信方式一般被称为网络透明性,这也几乎是X独一无二的特性。
docker容器运行时,在网络上可以看成一个独立于宿主的机器,所以docker运行GUI程序是第二种类型,显示器和Server运行在宿主(本地)机器上,Client运行在容器(远端)中。在docker中运行GUI程序与常见的linux远程桌面所用技术相同。
下面依次介绍4种不同的docker GUI方案。
二、基于VNCServer+noVNC构建Docker桌面系统
2.1 VNC (VirtualNetwork Computer)
VNC(Virtual Network Computer)是虚拟网络计算机的缩写。VNC 是一款优秀的远程控制工具软件,由著名的AT&T的欧洲研究实验室开发的。VNC 是在基于 UNIX和 Linux 操作系统的免费的开源软件,远程控制能力强大,高效实用,其性能可以和Windows和 MAC 中的任何远程控制软件媲美。 在 Linux 中,VNC 包括以下四个命令:vncserver,vncviewer,vncpasswd,和 vncconnect。大多数情况下用户只需要其中的两个命令:vncserver 和 vncviewer。
VNC基本上是由两部分组成:一部分是客户端的应用程序(vncviewer);另外一部分是服务器端的应用程序(vncserver)。VNC的基本运行原理和一些Windows下的远程控制软件很相像。VNC的服务器端应用程序在UNIX和Linux操作系统中适应性很强,图形用户界面十分友好,看上去和Windows下的软件界面也很类似。在任何安装了客户端的应用程序(vncviewer)的Linux平台的计算机都能十分方便地和安装了服务器端的应用程序(vncserver)的计算机相互连接。另外,服务器端 (vncserver)还内建了JavaWeb接口,这样用户通过服务器端对其他计算机的操作就能通过Netscape显示出来了,这样的操作过程和显示方式比较直观方便。
同样可能远程连入UNIX、Linux进行图形化操作的还有流行的Xmanager,VNC与之相比——两者工作原理不一样,前者(VNC)是远程连入操作系统,所有操作在UNIX、Linux主机服务端进行,即使操作过程中“本地电脑与操作主机网络断开”,也不影响操作的顺利进行;而后者(Xmanager)是通过端口将主机服务器的UI界面引导到本地电脑进行展现,如操作过程出现“本地电脑与操 作主机网络断开”,操作将中断失败!如果操作中进行的工作任务非常重要,不能中断,如Oracle RAC实施,结果是灾难性的!
2.2 noVNC
noVNC提供一种在网页上通过HTML5的Canvas,访问机器上vncserver提供的vnc服务,需要做tcp到websocket的转化,才能在html5中显示出来。网页就是一个客户端,类似win下面的vncviewer,只是此时填的不是裸露的vnc服务的ip+port,而是由noVNC提供的websockets的代理,在noVNC代理服务器上要配置每个vnc服务,noVNC提供一个标识,去反向代理所配置的vnc服务。
2.3方案流程及效果
Github地址:https://github.com/fcwu/docker-ubuntu-vnc-desktop
镜像:dockerpull dorowu/ubuntu-desktop-lxde-vnc
该方案在docker容器中提供VNC服务和配置noVNC,宿主机器访问页面http://localhost:6080/(端口可设置),即可看到相应桌面,并启动容器内应用。
2.3.1方案步骤:
1、docker run -i -t -p 6080:6080 dorowu/ubuntu-desktop-lxde-vnc
2、访问页面http://localhost:6080
2.3.2Dockerfile分析:
FROM ubuntu:14.04
MAINTAINER Doro Wu <fcwu.tw@gmail.com>
ENV DEBIAN_FRONTEND noninteractive
ENV HOME /root
#安装软件
RUN apt-get update
&& apt-get install -y --force-yes --no-install-recommends supervisor
openssh-server pwgen sudo vim-tiny
net-tools
lxde x11vnc xvfb
gtk2-engines-murrine ttf-ubuntu-font-family
libreoffice firefox
fonts-wqy-microhei
language-pack-zh-hant language-pack-gnome-zh-hant firefox-locale-zh-hant libreoffice-l10n-zh-tw
&& apt-get autoclean
&& apt-get autoremove
&& rm -rf /var/lib/apt/lists/*
ADD noVNC /noVNC/
ADD startup.sh / #加入启动脚本
ADD supervisord.conf /etc/ #管理服务
EXPOSE 6080 #暴露端口号
WORKDIR /root
ENTRYPOINT ["/startup.sh"] #启动容器时运行脚本
其中startup.sh脚本主要负责创建用户以及运行命令supervisord管理服务。
最终方案效果图如图2:
图2基于VNCServer+noVNC构建Docker桌面系统效果图
三、基于ssh+Xpra+Xephyr构建Docker桌面系统
3.1 Xpra
Xpra是一个允许你本地显示一个窗口的工具,该窗口的显示的是在远端主机上运行的X Clients。它不同于X转发,它能在不打断远端进程的同时断开或者重连远端主机。它也不同于VNC,通过xpra, 远端的应用能被本地的窗口管理其当作一个普通的桌面应用进行管理,而不像VNC只能在vncviewer或类似软件(如上文中的浏览器)中显示。
3.2 Xephyr
Xephyr 是一个 Xserver,但是它执行在一个存在的 X server 里面,这个可以用来做很多事情,比如需要通过 XDMCP 连接到另外一台主机,那么不需要另外打开一个新的 X server;又比如正在写一个 window manager,那么在一个 X server 里面打开的 X server 里面调试,将会比直接在现有的 X server 里面替换现有的 window manager 方便很多。
在本方案中,Xephyr是xpra运行的一个应用,之后通过Xephyr运行完整的一套桌面环境。
3.3方案流程及效果图
Github地址:https://github.com/rogaha/docker-desktop
镜像:docker build -t [username]/docker-desktop git://github.com/rogaha/docker-desktop.git
3.3.1方案步骤:
1、CONTAINER_ID=$(docker run -d -P [username]/docker-desktop)
2、echo $(docker logs $CONTAINER_ID | sed -n 1p)
3、docker port $CONTAINER_ID 22
4、 ssh docker@192.168.56.102 -p 49153 "sh -c './docker-desktop -s 800x600 -d 10 > /dev/null 2>&1 &'" #调用docker-desktop脚本设置参数,启动xpra和xephyr
5、 xpra --ssh="ssh -p 49153" attach ssh:docker@192.168.56.102:10 #xpra显示桌面
或者运行如下脚本:
#!/bin/bash
xhost+
# docker image to use
DOCKER_IMAGE_NAME="desktop_ssh_passwd"
# local name for the container
DOCKER_CONTAINER_NAME="lc_test_desktop"
# check if container already present
TMP=$(docker ps -a | grep ${DOCKER_CONTAINER_NAME})
CONTAINER_FOUND=$?
TMP=$(docker ps | grep ${DOCKER_CONTAINER_NAME})
CONTAINER_RUNNING=$?
if [ $CONTAINER_FOUND -eq 0 ]; then
echo -n "container '${DOCKER_CONTAINER_NAME}' found, "
if [ $CONTAINER_RUNNING -eq 0 ]; then
echo "already running"
else
echo -n "not running, starting..."
TMP=$(docker start ${DOCKER_CONTAINER_NAME})
echo "done"
fi
else
echo -n "container '${DOCKER_CONTAINER_NAME}' not found, creating..."
TMP=$(docker run -d -P --name ${DOCKER_CONTAINER_NAME} ${DOCKER_IMAGE_NAME})
echo "done"
fi
#wait for container to come up
sleep 2
# find ssh port
SSH_URL=$(docker port ${DOCKER_CONTAINER_NAME} 22)
SSH_URL_REGEX="(.*):(.*)"
SSH_INTERFACE=$(echo $SSH_URL | awk -F ":" '/1/ {print $1}')
SSH_PORT=$(echo $SSH_URL | awk -F ":" '/1/ {print $2}')
echo "ssh running at ${SSH_INTERFACE}:${SSH_PORT}"
ssh docker@${SSH_INTERFACE} -p ${SSH_PORT} "sh -c './docker-desktop -s 1440x900 -d 10 > /dev/null 2>&1 &'"
sleep 15
xpra --ssh="ssh -p ${SSH_PORT}" attach ssh:docker@${SSH_INTERFACE}:10
#echo "ssh docker@${SSH_INTERFACE} -p ${SSH_PORT} "sh -c './docker-desktop -s 1440x900 -d 10 > /dev/null 2>&1 &'""
#echo xpra --ssh="ssh -p ${SSH_PORT}" attach ssh:docker@${SSH_INTERFACE}:10
3.3.2 Dockerfile分析:
FROM ubuntu:14.04
MAINTAINER Roberto G.Hashioka "roberto_hashioka@hotmail.com"
RUN apt-get update -y
RUN apt-get upgrade -y
# Set the env variableDEBIAN_FRONTEND to noninteractive
ENV DEBIAN_FRONTENDnoninteractive
# Installing theenvironment required: xserver, xdm, flux box, roc-filer and ssh
RUN apt-get install -yxpra rox-filer openssh-server pwgen xserver-xephyr xdm fluxbox xvfb sudo
# Configuring xdm toallow connections from any IP address and ssh to allow X11 Forwarding.
RUN sed -i's/DisplayManager.requestPort/!DisplayManager.requestPort/g'/etc/X11/xdm/xdm-config
RUN sed -i '/#anyhost/c*' /etc/X11/xdm/Xaccess
RUN ln -s/usr/bin/Xorg /usr/bin/X
RUN echo X11Forwardingyes >> /etc/ssh/ssh_config #配置X转发
# Fix PAM login issuewith sshd
RUN sed -i's/session required pam_loginuid.so/#session required pam_loginuid.so/g' /etc/pam.d/sshd
# Upstart and DBushave issues inside docker. We work around in order to install firefox.
RUN dpkg-divert--local --rename --add /sbin/initctl && ln -sf /bin/true /sbin/initctl
# Installing fusepackage (libreoffice-Java dependency) and it's going to try to create
# a fuse devicewithout success, due the Container permissions. || : help us to ignore it.
# Then we are going todelete the postinst fuse file and try to install it again!
# Thanks Jerome forhelping me with this workaround solution! :)
# Now we are able toinstall the libreoffice-java package
RUN apt-get -y installfuse || :
RUN rm -rf/var/lib/dpkg/info/fuse.postinst
RUN apt-get -y installfuse
# Installing the apps:Firefox, flash player plugin, LibreOffice and xterm
# libreoffice-baseinstalls libreoffice-Java mentioned before
RUN apt-get install -ylibreoffice-base firefox libreoffice-gtk libreoffice-calc xterm
# Set locale (fix thelocale warnings)
RUN localedef -v -c -ien_US -f UTF-8 en_US.UTF-8 || :
# Copy the files intothe container
ADD . /src
EXPOSE 22
# Start xdm and sshservices.
CMD["/bin/bash", "/src/startup.sh"] #启动容器时运行脚本startup.sh,该脚本主要负责创建用户并设置用户密码以供ssh连接时使用
注:原始startup.sh脚本中采用pwgen生成密码,每次启动新容器时密码都会变化,为了测试使用,可将startup.sh中DOCKER_PASSWD变量设置为固定值。可采用两种方法进行修改,法1重新build镜像,法2进入容器内,修改startup.sh脚本,然后到commit得到新的镜像。
最终方案效果如图3所示:
图3基于ssh+Xpra+Xephyr构建Docker桌面系统
四、容器与宿主机器共用X11的socket运行GUI apps
4.1 技术细节
本文第一章中提到,X window协议主要有3部分组成,属于C/S架构,在linux系统中,Xserver和X Client通过/tmp/.X11-unix下的socket进行通讯,本方案直接利用宿主机器的socket,将/tmp/.X11-unix映射至容器中,使得容器和宿主共用同一个socket,结构上宿主机器提供X server端,容器中的应用提供X Client端。
4.2方案流程及效果图
地址:http://fabiorehm.com/blog/2014/09/11/running-gui-apps-with-docker/
4.2.1Dockerfile分析:
FROM ubuntu:14.04
RUNapt-get update
&&apt-get install -y firefox
# Replace 1000 with your user / group idRUNexport uid=1000
gid=1000
&&
mkdir -p /home/developer
&&
echo
"developer:x:${uid}:${gid}:Developer,,,:/home/developer:/bin/bash"
>> /etc/passwd
&&
echo
"developer:x:${uid}:"
>> /etc/group
&&
echo
"developer ALL=(ALL) NOPASSWD: ALL"
> /etc/sudoers.d/developer
&&
chmod
0440/etc/sudoers.d/developer
&&
chown
${uid}:
${gid}-R /home/developer
USER developer
ENVHOME /home/developer
CMD/usr/bin/firefox
Dockerfile新建了一个用户,并赋予连该用户sudo权限
镜像:docker build -t[imagename] .
方案步骤:
1、docker run -ti --rm -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix firefox
方案效果图如图4
图4容器与宿主机器共用X11的socket运行firefox
五、基于ssh转发X窗口运行GUI apps
5.1 基于ssh的X转发
实现ssh的X转发需要满足以下条件:
引用:http://blog.csdn.NET/ggicci/article/details/8238118
1. 在客户端,通过在 ssh 命令添加 –X(大写)参数来启用 X11 转发,不过你可以通过设置~/.ssh/config 文件中 ForwardX11 yes 来使得X11转发为所有的连接或者指定的连接是默认的。
2. 在服务端,/etc/ssh/sshd_config 中必须指定X11Forwarding yes ,默认是 no(不过有些Linux版本默认是yes),用户是不能覆盖这个设置的。
3. 在服务端还必须装有 xauth 。如果机子上有任何 X11 的程序,xauth 很有可能就已经装好了。在不太可能的情况下,xauth 被安装在一个不标准的地方,你可以通过~/.ssh/rc 来调用它(服务端)。
4. 需要注意的是,你不需要在服务端做任何环境变量的修改。 DISPLAY 和 XAUTHORITY 会自动地被设置为正确的值。如果你在执行 ssh 命令的过程中报错说DISPLAY 没有设置好,那么说明 ssh 根本就没有转发 X11 连接。
5. 确认 ssh 转发 X11,通过 ssh –v –X 检查,如果有一行输出中包含 Requesting X11 forwarding,说明服务端有转发 X11 的能力了。
5.2方案流程及效果图
地址:http://pelle.io/2014/07/11/delivering-gui-applications-with-docker/
Dockerfile分析:
FROM ubuntu:12.04
MAINTAINER ChristianPelster <pelle@pelle.io>
# make sure thepackage repository is up to date
RUN apt-get update
RUN apt-get install -yopenssh-server firefox vim-gtk
RUN mkdir/var/run/sshd
# make the ssh portavailable
EXPOSE 22 #暴露端口
ADD id_rsa.pub/root/.ssh/authorized_keys #加入ssh pubkey,将该key(id_rsa.pub)放置在与Dockerfile同级目录下,镜像构建过程中,拷入authorized_keys,用于免密码登录。
RUN chown root:root/root/.ssh/authorized_keys
# start the ssh daemon
CMD["/usr/sbin/sshd", "-D"]
镜像:docker build -t[imagename] .
方案步骤:
使用脚本(start.sh)登录
#!/bin/bash
# docker image to use
DOCKER_IMAGE_NAME="ssh_x"
# local name for the container
DOCKER_CONTAINER_NAME="lc_test_ssh_x"
SSH_KEY_PRIVATE="-----BEGIN RSAPRIVATE KEY-----
此处为Dockerfile中pubkey对应的私钥
-----END RSA PRIVATE KEY-----
"
# write ssh key to temp. file
SSH_KEY_FILE_PRIVATE=$(tempfile)
echo "${SSH_KEY_PRIVATE}"> ${SSH_KEY_FILE_PRIVATE}
# check if container already present
TMP=$(docker ps -a | grep${DOCKER_CONTAINER_NAME})
CONTAINER_FOUND=$?
TMP=$(docker ps | grep${DOCKER_CONTAINER_NAME})
CONTAINER_RUNNING=$?
if [ $CONTAINER_FOUND -eq 0 ]; then
echo-n "container '${DOCKER_CONTAINER_NAME}' found, "
if[ $CONTAINER_RUNNING -eq 0 ]; then
echo"already running"
else
echo-n "not running, starting..."
TMP=$(dockerstart ${DOCKER_CONTAINER_NAME})
echo"done"
fi
else
echo-n "container '${DOCKER_CONTAINER_NAME}' not found, creating..."
TMP=$(dockerrun -d -P --name ${DOCKER_CONTAINER_NAME} ${DOCKER_IMAGE_NAME})
echo"done"
fi
#wait for container to come up
sleep 2
# find ssh port
SSH_URL=$(docker port${DOCKER_CONTAINER_NAME} 22)
SSH_URL_REGEX="(.*):(.*)"
SSH_INTERFACE=$(echo $SSH_URL | awk-F ":" '/1/ {print $1}')
SSH_PORT=$(echo $SSH_URL | awk -F ":" '/1/ {print $2}')
echo "ssh running at${SSH_INTERFACE}:${SSH_PORT}"
ssh -i${SSH_KEY_FILE_PRIVATE} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null-Y -X root@${SSH_INTERFACE} -p ${SSH_PORT} $1
rm -f ${SSH_KEY_FILE_PRIVATE}
该脚本主要命令为上文中标黑字体部分。
效果图如下图5(gvim),图6(firefox):
图5基于ssh转发X窗口运行(gvim)
图6基于ssh转发X窗口运行(firefox)