Windows Docker第一時間揭秘
原文 http://dockone.io/article/369
這是盆盆談微軟兩會的系列之一,所謂微軟兩會,是指微軟Build大會和微軟Ignite大會。
在咱們微信群里聽一位兄弟提到,Docker能將DevOps(意即"開發"和"運維")整合在一起,暗合王陽明先生的“知行合一”之教,這真是一種有趣的說法。
話說從頭,盆盆在 《Windows Docker深入原理分析》 里曾經提到微軟Build大會后,會第一時間給大家介紹Windows Docker技術。本文最初發表在盆盆自己的微信 華來四公眾號 上。
還沒看過Build在線視頻的朋友,您可以泡杯咖啡,帶上耳機,靜靜地欣賞以下由Taylor Brown主講的Windows Docker講座,我們的文章就以此為藍本。
http://channel9.msdn.com/Events/Build/2015/2-704
這里需要注意的是:以下的論述,大多是盆盆根據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博客上的文章,介紹會話隔離技術:
http://blogs.itecn.net/blogs/w ... .aspx
視頻里演示了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應用大多是有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容器里的代碼。