JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

虛擬機器對上容器:隔離強度與份量輕重

在這一階裡,你認識了兩種截然不同的方式,去執行那些自以為獨佔了一台機器的軟體:一種是跑在超管理器上、完整的虛擬機器,另一種是與主機核心共用的輕量容器。這篇收尾的導覽把兩者並排來看——各自複製了什麼、各自共用了什麼,以及為什麼那筆誠實的取捨永遠是同一筆:更強的隔離,對上更輕的份量。

同一個問題的兩個答案

整個這一階都在追逐同一個願望:讓許多彼此獨立的工作負載,在同一台實體機器上,各自表現得像獨佔了一台機器。你很早就見過第一個答案——虛擬機器,一台由超管理器變出來的、完整的假電腦,從它自己的核心往上,跑著一整套自己的客體作業系統。接著你遇到了第二個、更輕的答案——容器,它根本不去假造硬體,而是把那唯一一套真實的作業系統,切成許多看起來各自私有的薄片。兩者交付的都是同一種「這是我的機器」的幻覺,但它們把這幻覺蓋在完全不同的地方,而那個差別,就是整篇故事。

要把這組對比裝進腦袋裡,最乾淨的講法是這樣。虛擬機器複製了作業系統:每一台 VM 都帶著自己的核心、自己的分頁表、自己的一切,而超管理器在它們全體底下劃出一條硬邊界。容器則共用作業系統:一台主機上所有的容器,都跑在那唯一一套主機核心上,由那套核心默默地遞給每個容器一份自己私有的系統視角。複製,買到的是強度;共用,買到的是輕量。你沒辦法同時把兩者都拉到最大,而假裝可以,正是這個領域角落裡最常見的一種混淆。

  VIRTUAL MACHINES                 CONTAINERS
  ----------------                 ----------
   [ App A ] [ App B ]              [ App A ] [ App B ] [ App C ]
   [Guest OS][Guest OS]            ---------------------------------
   [ kernel ][ kernel ]            |  namespaces + cgroups (views) |
  --------------------            ---------------------------------
  |     HYPERVISOR     |          |     ONE shared host kernel     |
  --------------------            ---------------------------------
  |      hardware      |          |           hardware            |
  --------------------            ---------------------------------

  Each VM = its own kernel.   All containers = the SAME kernel.
虛擬機器把一整套客體作業系統(連同核心)疊在超管理器上;容器則只是疊在同一套共用主機核心之上的一層層隔離視角。

份量:各自搬上樓的東西

把「份量」想成:在你真正的程式跑起來之前,你得搬進記憶體、開機載入的所有東西。一台虛擬機器得把一整套客體作業系統搬上樓:它的核心、它的驅動程式、它的背景服務、它自己的分頁表。把它開機,等於把一整台電腦開機,這要花上數秒到數分鐘、以及一大塊 RAM,哪怕裡頭那支應用程式小得可憐。一個容器則幾乎不額外搬任何東西:核心早已在主機上跑著了,所以啟動一個容器,比較接近啟動一個普通的行程,而不是把一台機器開機——只要幾毫秒,而且只佔應用程式本身需要的那點記憶體。

這一階先前的兩個主意,能把「輕」解釋得更精準。第一,容器的檔案系統通常是一套覆疊檔案系統:與其給每個容器一份完整、自己的作業系統檔案副本,主機保留共用的唯讀基底層,再替每個容器在頂上疊一層薄薄的可寫層——於是用同一個映像檔造出來的一百個容器,成本大約是一份映像檔,加上一百個小小的差異,而不是一百份完整副本。第二,容器在 CPU 與記憶體上的限制,來自控制群組(cgroups),它不過是替一個普通行程的資源用量計量,而不是替一台假機器預留一塊固定的板子。共用、分層、計量,正是容器之所以顯得如此便宜的原因。

隔離:那道牆到底立在哪裡

現在來看這筆取捨的另一面,以及虛擬機器之所以沒有消失的原因。隔離,講的是兩個工作負載之間那道牆有多高多厚——而最關鍵的是,那道牆共用的那一側,擺著什麼。對 VM 來說,那道牆就是超管理器本身,而兩個客體唯一共用的,是超管理器那道狹窄、嚴加看守的介面。每個客體都有自己的核心,所以某個客體核心裡的臭蟲或淪陷,會被困在那個客體的假機器裡;要逃出來,攻擊者必須擊破超管理器——那是一小段被高度審視的程式碼。這正是我們說 VM 提供強隔離的原因。

容器把那道牆劃在一個風險更高的地方。容器之間的分隔,是用命名空間搭起來的——每個容器只看得到自己的行程、自己的網路、自己的檔案系統掛載樹,因為核心遞給它的是一份過濾過的視角——但每一個容器,都在向底下那唯一一套主機核心發出真正的系統呼叫。牆共用的那一側,是整套核心:一片巨大而複雜的表面。那唯一一套共用核心深處的一個瑕疵,原則上就能讓某個容器裡的行程逃出來,碰到另一個容器,甚至碰到主機。這就是較弱的隔離:在正常使用下並不弱,而是「比較弱」,因為共用的那塊,遠比超管理器那道薄薄的介面更大、也更有威力。

所以要記住的規則很精確:容器共用主機核心,因此給的是較輕、較弱的隔離;虛擬機器在超管理器上跑一整套客體作業系統,因此給的是較重、較強的隔離。沒有哪個「比較好」——它們回答的是不同的問題。想把你親手寫、也信得過的幾百個彼此協作的微服務,快速地塞進一台主機?用容器。想執行你並不信任的程式碼,或是讓兩個就算核心有個洞、也絕不能碰到彼此的租戶共存?用 VM,或是用額外層(一個沙箱、一個微小的、每容器一份的客體核心)加固過的容器——這些刻意花掉 VM 一部分的份量,把它的一部分強度買回來。

一個實作對照:啟動四十份副本

把它變具體。假設你得在一台有 32 GB RAM 的伺服器上,跑同一個小型網路服務的四十份副本。先走 VM 這條路:每台 VM 光是要存在,就需要比方說 512 MB 的客體作業系統,這還在服務自己那 100 MB 之前。四十台要的記憶體大約是 40 乘 612 MB,約 24 GB,其中一大部分,是四十份幾乎一模一樣的核心與服務的多餘副本——而且每一台都要花上數十秒才開得了機。不過它們之間的牆固若磐石:四十個各自獨立的核心,後頭擋著一台超管理器。

  1. 拉取一份共用映像檔;它的唯讀層在主機上只存一份,作為覆疊的基底。
  2. 對四十個容器中的每一個,核心建立一組全新的命名空間(自己的行程清單、網路、掛載樹)——是一份私有視角,而不是一台私有機器。
  3. 在共用基底之上,替每個容器疊一層薄薄的可寫層,於是各自都能寫入,而不會擾動其他容器或基底。
  4. 替每一個套上一個 cgroup,讓它無法把 CPU 或記憶體餓死其他容器。
  5. 把服務當作那唯一一套共用核心上的一個普通行程,在每個容器裡啟動——四十個全部在遠不到一秒內就緒,共用一套核心、一份基底映像檔。

現在來看容器這邊的總帳:一份共用層,加上四十乘大約 100 MB 的真正服務,也許是 4 到 5 GB,而不是 24 GB,並且在一秒內、而非數分鐘內就啟動完畢。同樣四十個工作負載,五分之一的記憶體,百分之一的啟動時間。而那個代價,正正是我們點名過的那個:這四十個服務如今全靠那唯一一套共用核心撐著,所以它們是被命名空間與 cgroup 隔開的,而不是被四十道真牆隔開的。如果這四十個都是你自己、信得過的程式碼,那筆交易簡直是撿到便宜。但只要其中有一個懷有敵意,你也許終究會想要那道更重的牆。

共處之道:編排、遷移,與界線的模糊

因為單一一台主機如今裝著數十甚至數百個這種輕量單位,總得有人來安置它們、把掛掉的重啟、按需把它們放大縮小、並接好它們的網路——靠人手做,規模一大就不可能。這份工作叫做編排(orchestration):一套控制系統,把一整支機隊當成單一一池資源來看待,再把容器排程到上頭,很像你在更早一階遇過的作業系統排程器把行程安置到 CPU 上,只不過往上提了一層、橫跨許多台機器。編排器之於一群主機,正如核心之於一台機器:那個決定誰在哪裡跑的管理者。

VM 自己也有一項機隊層級的超能力,而那是容器比較難辦到的:即時遷移。因為一台 VM 是一台自成一體的機器,有自己的核心、在超管理器那裡有一條乾淨的邊界,超管理器可以把一台執行中的 VM 的記憶體複製到另一台實體主機,幾乎不打嗝地切換過去——把一整套活著的作業系統,從一台你得關機維護的機器上搬走。容器則因為和某台特定主機核心的即時狀態糾纏在一起,通常是靠在別處停掉再重啟來搬動,而不是在飛行途中遷移;那份讓它們變輕的共用,恰恰也讓它們更難被整個地抱起來搬走。

把整階收進一桿秤

退一步看,整個這一階就收斂成一桿秤。超管理器,以及你學到的那些虛擬化 CPU、記憶體(巢狀分頁表)與輸入輸出的技術,全都是為了讓那個沉重、強隔離的幻覺——一台完整複製的機器——有效率到值得一用。命名空間、cgroup、覆疊檔案系統,全都是為了讓那個輕盈、弱隔離的幻覺——一台共用機器的一份私有視角——強韌到足夠安全。兩者回答的都是「我要怎麼讓彼此不信任的工作負載,在同一台機器上共存?」;它們只是在份量與牆之間,把預算花得不一樣。

牢牢抓住那句能熬過所有細節的話:容器共用主機核心,這就是它輕、它的隔離也較弱的原因;VM 在超管理器上跑一整套客體作業系統,這就是它重、它的隔離也較強的原因。這一階裡其他每一條事實——快開機對上慢開機、一份映像檔對上四十份副本、容易編排對上容易即時遷移——全都是從「什麼被複製、什麼被共用」這個唯一的差別裡掉出來的。把這點抓住,你就能讀懂這個領域裡任何一件新工具,並立刻問出那個唯一要緊的問題:它把牆劃在哪裡,又逼你去共用什麼?