7.1 1979-chroot 到 2013-Docker:容器技術史#
定位:容器是 OS 的「借用者」#
第 5 章結尾留了一個理論根據:沙箱必須由 OS 層兜底(JVM SecurityManager 的教訓)。第 6 章建立了 OS 的地基(程序、頁表、系統呼叫)。本章的主角——容器——正是「在 OS 之上、VM 之下」的隔離層:
graph TD
A[應用] --> B[容器<br/>namespace + cgroups]
B --> C[作業系統核心<br/>所有容器共用]
C --> D[硬體]
A2[應用] --> B2[客戶作業系統]
B2 --> V[系統 VM / hypervisor]
V --> C2[宿主作業系統或裸機]
C2 --> D2[硬體]| 虛擬機 | 容器 | |
|---|---|---|
| 每單位開銷 | GB 級(整個客戶 OS) | MB 級(只有根檔案系統) |
| 啟動 | 數十秒 | 毫秒 |
| 隔離強度 | 硬體級(VT-x) | 核心級(軟體邊界) |
| 核心共用 | 否(各自核心) | 是(共用宿主核心) |
「共用核心」既是容器的效率來源(第 5 章說 VM 用 OS 的程序抽象實現虛擬機,容器則更徹底:容器就是一個普通的 Linux 行程),也是它的風險來源(核心漏洞可能穿透所有容器——2016 年 Dirty COW 之後,多租戶環境對「容器不是 VM」有明確認知)。
歷史:七步演化,每步都由需求驅動#
1979:chroot——囚籠的發明#
Version 7 Unix 加入 chroot(change root):把行程的根目錄改到某個子目錄。原始動機不是安全,而是開發環境——在隔離的目錄樹中測試軟體,不弄髒主系統。後來它被當安全工具用(FTP 服務 chroot 到 /srv/ftp),但 2000 年代初人們發現它的漏洞:root 行程可以 chroot 逃逸(chroot 之後再 chdir("..") + chroot 一次)。教訓:單一機制的隔離不夠,這推動了 namespace 的出現。
2000:FreeBSD Jail——多維囚籠#
FreeBSD 的 jail 在 chroot 之上加:程序隔離(jail 內看不到外部程序)、網路隔離(獨立 IP)、資源限制。「隔離需要多個維度同時做」的認識成形。
2005:Solaris Zones#
Sun 的 Zones 把容器做成商業產品級:每個 zone 有獨立程序空間、網路、資源配額。需求驅動:一台 Sun 伺服器太貴,客戶要在單機上安全地分租。此時容器概念成熟,但 Linux 沒有對應物。
2006-2008:Google cgroups 與 LXC#
Google 在自己的內部基礎設施(Borg,Kubernetes 前身)中大量使用容器,2006 年把資源帳務與限制(cgroups,control groups)推進 Linux 主線(2007 進入 2.6.24)。同時 OpenVZ/Parallels 把 namespace 推進主線(2008 年的 2.6.24 前後逐步完成六種 namespace)。LXC 把兩者組合成使用者可用的容器工具。
這裡有一個歷史因果鏈值得注意:Google 的搜尋/廣告基礎設施規模,逼出了資源隔離的通用機制;機制進入主線 Linux,才使後來的 Docker 成為可能。沒有 cgroups,Docker 只是 chroot 的花俏包裝。
2013:Docker——把容器變成產品#
dotCloud(PaaS 公司)的 Solomon Hykes 發布 Docker。它的技術成分沒有一項是新的:
- namespace(Linux 主線已有)
- cgroups(Google 已推進主線)
- AUFS/聯合檔案系統(已有)
- LXC(已有的執行器)
Docker 的革命是體驗與分發:
- 映像(image):分層檔案系統 + 可下載、可複製——「在我機器上能跑」的問題第一次有了工程解
- 一條命令:
docker run ubuntu echo hello——LXC 時代需要幾十行設定檔 - 註冊中心(Docker Hub):映像的 npm/PyPI
技術不是瓶頸,封裝才是。這是系統軟體史反覆出現的主題(也呼應第 1.1 節的驅動關係:底層機制成熟後,上層的抽象與工具鏈才是價值所在)。
2015:OCI 標準與 runc#
Docker 太成功,生態需要標準化。OCI(Open Container Initiative)定義了兩個規格:
- runtime spec:容器執行時的介面(JSON 描述 namespace/cgroups/mounts → 起一個隔離行程)——參考實現是 runc(從 Docker 剝離出來,Go 寫的)
- image spec:映像格式
runc 之後,容器的技術棧變成分層的:
graph TD
D[docker / podman / containerd] -->|高階管理| R[runc / crun / youki<br/>OCI runtime:真正起容器的東西]
R --> NS[namespace 系統呼叫]
R --> CG[cgroups]
R --> FS[聯合檔案系統]
NS & CG & FS --> K[Linux 核心]youki(2021,日本大和總研)用 Rust 重寫了 runc,是 OCI runtime 的現代實作——本章 7.3 節將實作一個縮小版的 youki。
2014-至今:Kubernetes 與容器的「編排時代」#
容器太多之後,管理容器成為新問題——Kubernetes(Google 開源,靈感來自內部 Borg)接管調度、擴縮、故障恢復。此時「容器」一詞的意義已從「一個隔離行程」升級為「可編排的計算單位」。本書範圍止於單機 runtime(7.2-7.3),編排是另一本書的題材。
關鍵認知:容器 = 三個機制的組合#
把 Docker 拆開,剩下的就是 Linux 的三個既有特性:
graph LR
C[容器] --> N[namespace<br/>看得見什麼]
C --> G[cgroups<br/>能用多少]
C --> F[聯合檔案系統<br/>檔案從哪來]- namespace:回答「這個行程看到的世界是什麼樣」——隔離的視圖
- cgroups:回答「這個行程群組能消耗多少資源」——隔離的配額
- overlayfs:回答「程式的檔案從哪來」——分發與分層
第 5 章的 WASM 沙箱是「VM 內畫的線」,容器的隔離是「核心畫的線」;兩者的信任假設不同(WASM 假設驗證器正確,容器假設核心正確),但設計思想同源:把「看得見、用得起、放得下」三件事都框在邊界內。
習題#
- chroot 為什麼不夠?列出至少三種它隔離不到的資源(提示:程序列表、網路、使用者 ID)。
- Docker 沒有發明任何核心機制,為什麼成功了?用 1.1 節的「驅動關係」框架分析。
- 為什麼容器的安全模型在多租戶環境被認為弱於 VM?(提示:核心攻擊面、2016 年 Dirty COW、2022 年 seccomp/namespace 逃逸 CVE。)