新手也能看懂,Kubernetes其實很簡單
Kubernetes 是這兩年最熱門、最被人熟知的技術了,它為軟件工程師提供了強大的容器編排能力,模糊了開發(fā)和運維之間的邊界,讓我們開發(fā)、管理和維護一個大型的分布式系統(tǒng)和項目變得更加容易。
本文會先簡單介紹 Kuberentes 的背景、依賴的技術,它的架構以及設計理念,***會提及一些關鍵概念和實現原理。
Kuberentes 的背景
作為一個目前在生產環(huán)境已經廣泛使用的開源項目,Kubernetes 被定義成一個用于自動化部署、擴容和管理容器應用的開源系統(tǒng);它將一個分布式軟件的一組容器打包成一個個更容易管理和發(fā)現的邏輯單元。
Kubernetes 是希臘語『舵手』的意思,它最開始由 Google 的幾位軟件工程師創(chuàng)立,深受公司內部 Borg 和 Omega 項目的影響,很多設計都是從 Borg 中借鑒的,同時也對 Borg 的缺陷進行了改進。
Kubernetes 目前是 Cloud Native Computing Foundation (CNCF) 的項目,并且是很多公司管理分布式系統(tǒng)的解決方案。
在 Kubernetes 統(tǒng)治了容器編排這一領域之前,其實也有很多容器編排方案,例如 compose 和 Swarm,但是在運維大規(guī)模、復雜的集群時,這些方案基本已經都被 Kubernetes 替代了。
Kubernetes 將已經打包好的應用鏡像進行編排,所以如果沒有容器技術的發(fā)展和微服務架構中復雜的應用關系,其實也很難找到合適的應用場景去使用。
所以在這里我們會簡單介紹 Kubernetes 的兩大『依賴』——容器技術和微服務架構。
容器技術
Docker 已經是容器技術的事實標準了,作者在前面的文章中 Docker 核心技術與實現原理 曾經介紹過 Docker 的實現主要依賴于 Linux 的 namespace、cgroups 和 UnionFS。
它讓開發(fā)者將自己的應用以及依賴打包到一個可移植的容器中,讓應用程序的運行可以實現與環(huán)境無關。
我們能夠通過 Docker 實現進程、網絡以及掛載點和文件系統(tǒng)隔離的環(huán)境,并且能夠對宿主機的資源進行分配。
這能夠讓我們在同一個機器上運行多個不同的 Docker 容器,任意一個 Docker 的進程都不需要關心宿主機的依賴,都各自在鏡像構建時完成依賴的安裝和編譯等工作。
這也是為什么 Docker 是 Kubernetes 項目的一個重要依賴。
微服務架構
如果今天的軟件并不是特別復雜并且需要承載的峰值流量不是特別多,那么后端項目的部署其實也只需要在虛擬機上安裝一些簡單的依賴,將需要部署的項目編譯后運行就可以了。
但是隨著軟件變得越來越復雜,一個完整的后端服務不再是單體服務,而是由多個職責和功能不同的服務組成。
服務之間復雜的拓撲關系以及單機已經無法滿足性能需求使得軟件的部署和運維工作變得非常復雜,這也就使得部署和運維大型集群變成了非常迫切的需求。
小結:Kubernetes 的出現不僅主宰了容器編排的市場,更改變了過去的運維方式,不僅將開發(fā)與運維之間邊界變得更加模糊,而且讓 DevOps 這一角色變得更加清晰。
每一個軟件工程師都可以通過 Kubernetes 來定義服務之間的拓撲關系、線上的節(jié)點個數、資源使用量并且能夠快速實現水平擴容、藍綠部署等在過去復雜的運維操作。
Kuberentes 設計理念及架構
設計理念
我們先介紹 Kubernetes 的一些設計理念,這些關鍵字能夠幫助了解 Kubernetes 在設計時所做的一些選擇:
這里將按照順序分別介紹聲明式、顯式接口、無侵入性和可移植性這幾個設計的選擇能夠為我們帶來什么。
聲明式
聲明式(Declarative)的編程方式一直都會被工程師們拿來與命令式(Imperative)進行對比,這兩者是完全不同的編程方法。
我們最常接觸的其實是命令式編程,它要求我們描述為了達到某一個效果或者目標所需要完成的指令,常見的編程語言 Go、Ruby、C++ 都為開發(fā)者提供了命令式的編程方法。
在 Kubernetes 中,我們可以直接使用 YAML 文件定義服務的拓撲結構和狀態(tài):
- 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
這種聲明式的方式能夠大量地減少使用者的工作量,極大地增加開發(fā)的效率。
這是因為聲明式能夠簡化需要的代碼,減少開發(fā)人員的工作,如果我們使用命令式的方式進行開發(fā),雖然在配置上比較靈活,但是帶來了更多的工作。
- SELECT * FROM posts WHERE user_id = 1 AND title LIKE 'hello%';
SQL 其實就是一種常見的聲明式『編程語言』,它能夠讓開發(fā)者自己去指定想要的數據是什么。
Kubernetes 中的 YAML 文件也有著相同的原理,我們可以告訴 Kubernetes 想要的最終狀態(tài)是什么,而它會幫助我們從現有的狀態(tài)進行遷移。
如果 Kubernetes 采用命令式編程的方式提供接口,那么工程師可能就需要通過代碼告訴 Kubernetes 要達到某個狀態(tài)需要通過哪些操作,相比于更關注狀態(tài)和結果聲明式的編程方式,命令式的編程方式更強調過程。
總而言之,Kubernetes 中聲明式的 API 其實指定的是集群期望的運行狀態(tài)。
所以在出現任何不一致問題時,它本身都可以通過指定的 YAML 文件對線上集群進行狀態(tài)的遷移。
就像一個水平觸發(fā)的系統(tǒng),哪怕系統(tǒng)錯過了相應的事件,最終也會根據當前的狀態(tài)自動做出合適的操作。
顯式接口
第二個 Kubernetes 的設計規(guī)范其實就是:不存在內部的私有接口,所有的接口都是顯示定義的,組件之間通信使用的接口對于使用者來說都是顯式的,我們都可以直接調用。
當 Kubernetes 的接口不能滿足工程師的復雜需求時,我們需要利用已有的接口實現更復雜的特性,在這時 Kubernetes 的這一設計就不會成為自定義需求的障礙。
無侵入性
為了盡可能滿足用戶(工程師)的需求,減少工程師的工作量與任務并增強靈活性,Kubernetes 為工程師提供了無侵入式的接入方式。
每一個應用或者服務一旦被打包成了鏡像就可以直接在 Kubernetes 中無縫使用,不需要修改應用程序中的任何代碼。
Docker 和 Kubernetes 就像包裹在應用程序上的兩層,它們兩個為應用程序提供了容器化以及編排的能力。
在應用程序內部卻不需要任何的修改就能夠在 Docker 和 Kubernetes 集群中運行,這是 Kubernetes 在設計時選擇無侵入帶來***的好處,同時無侵入的接入方式也是目前幾乎所有應用程序或者服務都必須考慮的一點。
可移植性
在微服務架構中,我們往往都會讓所有處理業(yè)務的服務變成無狀態(tài)的服務。
以前在內存中存儲的數據、Session 等緩存,現在都會放到 Redis、ETCD 等數據庫中存儲,微服務架構要求我們對業(yè)務進行拆分并劃清服務之間的邊界,所以有狀態(tài)的服務往往會對架構的水平遷移帶來障礙。
然而有狀態(tài)的服務其實是無可避免的,我們將每一個基礎服務或者業(yè)務服務都變成了一個個只負責計算的進程。
但是仍然需要有其他的進程負責存儲易失的緩存和持久的數據,Kubernetes 對這種有狀態(tài)的服務也提供了比較好的支持。
Kubernetes 引入了 Persistent Volume 和 Persistent Volume Claim 的概念用來屏蔽底層存儲的差異性,目前的 Kubernetes 支持下列類型的 Persistent Volume:
這些不同的 Persistent Volume 會被開發(fā)者聲明的 Persistent Volume Claim 分配到不同的服務中。
對于上層來講所有的服務都不需要接觸 Persistent Volume,只需要直接使用 Persistent Volume Claim 得到的卷就可以了。
架構
Kubernetes 遵循非常傳統(tǒng)的客戶端服務端架構,客戶端通過 RESTful 接口或者直接使用 kubectl 與 Kubernetes 集群進行通信。
這兩者在實際上并沒有太多的區(qū)別,后者也只是對 Kubernetes 提供的 RESTful API 進行封裝并提供出來。
每一個 Kubernetes 集群都由一組 Master 節(jié)點和一系列的 Worker 節(jié)點組成,其中 Master 節(jié)點主要負責存儲集群的狀態(tài)并為 Kubernetes 對象分配和調度資源。
Master
作為管理集群狀態(tài)的 Master 節(jié)點,它主要負責接收客戶端的請求,安排容器的執(zhí)行并且運行控制循環(huán),將集群的狀態(tài)向目標狀態(tài)進行遷移,Master 節(jié)點內部由三個組件構成:
其中 API Server 負責處理來自用戶的請求,其主要作用就是對外提供 RESTful 的接口,包括用于查看集群狀態(tài)的讀請求以及改變集群狀態(tài)的寫請求,也是唯一一個與 etcd 集群通信的組件。
而 Controller 管理器運行了一系列的控制器進程,這些進程會按照用戶的期望狀態(tài)在后臺不斷地調節(jié)整個集群中的對象,當服務的狀態(tài)發(fā)生了改變,控制器就會發(fā)現這個改變并且開始向目標狀態(tài)遷移。
***的 Scheduler 調度器其實為 Kubernetes 中運行的 Pod 選擇部署的 Worker 節(jié)點,它會根據用戶的需要選擇最能滿足請求的節(jié)點來運行 Pod,它會在每次需要調度 Pod 時執(zhí)行。
Worker
其他的 Worker 節(jié)點實現就相對比較簡單了,它主要由 kubelet 和 kube-proxy 兩部分組成:
kubelet 是一個節(jié)點上的主要服務,它周期性地從 API Server 接受新的或者修改的 Pod 規(guī)范并且保證節(jié)點上的 Pod 和其中容器的正常運行,還會保證節(jié)點會向目標狀態(tài)遷移,該節(jié)點仍然會向 Master 節(jié)點發(fā)送宿主機的健康狀況。
另一個運行在各個節(jié)點上的代理服務 kube-proxy 負責宿主機的子網管理,同時也能將服務暴露給外部,其原理就是在多個隔離的網絡中把請求轉發(fā)給正確的 Pod 或者容器。
Kubernetes 實現原理
到現在,我們已經對 Kubernetes 有了一些簡單的認識和了解,也大概清楚了 Kubernetes 的架構,下面我們將介紹 Kubernetes 中的一些重要概念和實現原理。
對象
Kubernetes 對象是系統(tǒng)中的持久實體,它使用這些對象來表示集群中的狀態(tài),這些對象能夠描述:
這些對象描述了哪些應用應該運行在集群中,它們請求的資源下限和上限以及重啟、升級和容錯的策略。
每一個創(chuàng)建的對象都是我們對集群狀態(tài)的改變,這些對象描述的其實就是集群的期望狀態(tài),Kubernetes 會根據我們指定的期望狀態(tài)不斷檢查對當前的集群狀態(tài)進行遷移。
- 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"`
- }
每一個對象都包含兩個嵌套對象來描述規(guī)格(Spec)和狀態(tài)(Status),對象的規(guī)格其實就是我們期望的目標狀態(tài)。
而狀態(tài)描述了對象的當前狀態(tài),這部分一般由 Kubernetes 系統(tǒng)本身提供和管理,是我們觀察集群本身的一個接口。
Pod
Pod 是 Kubernetes 中最基本的概念,它也是 Kubernetes 對象模型中我們可以創(chuàng)建或者部署的最小并且最簡單的單元。
它將應用的容器、存儲資源以及獨立的網絡 IP 地址等資源打包到了一起,表示一個最小的部署單元。
但是每一個 Pod 中的運行的容器可能不止一個,這是因為 Pod 最開始設計時就能夠在多個進程之間進行協(xié)調,構建一個高內聚的服務單元,這些容器能夠共享存儲和網絡,非常方便地進行通信。
控制器
***要介紹的就是 Kubernetes 中的控制器,它們其實是用于創(chuàng)建和管理 Pod 的實例,能夠在集群的曾名提供復制、發(fā)布以及健康檢查的功能,這些控制器都運行在 Kubernetes 集群的主節(jié)點上。
在 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
- }
這些控制器會隨著控制器管理器的啟動而運行,它們會監(jiān)聽集群狀態(tài)的變更來調整集群中的 Kuberentes 對象的狀態(tài),在后面的文章中我們會展開介紹一些常見控制器的實現原理。
總結
通過上文我們已經了解了它出現的背景、依賴的關鍵技術,同時我們也介紹了 Kubernetes 的架構設計,主節(jié)點負責處理客戶端的請求、節(jié)點的調度,***我們提到了幾個 Kuberentes 中非常重要的概念:對象、Pod 和控制器。