阿里妹導讀:Kubernetes 近幾年很熱門,在各大技術論壇上被炒的很火。它提供了強大的容器編排能力,與此同時 DevOps 的概念也來到大家身邊,廣大的開發同學也能簡單地運維複雜的商業化分散式系統,打破了傳統開發和運維之間的界限。
本文會以初學者的視角,希望能讓讀者更好地理解 Kubernetes 出現的背景、超前的設計理念和優秀的技術架構。
背景
PaaS
PaaS 技術,一句話概括就是:它提供了“應用託管”的能力。
早期的主流做法基本上是租 AWS 或者 OpenStack 的虛擬機器,然後把這些虛擬機器當作物理機一樣,用指令碼或者手工的方式在上面部署應用。這個過程中如何保證本地環境和雲端環境的一致性是一個很大的課題,而提供雲端計算服務的公司的核心競爭力就是比拼誰做的更好。從某種意義上來說 PaaS 的出現,算是一個比較好的解決方案。
以 Cloud Foundry 為例,在虛擬機器上部署上 Cloud Foundry 專案後,使用者可以很方便地把自己的應用上雲。以上帝視角來看這個過程:Cloud Foundry 最核心的是提供了一套應用的打包和分發機制,它為不同的程式語言定義了不同的打包格式,它能把可執行檔案、啟動引數等等一起打包成壓縮包然後上傳至 Cloud Foundry 儲存中心,最後由排程器選擇虛擬機器,由虛擬機器上的 Agent 下載並啟動應用。
分散式系統
隨著軟體的規模越來越大,業務模式越來越複雜,使用者量的上升、地區的分佈、系統效能的苛刻要求都促成服務架構從最初的單體變成 SOA 再到如今的微服務,未來還可能演變為 Service Mesh ,Serverless 等等。
如今,一個完整的後端系統不再是單體應用架構了,多年前的 DDD 概念重新回到大家的視線中。現在的系統被不同的職責和功能拆成多個服務,服務之間複雜的關係以及單機的單點效能瓶頸讓部署和運維變得很複雜,所以部署和運維大型分散式系統的需求急迫待解決。
容器技術
前面提到諸如 Cloud Foundry 的 PaaS,使用者必須為不同語言、不同框架區分不同的打包方式,這個打包過程是非常具有災難性的。而現實往往更糟糕,當在本地跑的好好的應用,由於和遠端環境的不一致,在打包後卻需要在雲端各種除錯,最終才能讓應用“平穩”執行。
而 Docker 的出現改變了一切,它憑藉映象解決了這個問題。Docker 一不做二不休,乾脆把完整的作業系統目錄也打包進去,如此高的整合度,保證了雲端和本地環境的高度一致,並且隨時隨地輕易地移植。
誰也不知道就因為“映象”這個簡單的功能,Docker 完成了對 PaaS 的降維打擊,佔有了市場。此時,一些聰明的技術公司紛紛跟進 Docker,推出了自家的容器叢集管理專案,並且稱之為 CaaS。
容器技術利用 Namespace 實現隔離,利用 Cgroups 實現限制;在 Docker 實現上,透過映象,為容器提供完整的系統執行環境,並且透過 UnionFS 實現 Layer 的設計。
Docker 容器是完全使用沙箱機制,相互之間不會有任何介面。透過 Docker,實現程序、網路、掛載點和檔案隔離,更好地利用宿主機資源。Docker 強大到不需要關心宿主機的依賴,所有的一切都可以在映象構建時完成,這也是 Docker 目前成為容器技術標準的原因。所以我們能看到在 Kubernetes 中預設使用 Docker 作為容器(也支援 rkt)。
Kubernetes
鋪墊了這麼多,終於說到本文的主角了。說 Kubernetes 之前,不得不提 Compose、Swarm、Machine 三劍客,其實在 Kubernetes 還未一統江湖之前,它們已經能實現大部分容器編排的能力了。但是在真正的大型系統上,它們卻遠遠不如 Mesosphere 公司出品的大型叢集管理系統,更別說之後的 Kubernetes 了。
在容器化和微服務時代,服務越來越多,容器個數也越來越多。Docker 如它 Logo 所示一樣,一隻只鯨魚在大海里自由地遊蕩,而 Kubernetes 就像一個掌舵的船長,帶著它們,有序的管理它們,這個過程其實就是容器編排。
Kubernetes 起源於 Google,很多設計都是源自於 Borg,是一個開源的,用於管理雲平臺中多個主機上的容器化的應用,Kubernetes 的目標是讓部署容器化的應用簡單並且高效,並且提供了應用部署,規劃,更新,維護的一種機制。
小結
至此,讀者瞭解了 Kubernetes 的前世今生,由 PaaS 的火熱,引爆了容器技術的戰爭,而贏得這場戰爭中最關鍵的即是擁有強大的容器編排的能力,而 Kubernetes 無疑是這場戰爭的勝利者。
設計理念
這一部分,我們會圍繞 Kubernetes 的四個設計理念看看這些做法能給我們帶來什麼。
宣告式 VS 命令式
宣告式和命令式是截然不同的兩種程式設計方式,在命令式 API 中,我們可以直接發出伺服器要執行的命令,例如: “執行容器”、“停止容器”等;在宣告式 API 中,我們宣告系統要執行的操作,系統將不斷向該狀態驅動。
我們常用的 SQL 就是一種宣告式語言,告訴資料庫想要的結果集,資料庫會幫我們設計獲取這個結果集的執行路徑,並返回結果集。眾所周知,使用 SQL 語言獲取資料,要比自行編寫處理過程去獲取資料容易的多。
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: etcd-operator
spec:
replicas: 1
template:
metadata:
labels:
name: etcd-operator
spec:
containers:
- name: etcd-operator
image: quay.io/coreos/etcd-operator:v0.2.1
env:
- name: MY_POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
我們來看看相同設計的 YAML,利用它,我們可以告訴 Kubernetes 最終想要的是什麼,然後 Kubernetes 會完成目標。
宣告式 API 使系統更加健壯,在分散式系統中,任何元件都可能隨時出現故障。當元件恢復時,需要弄清楚要做什麼,使用命令式 API 時,處理起來就很棘手。但是使用宣告式 API ,元件只需檢視 API 伺服器的當前狀態,即可確定它需要執行的操作。
顯式的 API
Kubernetes 是透明的,它沒有隱藏的內部 API。換句話說 Kubernetes 系統內部用來互動的 API 和我們用來與 Kubernetes 互動的 API 相同。
這樣做的好處是,當 Kubernetes 預設的元件無法滿足我們的需求時,我們可以利用已有的 API 實現我們自定義的特性。
無侵入性
感謝 Docker 容器技術的流行,使得 Kubernetes 為大家提供了無縫的使用方式。在容器化的時代,我們的應用達到映象後,不需要改動就可以遨遊在 Kubernetes 叢集中。
Kubernetes 還提供儲存 Secret、Configuration 等包含但不侷限於密碼、證書、容器映象資訊、應用啟動引數能力。如此,Kubernetes 以一種友好的方式將這些東西注入 Pod,減少了大家的工作量,而無需重寫或者很大幅度改變原有的應用程式碼。
有狀態的移植
在有狀態的儲存場景下,Kubernetes 如何做到對於服務和儲存的分離呢?假設一個大型分散式系統使用了多家雲廠商的儲存方案,如何做到開發者無感於底層的儲存技術體系,並且做到方便的移植?
為了實現這一目標,Kubernetes 引入了 PersistentVolumeClaim(PVC)和 PersistentVolume(PV)API 物件。這些物件將儲存實現與儲存使用分離。
PersistentVolumeClaim 物件用作使用者以與實現無關的方式請求儲存的方法,透過它來抹除對底層 PersistentVolume 的差異性。這樣就使 Kubernetes 擁有了跨叢集的移植能力。
架構
首先要提及的是 Kubernetes 使用很具代表性的 C/S 架構方式,Client 可以使用 kubectl 命令列或者 RESTful 介面與 Kubernetes 叢集進行互動。下面這張圖是從宏觀上看 Kubernetes 的整體架構,每一個 Kubernetes 叢集都由 Master 節點 和 很多的 Node 節點組成。
Master
Master 是 Kubernetes 叢集的管理節點,負責管理叢集,提供叢集的資源資料訪問入口。擁有 Etcd 儲存服務,執行 API Server 程序,Controller Manager 服務程序及 Scheduler 服務程序,關聯工作節點 Node。
Kubernetes API Server 提供 HTTP Rest 介面的關鍵服務程序,是 Kubernetes 裡所有資源的增、刪、改、查等操作的唯一入口。也是叢集控制的入口程序; Kubernetes Controller Manager 是 Kubernetes 所有資源物件的自動化控制中心,它驅使叢集向著我們所需要的最終目的狀態; Kubernetes Schedule 是負責 Pod 排程的程序。
Node
Node 是 Kubernetes 叢集架構中執行 Pod 的服務節點。Node 是 Kubernetes 叢集操作的單元,用來承載被分配 Pod 的執行,是 Pod 執行的宿主機。關聯 Master 管理節點,擁有名稱和 IP、系統資源資訊。執行 Docker Runtime、kubelet 和 kube-proxy。
kubelet 負責對 Pod 對於的容器的建立、啟停等任務,傳送宿主機當前狀態; kube-proxy 實現 Kubernetes Service 的通訊與負載均衡機制的重要元件; Docker Runtime 負責本機容器的建立和管理工作。
實現原理
為了儘可能地讓讀者能明白 Kubernetes 是如何運作的,這裡不會涉及到具體的細節實現,如有讀者感興趣可以自行參閱官網文件。這裡以一個簡單的應用部署示例來闡述一些概念和原理。
建立 Kubernetes 叢集
介紹架構的時候我們知道,Kubernetes 叢集由 Master 和 Node 組成。
Master 管理叢集的所有行為例如:應用排程、改變應用的狀態,擴縮容,更新/降級應用等。
Node 可以是是一個虛擬機器或者物理機,它是應用的“邏輯主機”,每一個 Node 擁有一個 Kubelet,Kubelet 負責管理 Node 節點與 Master 節點的互動,同時 Node 還需要有容器操作的能力,比如 Docker 或者 rkt。理論上來說,一個 Kubernetes 為了應對生產環境的流量,最少部署3個 Node 節點。
當我們需要在 Kubernetes 上部署應用時,我們告訴 Master 節點,Master 會排程容器跑在合適的 Node 節點上。
我們可以使用 Minikube 在本地搭一個單 Node 的 Kubernetes 叢集。
部署應用
當建立好一個 Kubernetes 叢集后,就可以把容器化的應用跑在上面了。我們需要建立一個 Deployment,它會告訴 Kubernetes Master 如何去建立應用,也可以來更新應用。
當應用例項建立後,Deployment 會不斷地觀察這些例項,如果 Node 上的 Pod 掛了,Deployment 會自動建立新的例項並且替換它。相比傳統指令碼運維的方式,這種方式更加優雅。
我們能透過 kubectl 命令或者 YAML 檔案來建立 Deployment,在建立的時候需要指定應用映象和要跑的例項個數,之後 Kubernetes 會自動幫我們處理。
檢視 Pods 和 Nodes
下面來介紹下 Pod 和 Node:
當我們建立好 Deployment 的時候,Kubernetes 會自動建立 Pod 來承載應用例項。Pod 是一個抽象的概念,像一個“邏輯主機”,它代表一組應用容器的集合,這些應用容器共享資源,包括儲存,網路和相同的內部叢集 IP。
任何一個 Pod 都需要跑在一個 Node 節點上。Node 是一個“虛擬機器”,它可以是虛擬機器也可以是物理機,一個 Node 可以有多個 Pods,Kubernetes 會自動排程 Pod 到合適的 Node 上。
Service 與 LabelSelector
Pods 終有一死,也就是說 Pods 也有自己的生命週期,當一個 Pod 掛了的時候,ReplicaSet 會建立新的,並且排程到合適的 Node 節點上。考慮下訪問的問題,Pod 替換伴隨著 IP 的變化,對於訪問者來說,變化的 IP 是合理的;並且當有多個 Pod 節點時,如何 SLB 訪問也是個問題,Service 就是為了解決這些問題的。
Service 是一個抽象的概念,它定義了一組邏輯 Pods,並且提供訪問它們的策略。和其他物件一樣,Service 也能透過 kubectl 或者 YAML 建立。Service 定義的 Pod 可以寫在 LabelSelector 選項中(下文會介紹),也存在不指定 Pods 的情況,這種比較複雜,感興趣的讀者可以自行查閱資料。
Service 有以下幾種型別:
ClusterIP(預設):在叢集中內部IP上暴露服務,此型別使Service只能從群集中訪問;
NodePort:透過每個 Node 上的 IP 和靜態埠(NodePort)暴露服務。NodePort 服務會路由到 ClusterIP 服務,這個 ClusterIP 服務會自動建立。透過請求 :,可以從叢集的外部訪問一個 NodePort 服務;
LoadBalancer:使用雲提供商的負載均衡器,可以向外部暴露服務。外部的負載均衡器可以路由到 NodePort 服務和 ClusterIP 服務;
ExternalName:透過返回 CNAME 和它的值,(適用於外部 DNS 的場景)
Labels 和 Selectors 能夠讓 Kubernetes 擁有邏輯運算的能力,有點像 SQL。舉個例子:可以查詢 app=hello_word 的所有物件,也可以查詢 app in (a,b,c) abc的所有物件。
Labels是一個繫結在物件上的 K/V 結構,它可以在建立或者之後的時候的定義,在任何時候都可以改變。
擴容應用
前文提到我們可以使用 Deployment 增加例項個數,下圖是原始的叢集狀態:
我們可以隨意的更改 replicas (例項個數)來擴容,當我們更改了 Deployment 中的 replicas 值時,Kubernetes 會自動幫我們達到想要的目標例項個數,如下圖:
更新應用
更新應用和擴容類似,我們可以更改 Deployment 中的容器映象,然後 Kubernetes 會幫住我們應用更新(藍綠、金絲雀等方式),透過此功能,我們還可以實現切換應用環境、回滾、不停機 CI/CD。下面是部署的過程,需要注意的是我們可以指定新建立的 Pod 最大個數和不可用 Pod 最大個數:
總結
到了最後,大家對 Kubernetes 有個大概的瞭解了,但 Kubernetes 遠遠不止本文所介紹的這些內容。在雲原生概念逐漸清晰的今天,Kubernetes 作為 CNCF 中一個接地氣的落地專案,其重要性不言而喻。