從認證到調度,K8s 集羣上運行的小程序到底經歷了什麼?

做者 | 聲東  阿里雲售後技術專家linux

導讀:不知道你們有沒有意識到一個現實:大部分時候,咱們已經不像之前同樣,經過命令行,或者可視窗口來使用一個系統了。git

前言

如今咱們上微博、或者網購,操做的其實不是眼前這臺設備,而是一個又一個集羣。一般,這樣的集羣擁有成百上千個節點,每一個節點是一臺物理機或虛擬機。集羣通常遠離用戶,坐落在數據中心。爲了讓這些節點互相協做,對外提供一致且高效的服務,集羣須要操做系統。Kubernetes 就是這樣的操做系統。github

1.jpeg

比較 Kubernetes 和單機操做系統,Kubernetes 至關於內核,它負責集羣軟硬件資源管理,並對外提供統一的入口,用戶能夠經過這個入口來使用集羣,和集羣溝通。web

2.png

而運行在集羣之上的程序,與普通程序有很大的不一樣。這樣的程序,是「關在籠子裏」的程序。它們從被製做,到被部署,再到被使用,都不尋常。咱們只有深挖根源,才能理解其本質。算法

「關在籠子裏」的程序

代碼

咱們使用 go 語言寫了一個簡單的 web 服務器程序 app.go,這個程序監聽在 2580 這個端口。經過 http 協議訪問這個服務的根路徑,服務會返回 "This is a small app for kubernetes..." 字符串。json

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 命令編譯這個程序,產生 app 可執行文件。這是一個普通的可執行文件,它在操做系統裏運行,會依賴系統裏的庫文件。小程序

# 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)

「籠子」

爲了讓這個程序不依賴於操做系統自身的庫文件,咱們須要製做容器鏡像,即隔離的運行環境。Dockerfile 是製做容器鏡像的「菜譜」。咱們的菜譜就只有兩個步驟,下載一個 centos 的基礎鏡像,把 app 這個可執行文件放到鏡像中 /usr/local/bin 目錄中去。centos

FROM centos
ADD app /usr/local/bin

地址

製做好的鏡像存再本地,咱們須要把這個鏡像上傳到鏡像倉庫裏去。這裏的鏡像倉庫,至關於應用商店。咱們使用阿里雲的鏡像倉庫,上傳以後鏡像地址是:api

registry.cn-hangzhou.aliyuncs.com/kube-easy/app:latest

鏡像地址能夠拆分紅四個部分:倉庫地址/命名空間/鏡像名稱:鏡像版本。顯然,鏡像上邊的鏡像,在阿里雲杭州鏡像倉庫,使用的命名空間是 kube-easy,鏡像名:版本是 app:latest。至此,咱們有了一個能夠在 Kubernetes 集羣上運行的、「關在籠子裏」的小程序。瀏覽器

得其門而入

入口

Kubernetes 做爲操做系統,和普通的操做系統同樣,有 API 的概念。有了 API,集羣就有了入口;有了 API,咱們使用集羣,才能得其門而入。Kubernetes 的 API 被實現爲運行在集羣節點上的組件 API Server。這個組件是典型的 web 服務器程序,經過對外暴露 http(s) 接口來提供服務。

3.png

這裏咱們建立一個阿里雲 Kubernetes 集羣。登陸集羣管理頁面,咱們能夠看到 API Server 的公網入口。

API Server 內網鏈接端點: https://xx.xxx.xxx.xxx:6443

雙向數字證書驗證

阿里雲 Kubernetes 集羣 API Server 組件,使用基於 CA 簽名的雙向數字證書認證來保證客戶端與 api server 之間的安全通訊。這句話很繞口,對於初學者不太好理解,咱們來深刻解釋一下。

從概念上來說,數字證書是用來驗證網絡通訊參與者的一個文件。這和學校頒發給學生的畢業證書相似。在學校和學生之間,學校是可信第三方 CA,而學生是通訊參與者。若是社會廣泛信任一個學校的聲譽的話,那麼這個學校頒發的畢業證書,也會獲得社會承認。參與者證書和 CA 證書能夠類比畢業證和學校的辦學許可證。

這裏咱們有兩類參與者,CA 和普通參與者;與此對應,咱們有兩種證書,CA 證書和參與者證書;另外咱們還有兩種關係,證書籤發關係以及信任關係。這兩種關係相當重要。

咱們先看簽發關係。以下圖,咱們有兩張 CA 證書,三個參與者證書。

其中最上邊的 CA 證書,簽發了兩張證書,一張是中間的 CA 證書,另外一張是右邊的參與者證書;中間的 CA 證書,簽發了下邊兩張參與者證書。這六張證書以簽發關係爲聯繫,造成了樹狀的證書籤發關係圖。

4.png

然而,證書以及簽發關係自己,並不能保證可信的通訊能夠在參與者之間進行。以上圖爲例,假設最右邊的參與者是一個網站,最左邊的參與者是一個瀏覽器,瀏覽器相信網站的數據,不是由於網站有證書,也不是由於網站的證書是 CA 簽發的,而是由於瀏覽器相信最上邊的 CA,也就是信任關係。

理解了 CA(證書),參與者(證書),簽發關係,以及信任關係以後,咱們回過頭來看「基於 CA 簽名的雙向數字證書認證」。客戶端和 API Server 做爲通訊的普通參與者,各有一張證書。而這兩張證書,都是由 CA 簽發,咱們簡單稱它們爲集羣 CA 和客戶端 CA。客戶端信任集羣 CA,因此它信任擁有集羣 CA 簽發證書的 API Server;反過來 API Server 須要信任客戶端 CA,它才願意與客戶端通訊。

阿里雲 Kubernetes 集羣,集羣 CA 證書,和客戶端 CA 證書,實現上實際上是一張證書,因此咱們有這樣的關係圖。

5.png

KubeConfig 文件

登陸集羣管理控制檯,咱們能夠拿到 KubeConfig 文件。這個文件包括了客戶端證書,集羣 CA 證書,以及其餘。證書使用 base64 編碼,因此咱們可使用 base64 工具解碼證書,並使用 openssl 查看證書文本。

  • 首先,客戶端證書的簽發者 CN 是集羣 id c0256a3b8e4b948bb9c21e66b0e1d9a72,而證書自己的 CN 是子帳號 252771643302762862;
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 787224 (0xc0318)
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: O=c0256a3b8e4b948bb9c21e66b0e1d9a72, OU=default, CN=c0256a3b8e4b948bb9c21e66b0e1d9a72
        Validity
            Not Before: Nov 29 06:03:00 2018 GMT
            Not After : Nov 28 06:08:39 2021 GMT
        Subject: O=system:users, OU=, CN=252771643302762862
  • 其次,只有在 API Server 信任客戶端 CA 證書的狀況下,上邊的客戶端證書才能經過 API Server 的驗證。kube-apiserver 進程經過 client-ca-file 這個參數指定其信任的客戶端 CA 證書,其指定的證書是 /etc/kubernetes/pki/apiserver-ca.crt。這個文件實際上包含了兩張客戶端 CA 證書,其中一張和集羣管控有關係,這裏不作解釋,另一張以下,它的 CN 與客戶端證書的簽發者 CN 一致;
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 787224 (0xc0318)
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: O=c0256a3b8e4b948bb9c21e66b0e1d9a72, OU=default, CN=c0256a3b8e4b948bb9c21e66b0e1d9a72
        Validity
            Not Before: Nov 29 06:03:00 2018 GMT
            Not After : Nov 28 06:08:39 2021 GMT
        Subject: O=system:users, OU=, CN=252771643302762862
  • 再次,API Server 使用的證書,由 kube-apiserver 的參數 tls-cert-file 決定,這個參數指向證書 /etc/kubernetes/pki/apiserver.crt。這個證書的 CN 是 kube-apiserver,簽發者是 c0256a3b8e4b948bb9c21e66b0e1d9a72,即集羣 CA 證書;
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 2184578451551960857 (0x1e512e86fcba3f19)
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: O=c0256a3b8e4b948bb9c21e66b0e1d9a72, OU=default, CN=c0256a3b8e4b948bb9c21e66b0e1d9a72
        Validity
            Not Before: Nov 29 03:59:00 2018 GMT
            Not After : Nov 29 04:14:23 2019 GMT
        Subject: CN=kube-apiserver
  • 最後,客戶端須要驗證上邊這張 API Server 的證書,於是 KubeConfig 文件裏包含了其簽發者,即集羣 CA 證書。對比集羣 CA 證書和客戶端 CA 證書,發現兩張證書徹底同樣,這符合咱們的預期。
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 786974 (0xc021e)
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=CN, ST=ZheJiang, L=HangZhou, O=Alibaba, OU=ACS, CN=root
        Validity
            Not Before: Nov 29 03:59:00 2018 GMT
            Not After : Nov 24 04:04:00 2038 GMT
        Subject: O=c0256a3b8e4b948bb9c21e66b0e1d9a72, OU=default, CN=c0256a3b8e4b948bb9c21e66b0e1d9a72

訪問

理解了原理以後,咱們能夠作一個簡單的測試:以證書做爲參數,使用 curl 訪問 api server,並獲得預期結果。

# 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"
    }
  ]
}

擇優而居

兩種節點,一種任務

如開始所講,Kubernetes 是管理集羣多個節點的操做系統。這些節點在集羣中的角色,卻沒必要徹底同樣。Kubernetes 集羣有兩種節點:master 節點和 worker 節點。

這種角色的區分,實際上就是一種分工:master 負責整個集羣的管理,其上運行的以集羣管理組件爲主,這些組件包括實現集羣入口的 api server;而 worker 節點主要負責承載普通任務。

在 Kubernetes 集羣中,任務被定義爲 pod 這個概念。pod 是集羣可承載任務的原子單元,pod 被翻譯成容器組,實際上是意譯,由於一個 pod 實際上封裝了多個容器化的應用。原則上來說,被封裝在一個 pod 裏邊的容器,應該是存在至關程度的耦合關係。

6.png

擇優而居

調度算法須要解決的問題,是替 pod 選擇一個溫馨的「居所」,讓 pod 所定義的任務能夠在這個節點上順利地完成。

爲了實現「擇優而居」的目標,Kubernetes 集羣調度算法採用了兩步走的策略:

  • 第一步,從全部節點中排除不知足條件的節點,即預選;
  • 第二步,給剩餘的節點打分,最後得分高者勝出,即優選。

下面咱們使用文章開始的時候製做的鏡像,建立一個 pod,並經過日誌來具體分析一下,這個 pod 怎麼樣被調度到某一個集羣節點。

Pod 配置

首先,咱們建立 pod 的配置文件,配置文件格式是 json。這個配置文件有三個地方比較關鍵,分別是鏡像地址,命令以及容器的端口。

{
    "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
                    }
                ]
            }
        ]
    }
}

日誌級別

集羣調度算法被實現爲運行在 master 節點上的系統組件,這一點和 api server 相似。其對應的進程名是 kube-scheduler。kube-scheduler 支持多個級別的日誌輸出,但社區並無提供詳細的日誌級別說明文檔。查看調度算法對節點進行篩選、打分的過程,咱們須要把日誌級別提升到 10,即加入參數 --v=10。

kube-scheduler --address=127.0.0.1 --kubeconfig=/etc/kubernetes/scheduler.conf --leader-elect=true --v=10

建立 Pod

使用 curl,以證書和 pod 配置文件等做爲參數,經過 POST 請求訪問 api server 的接口,咱們能夠在集羣裏建立對應的 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

預選

預選是 Kubernetes 調度的第一步,這一步要作的事情,是根據預先定義的規則,把不符合條件的節點過濾掉。不一樣版本的 Kubernetes 所實現的預選規則有很大的不一樣,但基本的趨勢,是預選規則會愈來愈豐富。

比較常見的兩個預選規則是 PodFitsResourcesPred 和 PodFitsHostPortsPred。前一個規則用來判斷,一個節點上的剩餘資源,是否是可以知足 pod 的需求;然後一個規則,檢查一個節點上某一個端口是否是已經被其餘 pod 所使用了。

下圖是調度算法在處理測試 pod 的時候,輸出的預選規則的日誌。這段日誌記錄了預選規則 CheckVolumeBindingPred 的執行狀況。某些類型的存儲卷(PV),只能掛載到一個節點上,這個規則能夠過濾掉不知足 pod 對 PV 需求的節點。

從 app 的編排文件裏能夠看到,pod 對存儲卷並無什麼需求,因此這個條件並無過濾掉節點。

7.png

優選

調度算法的第二個階段是優選階段。這個階段,kube-scheduler 會根據節點可用資源及其餘一些規則,給剩餘節點打分。

目前,CPU 和內存是調度算法考量的兩種主要資源,但考量的方式並非簡單的,剩餘 CPU、內存資源越多,得分就越高。

日誌記錄了兩種計算方式:LeastResourceAllocation 和 BalancedResourceAllocation。

  • 前一種方式計算 pod 調度到節點以後,節點剩餘 CPU 和內存佔總 CPU 和內存的比例,比例越高得分就越高;
  • 第二種方式計算節點上 CPU 和內存使用比例之差的絕對值,絕對值越大,得分越少。

這兩種方式,一種傾向於選出資源使用率較低的節點,第二種但願選出兩種資源使用比例接近的節點。這兩種方式有一些矛盾,最終依靠必定的權重來平衡這兩個因素。

8.png

除了資源以外,優選算法會考慮其餘一些因素,好比 pod 與節點的親和性,或者若是一個服務有多個相同 pod 組成的狀況下,多個 pod 在不一樣節點上的分散程度,這是保證高可用的一種策略。

9.png

得分

最後,調度算法會給全部的得分項乘以它們的權重,而後求和獲得每一個節點最終的得分。由於測試集羣使用的是默認調度算法,而默認調度算法把日誌中出現的得分項所對應的權重,都設置成了 1,因此若是按日誌裏有記錄得分項來計算,最終三個節點的得分應該是 29,28 和 29。

10.png

之因此會出現日誌輸出的得分和咱們本身計算的得分不符的狀況,是由於日誌並無輸出全部的得分項,猜想漏掉的策略應該是 NodePreferAvoidPodsPriority,這個策略的權重是 10000,每一個節點得分 10,因此才得出最終日誌輸出的結果。

結束語

在本文中,咱們以一個簡單的容器化 web 程序爲例,着重分析了客戶端怎麼樣經過 Kubernetes 集羣 API Server 認證,以及容器應用怎麼樣被分派到合適節點這兩件事情。

在分析過程當中,咱們棄用了一些便利的工具,好比 kubectl,或者控制檯。咱們用了一些更接近底層的小實驗,好比拆解 KubeConfig 文件,再好比分析調度器日誌來分析認證和調度算法的運做原理。但願這些對你們進一步理解 Kubernetes 集羣有所幫助。

架構師成長系列直播

11.png

阿里巴巴雲原生關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,作最懂雲原生開發者的技術圈。」

相關文章
相關標籤/搜索