Linux 系統中 Docker 的設計與實作

Docker 和 Linux Container (LXC), BSD 的 Jail, 都是屬於 Lightweight Container (輕量級容器).  Docker 可以讓一個應用系統及其所需要的執行環境, 如系統設定, 環境參數和程式庫等, 打包成一個可以獨立執行的 Image.  和 VM 比起來, 因為 VM 需要安裝完整的作業系統, 而Docker 等 Lightweight Container 因為直接使用作業系統上的資源, 不需要安裝作業系統, 因此需要較少的資源, 在同一台實體機上可以部署更多應用系統, 有更高密度, 啟動時間也較短, 只需要約幾個 msec, 和啟動 VM 約需幾秒鐘相較, 要比 VM具有較好的彈性

Docker (及其他 Lightweight Container) 都需要修改作業系統的 Kernel 才能有效支援, 在 Linux 系統, 主要增加的有三個部分:

  1. Namespace, 可以讓一個 Process 對於系統有自己的視野, 也就是說讓不同的 Process 可以看到系統的不同面貌, 例如不同的 Process 可能看到的系統名稱可以不一樣.  Namespace 可以讓一個應用系統去設定自己所需要的執行環境, 因此可以在不同的機器上執行.
  2. Control Group, 提供屬於同一個 Group 的 Processes 的資源管理及追蹤的機制, 例如可以限制一個 Group 內的 Processes 總共使用的記憶體空間不能超過一個限度 (即使實體記憶體還有空間也不能使用, 有點類似 VM).    Control Group 可以有效地限制一個應用系統所能使用的資源.
  3. Union Filesystem, 提供一個介面讓不同檔案系統可以共存, 而使用者程式只會看到單一的檔案系統, 使用相同的方式做存取動作. 在此我們暫不討論 Union Filesystem.

Namespace

Namespace 可以讓一個 Process 對於主機系統有自己的視野, 也就是說在某種程度下, 一個 Process 可以有自己的系統參數及組態設定.  例如說 uname 這個 function 可以拿到主機名稱, 作業系統及版本等資訊. 一般而言, 同一系統下的所有 Processes 應該都會拿到相同的資訊, 但是透過 Namespace, 不同的 Processes 可以拿到不同的系統名稱.  因此, Namespace 也可以當成一種隔離機制. 目前 Linux 系統有七種 Namespaces, 即CGroup, IPC, Net, Mount, PID, User, 及 UTS.

在 Linux 系統中, 有三個 Namespace 相關的系統呼叫, clone, unshare, 和setns, 另外每一種 Namespace 都有相對應的 Flag.  在 Kernel 的實作上, 主要的資料結構是 nsproxy (include/linux/nsproxy.h), 這個資料結構是透過 task_struct (Process Table 的資料結構) 中的 nsproxy 這個變數指過來.  在 nsproxy 這個資料結構中, 則各有 pointers 指到相對應的 Namespace 所需要的資料結構, 例如 UTS 的 namespace 會有一個叫uts_ns 的 pointer 指到 uts_namespace (include/linux/ustname.h) 的資料結構, 而在 uts_namespace 的資料結構中, 有一個叫 name 的 pointer 指到 new_utsname (include/uapi/linux/utsname.h) 的資料結構, 裡面就存有主機和作業系統相關的資訊.  以 gethostname 這個取得主機名稱的系統呼叫為例, 他會直接呼叫 utsname 這個 function (include/linux/utsname.h) 來取得 new_utsname 的資料結構, 裡面就包含有主機名稱的資訊, 而這些資訊可隨著 Namespace 不同而有所不同.

Control Group (CGroup)

CGroup 負責管理及追蹤屬於同一 Group 中的 Processes 資源的使用, 如記憶體, CPU, 網路等.  目前 Linux 的 CGroup 有 V1 和 V2 兩個並存的版本, V1 有12個 Controllers (Control Group 的監控程式), V2 則有 3個 Controllers.  CGroup 並沒有新增系統呼叫, 但需要新增 Virtual File System (VFS) /proc/cgroups, 以及修改部分 Kernel 程式, 主要是 fork 和 exit 的部分, 而且在系統開機時要做一些起始化的動作.  在 Kernel 的實作上, 第一個是 cgroup_subsys 的資料結構 (include/cgroup-defs.h), 用來定義 cgroup 的 Callback functions, 這些 Callback 主要是提供資源管理等動作.  另一個資料結構是 cgroup_subsys_state (cgroup-defs.h), 用來記錄系統中現有的 cgroup 的階層架構 (因為 cgroup 會組成階層), 也就是目前系統中的狀態.  另一個叫 cgroup 的資料結構 (cgroup-defs.h), 則是用來記錄 cgroup 的檔案系統.

我們以 pids 這個 cgroup 為例, 這個 cgroup 可以用來防止一個 Process大量 fork 新的 Processes (fork bomb), 因為在 Linux 系統中, fork 是產生新的 Process 的唯一方式, 因此要產生新的 Process 都會呼叫 fork 這個系統呼叫. 這種 fork bomb 產生的原因可能是程式錯誤,或是其他原因, 如 denial of service 的攻擊.  例如在一個迴圈內無條件的呼叫 fork, 將瞬間產生大量的 Processes, 可能導致系統癱瘓. 當要產生新的 process 而去呼叫 fork 這個系統呼叫時(kernel/fork.c), fork 會叫呼叫 _do_fork, 然後 _do_fork 會再呼叫 copy_process (fork.c), 把整個 Process 複製一份.  在 copy_process 中會呼叫 cgroup_can_fork (kernel/cgroup/cgroup.c) 來檢查是不是可以 fork 出一個 新的Process. 如果 Processes 的個數已大於所設定的極限, 則不允許新增 Process, 而直接回傳錯誤訊息.  因此可以控制 Processes 的無限度增加.

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *