Kubernetes 是這兩年最熱門、最被人熟知的技術了,它為軟體工程師提供了強大的容器編排能力,模糊了開發和運維之間的邊界,讓我們開發、管理和維護一個大型的分布式系統和項目變得更加容易。
本文會先簡單介紹 Kuberentes 的背景、依賴的技術,它的架構以及設計理念,最後會提及一些關鍵概念和實作原理。
Kuberentes 的背景
作為一個目前在生産環境已經廣泛使用的開源項目,Kubernetes 被定義成一個用于自動化部署、擴容和管理容器應用的開源系統;它将一個分布式軟體的一組容器打包成一個個更容易管理和發現的邏輯單元。
Kubernetes 是希臘語『舵手』的意思,它最開始由 Google 的幾位軟體工程師創立,深受公司内部 Borg 和 Omega 項目的影響,很多設計都是從 Borg 中借鑒的,同時也對 Borg 的缺陷進行了改進。
Kubernetes 目前是 Cloud Native Computing Foundation (CNCF) 的項目,并且是很多公司管理分布式系統的解決方案。
在 Kubernetes 統治了容器編排這一領域之前,其實也有很多容器編排方案,例如 compose 和 Swarm,但是在運維大規模、複雜的叢集時,這些方案基本已經都被 Kubernetes 替代了。
Kubernetes 将已經打包好的應用鏡像進行編排,是以如果沒有容器技術的發展和微服務架構中複雜的應用關系,其實也很難找到合适的應用場景去使用。
是以在這裡我們會簡單介紹 Kubernetes 的兩大『依賴』——容器技術和微服務架構。
容器技術
Docker 已經是容器技術的事實标準了,作者在前面的文章中 Docker 核心技術與實作原理 曾經介紹過 Docker 的實作主要依賴于 Linux 的 namespace、cgroups 和 UnionFS。
它讓開發者将自己的應用以及依賴打包到一個可移植的容器中,讓應用程式的運作可以實作與環境無關。
我們能夠通過 Docker 實作程序、網絡以及挂載點和檔案系統隔離的環境,并且能夠對主控端的資源進行配置設定。
這能夠讓我們在同一個機器上運作多個不同的 Docker 容器,任意一個 Docker 的程序都不需要關心主控端的依賴,都各自在鏡像建構時完成依賴的安裝和編譯等工作。
這也是為什麼 Docker 是 Kubernetes 項目的一個重要依賴。
微服務架構
如果今天的軟體并不是特别複雜并且需要承載的峰值流量不是特别多,那麼後端項目的部署其實也隻需要在虛拟機上安裝一些簡單的依賴,将需要部署的項目編譯後運作就可以了。
但是随着軟體變得越來越複雜,一個完整的後端服務不再是單體服務,而是由多個職責和功能不同的服務組成。
服務之間複雜的拓撲關系以及單機已經無法滿足性能需求使得軟體的部署和運維工作變得非常複雜,這也就使得部署和運維大型叢集變成了非常迫切的需求。
小結:Kubernetes 的出現不僅主宰了容器編排的市場,更改變了過去的運維方式,不僅将開發與運維之間邊界變得更加模糊,而且讓 DevOps 這一角色變得更加清晰。
每一個軟體工程師都可以通過 Kubernetes 來定義服務之間的拓撲關系、線上的節點個數、資源使用量并且能夠快速實作水準擴容、藍綠部署等在過去複雜的運維操作。
Kuberentes 設計理念及架構
設計理念
我們先介紹 Kubernetes 的一些設計理念,這些關鍵字能夠幫助了解 Kubernetes 在設計時所做的一些選擇:
這裡将按照順序分别介紹聲明式、顯式接口、無侵入性和可移植性這幾個設計的選擇能夠為我們帶來什麼。
聲明式
聲明式(Declarative)的程式設計方式一直都會被工程師們拿來與指令式(Imperative)進行對比,這兩者是完全不同的程式設計方法。
我們最常接觸的其實是指令式程式設計,它要求我們描述為了達到某一個效果或者目标所需要完成的指令,常見的程式設計語言 Go、Ruby、C++ 都為開發者提供了指令式的程式設計方法。
在 Kubernetes 中,我們可以直接使用 YAML 檔案定義服務的拓撲結構和狀态:
apiVersion: v1
kind: Pod
metadata:
name: rss-site
labels:
app: web
spec:
containers:
- name: front-end
image: nginx
ports:
- containerPort: 80
- name: rss-reader
image: nickchase/rss-php-nginx:v1
ports:
- containerPort: 88
這種聲明式的方式能夠大量地減少使用者的工作量,極大地增加開發的效率。
這是因為聲明式能夠簡化需要的代碼,減少開發人員的工作,如果我們使用指令式的方式進行開發,雖然在配置上比較靈活,但是帶來了更多的工作。
SELECT * FROM posts WHERE user_id = 1 AND title LIKE 'hello%';
SQL 其實就是一種常見的聲明式『程式設計語言』,它能夠讓開發者自己去指定想要的資料是什麼。
Kubernetes 中的 YAML 檔案也有着相同的原理,我們可以告訴 Kubernetes 想要的最終狀态是什麼,而它會幫助我們從現有的狀态進行遷移。
如果 Kubernetes 采用指令式程式設計的方式提供接口,那麼工程師可能就需要通過代碼告訴 Kubernetes 要達到某個狀态需要通過哪些操作,相比于更關注狀态和結果聲明式的程式設計方式,指令式的程式設計方式更強調過程。
總而言之,Kubernetes 中聲明式的 API 其實指定的是叢集期望的運作狀态。
是以在出現任何不一緻問題時,它本身都可以通過指定的 YAML 檔案對線上叢集進行狀态的遷移。
就像一個水準觸發的系統,哪怕系統錯過了相應的事件,最終也會根據目前的狀态自動做出合适的操作。
顯式接口
第二個 Kubernetes 的設計規範其實就是:不存在内部的私有接口,所有的接口都是顯示定義的,元件之間通信使用的接口對于使用者來說都是顯式的,我們都可以直接調用。
當 Kubernetes 的接口不能滿足工程師的複雜需求時,我們需要利用已有的接口實作更複雜的特性,在這時 Kubernetes 的這一設計就不會成為自定義需求的障礙。
無侵入性
為了盡可能滿足使用者(工程師)的需求,減少工程師的工作量與任務并增強靈活性,Kubernetes 為工程師提供了無侵入式的接入方式。
每一個應用或者服務一旦被打包成了鏡像就可以直接在 Kubernetes 中無縫使用,不需要修改應用程式中的任何代碼。
Docker 和 Kubernetes 就像包裹在應用程式上的兩層,它們兩個為應用程式提供了容器化以及編排的能力。
在應用程式内部卻不需要任何的修改就能夠在 Docker 和 Kubernetes 叢集中運作,這是 Kubernetes 在設計時選擇無侵入帶來最大的好處,同時無侵入的接入方式也是目前幾乎所有應用程式或者服務都必須考慮的一點。
可移植性
在微服務架構中,我們往往都會讓所有處理業務的服務變成無狀态的服務。
以前在記憶體中存儲的資料、Session 等緩存,現在都會放到 Redis、ETCD 等資料庫中存儲,微服務架構要求我們對業務進行拆分并劃清服務之間的邊界,是以有狀态的服務往往會對架構的水準遷移帶來障礙。
然而有狀态的服務其實是無可避免的,我們将每一個基礎服務或者業務服務都變成了一個個隻負責計算的程序。
但是仍然需要有其他的程序負責存儲易失的緩存和持久的資料,Kubernetes 對這種有狀态的服務也提供了比較好的支援。
Kubernetes 引入了 Persistent Volume 和 Persistent Volume Claim 的概念用來屏蔽底層存儲的差異性,目前的 Kubernetes 支援下列類型的 Persistent Volume:
這些不同的 Persistent Vol
ume 會被開發者聲明的 Persistent Volume Claim 配置設定到不同的服務中。
對于上層來講所有的服務都不需要接觸 Persistent Volume,隻需要直接使用 Persistent Volume Claim 得到的卷就可以了。
架構
Kubernetes 遵循非常傳統的用戶端服務端架構,用戶端通過 RESTful 接口或者直接使用 kubectl 與 Kubernetes 叢集進行通信。
這兩者在實際上并沒有太多的差別,後者也隻是對 Kubernetes 提供的 RESTful API 進行封裝并提供出來。
每一個 Kubernetes 叢集都由一組 Master 節點和一系列的 Worker 節點組成,其中 Master 節點主要負責存儲叢集的狀态并為 Kubernetes 對象配置設定和排程資源。
Master
作為管理叢集狀态的 Master 節點,它主要負責接收用戶端的請求,安排容器的執行并且運作控制循環,将叢集的狀态向目标狀态進行遷移,Master 節點内部由三個元件構成:
其中 API Server 負責處理來自使用者的請求,其主要作用就是對外提供 RESTful 的接口,包括用于檢視叢集狀态的讀請求以及改變叢集狀态的寫請求,也是唯一一個與 etcd 叢集通信的元件。
而 Controller 管理器運作了一系列的控制器程序,這些程序會按照使用者的期望狀态在背景不斷地調節整個叢集中的對象,當服務的狀态發生了改變,控制器就會發現這個改變并且開始向目标狀态遷移。
最後的 Scheduler 排程器其實為 Kubernetes 中運作的 Pod 選擇部署的 Worker 節點,它會根據使用者的需要選擇最能滿足請求的節點來運作 Pod,它會在每次需要排程 Pod 時執行。
Worker
其他的 Worker 節點實作就相對比較簡單了,它主要由 kubelet 和 kube-proxy 兩部分組成:
kubelet 是一個節點上的主要服務,它周期性地從 API Server 接受新的或者修改的 Pod 規範并且保證節點上的 Pod 和其中容器的正常運作,還會保證節點會向目标狀态遷移,該節點仍然會向 Master 節點發送主控端的健康狀況。
另一個運作在各個節點上的代理服務 kube-proxy 負責主控端的子網管理,同時也能将服務暴露給外部,其原理就是在多個隔離的網絡中把請求轉發給正确的 Pod 或者容器。
Kubernetes 實作原理
到現在,我們已經對 Kubernetes 有了一些簡單的認識和了解,也大概清楚了 Kubernetes 的架構,下面我們将介紹 Kubernetes 中的一些重要概念和實作原理。
對象
Kubernetes 對象是系統中的持久實體,它使用這些對象來表示叢集中的狀态,這些對象能夠描述:
這些對象描述了哪些應用應該運作在叢集中,它們請求的資源下限和上限以及重新開機、更新和容錯的政策。
每一個建立的對象都是我們對叢集狀态的改變,這些對象描述的其實就是叢集的期望狀态,Kubernetes 會根據我們指定的期望狀态不斷檢查對目前的叢集狀态進行遷移。
type Deployment struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
Spec DeploymentSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
Status DeploymentStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}
每一個對象都包含兩個嵌套對象來描述規格(Spec)和狀态(Status),對象的規格其實就是我們期望的目标狀态。
而狀态描述了對象的目前狀态,這部分一般由 Kubernetes 系統本身提供和管理,是我們觀察叢集本身的一個接口。
Pod
Pod 是 Kubernetes 中最基本的概念,它也是 Kubernetes 對象模型中我們可以建立或者部署的最小并且最簡單的單元。
它将應用的容器、存儲資源以及獨立的網絡 IP 位址等資源打包到了一起,表示一個最小的部署單元。
但是每一個 Pod 中的運作的容器可能不止一個,這是因為 Pod 最開始設計時就能夠在多個程序之間進行協調,建構一個高内聚的服務單元,這些容器能夠共享存儲和網絡,非常友善地進行通信。
控制器
最後要介紹的就是 Kubernetes 中的控制器,它們其實是用于建立和管理 Pod 的執行個體,能夠在叢集的曾名提供複制、釋出以及健康檢查的功能,這些控制器都運作在 Kubernetes 叢集的主節點上。
在 Kuberentes 的 kubernetes/pkg/controller/ 目錄中包含了官方提供的一些常見控制器,我們可以通過下面這個函數看到所有需要運作的控制器:
func NewControllerInitializers(loopMode ControllerLoopMode) map[string]InitFunc {
controllers := map[string]InitFunc{}
controllers["endpoint"] = startEndpointController
controllers["replicationcontroller"] = startReplicationController
controllers["podgc"] = startPodGCController
controllers["resourcequota"] = startResourceQuotaController
controllers["namespace"] = startNamespaceController
controllers["serviceaccount"] = startServiceAccountController
controllers["garbagecollector"] = startGarbageCollectorController
controllers["daemonset"] = startDaemonSetController
controllers["job"] = startJobController
controllers["deployment"] = startDeploymentController
controllers["replicaset"] = startReplicaSetController
controllers["horizontalpodautoscaling"] = startHPAController
controllers["disruption"] = startDisruptionController
controllers["statefulset"] = startStatefulSetController
controllers["cronjob"] = startCronJobController
// ...
return controllers
}
這些控制器會随着控制器管理器的啟動而運作,它們會監聽叢集狀态的變更來調整叢集中的 Kuberentes 對象的狀态,在後面的文章中我們會展開介紹一些常見控制器的實作原理。
總結