Windows Docker 第一时间揭秘
【编者的话】这是盆盆谈微软两会(Build/Iginte)系列之一。文章引用孙建波老师关于 Linux 内核的 6 大命名空间隔离,看 Windows Docker 如何实现类似隔离,同时又有哪些不同。文章素材取自 Build 和 Ignite 大会视频,但主要展示盆盆自己的分析和研究,还望大家指正为谢。
在咱们微信群里听一位兄弟提到,Docker 能将 DevOps(意即开发和运维)整合在一起,暗合王阳明先生的“知行合一”之教,这真是一种有趣的说法。
话说从头,盆盆在 《Windows Docker 深入原理分析》 里曾经提到微软 Build 大会后,会第一时间给大家介绍 Windows Docker 技术。
还没看过 Build 在线视频的朋友,您可以泡杯咖啡,带上耳机,静静地欣赏以下由 Taylor Brown 主讲的 Windows Docker 讲座,我们的文章就以此为蓝本。
这里需要注意的是:以下的论述,大多是盆盆根据 Taylor 的 demo 效果所做的推论,并不是 Taylor 本人的陈述,所以并不一定正确。由于能力所限,必然谬误不少,还望大家能及时指出哈。
容器即隔离
拿大家熟悉的 Linux Docker 来看,其涉及到 Linux 内核所提供的 Namespace 隔离技术和资源控制的 CGroup 技术。
这里推荐大家阅读浙大 SEL 研究生孙建波老师的文章 《Docker 背后的内核知识——Namespace 资源隔离》 。
孙建波老师提到了一张表格,其中列出了 Linux 内核所支持的 6 种隔离:主机名、IPC、进程 ID、网络、文件系统、账号。
尽管 Windows 内核实现和 Linux 不同,但是两者还是有不少可比拟处。所以盆盆根据 Build 大会上 Mark Russinovich 这位大神以及 Taylor Brown 的讲座,来条分缕析。试图按照这几个 Namespace 隔离的框架来进行阐述。
文件系统隔离
先来看看浙大 SEL 的另一位大牛孙宏亮老师的文章 《Docker 源码分析(九):Docker 镜像 》 。这篇文章清晰地描述了 Linux Docker 的文件系统隔离,多层的可叠加文件系统。
孙宏亮老师指出,假设我们下拉了 Ubuntu:14.04 映像,并通过命令 docker run –it ubuntu:14.04 /bin/bash 将其启动运行。则 Docker 为其创建的 rootfs 以及容器可读写的文件系统参见下图。从容器的视角来看,虽然只有一个逻辑的完整文 件系统,但该文件系统由“2 层”组成,分别为读写文件系统和只读文件系统(按只读层还可以再逻辑分层,所以极大地节省磁盘空间)。
Windows Docker 同样如此,顶层的沙盒层(sandbox layer) 是可读写的,只允许该容器自己占用,而其他层则是只读的,可供不同容器共享。在下图中,底层的基础 OS 层和中间的应用程序框架层都是只读的, 而顶层的沙盒层则可读写,在容器的视角看来,它独占了完整的 OS。这有点类似于 Hyper-V 的差异磁盘链(顶部的子盘才能读写,其上方的所有父盘和 Base 盘都是只读的)。
为了说明文件系统隔离的魔力,Taylor 演示了在 Windows 容器里用del命令删除 C 盘根目录下所有文件,然后再用reg命令删除 HKLM\Software 下的所有注册表键值,尽管这个容器被毁了,但是并不会影响其他容器,更不会影响主机。
盆盆猜测,在 Windows 容器的视角里,如果只是读取一个文件,在最顶端的沙盒层里只有该文件的重解析点(reparse point);只有在修改该文件时,才会用 copy-on-writer 的方法从下方的只读层中把文件内容复制到可读写的沙盒层。
创建 Windows Container
视频里演示了一个 demo。用一段简单的代码,在 Docker Client 的 console 里显示“This is a pretty cool app”。
这是用来构建 Windows Docker 映像的 Dockerfile。这个文件由以下 4 行命令组成,基本上每一行命令等于叠加 1 个 Layer:
- 第 1 行:表明该映像基于 windowsservercore 这个 Base 映像,可以将其理解为 rootfs
- 第 2 行:表示工作目录是 C 盘根目录
- 第 3 行:将应用目录复制到容器映像里
- 第 4 行:启动该应用程序
用 docker build 命令将其构建为容器映像,Tag 是 1。从命令结果中可以看到 Dockerfile 里的每个命令都被执行,并叠加新的 Image Layer。
Docker 映像构建完成后,可以运行 docker image 命令查看新建的映像,运行 docker run 命令即可快速启动该映像,并成功显示”This is a pretty cool app…”。
通过使用 docker history 命令,我们可以查看容器映像的构建历史,这甚至可以用来逆向生成 Dockerfile。例如视频里演示了 sysinternals 这个映像 的构建历史。从中我们可以看到:首先记录维护人员是谁;然后指定工作目录是 C 盘根目录;再者是将 sysinternals suite 这个工具软件目录拷贝到容器映像里;然后设定容器的运行账户(本文后面的账户隔离一节还会再阐述);最后设置启动 ipconfig 以便显示容器 的 IP 地址。
IPC 隔离
和 Linux Docker 容器一样,Windows 容器也采用 IPC 隔离机制。Windows 容器使用 Windows 自己的 session 隔离机制。
会话(session) 隔离机制,最初是用在 Windows 终端服务和快速用户切换中,以便这些不同用户的进程不会互相干扰,确保安全。从 Windows Vista 开始,也采用这种技术对系统会话进行隔离,Windows 系统自己的服务和进程会占用原来的控制台会话(会话 0),而后续的用户会依次使用会话 1、会话 2 等等,这就是我们不能再使用 mstsc /console 进行控制台登录的原因。
从《Windows Internals》里我们可以学习到:不同会话里的应用,不能够发送窗口消息(Window Message),以防止粉碎攻击。每个会话拥有自己专用的对象命名空间。同样每个 Windows 容器,也会有自己的独立会话,有自己专用的 BaseNamedObjects 等对象命名空间,以便隔离事件、互斥信号和内存段等对象。这样不同容器在同一个 Windows 主机上访问同一个命名对 象,就不会导致冲突。以下的 WinObj 对话框是在盆盆自己的 Windows 10 上截图的,虽然不是取自 Windows Docker 系统,但是道理是一样的,可以看到不同的会话,拥有自己专用的对象命名空间。
有兴趣的朋友可以参考盆盆在 9 年前发表在 ITECN 博客 上的文章,介绍会话隔离技术。
视频里演示了 Docker 容器的会话隔离能力。Taylor 启动了两个容器,都是从同一个 windowsservercore 映像里派生出来的。 其中一个容器,可以运行 Tasklist 命令,看到该容器运行在会话 14 中。而且还能看到两个系统进程,一个是 System 进程,代表操作系统本身,另一 个是空闲进程,这两个进程都运行在会话 0 里。
在同一个映像所创建的另一个容器里,我们可以看到该容器运行在会话 15 中,同样可以看到 System 进程和空闲进程。
Taylor 的解释是由于容器是共享 Windows Kernel 的,所以容易可以看到 System 进程的 PID 是一样的,都是 4。其实 System 进程并不是用户模式进程,而是用来代表所有操作系统和驱动 程序的内核模式线程,在所有的 Windows 主机上,System 进程的 PID 都是 4,空闲进程也是同样的道理。
网络隔离+图形化访问
和 Linux 容器一样,Windows 容器也可以有自己的独立网络配置。
此外,由于最新的 Windows Server 2016 具备 SDN 功能,不但包含先前 Windows 2012 对于 NVGRE 的支持,同时也支持 Vxlan。理论上来说,Window 内置的网络支持能力如果能够和 Windows Docker 有机的整合,可以更好地支持 Windows Docker 功能。
由于传统的 Windows 应用大多是有 GUI 的,所以这些应用可能需要通过图形化方式进行远程操控。
视频里 Taylor 举了一个 sysinternals 容器的例子。有趣的是,这个 demo 本来是 Mark Russsinovich 的保留曲目,可惜 Build 大会 Keynote 的时间十分宝贵,Mark 没有足够的时间去演示,而由 Taylor 代劳。
Taylor 演示直接在 docker client 上下文里启动 Sysinternals Suite 里的经典工具 Process Explorer,由于该工具带 GUI,所以虽然进程已经在容器里启动,但是在 Docker Client 里无法直接远程显示。
如何才能正常显示呢?Taylor 用了一个 CC 命令,直接连接到该容器的 IP 地址(192.168.0.23)。从 demo 里我们可以看出,实际 上这个 CC 命令连接到容器的 RDP 服务上,这样就相当于直接通过终端服务连接到容器里的会话里,哈哈,有点类似 RemoteApp(或者 Citrix XenApp) 的效果!
盆盆在 《Windows Dcoker 深入原理分析》 里曾经提到,Windows Docker 的前身 DrawBridge 在其沙盒里实现了 RDP 服务,Windows Docker 的原理应该类似。
Linux Docker 也能访问图形化界面,在这篇文章 《在 Docker 中运行 OpenOffice》 里介绍,只需在 Dockerfiles 里添加安装 X Server 的命令,就能借助 VNC 客户端连接到 OpenOffice 图形化界面。
PID 隔离
在 Linux 容器里,容器里的 PID 有自己的独立命名空间。从演示的情况来看,Windows 容器的 PID 隔离方法看上去略有不同。
在前面 IPC 隔离一节,我们可以通过 tasklist 命令确认不同的容器,其 CSRSS、Lsass、SVCHOST 等重要进程的 PID 有所不同,用户进程的 PID 也不一样,可见彼此之间是完全隔离的。
那么从宿主机的角度来看呢?
我们可以用 Process Explorer 的例子来确认,这需要观看 Ignite 大会上由 Taylor 所主讲的视频,由于 Process Explorer 是在终端会话里打开的,所以我们可以在容器的任务管理器里看到有两个会话:
- 会话 14 是 Docker 客户端访问生成
- 会话 15 则是通过 RDP 访问生成
可以看到 Process Explorer 的进程有两个版本。显然,会话 14 是 Taylor 在 Docker 客户端里运行的结果(但是我们无法看到图形化界面),而会话 15 则是 RDP 访问的结果。
两者的运行账户不一样,RDP 登录的运行身份为 Administrator(应该和 Docker History 一致),而会话 14 则是 System 账户。
以下是从容器视角所看到的任务管理器。这个任务管理器是从容器的角度来看的,我们可以记下其中的若干 SVCHOST 进程的 PID。
接下来我们打开宿主机的任务管理器,从全局的角度来查看。可以发现,从宿主机的角度来看,能看到每个容器里的进程,其 PID 和容器里面的版本是一样的。
用户账户隔离
Linux 内核拥有账户隔离能力,可以让容器里的进程以 root 身份运行,而在宿主机上,该账户实际上是普通用户权限,这样可以极大地改进安全性。
在 Taylor 的这个演示中,我们也能“察觉”到一些蛛丝马迹。在 PID 隔离一节的两个任务管理器截图里,从容器的视角来看,可以看到 Process Explorer 的运行账户为 Administrator,但是从宿主机上查看,对应进程的运行账户为空。所以 Windows 容器可能也实现了类似的账户 隔离技术(抱歉这只是推测,我们现在还拿不到 Windows Docker 的测试版本)。
计算机名和域名隔离
Windows 容器拥有自己的计算机名和域名隔离能力,这样在网络上,Windows 容器看上去类似于一台独立的虚拟机。
不过由于共享内核,所以 Windows 容器目前应该不支持活动目录,毕竟同一台宿主机上所有容器应该都具有同一个 SID,这样就无法加域(无法验证计算机账户)。
盆盆推测,到了 Windows 容器时代,类似于活动目录这类比较笨重的验证协议可能会逐渐退出历史舞台。毕竟活动目录需要开放那么多端口,需要借助 ADFS 等手段才能穿透 Internet。
不过 Windows 容器的验证并不会存在问题,Azure AD 和证书等都是很合适的办法。
容器的优势
容器非常适合开发的快速迭代、快速回滚。Taylor 做了一个简单的演示,对前面所述的代码进行修改,调用私有的 msvcr120.dll 文件里的_snwprintf 函数,以显示”Now it’s really cool”。
但是在 docker build 的时候,Taylor 没有修改 Dockerfile,没有像 Mark 之前的 demo 那样把私有的 msvcr120.dll 拷贝到容器映像中,以便生成新的 Layer。
结果由于容器的目标宿主机上没有安装 Visual Studio,所以新 Build 的容器运行失败,提醒缺少 msvcr120.dll 文件,而无法显示”Now it’s really cool”。
解决这个问题很简单,可以根据先前的映像快速生成新的容器,这大概只需要几秒钟时间。这样就可以有充足的时间去调试修改了。
在 Build 大会的 Keynote 上,Mark Russinovich 演示了用 Visual Studio 把同一个电商网站的代码签入到 Windows 容器和 Linux 容器里,并且可以用 Visual Studio 来调试 Linux 容器里的代码。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: 一些重要 Docker 命令的简单介绍
下一篇: 彻底找到 Tomcat 启动速度慢的元凶
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论