众所周知,Docker 能打通开发和运维的任督二脉,所谓 DevOps 是也。有朋友说,这符合王阳明的"知行合一"之教。
而 Windows Server 2016 内置的 Windows Docker 亦已经出来一段时间,这里就来和诸公汇报一下测试结果。
Linux 和 Windows,Docker 里各有多少进程
在安装配置 Container Host 的时候,经常报错 Container OS Image 下载失败 (没办法,墙内的缘故)。
什么是 Container OS?顾名思义,是从容器角度看到的 OS。
Container OS 实际是应用所依赖的用户模式 (User mode)OS 组件,对于 Windows 容器来说,例如 ntdll.dll、kernel32.dll 或者 coresystem.dll 之类的 System DLL。主机上的所有容器共享内核模式 (Kernel mode)OS 组件,对于 Windows,就是 ntoskrnl.exe,还有驱动等。
例如对于以下命令,意味着 Windows 系统从 docker 映像中获取 Windows Server Core 的用户模式 OS 组件,并启动 cmd 获得 Shell。
docker run -it windowsservercore cmd
Linux 也是一理,如果运行以下命令,意味着从 docker 映像中获取 Ubuntu 的用户模式组件,并且启动 Bash Shell。
docker run -it ubuntu /bin/bash
对于以上两个容器,Linux 容器里的进程比较少,可以参考以下截图:
而 Windows 容器,则情况略有不同。
在 Windows 主机上启动 Process Explorer,可以看到这个 Windows 容器的进程相对多一些:
这是因为在 Windows 系统中,需要给应用提供一些用户模式的系统服务,例如 DNS、DHCP、RPC 等服务,这样从容器的角度来看,容器获得了自己独有的服务 (一般是在各自的 svchost 或者其他服务宿主进程里运行),构成了所谓的 Container OS。
我们可以用 PowerShell 命令查看容器内部启动的 Windows 服务,大概有 27 个,参考附图。
很可惜,这个版本的 Windows docker 里,虽然有远程桌面服务,但是目前还不支持远程桌面到容器,所以无法使用容器应用的图形化界面。
容器里的应用,到底应该启动多少 Windows 服务?由于 Windows 服务的具体作用是非文档化的,所以不像 Linux 可以做到最精简。但是由于这些服务几乎不占用什么额外的资源,对于容器性能没有影响。
Windows 容器的进程如何隔离
由于在最新的测试版本里,容器对象的权限设置有了改变,只有 SYSTEM 权限才能查看。所以要查看 Windows 容器的进程隔离,需要用 SYSTEM 权限启动 Winobj。这可以借助 Psexec 来实现:
Psexec -i -d -s winobj.exe
可以看到 Windows 对象空间里多了一个 Containers 的节点,其下有若干个 GUID 分支,这些 GUID 代表系统里的容器。其下每个容器有自己独立的 BaseNamedObjects 等命名空间,包括互斥信号量、内存 Section、事件等。
可以用 PowerShell 查看容器的 GUID,参考附图。
每个容器节点下,有自己的 Session 分支,例如该容器,占据了 Windows 系统的 Session 2。如附图所示。
这就是为什么,不管用任务管理器,还是 PowerShell,抑或是 Process Explorer 等工具,我们都在 Windows 主机里看到容器里的所有进程都会标记 Session 为 2。
借助 Process Explorer,我们可以看到容器里的进程,所打开的 Handle,其中就指向先前所看到的 Windows 容器对象命名空间。
同时还能看到,容器进程所在的 WindowStation 并不是 WinSta0,而是 Service-0x0-3e7$,3e7 的 10 进制等于 999,等于九五之尊,这是 SYSTEM 服务所在的窗口站。所以容器进程无法在 Windows 桌面上拥有图形化界面。
还可以查看一个有意义的对象,Windows 容器所挂载的主机目录,类似于 Linux 容器的 Volume。
Windows 容器的文件系统如何隔离
和 Linux 一样,Windows 容器映像采用分层的文件系统,基于映像创建容器后,相当于在只读的分层文件系统上再覆盖一层可读写的文件系统层。如果要修改的文件在最上层的可读写层里没有,则沿着分层的 Layer 找到目标文件后,将其用 COW(Copy on write:写时复制) 复制到可读写层再修改。
让我们进入到 Windows 主机的以下目录:
C:\ProgramData\Microsoft\Windows\Hyper-V\Containers
该目录下列出所有通过 PowerShell 命令创建的容器文件。其下有文件夹和文件,都以容器的 GUID 来命名。
其中的 926A300B-ACB7-4B28-9D86-45BF82C1211C.vhdx 就是该容器的最上层的可读写层,是一个 VHDX 文件。
记住该可读写层并不是一个完整的文件系统,它需要和 Image 的现有文件系统组成 Union File System。如果尝试双击该 VHDX(只能尝试挂载停止状态的容器 VHDX),试图挂载到 Windows 系统,会弹出以下报错信息,提示该虚拟硬盘无法挂载。
Image 的文件系统位于以下路径 (Windows Server Core 的 Container OS 文件):
C:\ProgramData\Microsoft\Windows\Images\CN=Microsoft_WindowsServerCore_10.0.10586.0\Files
如果用 Process Explorer 查看容器进程访问的 Dll,可以看到其访问的路径为 Container OS 文件。
如果是用 docker 命令创建的进程,道理类似,但是其可读写层文件系统位于以下路径:
C:\ProgramData\docker\windowsfilter
Windows 容器还有注册表
和 Linux 不一样,Windows 容器还需要考虑注册表的隔离问题。和文件系统命名空间隔离一样,注册表命名空间隔离也采用类似 Union FS 形式。
下面让我们进入 PowerShell 命令创建的 Windows 容器文件夹内部。
C:\ProgramData\Microsoft\Windows\Hyper-V\Containers\926A300B-ACB7-4B28-9D86-45BF82C1211C\Hives
在这个 Hives 文件夹下方,有很多命名为 *_Delta 的文件,这是容器所访问的注册表配置单元文件。
从命名方式中可以看到,容器的注册表和文件系统一样,也采用分层架构,最上层的是可读写的注册表命名空间。而 Image 映像也有只读部分的注册表空间,路径如下。
C:\ProgramData\Microsoft\Windows\Images\CN=Microsoft_WindowsServerCore_10.0.10586.0\Hives
在 Process Explorer 里可以看到可读写层、只读层注册表合并后所加载的内容。
Docker 命令所创建的容器,方法类似,位于类似以下路径:
Windows 容器的资源限制
大家知道,Docker 可以调用 CGroup 技术来限制 Linux 容器的 CPU、内存等资源占用。而在 Windows 容器里,内存资源的限制,则是通过 Windows 的 JO(作业对象) 技术来实现。
可以参考以下技术来限定 Windows 容器的 CPU、内存和磁盘 IO。例如可以将容器的内存限定为最大占用为 5GB。
https://msdn.microsoft.com/en-us/virtualization/windowscontainers/management/manage_resources?f=255&MSPPError=-2147217396
然后用 Process Explorer 打开任意一个容器进程的属性对话框,切换到 Job 标签页。
可以看到所有容器进程共享一个作业对象,而且该作业对象的内存限额 (Job Memory Limit) 为 5GB。
作者简介
彭爱华 网名盆盆,微软混合云技术顾问。11 届微软最有价值专家 (MVP),微软高级认证讲师。出版过近 20 本技术图书,2005 年创建 ITECN 博客 (09 年全国 IT 博客五十强)。微信公众号 (华来四笑侃 Windows):sysinternal。曾获得微软技术大会 TechEd 最佳讲师、中国首届 IT 管理技术大会 (2009) 最佳讲师的光荣称号。
感谢陈兴璐对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们。
评论