从认证到调度,K8s 集群上运行的小程序到底经历了什么?
作者 | 聲東? 阿里云售后技術(shù)專家
導(dǎo)讀:不知道大家有沒有意識到一個現(xiàn)實(shí):大部分時候,我們已經(jīng)不像以前一樣,通過命令行,或者可視窗口來使用一個系統(tǒng)了。
前言
現(xiàn)在我們上微博、或者網(wǎng)購,操作的其實(shí)不是眼前這臺設(shè)備,而是一個又一個集群。通常,這樣的集群擁有成百上千個節(jié)點(diǎn),每個節(jié)點(diǎn)是一臺物理機(jī)或虛擬機(jī)。集群一般遠(yuǎn)離用戶,坐落在數(shù)據(jù)中心。為了讓這些節(jié)點(diǎn)互相協(xié)作,對外提供一致且高效的服務(wù),集群需要操作系統(tǒng)。Kubernetes 就是這樣的操作系統(tǒng)。
比較 Kubernetes 和單機(jī)操作系統(tǒng),Kubernetes 相當(dāng)于內(nèi)核,它負(fù)責(zé)集群軟硬件資源管理,并對外提供統(tǒng)一的入口,用戶可以通過這個入口來使用集群,和集群溝通。
而運(yùn)行在集群之上的程序,與普通程序有很大的不同。這樣的程序,是“關(guān)在籠子里”的程序。它們從被制作,到被部署,再到被使用,都不尋常。我們只有深挖根源,才能理解其本質(zhì)。
“關(guān)在籠子里”的程序
代碼
我們使用 go 語言寫了一個簡單的 web 服務(wù)器程序 app.go,這個程序監(jiān)聽在 2580 這個端口。通過 http 協(xié)議訪問這個服務(wù)的根路徑,服務(wù)會返回 “This is a small app for kubernetes…” 字符串。
package main import ("github.com/gorilla/mux""log""net/http" ) func about(w http.ResponseWriter, r *http.Request) {w.Write([]byte("This is a small app for kubernetes...\n")) } func main() {r := mux.NewRouter()r.HandleFunc("/", about)log.Fatal(http.ListenAndServe("0.0.0.0:2580", r)) }使用 go build 命令編譯這個程序,產(chǎn)生 app 可執(zhí)行文件。這是一個普通的可執(zhí)行文件,它在操作系統(tǒng)里運(yùn)行,會依賴系統(tǒng)里的庫文件。
# ldd app linux-vdso.so.1 => (0x00007ffd1f7a3000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f554fd4a000) libc.so.6 => /lib64/libc.so.6 (0x00007f554f97d000) /lib64/ld-linux-x86-64.so.2 (0x00007f554ff66000)“籠子”
為了讓這個程序不依賴于操作系統(tǒng)自身的庫文件,我們需要制作容器鏡像,即隔離的運(yùn)行環(huán)境。Dockerfile 是制作容器鏡像的“菜譜”。我們的菜譜就只有兩個步驟,下載一個 centos 的基礎(chǔ)鏡像,把 app 這個可執(zhí)行文件放到鏡像中 /usr/local/bin 目錄中去。
FROM centos ADD app /usr/local/bin地址
制作好的鏡像存再本地,我們需要把這個鏡像上傳到鏡像倉庫里去。這里的鏡像倉庫,相當(dāng)于應(yīng)用商店。我們使用阿里云的鏡像倉庫,上傳之后鏡像地址是:
registry.cn-hangzhou.aliyuncs.com/kube-easy/app:latest鏡像地址可以拆分成四個部分:倉庫地址/命名空間/鏡像名稱:鏡像版本。顯然,鏡像上邊的鏡像,在阿里云杭州鏡像倉庫,使用的命名空間是 kube-easy,鏡像名:版本是 app:latest。至此,我們有了一個可以在 Kubernetes 集群上運(yùn)行的、“關(guān)在籠子里”的小程序。
得其門而入
入口
Kubernetes 作為操作系統(tǒng),和普通的操作系統(tǒng)一樣,有 API 的概念。有了 API,集群就有了入口;有了 API,我們使用集群,才能得其門而入。Kubernetes 的 API 被實(shí)現(xiàn)為運(yùn)行在集群節(jié)點(diǎn)上的組件 API Server。這個組件是典型的 web 服務(wù)器程序,通過對外暴露 http(s) 接口來提供服務(wù)。
這里我們創(chuàng)建一個阿里云 Kubernetes 集群。登錄集群管理頁面,我們可以看到 API Server 的公網(wǎng)入口。
API Server 內(nèi)網(wǎng)連接端點(diǎn): https://xx.xxx.xxx.xxx:6443雙向數(shù)字證書驗(yàn)證
阿里云 Kubernetes 集群 API Server 組件,使用基于 CA 簽名的雙向數(shù)字證書認(rèn)證來保證客戶端與 api server 之間的安全通信。這句話很繞口,對于初學(xué)者不太好理解,我們來深入解釋一下。
從概念上來講,數(shù)字證書是用來驗(yàn)證網(wǎng)絡(luò)通信參與者的一個文件。這和學(xué)校頒發(fā)給學(xué)生的畢業(yè)證書類似。在學(xué)校和學(xué)生之間,學(xué)校是可信第三方 CA,而學(xué)生是通信參與者。如果社會普遍信任一個學(xué)校的聲譽(yù)的話,那么這個學(xué)校頒發(fā)的畢業(yè)證書,也會得到社會認(rèn)可。參與者證書和 CA 證書可以類比畢業(yè)證和學(xué)校的辦學(xué)許可證。
這里我們有兩類參與者,CA 和普通參與者;與此對應(yīng),我們有兩種證書,CA 證書和參與者證書;另外我們還有兩種關(guān)系,證書簽發(fā)關(guān)系以及信任關(guān)系。這兩種關(guān)系至關(guān)重要。
我們先看簽發(fā)關(guān)系。如下圖,我們有兩張 CA 證書,三個參與者證書。
其中最上邊的 CA 證書,簽發(fā)了兩張證書,一張是中間的 CA 證書,另一張是右邊的參與者證書;中間的 CA 證書,簽發(fā)了下邊兩張參與者證書。這六張證書以簽發(fā)關(guān)系為聯(lián)系,形成了樹狀的證書簽發(fā)關(guān)系圖。
然而,證書以及簽發(fā)關(guān)系本身,并不能保證可信的通信可以在參與者之間進(jìn)行。以上圖為例,假設(shè)最右邊的參與者是一個網(wǎng)站,最左邊的參與者是一個瀏覽器,瀏覽器相信網(wǎng)站的數(shù)據(jù),不是因?yàn)榫W(wǎng)站有證書,也不是因?yàn)榫W(wǎng)站的證書是 CA 簽發(fā)的,而是因?yàn)闉g覽器相信最上邊的 CA,也就是信任關(guān)系。
理解了 CA(證書),參與者(證書),簽發(fā)關(guān)系,以及信任關(guān)系之后,我們回過頭來看“基于 CA 簽名的雙向數(shù)字證書認(rèn)證”。客戶端和 API Server 作為通信的普通參與者,各有一張證書。而這兩張證書,都是由 CA 簽發(fā),我們簡單稱它們?yōu)榧?CA 和客戶端 CA。客戶端信任集群 CA,所以它信任擁有集群 CA 簽發(fā)證書的 API Server;反過來 API Server 需要信任客戶端 CA,它才愿意與客戶端通信。
阿里云 Kubernetes 集群,集群 CA 證書,和客戶端 CA 證書,實(shí)現(xiàn)上其實(shí)是一張證書,所以我們有這樣的關(guān)系圖。
KubeConfig 文件
登錄集群管理控制臺,我們可以拿到 KubeConfig 文件。這個文件包括了客戶端證書,集群 CA 證書,以及其他。證書使用 base64 編碼,所以我們可以使用 base64 工具解碼證書,并使用 openssl 查看證書文本。
- 首先,客戶端證書的簽發(fā)者 CN 是集群 id c0256a3b8e4b948bb9c21e66b0e1d9a72,而證書本身的 CN 是子賬號 252771643302762862;
- 其次,只有在 API Server 信任客戶端 CA 證書的情況下,上邊的客戶端證書才能通過 API Server 的驗(yàn)證。kube-apiserver 進(jìn)程通過 client-ca-file 這個參數(shù)指定其信任的客戶端 CA 證書,其指定的證書是 /etc/kubernetes/pki/apiserver-ca.crt。這個文件實(shí)際上包含了兩張客戶端 CA 證書,其中一張和集群管控有關(guān)系,這里不做解釋,另外一張如下,它的 CN 與客戶端證書的簽發(fā)者 CN 一致;
- 再次,API Server 使用的證書,由 kube-apiserver 的參數(shù) tls-cert-file 決定,這個參數(shù)指向證書 /etc/kubernetes/pki/apiserver.crt。這個證書的 CN 是 kube-apiserver,簽發(fā)者是 c0256a3b8e4b948bb9c21e66b0e1d9a72,即集群 CA 證書;
- 最后,客戶端需要驗(yàn)證上邊這張 API Server 的證書,因而 KubeConfig 文件里包含了其簽發(fā)者,即集群 CA 證書。對比集群 CA 證書和客戶端 CA 證書,發(fā)現(xiàn)兩張證書完全一樣,這符合我們的預(yù)期。
訪問
理解了原理之后,我們可以做一個簡單的測試:以證書作為參數(shù),使用 curl 訪問 api server,并得到預(yù)期結(jié)果。
# curl --cert ./client.crt --cacert ./ca.crt --key ./client.key https://xx.xx.xx.xxx:6443/api/ {"kind": "APIVersions","versions": ["v1"],"serverAddressByClientCIDRs": [{"clientCIDR": "0.0.0.0/0","serverAddress": "192.168.0.222:6443"}] }擇優(yōu)而居
兩種節(jié)點(diǎn),一種任務(wù)
如開始所講,Kubernetes 是管理集群多個節(jié)點(diǎn)的操作系統(tǒng)。這些節(jié)點(diǎn)在集群中的角色,卻不必完全一樣。Kubernetes 集群有兩種節(jié)點(diǎn):master 節(jié)點(diǎn)和 worker 節(jié)點(diǎn)。
這種角色的區(qū)分,實(shí)際上就是一種分工:master 負(fù)責(zé)整個集群的管理,其上運(yùn)行的以集群管理組件為主,這些組件包括實(shí)現(xiàn)集群入口的 api server;而 worker 節(jié)點(diǎn)主要負(fù)責(zé)承載普通任務(wù)。
在 Kubernetes 集群中,任務(wù)被定義為 pod 這個概念。pod 是集群可承載任務(wù)的原子單元,pod 被翻譯成容器組,其實(shí)是意譯,因?yàn)橐粋€ pod 實(shí)際上封裝了多個容器化的應(yīng)用。原則上來講,被封裝在一個 pod 里邊的容器,應(yīng)該是存在相當(dāng)程度的耦合關(guān)系。
擇優(yōu)而居
調(diào)度算法需要解決的問題,是替 pod 選擇一個舒適的“居所”,讓 pod 所定義的任務(wù)可以在這個節(jié)點(diǎn)上順利地完成。
為了實(shí)現(xiàn)“擇優(yōu)而居”的目標(biāo),Kubernetes 集群調(diào)度算法采用了兩步走的策略:
- 第一步,從所有節(jié)點(diǎn)中排除不滿足條件的節(jié)點(diǎn),即預(yù)選;
- 第二步,給剩余的節(jié)點(diǎn)打分,最后得分高者勝出,即優(yōu)選。
下面我們使用文章開始的時候制作的鏡像,創(chuàng)建一個 pod,并通過日志來具體分析一下,這個 pod 怎么樣被調(diào)度到某一個集群節(jié)點(diǎn)。
Pod 配置
首先,我們創(chuàng)建 pod 的配置文件,配置文件格式是 json。這個配置文件有三個地方比較關(guān)鍵,分別是鏡像地址,命令以及容器的端口。
{"apiVersion": "v1","kind": "Pod","metadata": {"name": "app"},"spec": {"containers": [{"name": "app","image": "registry.cn-hangzhou.aliyuncs.com/kube-easy/app:latest","command": ["app"],"ports": [{"containerPort": 2580}]}]} }日志級別
集群調(diào)度算法被實(shí)現(xiàn)為運(yùn)行在 master 節(jié)點(diǎn)上的系統(tǒng)組件,這一點(diǎn)和 api server 類似。其對應(yīng)的進(jìn)程名是 kube-scheduler。kube-scheduler 支持多個級別的日志輸出,但社區(qū)并沒有提供詳細(xì)的日志級別說明文檔。查看調(diào)度算法對節(jié)點(diǎn)進(jìn)行篩選、打分的過程,我們需要把日志級別提高到 10,即加入?yún)?shù) --v=10。
kube-scheduler --address=127.0.0.1 --kubeconfig=/etc/kubernetes/scheduler.conf --leader-elect=true --v=10創(chuàng)建 Pod
使用 curl,以證書和 pod 配置文件等作為參數(shù),通過 POST 請求訪問 api server 的接口,我們可以在集群里創(chuàng)建對應(yīng)的 pod。
# curl -X POST -H 'Content-Type: application/json;charset=utf-8' --cert ./client.crt --cacert ./ca.crt --key ./client.key https://47.110.197.238:6443/api/v1/namespaces/default/pods -d@app.json預(yù)選
預(yù)選是 Kubernetes 調(diào)度的第一步,這一步要做的事情,是根據(jù)預(yù)先定義的規(guī)則,把不符合條件的節(jié)點(diǎn)過濾掉。不同版本的 Kubernetes 所實(shí)現(xiàn)的預(yù)選規(guī)則有很大的不同,但基本的趨勢,是預(yù)選規(guī)則會越來越豐富。
比較常見的兩個預(yù)選規(guī)則是 PodFitsResourcesPred 和 PodFitsHostPortsPred。前一個規(guī)則用來判斷,一個節(jié)點(diǎn)上的剩余資源,是不是能夠滿足 pod 的需求;而后一個規(guī)則,檢查一個節(jié)點(diǎn)上某一個端口是不是已經(jīng)被其他 pod 所使用了。
下圖是調(diào)度算法在處理測試 pod 的時候,輸出的預(yù)選規(guī)則的日志。這段日志記錄了預(yù)選規(guī)則 CheckVolumeBindingPred?的執(zhí)行情況。某些類型的存儲卷(PV),只能掛載到一個節(jié)點(diǎn)上,這個規(guī)則可以過濾掉不滿足 pod 對 PV 需求的節(jié)點(diǎn)。
從 app 的編排文件里可以看到,pod 對存儲卷并沒有什么需求,所以這個條件并沒有過濾掉節(jié)點(diǎn)。
優(yōu)選
調(diào)度算法的第二個階段是優(yōu)選階段。這個階段,kube-scheduler 會根據(jù)節(jié)點(diǎn)可用資源及其他一些規(guī)則,給剩余節(jié)點(diǎn)打分。
目前,CPU 和內(nèi)存是調(diào)度算法考量的兩種主要資源,但考量的方式并不是簡單的,剩余 CPU、內(nèi)存資源越多,得分就越高。
日志記錄了兩種計(jì)算方式:LeastResourceAllocation 和 BalancedResourceAllocation。
- 前一種方式計(jì)算 pod 調(diào)度到節(jié)點(diǎn)之后,節(jié)點(diǎn)剩余 CPU 和內(nèi)存占總 CPU 和內(nèi)存的比例,比例越高得分就越高;
- 第二種方式計(jì)算節(jié)點(diǎn)上 CPU 和內(nèi)存使用比例之差的絕對值,絕對值越大,得分越少。
這兩種方式,一種傾向于選出資源使用率較低的節(jié)點(diǎn),第二種希望選出兩種資源使用比例接近的節(jié)點(diǎn)。這兩種方式有一些矛盾,最終依靠一定的權(quán)重來平衡這兩個因素。
除了資源之外,優(yōu)選算法會考慮其他一些因素,比如 pod 與節(jié)點(diǎn)的親和性,或者如果一個服務(wù)有多個相同 pod 組成的情況下,多個 pod 在不同節(jié)點(diǎn)上的分散程度,這是保證高可用的一種策略。
得分
最后,調(diào)度算法會給所有的得分項(xiàng)乘以它們的權(quán)重,然后求和得到每個節(jié)點(diǎn)最終的得分。因?yàn)闇y試集群使用的是默認(rèn)調(diào)度算法,而默認(rèn)調(diào)度算法把日志中出現(xiàn)的得分項(xiàng)所對應(yīng)的權(quán)重,都設(shè)置成了 1,所以如果按日志里有記錄得分項(xiàng)來計(jì)算,最終三個節(jié)點(diǎn)的得分應(yīng)該是 29,28 和 29。
之所以會出現(xiàn)日志輸出的得分和我們自己計(jì)算的得分不符的情況,是因?yàn)槿罩静]有輸出所有的得分項(xiàng),猜測漏掉的策略應(yīng)該是 NodePreferAvoidPodsPriority,這個策略的權(quán)重是 10000,每個節(jié)點(diǎn)得分 10,所以才得出最終日志輸出的結(jié)果。
結(jié)束語
在本文中,我們以一個簡單的容器化 web 程序?yàn)槔?#xff0c;著重分析了客戶端怎么樣通過 Kubernetes 集群 API Server 認(rèn)證,以及容器應(yīng)用怎么樣被分派到合適節(jié)點(diǎn)這兩件事情。
在分析過程中,我們棄用了一些便利的工具,比如 kubectl,或者控制臺。我們用了一些更接近底層的小實(shí)驗(yàn),比如拆解 KubeConfig 文件,再比如分析調(diào)度器日志來分析認(rèn)證和調(diào)度算法的運(yùn)作原理。希望這些對大家進(jìn)一步理解 Kubernetes 集群有所幫助。
架構(gòu)師成長系列直播
“阿里巴巴云原生關(guān)注微服務(wù)、Serverless、容器、Service Mesh 等技術(shù)領(lǐng)域、聚焦云原生流行技術(shù)趨勢、云原生大規(guī)模的落地實(shí)踐,做最懂云原生開發(fā)者的技術(shù)圈。”
總結(jié)
以上是生活随笔為你收集整理的从认证到调度,K8s 集群上运行的小程序到底经历了什么?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 国内首个 Kubernetes SIG-
- 下一篇: Heroku 的“得”与“失”