自下而上學習容器

作者丨 iximiuz

譯者丨屠靈

策劃丨閆園園

我從 2015 年開始使用容器,我對容器最初的理解就是把它們看成是輕量級的虛擬機,只是啓動時間比虛擬機快了很多。腦子裏有了這樣的概念,就很容易看懂網上那些關於如何將 Python 或 Node 應用程序裝入容器的教程。但很快,我意識到僅僅將容器看成是輕量級的虛擬機有點跳過簡單化了,這導致我無法對以下這些問題做出判斷:

既然 “容器就是虛擬機” 這種理解有失偏頗,我就開始深入探究,看看容器到底是什麼,而 Docker 無疑是最好的切入點。問題是,Docker 是一個可以用來做各種各樣事情的龐然大物,而運行它的命令又如此簡單(比如 docker run nginx),很容易就矇蔽了我們。與 Docker 相關的資料有很多,它們要麼是太過淺顯的教程,要麼太多艱深,新手根本就看不懂。

於是,我花了一些時間,爲讀者鋪平了學習容器的道路。

多年來,我嘗試從不同的角度探究,終於找到了一條適合我的學習路徑。不久前,我在推特上分享了我的學習路徑,引起了很多人的共鳴。

本文並不打算一次性解釋完所有有關容器的東西。相反,它是我多年來對這個領域探究的一道 “前菜”。它介紹了我的學習路徑,你可以順着這條路徑,再去閱讀其他更加深入介紹容器的文章。

掌握容器知識不是一項簡單的任務,所以慢慢來,不要跳過實操的部分!

1 容器學習路徑

我發現按照下面這樣的順序來學習容器非常有效:

2 容器不是虛擬機

容器是一種隔離(命名空間)且受約束(通過 cgroups、capabilities、seccomp)的進程。

上面的這個解釋非常有助於我理解什麼是容器。當然,這個解釋並非絕對準確,當你讀到這篇文章的末尾你就會知道,但在剛開始學習容器時,這樣的解釋是很合適的。

要在 Linux 上啓動一個進程,需要 fork/exec 它。但要啓動一個容器化的進程,要先創建命名空間、配置 cgroups,等等。或者,換句話說,爲進程準備一個箱子,讓進程在箱子裏運行。容器運行時就是一種用來創建這種箱子的工具。容器運行時知道怎樣準備好箱子,然後在箱子裏啓動一個容器化的進程。又因爲大多數運行時都遵循常用的規範,容器就成爲一種標準的工作負載單元。

使用最廣的容器運行時是 runc。runc 是一種普通的命令行工具,所以可以在沒有 Docker 或其他高級容器軟件的情況下直接使用它。

runc 啓動一個容器化進程的過程

我對此感到興奮萬分,甚至還寫了一系列關於容器運行時墊片(shim)的文章。墊片是指底層容器運行時 (如 runc) 和高級容器管理器 (如 containerd) 之間的一種軟件。要做好墊片,需要對運行時瞭如指掌,所以這一系列文章先從深入分析使用最爲廣泛的容器運行時開始。

容器運行時墊片

3 運行容器不一定需要鏡像

不過,構建鏡像需要容器。

對於熟悉 runc 是如何啓動容器的人來說,他們都知道鏡像並非是必需的。要運行一個容器,運行時需要一個 bundle,其中包括:

通常,bundle 的目錄結構與 Linux 發行版的文件結構類似(/var、/usr、/lib、/etc,等等)。當 runc 啓動這樣的一個容器,運行在容器中的進程就獲得了一個根文件系統,看起來與 Linux(比如 Debian、CentOS 或 Alpine)很像。

但這種文件結構並非是強制性的。現在,所謂的 Scratch 或 Distroless 容器越來越流行,越是小巧的容器出現安全漏洞的可能性就越少。

使用 dive 查看 scratch 鏡像

既然運行容器不一定需要鏡像,那我們爲什麼還要有容器鏡像?

當每一個容器都包含根文件系統的一個數兆字節那麼大的拷貝副本時,所需的磁盤空間就會急劇增加。因此,鏡像的存在是爲了有效地解決存儲和發行問題。對這個問題感興趣的可以閱讀這篇文章。

你有沒有想過鏡像是如何構建出來的?

Docker 所推廣的工作流程試圖讓你認爲鏡像纔是主要的,容器次之。在執行 docker run 命令時,你需要指定一個鏡像才能運行容器。但我們知道,嚴格來說,事情並沒有這麼簡單。實際上,你需要 (臨時) 運行容器來構建鏡像!想知道爲什麼,請閱讀這篇文章。

4 單宿主機上的容器管理器

在現實世界中,我們發明了集裝箱是爲了增加一艘船可以裝載的物品數量,類似的,容器是爲了提高服務器的資源利用率。

一個典型的服務器現在運行數十或數百個容器。因此,它們需要有效地共存在一臺服務器上。單個容器運行時關注的是單個容器的生命週期,而容器管理器關注的是在單臺主機上共存的多個容器。

容器管理器的主要職責包括鏡像的拉取、解包、配置容器間網絡、存儲容器日誌,等等。

在這方面,你可能認爲 Docker 就是一個很好的例子。但我發現 containerd 是一個更具代表性的例子。與 runc 一樣,containerd 在一開始只是 Docker 的一個組件,後來被提取到一個獨立的項目中。containerd 可以使用 runc 或任何實現了 containerd-shim 接口的運行時。最酷的是,你可以像使用 Docker 一樣使用 containerd 來輕鬆地運行容器。

containerd 與 docker

現在,我們準備好要了解 Docker 了!如果我們忽略 (現在已棄用)Swarm,那麼 Docker 包含如下這些:

Docker 的分層架構

在我看來,Docker 目前的主要任務是讓容器工作流變得更友好。爲了簡化開發人員的工作,Docker 將所有主要容器用例整合到一個工具中:

但是到了 2021 年,幾乎每個用例都被寫成了一個定製的軟件 (如 podman、buildah、skopeo、kaniko,等等),以便提供更好的替代解決方案。

5 多宿主容器編配器

在單臺主機上協調運行的容器已經很難了,在多個主機之間協調容器就更困難了。還記得 Docker Swarm 嗎?Docker 在加入多主機容器編配特性時就已經相當可怕了,因爲給已有的守護進程帶來了更多的責任……

忽略守護進程數量不斷膨脹這個問題,Docker Swarm 看起來還是不錯的。但另一種編配器贏得了比賽——Kubernetes!所以,大約從 2020 年開始,Docker Swarm 就過時了,我們每週都會聽到幾個新出現的 “古希臘” 詞彙。

Kubernetes 將多個服務器 (節點) 連接到一個集羣中,每個節點都有一個叫做 kubelet 的本地代理。kubelet 負責啓動 Pod(一組容器),但並不是它自己做這些事情。過去,它使用 dockerd,但現在這種方法已被棄用,取而代之的是更通用的容器運行時接口 (CRI)。

Kubernetes 可以使用 containerd、cri-o 或其他 CRI 運行時

容器編配器需要完成很多任務。

Kubernetes 和其他編配器 (如 Nomad 或 AWS ECS) 可以幫助開發團隊更容易地創建獨立的服務。它幫助解決了很多管理上的問題,尤其是對大公司來說。但它也帶來了很多傳統虛擬機所沒有的新技術問題!管理大量分佈式服務變得非常具有挑戰性,從而催生了 “雲原生項目動物園”。

6 有一些容器就是虛擬機

你從實現和使用的角度對容器有了更好的理解,現在可以告訴你真相了。容器不是 Linux 進程!

甚至在技術上講,Linux 容器也不是進程。它們是隔離且受約束的環境,可在其中運行一個或多個進程。

按照上面的定義,至少有些容器可以使用命名空間和 cgroups 之外的機制來實現,這就不足爲奇了。事實上,有些項目(如 Kata)就使用真正的虛擬機作爲容器!幸好有了像 OCI Runtime Spec、OCI Image Spec 或 Kubernetes CRI 這樣的開放標準,基於虛擬機的容器可以在不進行重大調整的情況下被更高級的工具 (如 containerd 和 Kubernetes) 使用。

要了解更多,請閱讀這篇關於 OCI 運行時規範如何定義標準容器的文章:

7 結論

只通過 Docker 或 Kubernetes 等高級工具無法真正瞭解容器。這個領域很複雜,只從一個方向瞭解它會留下太多的盲點。

我認爲更好的方法是從更廣泛的生態系統開始,將其分解到各個層面,然後利用在每一步中獲得的知識,從底層開始逐個擊破:

原文鏈接:

https://iximiuz.com/en/posts/container-learning-path/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/pYOKYgaghkiy5nbMc6pEUg