總體架構
Docker 采用的是 C/S 架構,使用 REST API、UNIX 套接字或網絡接口進行通信。一般用戶端會和 Docker 服務運作在同一台機子上,像我們平常使用的 docker build、pull、run 等指令就是發送到本地用戶端上的,本地用戶端再發送給 Docker 服務端。另外,用戶端也可以獨立部署,像 Docker Compose。
Docker 服務一般是以守護程序的形式運作,它會監聽用戶端的請求,并且進行容器的建構、運作和分發,下面即 Docker 的總體架構:
-
:偵聽 Docker API 請求并管理 Docker 對象,例如鏡像、容器、網絡和卷。守護程序還可以與其他守護程序通信以管理 Docker 服務。Docker 守護程序
-
:通過 Docker API 發送指令給 Docker 守護程序(Docker 用戶端
),讓守護程序執行對應的指令動作,例如發送 docker run 指令。dockerd
-
:存儲了 Docker 鏡像。像 Docker Hub 就是一個任何人都可以使用的公共注冊中心,Docker 會預設地從 Docker Hub 上查找鏡像。當然,我們也可以自己搭建個 Docker Registry。Docker Registry
容器的演變
一開始,Docker 是基于 Linux 核心提供的技術進行容器管理的,它将 Linux 複雜的容器管理進行了簡化,形成了自己獨有的一套指令體系。後來,Docker 将底層技術進行了抽象,定義了一組接口,隻要實作了這組接口,那麼就可以進行容器的管理,這就是
Libcontainer
。
随着 Docker 的火熱,越來越多的公司加入容器技術的開發,在 2015 年谷歌、微軟、Docker 等公司成立了 OCI 組織,緻力于定制一緻的容器标準。在 Libcontainer 的基礎上推出了容器引擎:
runC
可能大家會比較好奇的是 windows 的容器架構又是怎麼樣的?其實在 windows 上也抽象出來了
CGroup
和
Namespace
,它也是符合 OCI 容器标準的,如下圖:
(圖檔來自 Black Belt 在 DockerCon 的演講:
Docker 與 Windows 容器揭秘)
底層技術
Docker 是用 Go 語言編寫的,是以天生就支援這種跨平台的部署。不過主流的伺服器都是 Linux 系統,是以我們來看看關于 Linux 的容器底層技術:
Namespaces
(資源隔離)、
CGroups
(資源限制)、
UnionFS
(鏡像和容器分層)。
Namespaces(資源隔離)
Namespaces
是 Linux 核心在 2.4.19 版本後陸續引入的概念,它将系統的全局資源通過抽象劃分,使得在同一 namespace 中的程序看起來擁有自己的全局資源。目前 Linux 支援以下六種 Namespace
namespace | 隔離的系統資源 |
---|---|
Mount namespaces | 檔案系統挂接點 |
IPC namespaces | 特定的程序間通信資源 |
UTS namespaces | nodename 和 domainname |
PID namespaces | 程序 ID |
Network namespaces | 網絡相關的系統資源 |
User namespaces | 使用者群組 ID 空間 |
我們可以看到有 Network 網絡的,也有使用者 User 的 隔離。當容器被建立時,會建立上面對應的 Namespace 執行個體,然後将容器程序劃分到此 Namespace 裡,以此實作了隔離功能。
CGroups(資源限制)
上面的 Namespace 為我們提供了環境隔離的功能,但這還遠遠不夠,因為各個程序所使用的資源還是沒有限制的,比如 CPU、記憶體等。一旦某個容器超過上限,則有可能會被 kill。是以,對資源的限制使用就很重要了,而 Linux 核心的
CGroups
就提供了此功能。
當我們建立了一個容器時,預設的會在
/sys/fs/cgroup
目錄下生成對應的資源使用目錄,比如
docker run nginx:test
,則會在
/sys/fs/cgroup/memory/docker/nginx容器ID
目錄下有對應的資源描述檔案:
使用指令
docker run --memory 1024M nginx:test
時,就可以進行記憶體資源的限制了。其他資源限制指令也類似。
UnionFS(鏡像和容器分層)
Linux 的
UnionFS
(聯合檔案系統) 技術是用來将不同實體位置的目錄合并挂載到同一個目錄中。實際上 UnionFS 在不同的系統上有不同的實作,現在主流的是 AUFS、Devicemapper 和 OverlayFS。在 Docker 中最常用的是 AUFS,我們主要來看看 AUFS 的相關知識。
首先,預設情況下 AUFS 有個特點,就是要聯合的第一個檔案是可讀可寫的,後面的檔案目錄則隻能隻讀。例如,我們将 teacher、student 目錄聯合到 mnt 目錄下:
# 将 teacher 和 student 聯合到 mnt
sudo mount -t aufs -o dirs=./teacher:./student none ./mnt
├── teacher
│ ├── A
│ └── C
└── student
├── B
└── C
# 檢視./mnt
$ tree ./mnt
├── A
├── B
└── C
當我們對 mnt 下的 C 目錄修改後,會在 teacher 目錄下同步看到修改,但 student 目錄就不會被修改了,因為它是隻讀的。那這樣的機制在 Docker 裡有什麼作用呢?
首先,Docker 将檔案系統分為容器層和鏡像層,這裡的容器層相當于上面的 teacher 目錄,鏡像層相當于 student 目錄。也就是容器層檔案是可讀可寫,而鏡像層是隻讀的。這樣的話,有利于多個容器共享一個鏡像檔案。
而且 Docker 在一開始的時候并不會建立容器層,而是先使用鏡像層檔案,隻有當容器裡的檔案發生了修改,此時才會真正的建立出可讀可寫的容器層,以保證不影響鏡像層檔案。而這種類似
寫時複制
技術,為系統節省了很多不必要的存儲檔案。
Docker 的安全
在審查 Docker 的安全性時,主要從下面四個方面考慮:
(一)Namespaces、CGroups 的安全
Docker 容器與 LXC 容器非常相似,它們具有一樣的安全特性。Namespaces 提供了第一種也是最直接的隔離形式,使得在容器内運作的程序無法看到在另一個容器或主機系統中運作的程序。每個容器也有屬于自己的網絡堆棧,這意味着一個容器不能獲得對另一個容器的套接字或接口的特權通路。
CGroups 是 Linux 容器的另一個關鍵元件,能對資源進行核算和限制,提供了許多有效名額,確定每個容器獲得公平的資源使用(例如記憶體、CPU、磁盤 I/O),使得單個容器無法耗盡系統資源。這在多租戶平台(例如 PaaS)上尤為重要,能保證使用者一緻的正常運作性能。
(二)Docker 守護程序的安全性
運作 Docker 守護程序是需要 root 特權的,是以隻有受信任的 User 才能運作 Docker 守護程序。但是由于 Docker 是允許主機和容器共享檔案夾的,如果我們将系統檔案映射到 Docker 容器裡,那肯定也是能突破系統防護的。不過,這主要取決于我們關聯的主機檔案,一般還比較好控制。
Docker 也需要防止某些非法請求建立了破壞性的容器。在 0.5.2 之後為了防止一些惡意使用者的跨站腳本攻擊,Docker 使用了本地的 UNIX 套接字而不是綁定在 127.0.0.1 上的 TCP 套接字,這樣就允許使用者進行本地權限檢查,以進行安全通路了。
(三)Linux 核心的安全
預設情況下,Docker 啟動的是一組功能受限的容器,這使得容器中的“root”比真正的“root”擁有更少的特權,例如:
- 禁止任何挂載操作;
- 禁止通路本地套接字(以防止資料包欺騙);
- 禁止某些檔案系統的操作,例更改檔案所有者或屬性;
- 禁止子產品加載;
這使得入侵者設法更新到容器内的 root,也很難以對主機造成嚴重性的破壞。
(四)其他核心安全特性
- 允許配置隻能拉取指定秘鑰簽名的鏡像倉庫
- 使用 GRSEC 和 PAX 運作核心,在編譯和運作時增加許多安全檢查
- 使用具備安全特性的容器模闆
- 自定義通路控制政策