Kubernetes 如何重塑虛擬機

Kubernetes 大規模使用過的都說簡單,沒有用過清一色的都是使用複雜、概念晦澀難懂,因此即使是那些具有一定服務器端知識的人也可能會感到困惑。讓我在這裏嘗試一些不同的東西。與其解釋一個不熟悉的問題(如何在 Kubernetes 中運行 Web 服務?)和另一個(你只需要一個清單,三個 sidecar 和一堆 gobbledygook),我將嘗試揭示 Kubernetes 技術發展趨勢。

如果您已經知道如何使用虛擬機運行服務,希望您會發現最終並沒有太大區別。如果您對大規模運營服務完全不熟悉,那麼跟隨技術的發展可能會幫助您瞭解當代方法。

像往常一樣,這篇文章並不全面。相反,它試圖總結我的個人經歷以及計算機多年來虛擬化是如何形成的。

如何使用虛擬機部署服務

早在 2010 年,當我剛剛開始我的軟件工程師職業生涯時,使用虛擬機(或有時是裸機)部署應用程序非常普遍。

你需要一個臨時的 Linux 虛擬機,將 Nginx 或 Apache 反向代理放在它前面,然後在它旁邊運行一堆守護進程和 cronjobs。

這樣的機器將代表服務的單個實例,打個比方,就類似於一個盒子,而服務本身將只是分佈在網絡上的一組命名的相同機器。根據您的業務規模,您可能只有幾個、幾十個、幾百個甚至幾千個盒子分佈在爲生產流量提供服務的多個盒子中。

服務的抽象將應用程序的複雜性隱藏在單個入口點之後

使用虛擬機部署服務帶來的挑戰

通常,機器羣的大小將定義配置(安裝操作系統和軟件包)、擴展(產生相同的盒子)、服務發現(將一組盒子隱藏在一個名稱後面)和部署(運送新版本的代碼)的方式到盒子)完成了。

如果你是一個只有幾個類似寵物的盒子的公司,您可能會發現自己很少半手動地配置新盒子。這通常意味着總線係數低(由於缺乏自動化)、安全狀況差(由於缺乏定期補丁更新)以及可能更長的災難恢復。從好的方面來說,管理成本會非常低,因爲不需要擴展,您的部署會很簡單(只需幾個盒子來交付代碼),而且服務發現會很簡單(由於相當靜態地址池)。

對於擁有大量盒子的公司來說,現實情況會有所不同。大量機器通常會導致更頻繁地需要配置新盒子(更多的盒子意味着更多的破損)。你會投資自動化(投資回報率會很高),最終得到許多牛一樣的盒子。作爲不斷重新創建盒子的副產品,您將增加總線因素並改善安全狀況(將自動更新和安裝補丁)。在它的反面,會存在低效的擴展(由於每日 / 每年的流量分佈不均勻),過於複雜的部署(很難將代碼快速交付到許多機器上),以及脆弱的服務發現(您是否嘗試過大規模運行 consul 或 zookeeper?)會導致更高的運營成本。

Amazon Elastic Compute Cloud (EC2) 等早期雲產品允許更快地啓動(和關閉)機器;使用 packer 製作並使用 cloud-init 自定義的機器鏡像,使配置稍微容易一些;puppet 和 ansible 等自動化工具支持應用基礎架構更改並大規模交付新版本的軟件。但是,仍有很大的改進空間。

Docker 容器解決了什麼問題

在過去,擁有不同的生產和開發環境是很常見的。這將導致應用程序可能在您安裝的 Debian 機器上本地運行,但由於缺少依賴項而無法在生產中的 vanilla CentOS 上啓動。相反,在本地安裝應用程序的依賴項可能會遇到一些麻煩,但由於資源需求高,爲每個服務運行預配置的虛擬機進行開發將是不可行的。

即使在生產中,虛擬機的龐大也是一個問題。每個服務擁有一個虛擬機可能會導致低於最佳資源利用率和 / 或相當大的存儲和計算開銷,但是將多個服務放在一個盒子中可能會使它們發生資源搶佔衝突。

世界顯然需要一個更輕量級的盒子。

容器 - 單個應用程序的盒子

這就是容器的用武之地。就像允許將裸機服務器分割成幾臺更小(更便宜)的機器的虛擬機一樣,容器將一個 Linux 機器分割成數十個甚至數百個獨立的環境。

在一個容器中,您可能會覺得您擁有自己的虛擬機,以及您最喜歡的 Linux 發行版。好吧,至少乍一看。從外部看,容器只是在主機操作系統上運行並共享其內核的常規進程。

👉 進一步閱讀:並非每個容器內部都有操作系統 並非每個容器內部都能包含一個操作系統

打包應用程序及其所有依賴項(包括特定版本的操作系統用戶空間和庫)的能力,將其作爲容器鏡像發送,並在安裝了 Docker(或類似工具)的任何位置的標準化執行環境中運行,極大地提高了工作負載的可重複性.

由於容器邊界的輕量級實現,計算開銷顯著降低,允許單個生產服務器運行可能屬於多個(微)服務的數十個不同容器。當然,這可能以降低安全性爲代價。

由於不可變和共享的鏡像層,鏡像存儲和分發也變得更加高效。

在某種程度上,容器也改變了供應的方式。使用(粗心編寫的)Dockerfiles 和 ko 和 Jib 之類的(神奇的)工具,責任極大地轉移到了開發人員身上,簡化了生產 VM 的要求——從開發人員的角度來看,你只需要一個 Docker-(或更高版本的 OCI-)兼容應用程序的運行時,因此您不會再因爲要求安裝某個版本的 Linux 或系統包而惹惱您的運維朋友。

最重要的是,容器加速了運行服務的替代方式的開發。現在有 17 種方法可以在 AWS 上運行容器https://www.lastweekinaws.com/blog/the-17-ways-to-run-containers-on-aws/,其中大部分是完全無服務器的,在足夠簡單的情況下,您可以使用 Lambda 或 Fargate 並從牛一樣的盒子中受益!

容器不能解決什麼問題

容器被證明是一個非常方便的開發工具。構建容器鏡像也比構建 VM 更簡單、更快捷。再加上如何有效分離團隊之間職責的老組織問題,導致典型企業的平均服務數量顯著增加,每個服務的盒子數量也有類似的增加。

Docker 普及的容器形式實際上具有很強的欺騙性。乍一看,每個服務實例都有一個便宜的專用 VM。但是,如果這樣的實例需要 sidecar(例如在您的 Web 應用程序前面運行的本地反向代理來終止 TLS 連接或加載祕密和 / 或預熱緩存的守護程序),您會立即感覺到疼痛,這就是容器與虛擬機的本質區別。

Docker 容器被刻意設計爲只包含一個應用程序。一個容器——一個 Nginx;一個容器 - 一個 Python Web 服務器;一個容器 - 一個守護進程。容器的生命週期將綁定到該應用程序的生命週期。並且特別不鼓勵將像 systemd 這樣的 init 進程作爲頂級入口點運行。

因此,要從本文開頭的圖表重新創建一個 VM-box,您需要擁有三個具有共享網絡堆棧的協調容器 - box(嗯,至少 localhost 需要相同)。要運行該服務的兩個實例,您需要三個三個一組的六個容器!

從擴展的角度來看,這意味着我們需要一起擴展(和縮減)一些容器。部署也需要同步進行。新版本的 Web 應用程序容器可能會開始使用新的端口號,並與舊版本的反向代理容器不兼容。

我們顯然在這裏錯過了一個抽象,它與容器一樣輕量級,但與原始 VM 盒子一樣富有表現力。

此外,容器本身也沒有提供任何將盒子分組爲服務的方法。但他們促成了箱子人數的增加!Docker 競相用它的 Swarm 產品解決這些問題,但另一個系統贏了……

Kubernetes 解決了這一切…… 還是沒有?

Kubernetes 設計師顯然沒有發明新的運行容器的方法,而是決定重新創建良好的舊的基於 VM 的服務架構,但使用容器作爲構建塊。好吧,至少這是我的看法。

但對我來說,作爲以前有 VM 經驗的人,一旦我瞭解了新術語並弄清楚了類似的概念,許多最初的 Kubernetes 想法就會開始看起來很熟悉。

Kubernetes Pod 是新的虛擬機

讓我們從 Pod 抽象開始。Pod 是您可以在 Kubernetes 中運行的最小的東西。最簡單的 Pod 定義如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.20.1
    ports:
    - containerPort: 80

乍一看,上面的清單只是說明要運行什麼鏡像(以及如何命名)。但是請注意 containers 屬性是一個列表!現在,回到那個 nginx + web app 例子,在 Kubernetes 中,您可以簡單地將反向代理和應用程序本身放在一個盒子中,而不是爲 Web 應用程序容器運行額外的 Pod:

apiVersion: v1
kind: Pod
metadata:
  name: foo-instance-1
spec:
  containers:
  - name: nginx            # <-- sidecar container
    image: nginx:1.20.1
    ports:
    - containerPort: 80
  - name: app              # <-- main container
    image: app:0.3.2

然而,Pod 不僅僅是一組容器。Pod 中容器之間的隔離邊界被削弱。就像在 VM 上運行的常規進程一樣,Pod 中的容器可以通過 localhost 或使用傳統的 IPC 方式自由通信。同時,每個容器仍然有一個獨立的根文件系統,以保持打包應用程序及其依賴項的好處。對我來說,這看起來像是在嘗試同時利用 VM 和容器世界的最佳部分:

擴展和部署 Pod 很簡單

現在,當我們得到新的盒子時,我們如何運行多個它們來組成一個服務?換句話說,如何在 Kubernetes 中進行擴展和部署?

事實證明,它非常簡單,至少在基本場景中是這樣。Kubernetes 引入了一個方便的抽象,稱爲 Deployment。最小的 Deployment 定義由名稱和 Pod 模板組成,但指定所需的 Pod 副本數量也很常見:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo-deployment-1
  labels:
    app: foo
spec:
  replicas: 10
  selector:
    matchLabels:
      app: foo
  template:
    metadata:
      labels:
        app: foo
    spec:
      <...Pod definition comes here>

Kubernetes 的偉大之處在於,作爲開發人員,您並不關心服務器(或 Kubernetes 術語中的節點)。您根據 Pod 組進行思考和操作,它們會自動調動(和重新分佈)到集羣節點:

這使得 Kubernetes 更像是一種無服務器技術。但同時,Pod 的外觀和行爲與過去熟悉的 VM 非常相似(除了您不需要管理它們),因此您可以在熟悉的抽象中設計和推理您的應用程序:

內置服務發現

Kubernetes Service - 一組命名的 Pod。

Kubernetes 設計人員肯定知道,僅僅創建 N 個盒子的副本並將其稱爲服務是不夠的。客戶端應該能夠使用單個(可能是邏輯的)名稱訪問服務,並且服務發現系統應該能夠將該名稱轉換爲特定的 IP 地址(類似於我們理解的負載均衡器,服務於特定的實例) )。

過去,您需要一個單獨的(並且要求非常高的)解決方案。但是,Kubernetes 內置了這個功能,而且默認實現還不錯!它還可以使用 Linkerd 或 Istio 等服務網格進行擴展,使其更加強大。

將一組 Pod 轉換爲服務唯一需要做的就是創建一個 Service 對象(不是真正的創建服務,只是一個網絡層面的抽象)。

下面是一個簡單的 Kubernetes Service 定義的樣子:

apiVersion: v1
kind: Service
metadata:
  name: foo
spec:
  selector:
    app: foo
  ports:
    - protocol: TCP
      port: 80

上面的清單允許 app=foo 使用 defaultDNS 名稱(如 foo.default.svc.cluster.local. 而且這一切都沒有在集羣中安裝任何額外的軟件!

請注意 Service 定義在任何地方都沒有提到 Deployment。就像 Deployment 本身一樣,它根據 Pod 和標籤運行,這使它非常強大!例如,Kubernetes 中良好的藍 / 綠或金絲雀部署可以通過讓兩個 Deployment 對象在單個 Service 選擇具有公共標籤的 Pod 後運行不同版本的應用程序鏡像來實現:

現在,最有趣的部分 - 你注意到 Kubernetes 服務與我們舊的基於 VM 的服務沒有什麼區別了嗎?

Kubernetes 即服務

那麼,Kubernetes 是不是就像 VM 一樣,但更簡單?嗯,是的,但也不是。因爲他跟虛擬機存在本質上的差別,套用 Kelsey Hightower 的話,我們應該區分駕駛汽車的複雜性和修理汽車的複雜性。我們中的許多人會開車,但很少有人擅長修理發動機。幸運的是,有專門的商店!這同樣適用於 Kubernetes。

使用 EKS 或 GKE 等託管 Kubernetes 產品運行服務確實很相似,但比使用 VM 簡單得多。但如果你必須維護 Kubernetes 集羣背後的實際服務器,那就完全不同了……,所以僅僅使用 Kubernetes 和維護 Kubernetes 是兩碼事。

總結

爲了改善在 VM 上運行服務的體驗,容器改變了我們打包軟件的方式,大大降低了對服務器配置的要求,並啓用了替代方法來部署我們的工作負載。但就其本身而言,容器並沒有成爲大規模運行服務的解決方案。頂部仍然需要額外的編排層。

Kubernetes 作爲容器原生編排系統之一,使用容器作爲基本構建塊重新創建了過去熟悉的架構模式。Kubernetes 還通過提供用於擴展、部署和服務發現的內置方法來解決傳統方案的痛點。

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