您好,登錄后才能下訂單哦!
我是誰
我叫張磊,是 Kubernetes 社區的一位資深成員和項目維護者。在 Kubernetes 和 Kata Containers 社區從事上游開發工作,先后發起了容器鏡像親密性調度、基于等價類的調度優化等多個核心特性,參與了容器運行時接口、安全容器沙盒等多個基礎特性的設計和研發。作為主要的研發人員和維護者之一,親歷了 Serverless Container 概念的誕生與崛起。
在工作之余,發起和組織撰寫了《Docker 容器與容器云》一書,受到了廣大希望進階容器技術的讀者的好評。參與和親歷了容器技術從“初出茅廬”到“塵埃落定”的全過程
文末福利有獲取 K8S 知識福利的指南。
從“容器”到“容器云”
我曾經提過:一個“容器”,實際上是一個由 Linux Namespace、Linux Cgroups 和 rootfs 三種技術構建出來的進程的隔離環境。
所以,一個正在運行的 Linux 容器,其實可以被“一分為二”地看待:
一組聯合掛載在 /var/lib/docker/aufs/mnt 上的 rootfs,這一部分我們稱為“容器鏡像”(Container Image),是容器的靜態視圖;
一個由 Namespace+Cgroups 構成的隔離環境,這一部分我們稱為“容器運行時”(Container Runtime),是容器的動態視圖。
作為一名開發者,我們并不關心容器運行時的差異。因為,在整個“開發 - 測試 - 發布”的流程中,真正承載著容器信息進行傳遞的,是容器鏡像,而不是容器運行時。
這個重要假設,正是容器技術圈在 Docker 項目成功后不久,就迅速走向了“容器編排”這個“上層建筑”的主要原因:作為一家云服務商或者基礎設施提供商,我只要能夠將用戶提交的 Docker 鏡像以容器的方式運行起來,就能成為這個非常熱鬧的容器生態圖上的一個承載點,從而將整個容器技術棧上的價值,沉淀在我的這個節點上。
更重要的是,只要從我這個承載點向 Docker 鏡像制作者和使用者方向回溯,整條路徑上的各個服務節點,比如 CI/CD、監控、安全、網絡、存儲等等,都有我可以發揮和盈利的余地。這個邏輯,正是所有云計算提供商如此熱衷于容器技術的重要原因:通過容器鏡像,它們可以和潛在用戶(即開發者)直接關聯起來。
從一個開發者和單一的容器鏡像,到無數開發者和龐大的容器集群,容器技術實現了從“容器”到“容器云”的飛躍,標志著它真正得到了市場和生態的認可。
這樣,容器就從一個開發者手里的小工具,一躍成為了云計算領域的絕對主角;而能夠定義容器組織和管理規范的“容器編排”技術,則當仁不讓地坐上了容器技術領域的“頭把交椅”。
這其中,最具代表性的容器編排工具,當屬 Docker 公司的 Compose+Swarm 組合,以及 Google 與 RedHat 公司共同主導的 Kubernetes 項目。
Kubernetes 項目的設計與架構
想和大家聊聊 Kubernetes 項目的設計與架構。
跟很多基礎設施領域先有工程實踐、后有方法論的發展路線不同,Kubernetes 項目的理論基礎則要比工程實踐走得靠前得多,這當然要歸功于 Google 公司在 2015 年 4 月發布的 Borg 論文了。
Borg 系統,一直以來都被譽為 Google 公司內部最強大的“秘密武器”。雖然略顯夸張,但這個說法倒不算是吹牛。因為,相比于 Spanner、BigTable 等相對上層的項目,Borg 要承擔的責任,是承載 Google 公司整個基礎設施的核心依賴。在 Google 公司已經公開發表的基礎設施體系論文中,Borg 項目當仁不讓地位居整個基礎設施技術棧的最底層。
圖片來源:Malte Schwarzkopf. “Operating system support for warehouse-scale computing”. PhD thesis. University of Cambridge Computer Laboratory (to appear), 2015, Chapter 2.
上面這幅圖,來自于 Google Omega 論文的第一作者的博士畢業論文。在這個圖里,你既可以找到 MapReduce、BigTable 等知名項目,也能看到 Borg 和它的繼任者 Omega 位于整個技術棧的最底層。
正是這樣,Borg 可以說是 Google 最不可能開源的一個項目。幸運地是,得益于 Docker 項目和容器技術的風靡,它卻終于得以以另一種方式與開源社區見面,這個方式就是 Kubernetes 項目。
所以,相比于“小打小鬧”的 Docker 公司、“舊瓶裝新酒”的 Mesos 社區,Kubernetes 項目從一開始就比較幸運地站上了一個他人難以企及的高度:在它的成長階段,每個核心特性的提出,幾乎都脫胎于 Borg/Omega 系統的設計與經驗。更重要的是,這些特性在開源社區落地的過程中和社區的合力之下得到了極大的改進,修復了很多當年遺留在 Borg 體系中的缺陷和問題。
所以,盡管在發布之初被批評是“曲高和寡”,但是在逐漸覺察到 Docker 技術棧的“稚嫩”和 Mesos 社區的“老邁”之后,這個社區很快就明白了:Kubernetes 項目在 Borg 體系的指導下,體現出了一種獨有的“先進性”與“完備性”,而這些特質才是一個基礎設施領域開源項目賴以生存的核心價值。
為了更好地理解這兩種特質,我們不妨從 Kubernetes 的頂層設計說起。
Kubernetes 項目要解決的問題
編排?調度?容器云?還是集群管理?
實際上,這個問題到目前為止都沒有固定的答案。因為在不同的發展階段,Kubernetes 需要著重解決的問題是不同的。
但是,對于大多數用戶來說,他們希望 Kubernetes 項目帶來的體驗是確定的:現在我有了應用的容器鏡像,請幫我在一個給定的集群上把這個應用運行起來。
更進一步地說,我還希望 Kubernetes 能給我提供路由網關、水平擴展、監控、備份、災難恢復等一系列運維能力。
等一下,這些功能聽起來好像有些耳熟?這不就是經典 PaaS(比如,Cloud Foundry)項目的能力嗎?
而且,有了 Docker 之后,我根本不需要什么 Kubernetes、PaaS,只要使用 Docker 公司的 Compose+Swarm 項目,就完全可以很方便地 DIY 出這些功能了!
所以說,如果 Kubernetes 項目只是停留在拉取用戶鏡像、運行容器,以及提供常見的運維功能的話,那么別說跟“原生”的 Docker Swarm 項目競爭了,哪怕跟經典的 PaaS 項目相比也難有什么優勢可言。
而實際上,在定義核心功能的過程中,Kubernetes 項目正是依托著 Borg 項目的理論優勢,才在短短幾個月內迅速站穩了腳跟,進而確定了一個如下圖所示的全局架構:
從這個架構中我們可以看到,Kubernetes 項目的架構,跟它的原型項目 Borg 非常類似,都由 Master 和 Node 兩種節點組成,而這兩種角色分別對應著控制節點和計算節點。
其中,控制節點,即 Master 節點,由三個緊密協作的獨立組件組合而成,它們分別是負責 API 服務的 kube-apiserver、負責調度的 kube-scheduler,以及負責容器編排的 kube-controller-manager。整個集群的持久化數據,則由 kube-apiserver 處理后保存在 Ectd 中。
而計算節點上最核心的部分,則是一個叫作 kubelet 的組件。
kubelet 組件
在 Kubernetes 項目中,kubelet 主要負責同容器運行時(比如 Docker 項目)打交道。而這個交互所依賴的,是一個稱作 CRI(Container Runtime Interface)的遠程調用接口,這個接口定義了容器運行時的各項核心操作,比如:啟動一個容器需要的所有參數。
這也是為何,Kubernetes 項目并不關心你部署的是什么容器運行時、使用的什么技術實現,只要你的這個容器運行時能夠運行標準的容器鏡像,它就可以通過實現 CRI 接入到 Kubernetes 項目當中。而具體的容器運行時,比如 Docker 項目,則一般通過 OCI 這個容器運行時規范同底層的 Linux 操作系統進行交互,即:把 CRI 請求翻譯成對 Linux 操作系統的調用(操作 Linux Namespace 和 Cgroups 等)。
此外,kubelet 還通過 gRPC 協議同一個叫作 Device Plugin 的插件進行交互。這個插件,是 Kubernetes 項目用來管理 GPU 等宿主機物理設備的主要組件,也是基于 Kubernetes 項目進行機器學習訓練、高性能作業支持等工作必須關注的功能。
而 kubelet 的另一個重要功能,則是調用網絡插件和存儲插件為容器配置網絡和持久化存儲。這兩個插件與 kubelet 進行交互的接口,分別是 CNI(Container Networking Interface)和 CSI(Container Storage Interface)。
實際上,kubelet 這個奇怪的名字,來自于 Borg 項目里的同源組件 Borglet。不過,如果你瀏覽過 Borg 論文的話,就會發現,這個命名方式可能是 kubelet 組件與 Borglet 組件的唯一相似之處。因為 Borg 項目,并不支持我們這里所講的容器技術,而只是簡單地使用了 Linux Cgroups 對進程進行限制。
這就意味著,像 Docker 這樣的“容器鏡像”在 Borg 中是不存在的,Borglet 組件也自然不需要像 kubelet 這樣考慮如何同 Docker 進行交互、如何對容器鏡像進行管理的問題,也不需要支持 CRI、CNI、CSI 等諸多容器技術接口。
可以說,kubelet 完全就是為了實現 Kubernetes 項目對容器的管理能力而重新實現的一個組件,與 Borg 之間并沒有直接的傳承關系。
備注:雖然不使用 Docker,但 Google 內部確實在使用一個包管理工具,名叫 Midas Package Manager (MPM),其實它可以部分取代 Docker 鏡像的角色。
Borg 的指導作用
Borg 對于 Kubernetes 項目的指導作用什么?答案是,Master 節點。
雖然在 Master 節點的實現細節上 Borg 項目與 Kubernetes 項目不盡相同,但它們的出發點卻高度一致,即:如何編排、管理、調度用戶提交的作業?
所以,Borg 項目完全可以把 Docker 鏡像看做是一種新的應用打包方式。這樣,Borg 團隊過去在大規模作業管理與編排上的經驗就可以直接“套”在 Kubernetes 項目上了。
這些經驗最主要的表現就是,從一開始,Kubernetes 項目就沒有像同時期的各種“容器云”項目那樣,把 Docker 作為整個架構的核心,而僅僅把它作為最底層的一個容器運行時實現。
而 Kubernetes 項目要著重解決的問題,則來自于 Borg 的研究人員在論文中提到的一個非常重要的觀點:
運行在大規模集群中的各種任務之間,實際上存在著各種各樣的關系。這些關系的處理,才是作業編排和管理系統最困難的地方。
事實也正是如此。
其實,這種任務與任務之間的關系,在我們平常的各種技術場景中隨處可見。比如,一個 Web 應用與數據庫之間的訪問關系,一個負載均衡器和它的后端服務之間的代理關系,一個門戶應用與授權組件之間的調用關系。
更進一步地說,同屬于一個服務單位的不同功能之間,也完全可能存在這樣的關系。比如,一個 Web 應用與日志搜集組件之間的文件交換關系。
而在容器技術普及之前,傳統虛擬機環境對這種關系的處理方法都是比較“粗粒度”的。你會經常發現很多功能并不相關的應用被一股腦兒地部署在同一臺虛擬機中,只是因為它們之間偶爾會互相發起幾個 HTTP 請求。更常見的情況則是,一個應用被部署在虛擬機里之后,你還得手動維護很多跟它協作的守護進程(Daemon),用來處理它的日志搜集、災難恢復、數據備份等輔助工作。
但容器技術出現以后,你就不難發現,在“功能單位”的劃分上,容器有著獨一無二的“細粒度”優勢:畢竟容器的本質,只是一個進程而已。也就是說,只要你愿意,那些原先擁擠在同一個虛擬機里的各個應用、組件、守護進程,都可以被分別做成鏡像,然后運行在一個個專屬的容器中。它們之間互不干涉,擁有各自的資源配額,可以被調度在整個集群里的任何一臺機器上。而這,正是一個 PaaS 系統最理想的工作狀態,也是所謂“微服務”思想得以落地的先決條件。
當然,如果只做到“封裝微服務、調度單容器”這一層次,Docker Swarm 項目就已經綽綽有余了。如果再加上 Compose 項目,你甚至還具備了處理一些簡單依賴關系的能力,比如:一個“Web 容器”和它要訪問的數據庫“DB 容器”。
在 Compose 項目中,你可以為這樣的兩個容器定義一個“link”,而 Docker 項目則會負責維護這個“link”關系,其具體做法是:Docker 會在 Web 容器中,將 DB 容器的 IP 地址、端口等信息以環境變量的方式注入進去,供應用進程使用,比如:
DB_NAME=/web/db DB_PORT=tcp://172.17.0.5:5432DB_PORT_5432_TCP=tcp://172.17.0.5:5432DB_PORT_5432_TCP_PROTO=tcpDB_PORT_5432_TCP_PORT=5432DB_PORT_5432_TCP_ADDR=172.17.0.5
而當 DB 容器發生變化時(比如,鏡像更新,被遷移到其他宿主機上等等),這些環境變量的值會由 Docker 項目自動更新。這就是平臺項目自動地處理容器間關系的典型例子。
可是,如果我們現在的需求是,要求這個項目能夠處理前面提到的所有類型的關系,甚至還要能夠支持未來可能出現的更多種類的關系呢?
這時,“link”這種單獨針對一種案例設計的解決方案就太過簡單了。如果你做過架構方面的工作,就會深有感觸:一旦要追求項目的普適性,那就一定要從頂層開始做好設計。
所以,Kubernetes 項目最主要的設計思想是,從更宏觀的角度,以統一的方式來定義任務之間的各種關系,并且為將來支持更多種類的關系留有余地。
比如,Kubernetes 項目對容器間的“訪問”進行了分類,首先總結出了一類非常常見的“緊密交互”的關系,即:這些應用之間需要非常頻繁的交互和訪問;又或者,它們會直接通過本地文件進行信息交換。
在常規環境下,這些應用往往會被直接部署在同一臺機器上,通過 Localhost 通信,通過本地磁盤目錄交換文件。而在 Kubernetes 項目中,這些容器則會被劃分為一個“Pod”,Pod 里的容器共享同一個 Network Namespace、同一組數據卷,從而達到高效率交換信息的目的。
Pod 是 Kubernetes 項目中最基礎的一個對象,源自于 Google Borg 論文中一個名叫 Alloc 的設計。在后續的章節中,我們會對 Pod 做更進一步地闡述。
而對于另外一種更為常見的需求,比如 Web 應用與數據庫之間的訪問關系,Kubernetes 項目則提供了一種叫作“Service”的服務。像這樣的兩個應用,往往故意不部署在同一臺機器上,這樣即使 Web 應用所在的機器宕機了,數據庫也完全不受影響。可是,我們知道,對于一個容器來說,它的 IP 地址等信息不是固定的,那么 Web 應用又怎么找到數據庫容器的 Pod 呢?
所以,Kubernetes 項目的做法是給 Pod 綁定一個 Service 服務,而 Service 服務聲明的 IP 地址等信息是“終生不變”的。這個 Service 服務的主要作用,就是作為 Pod 的代理入口(Portal),從而代替 Pod 對外暴露一個固定的網絡地址。
這樣,對于 Web 應用的 Pod 來說,它需要關心的就是數據庫 Pod 的 Service 信息。不難想象,Service 后端真正代理的 Pod 的 IP 地址、端口等信息的自動更新、維護,則是 Kubernetes 項目的職責。
像這樣,圍繞著容器和 Pod 不斷向真實的技術場景擴展,我們就能夠摸索出一幅如下所示的 Kubernetes 項目核心功能的“全景圖”。
按照這幅圖的線索,我們從容器這個最基礎的概念出發,首先遇到了容器間“緊密協作”關系的難題,于是就擴展到了 Pod;有了 Pod 之后,我們希望能一次啟動多個應用的實例,這樣就需要 Deployment 這個 Pod 的多實例管理器;而有了這樣一組相同的 Pod 后,我們又需要通過一個固定的 IP 地址和端口以負載均衡的方式訪問它,于是就有了 Service。
可是,如果現在兩個不同 Pod 之間不僅有“訪問關系”,還要求在發起時加上授權信息。最典型的例子就是 Web 應用對數據庫訪問時需要 Credential(數據庫的用戶名和密碼)信息。那么,在 Kubernetes 中這樣的關系又如何處理呢?
Kubernetes 項目提供了一種叫作 Secret 的對象,它其實是一個保存在 Etcd 里的鍵值對數據。這樣,你把 Credential 信息以 Secret 的方式存在 Etcd 里,Kubernetes 就會在你指定的 Pod(比如,Web 應用的 Pod)啟動時,自動把 Secret 里的數據以 Volume 的方式掛載到容器里。這樣,這個 Web 應用就可以訪問數據庫了。
除了應用與應用之間的關系外,應用運行的形態是影響“如何容器化這個應用”的第二個重要因素。
為此,Kubernetes 定義了新的、基于 Pod 改進后的對象。比如 Job,用來描述一次性運行的 Pod(比如,大數據任務);再比如 DaemonSet,用來描述每個宿主機上必須且只能運行一個副本的守護進程服務;又比如 CronJob,則用于描述定時任務等等。
如此種種,正是 Kubernetes 項目定義容器間關系和形態的主要方法。
可以看到,Kubernetes 項目并沒有像其他項目那樣,為每一個管理功能創建一個指令,然后在項目中實現其中的邏輯。這種做法,的確可以解決當前的問題,但是在更多的問題來臨之后,往往會力不從心。
相比之下,在 Kubernetes 項目中,我們所推崇的使用方法是:
首先,通過一個“編排對象”,比如 Pod、Job、CronJob 等,來描述你試圖管理的應用;
然后,再為它定義一些“服務對象”,比如 Service、Secret、Horizontal Pod Autoscaler(自動水平擴展器)等。這些對象,會負責具體的平臺級功能。
這種使用方法,就是所謂的“聲明式 API”。這種 API 對應的“編排對象”和“服務對象”,都是 Kubernetes 項目中的 API 對象(API Object)。
這就是 Kubernetes 最核心的設計理念,也是接下來我會重點剖析的關鍵技術點。
如何啟動容器化任務
比如,我現在已經制作好了一個 Nginx 容器鏡像,希望讓平臺幫我啟動這個鏡像。并且,我要求平臺幫我運行兩個完全相同的 Nginx 副本,以負載均衡的方式共同對外提供服務。
如果是自己 DIY 的話,可能需要啟動兩臺虛擬機,分別安裝兩個 Nginx,然后使用 keepalived 為這兩個虛擬機做一個虛擬 IP。
而如果使用 Kubernetes 項目呢?你需要做的則是編寫如下這樣一個 YAML 文件(比如名叫 nginx-deployment.yaml):
apiVersion: apps/v1kind: Deploymentmetadata: name: nginx-deployment labels: app: nginxspec: replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.7.9 ports: - containerPort: 80
在上面這個 YAML 文件中,我們定義了一個 Deployment 對象,它的主體部分(spec.template 部分)是一個使用 Nginx 鏡像的 Pod,而這個 Pod 的副本數是 2(replicas=2)。
然后執行:
$ kubectl create -f nginx-deployment.yaml
這樣,兩個完全相同的 Nginx 容器副本就被啟動了。
不過,這么看來,做同樣一件事情,Kubernetes 用戶要做的工作也不少嘛。
后面我會陸續介紹 Kubernetes 項目這種“聲明式 API”的種種好處,以及基于它實現的強大的編排能力。
彩蛋 :Kubernetes 技能圖譜免費領取
關注本公眾號回復「K8S」,即可免費領取由 Kubernetes 項目維護者張磊 &Etcd 項目作者、阿里系統軟件事業部資深技術專家李響出品的「Kubernetes 技能圖譜」。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。