天天看點

k8s管理linux程序,Kubernetes 資源管理概述

Kubernetes 從建立之初的核心子產品之一就是資源排程。想要在生産環境使用好 Kubernetes,必須對它的資源模型以及資源管理非常了解。這篇文章算是對散布在網絡上的 Kubernetes 資源管理内容的一個總結。幹貨文章,強列推薦一讀。

Kubernetes 資源簡介

什麼是資源?

在 Kubernetes 中,有兩個基礎但是非常重要的概念:Node 和 Pod。Node 翻譯成節點,是對叢集資源的抽象;Pod 是對容器的封裝,是應用運作的實體。Node 提供資源,而 Pod 使用資源,這裡的資源分為計算(CPU、Memory、GPU)、存儲(Disk、SSD)、網絡(Network Bandwidth、IP、Ports)。這些資源提供了應用運作的基礎,正确了解這些資源以及叢集排程如何使用這些資源,對于大規模的 Kubernetes 叢集來說至關重要,不僅能保證應用的穩定性,也可以提高資源的使用率。

在這篇文章,我們主要介紹 CPU 和記憶體這兩個重要的資源,它們雖然都屬于計算資源,但也有所差距。CPU 可配置設定的是使用時間,也就是作業系統管理的時間片,每個程序在一定的時間片裡運作自己的任務(另外一種方式是綁核,也就是把 CPU 完全配置設定給某個 Pod 使用,但這種方式不夠靈活會造成嚴重的資源浪費,Kubernetes 中并沒有提供);而對于記憶體,系統提供的是記憶體大小。

CPU 的使用時間是可壓縮的,換句話說它本身無狀态,申請資源很快,也能快速正常回收;而記憶體大小是不可壓縮的,因為它是有狀态的(記憶體裡面儲存的資料),申請資源很慢(需要計算和配置設定記憶體塊的空間),并且回收可能失敗(被占用的記憶體一般不可回收)。

把資源分成可壓縮和不可壓縮,是因為在資源不足的時候,它們的表現很不一樣。對于不可壓縮資源,如果資源不足,也就無法繼續申請資源(記憶體用完就是用完了),并且會導緻 Pod 的運作産生無法預測的錯誤(應用申請記憶體失敗會導緻一系列問題);而對于可壓縮資源,比如 CPU 時間片,即使 Pod 使用的 CPU 資源很多,CPU 使用也可以按照權重配置設定給所有 Pod 使用,雖然每個人使用的時間片減少,但不會影響程式的邏輯。

在 Kubernetes 叢集管理中,有一個非常核心的功能:就是為 Pod 選擇一個主機運作。排程必須滿足一定的條件,其中最基本的是主機上要有足夠的資源給 Pod 使用。

k8s管理linux程式,Kubernetes 資源管理概述

資源除了和排程相關之外,還和很多事情緊密相連,這正是這篇文章要解釋的。

Kubernetes 資源的表示

使用者在 Pod 中可以配置要使用的資源總量,Kubernetes 根據配置的資源數進行排程和運作。目前主要可以配置的資源是 CPU 和 Memory,對應的配置字段是 spec.containers[].resource.limits/request.cpu/memory。

需要注意的是,使用者是對每個容器配置 Request 值,所有容器的資源請求之和就是 Pod 的資源請求總量,而我們一般會說 Pod 的資源請求和 Limits。

Limits 和 Requests 的差別我們下面會提到,這裡先說說比較容易了解的 CPU 和 Memory。

CPU 一般用核數來辨別,一核 CPU 相對于實體伺服器的一個超線程核,也就是作業系統 /proc/cpuinfo 中列出來的核數。因為對資源進行了池化和虛拟化,是以 Kubernetes 允許配置非整數個的核數,比如 0.5 是合法的,它辨別應用可以使用半個 CPU 核的計算量。CPU 的請求有兩種方式,一種是剛提到的 0.5,1 這種直接用數字辨別 CPU 核心數;另外一種表示是 500m,它等價于 0.5,也就是說 1 Core = 1000m。

記憶體比較容易了解,是通過位元組大小指定的。如果直接一個數字,後面沒有任何機關,表示這麼多位元組的記憶體;數字後面還可以跟着機關, 支援的機關有 E、P、T、G、M、K,前者分别是後者的 1000 倍大小的關系,此外還支援 Ei、Pi、Ti、Gi、Mi、Ki,其對應的倍數關系是 2^10 = 1024。比如要使用 100M 記憶體的話,直接寫成 100Mi即可。

節點可用資源

理想情況下,我們希望節點上所有的資源都可以配置設定給 Pod 使用,但實際上節點上除了運作 Pods 之外,還會運作其他的很多程序:系統相關的程序(比如 SSHD、Udev等),以及 Kubernetes 叢集的元件(Kubelet、Docker等)。我們在配置設定資源的時候,需要給這些程序預留一些資源,剩下的才能給 Pod 使用。預留的資源可以通過下面的參數控制:

--kube-reserved=[cpu=100m][,][memory=100Mi][,][ephemeral-storage=1Gi]:控制預留給 Kubernetes 叢集元件的 CPU、Memory 和存儲資源。

--system-reserved=[cpu=100mi][,][memory=100Mi][,][ephemeral-storage=1Gi]:預留給系統的 CPU、Memory 和存儲資源。

這兩塊預留之後的資源才是 Pod 真正能使用的,不過考慮到 Eviction 機制(下面的章節會提到),Kubelet 會保證節點上的資源使用率不會真正到 100%,是以 Pod 的實際可使用資源會稍微再少一點。主機上的資源邏輯配置設定圖如下所示:

k8s管理linux程式,Kubernetes 資源管理概述

NOTE:需要注意的是,Allocatable 不是指目前機器上可以配置設定的資源,而是指能配置設定給 Pod 使用的資源總量,一旦 Kubelet 啟動這個值是不會變化的。

Allocatable 的值可以在 Node 對象的 status 字段中讀取,比如下面這樣:

1

2

3

4

5

6

7

8

9

10

11

12

13status:

allocatable:

cpu: "2"

ephemeral-storage: "35730597829"

hugepages-2Mi: "0"

memory: 3779348Ki

Pods: "110"

capacity:

cpu: "2"

ephemeral-storage: 38770180Ki

hugepages-2Mi: "0"

memory: 3881748Ki

Pods: "110"

Kubernetes 資源對象

在這部分,我們來介紹 Kubernetes 中提供的讓我們管理 Pod 資源的原生對象。

請求(Requests)和上限(Limits)

前面說過使用者在建立 Pod 的時候,可以指定每個容器的 Requests 和 Limits 兩個字段,下面是一個執行個體:

1

2

3

4

5

6

7resources:

requests:

memory: "64Mi"

cpu: "250m"

limits:

memory: "128Mi"

cpu: "500m"

Requests 是容器請求要使用的資源,Kubernetes 會保證 Pod 能使用到這麼多的資源。請求的資源是排程的依據,隻有當節點上的可用資源大于 Pod 請求的各種資源時,排程器才會把 Pod 排程到該節點上(如果 CPU 資源足夠,記憶體資源不足,排程器也不會選擇該節點)。

需要注意的是,排程器隻關心節點上可配置設定的資源,以及節點上所有 Pods 請求的資源,而不關心節點資源的實際使用情況,換句話說,如果節點上的 Pods 申請的資源已經把節點上的資源用滿,即使它們的使用率非常低,比如說 CPU 和記憶體使用率都低于 10%,排程器也不會繼續排程 Pod 上去。

Limits 是 Pod 能使用的資源上限,是實際配置到核心 cgroups 裡面的配置資料。對于記憶體來說,會直接轉換成 docker run 指令行的 --memory 大小,最終會配置到 cgroups 對應任務的 /sys/fs/cgroup/memory/……/memory.limit_in_bytes 檔案中。

NOTE:如果 Limit 沒有配置,則表明沒有資源的上限,隻要節點上有對應的資源,Pod 就可以使用。

使用 Requests 和 Limits 概念,我們能配置設定更多的 Pod,提升整體的資源使用率。但是這個體系有個非常重要的問題需要考慮,那就是怎麼去準确地評估 Pod 的資源 Requests?如果評估地過低,會導緻應用不穩定;如果過高,則會導緻使用率降低。這個問題需要開發者和系統管理者共同讨論和定義。

Limit Range(預設資源配置)

為每個 Pod 都手動配置這些參數是挺麻煩的事情,Kubernetes 提供了 LimitRange 資源,可以讓我們配置某個 Namespace 預設的 Request 和 Limit 值,比如下面的執行個體:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19apiVersion: "v1"

kind: "LimitRange"

metadata:

name: you-shall-have-limits

spec:

limits:

- type: "Container"

max:

cpu: "2"

memory: "1Gi"

min:

cpu: "100m"

memory: "4Mi"

default:

cpu: "500m"

memory: "200Mi"

defaultRequest:

cpu: "200m"

memory: "100Mi"

如果對應 Namespace 建立的 Pod 沒有寫資源的 Requests 和 Limits 字段,那麼它會自動擁有下面的配置資訊:

記憶體請求是 100Mi,上限是 200Mi

CPU 請求是 200m,上限是 500m

當然,如果 Pod 自己配置了對應的參數,Kubernetes 會使用 Pod 中的配置。使用 LimitRange 能夠讓 Namespace 中的 Pod 資源規範化,便于統一的資源管理。

資源配額(Resource Quota)

前面講到的資源管理和排程可以認為 Kubernetes 把這個叢集的資源整合起來,組成一個資源池,每個應用(Pod)會自動從整個池中配置設定資源來使用。預設情況下隻要叢集還有可用的資源,應用就能使用,并沒有限制。Kubernetes 本身考慮到了多使用者和多租戶的場景,提出了 Namespace 的概念來對叢集做一個簡單的隔離。

基于 Namespace,Kubernetes 還能夠對資源進行隔離和限制,這就是 Resource Quota 的概念,翻譯成資源配額,它限制了某個 Namespace 可以使用的資源總額度。這裡的資源包括 CPU、Memory 的總量,也包括 Kubernetes 自身對象(比如 Pod、Services 等)的數量。通過 Resource Quota,Kubernetes 可以防止某個 Namespace 下的使用者不加限制地使用超過期望的資源,比如說不對資源進行評估就大量申請 16核 CPU 32G 記憶體的 Pod。

下面是一個資源配額的執行個體,它限制了 Namespace 隻能使用 20 核 CPU 和 1G 記憶體,并且能建立 10 個 Pod、20 個 RC、5 個 Service,可能适用于某個測試場景。

1

2

3

4

5

6

7

8

9

10

11

12apiVersion: v1

kind: ResourceQuota

metadata:

name: quota

spec:

hard:

cpu: "20"

memory: 1Gi

Pods: "10"

replicationcontrollers: "20"

resourcequotas: "1"

services: "5"

Resource Quota 能夠配置的選項還很多,比如 GPU、存儲、Configmaps、PersistentVolumeClaims 等等,更多資訊可以參考官方文檔。

Resource Quota 要解決的問題和使用都相對獨立和簡單,但是它也有一個限制:那就是它不能根據叢集資源動态伸縮。一旦配置之後,Resource Quota 就不會改變,即使叢集增加了節點,整體資源增多也沒有用。Kubernetes 現在沒有解決這個問題,但是使用者可以通過編寫一個 Controller 的方式來自己實作。

應用優先級

QoS(服務品質)

Requests 和 Limits 的配置除了表明資源情況和限制資源使用之外,還有一個隐藏的作用:它決定了 Pod 的 QoS 等級。

上一節我們提到了一個細節:如果 Pod 沒有配置 Limits ,那麼它可以使用節點上任意多的可用資源。這類 Pod 能靈活使用資源,但這也導緻它不穩定且危險,對于這類 Pod 我們一定要在它占用過多資源導緻節點資源緊張時處理掉。優先處理這類 Pod,而不是處理資源使用處于自己請求範圍内的 Pod 是非常合理的想法,而這就是 Pod QoS 的含義:根據 Pod 的資源請求把 Pod 分成不同的重要性等級。

Kubernetes 把 Pod 分成了三個 QoS 等級:

Guaranteed:優先級最高,可以考慮資料庫應用或者一些重要的業務應用。除非 Pods 使用超過了它們的 Limits,或者節點的記憶體壓力很大而且沒有 QoS 更低的 Pod,否則不會被殺死。

Burstable:這種類型的 Pod 可以多于自己請求的資源(上限由 Limit 指定,如果 Limit 沒有配置,則可以使用主機的任意可用資源),但是重要性認為比較低,可以是一般性的應用或者批處理任務。

Best Effort:優先級最低,叢集不知道 Pod 的資源請求情況,排程不考慮資源,可以運作到任意節點上(從資源角度來說),可以是一些臨時性的不重要應用。Pod 可以使用節點上任何可用資源,但在資源不足時也會被優先殺死。

Pod 的 Requests 和 Limits 是如何對應到這三個 QoS 等級上的,可以用下面一張表格概括:

k8s管理linux程式,Kubernetes 資源管理概述

看到這裡,你也許看出來一個問題了:如果不配置 Requests 和 Limits,Pod 的 QoS 竟然是最低的。沒錯,是以推薦大家了解 QoS 的概念,并且按照需求一定要給 Pod 配置 Requests 和 Limits 參數,不僅可以讓排程更準确,也能讓系統更加穩定。

NOTE:按照現在的方法根據 Pod 請求的資源進行配置不夠靈活和直覺,更理想的情況是使用者可以直接配置 Pod 的 QoS,而不用關心具體的資源申請和上限值。但 Kubernetes 目前還沒有這方面的打算。

Pod 的 QoS 還決定了容器的 OOM(Out-Of-Memory)值,它們對應的關系如下:

k8s管理linux程式,Kubernetes 資源管理概述

可以看到,QoS 越高的 Pod OOM 值越低,也就越不容易被系統殺死。對于 Bustable Pod,它的值是根據 Request 和節點記憶體總量共同決定的:

1oomScoreAdjust := 1000 - (1000*memoryRequest)/memoryCapacity

其中 memoryRequest 是 Pod 申請的資源,memoryCapacity 是節點的記憶體總量。可以看到,申請的記憶體越多,OOM 值越低,也就越不容易被殺死。

QoS 的作用會在後面介紹 Eviction 的時候詳細講解。

Pod 優先級(Priority)

除了 QoS,Kubernetes 還允許我們自定義 Pod 的優先級,比如:

1

2

3

4

5

6

7apiVersion: scheduling.k8s.io/v1alpha1

kind: PriorityClass

metadata:

name: high-priority

value: 1000000

globalDefault: false

description: "This priority class should be used for XYZ service Pods only."

優先級的使用也比較簡單,隻需要在 Pod.spec.PriorityClassName 指定要使用的優先級名字,即可以設定目前 Pod 的優先級為對應的值。

Pod 的優先級在排程的時候會使用到。首先,待排程的 Pod 都在同一個隊列中,啟用了 Pod priority 之後,排程器會根據優先級的大小,把優先級高的 Pod 放在前面,提前排程。

另外,如果在排程的時候,發現某個 Pod 因為資源不足無法找到合适的節點,排程器會嘗試 Preempt 的邏輯。簡單來說,排程器會試圖找到這樣一個節點:找到它上面優先級低于目前要排程 Pod 的所有 Pod,如果殺死它們,能騰足夠的資源,排程器會執行删除操作,把 Pod 排程到節點上。更多内容可以參考:Pod Priority and Preemption - Kubernetes

驅逐(Eviction)

至此,我們講述的都是理想情況下 Kubernetes 的工作狀況,我們假設資源完全夠用,而且應用也都是在使用規定範圍内的資源。

但現實不會如此簡單,在管理叢集的時候我們常常會遇到資源不足的情況,在這種情況下我們要保證整個叢集可用,并且盡可能減少應用的損失。保證叢集可用比較容易了解,首先要保證系統層面的核心程序正常,其次要保證 Kubernetes 本身元件程序不出問題;但是如何量化應用的損失呢?首先能想到的是如果要殺死 Pod,要盡量減少總數。另外一個就和 Pod 的優先級相關了,那就是盡量殺死不那麼重要的應用,讓重要的應用不受影響。

Pod 的驅逐是在 Kubelet 中實作的,因為 Kubelet 能動态地感覺到節點上資源使用率實時的變化情況。其核心的邏輯是:Kubelet 實時監控節點上各種資源的使用情況,一旦發現某個不可壓縮資源出現要耗盡的情況,就會主動終止節點上的 Pod,讓節點能夠正常運作。被終止的 Pod 所有容器會停止,狀态會被設定為 Failed。

驅逐觸發條件

那麼哪些資源不足會導緻 Kubelet 執行驅逐程式呢?目前主要有三種情況:實際記憶體不足、節點檔案系統的可用空間(檔案系統剩餘大小和 Inode 數量)不足、以及鏡像檔案系統的可用空間(包括檔案系統剩餘大小和 Inode 數量)不足。

下面這圖是具體的觸發條件:

k8s管理linux程式,Kubernetes 資源管理概述

有了資料的來源,另外一個問題是觸發的時機,也就是到什麼程度需要觸發驅逐程式?Kubernetes 運作使用者自己配置,并且支援兩種模式:按照百分比和按照絕對數量。比如對于一個 32G 記憶體的節點當可用記憶體少于 10% 時啟動驅逐程式,可以配置 memory.available<10%或者 memory.available<3.2Gi。

NOTE:預設情況下,Kubelet 的驅逐規則是 memory.available<100Mi,對于生産環境這個配置是不可接受的,是以一定要根據實際情況進行修改。

軟驅逐(Soft Eviction)和硬驅逐(Hard Eviction)

因為驅逐 Pod 是具有毀壞性的行為,是以必須要謹慎。有時候記憶體使用率增高隻是暫時性的,有可能 20s 内就能恢複,這時候啟動驅逐程式意義不大,而且可能會導緻應用的不穩定,我們要考慮到這種情況應該如何處理;另外需要注意的是,如果記憶體使用率過高,比如高于 95%(或者 90%,取決于主機記憶體大小和應用對穩定性的要求),那麼我們不應該再多做評估和考慮,而是趕緊啟動驅逐程式,因為這種情況再花費時間去判斷可能會導緻記憶體繼續增長,系統完全崩潰。

為了解決這個問題,Kubernetes 引入了 Soft Eviction 和 Hard Eviction 的概念。

軟驅逐可以在資源緊缺情況并沒有哪些嚴重的時候觸發,比如記憶體使用率為 85%,軟驅逐還需要配置一個時間指定軟驅逐條件持續多久才觸發,也就是說 Kubelet 在發現資源使用率達到設定的門檻值之後,并不會立即觸發驅逐程式,而是繼續觀察一段時間,如果資源使用率高于門檻值的情況持續一定時間,才開始驅逐。并且驅逐 Pod 的時候,會遵循 Grace Period ,等待 Pod 處理完清理邏輯。和軟驅逐相關的啟動參數是:

--eviction-soft:軟驅逐觸發條件,比如 memory.available<1Gi。

--eviction-sfot-grace-period:觸發條件持續多久才開始驅逐,比如 memory.available=2m30s。

--eviction-max-Pod-grace-period:Kill Pod 時等待 Grace Period 的時間讓 Pod 做一些清理工作,如果到時間還沒有結束就做 Kill。

前面兩個參數必須同時配置,軟驅逐才能正常工作;後一個參數會和 Pod 本身配置的 Grace Period 比較,選擇較小的一個生效。

硬驅逐更加直接幹脆,Kubelet 發現節點達到配置的硬驅逐門檻值後,立即開始驅逐程式,并且不會遵循 Grace Period,也就是說立即強制殺死 Pod。對應的配置參數隻有一個 --evictio-hard,可以選擇上面表格中的任意條件搭配。

設定這兩種驅逐程式是為了平衡節點穩定性和對 Pod 的影響,軟驅逐照顧到了 Pod 的優雅退出,減少驅逐對 Pod 的影響;而硬驅逐則照顧到節點的穩定性,防止資源的快速消耗導緻節點不可用。

軟驅逐和硬驅逐可以單獨配置,不過還是推薦兩者都進行配置,一起使用。

驅逐哪些 Pods?

上面我們已經整體介紹了 Kubelet 驅逐 Pod 的邏輯和過程,那這裡就牽涉到一個具體的問題:要驅逐哪些 Pod?驅逐的重要原則是盡量減少對應用程式的影響。

如果是存儲資源不足,Kubelet 會根據情況清理狀态為 Dead 的 Pod 和它的所有容器,以及清理所有沒有使用的鏡像。如果上述清理并沒有讓節點回歸正常,Kubelet 就開始清理 Pod。

一個節點上會運作多個 Pod,驅逐所有的 Pods 顯然是不必要的,是以要做出一個抉擇:在節點上運作的所有 Pod 中選擇一部分來驅逐。雖然這些 Pod 乍看起來沒有差別,但是它們的地位是不一樣的,正如喬治·奧威爾在《動物莊園》的那句話:

所有動物生而平等,但有些動物比其他動物更平等。

Pod 也是不平等的,有些 Pod 要比其他 Pod 更重要。隻管來說,系統元件的 Pod 要比普通的 Pod 更重要,另外運作資料庫的 Pod 自然要比運作一個無狀态應用的 Pod 更重要。Kubernetes 又是怎麼決定 Pod 的優先級的呢?這個問題的答案就藏在我們之前已經介紹過的内容裡:Pod Requests 和 Limits、優先級(Priority),以及 Pod 實際的資源使用。

簡單來說,Kubelet 會根據以下内容對 Pod 進行排序:Pod 是否使用了超過請求的緊張資源、Pod 的優先級、然後是使用的緊缺資源和請求的緊張資源之間的比例。具體來說,Kubelet 會按照如下的順序驅逐 Pod:

使用的緊張資源超過請求數量的 BestEffort 和 Burstable Pod,這些 Pod 内部又會按照優先級和使用比例進行排序。

緊張資源使用量低于 Requests 的 Burstable 和 Guaranteed 的 Pod 後面才會驅逐,隻有當系統元件(Kubelet、Docker、Journald 等)記憶體不夠,并且沒有上面 QoS 比較低的 Pod 時才會做。執行的時候還會根據 Priority 排序,優先選擇優先級低的 Pod。

防止波動

這裡的波動有兩種情況,我們先說說第一種。驅逐條件出發後,如果 Kubelet 驅逐一部分 Pod,讓資源使用率低于門檻值就停止,那麼很可能過一段時間資源使用率又會達到門檻值,進而再次出發驅逐,如此循環往複……為了處理這種問題,我們可以使用 --eviction-minimum-reclaim解決,這個參數配置每次驅逐至少清理出來多少資源才會停止。

另外一個波動情況是這樣的:Pod 被驅逐之後并不會從此消失不見,常見的情況是 Kubernetes 會自動生成一個新的 Pod 來取代,并經過排程選擇一個節點繼續運作。如果不做額外處理,有理由相信 Pod 選擇原來節點的可能性比較大(因為排程邏輯沒變,而它上次排程選擇的就是該節點),之是以說可能而不是絕對會再次選擇該節點,是因為叢集 Pod 的運作和分布和上次排程時極有可能發生了變化。

無論如何,如果被驅逐的 Pod 再次排程到原來的節點,很可能會再次觸發驅逐程式,然後 Pod 再次被排程到目前節點,循環往複…… 這種事情當然是我們不願意看到的,雖然看似複雜,但這個問題解決起來非常簡單:驅逐發生後,Kubelet 更新節點狀态,排程器感覺到這一情況,暫時不往該節點排程 Pod 即可。--eviction-pressure-transition-period 參數可以指定 Kubelet 多久才上報節點的狀态,因為預設的上報狀态周期比較短,頻繁更改節點狀态會導緻驅逐波動。

做一個總結,下面是一個使用了上面多種參數的驅逐配置執行個體(你應該能看懂它們是什麼意思了):

1

2

3

4

5

6–eviction-soft=memory.available<80%,nodefs.available<2Gi \

–eviction-soft-grace-period=memory.available=1m30s,nodefs.available=1m30s \

–eviction-max-Pod-grace-period=120 \

–eviction-hard=memory.available<500Mi,nodefs.available<1Gi \

–eviction-pressure-transition-period=30s \

--eviction-minimum-reclaim="memory.available=0Mi,nodefs.available=500Mi,imagefs.available=2Gi"

碎片整理和重排程

Kubernetes 的排程器在為 Pod 選擇運作節點的時候,隻會考慮到排程那個時間點叢集的狀态,經過一系列的算法選擇一個當時最合适的節點。但是叢集的狀态是不斷變化的,使用者建立的 Pod 也是動态的,随着時間變化,原來排程到某個節點上的 Pod 現在看來可能有更好的節點可以選擇。比如考慮到下面這些情況:

排程 Pod 的條件已經不再滿足,比如節點的 Taints 和 Labels 發生了變化。

新節點加入了叢集。如果預設配置了把 Pod 打散,那麼應該有一些 Pod 最好運作在新節點上。

節點的使用率不均勻。排程後,有些節點的配置設定率和使用率比較高,另外一些比較低。

節點上有資源碎片。有些節點排程之後還剩餘部分資源,但是又低于任何 Pod 的請求資源;或者 Memory 資源已經用完,但是 CPU 還有挺多沒有使用。

想要解決上述的這些問題,都需要把 Pod 重新進行排程(把 Pod 從目前節點移動到另外一個節點)。但是預設情況下,一旦 Pod 被排程到節點上,除非給殺死否則不會移動到另外一個節點的。

為此 Kubernetes 社群孵化了一個稱為 Descheduler 的項目,專門用來做重排程。重排程的邏輯很簡單:找到上面幾種情況中已經不是最優的 Pod,把它們驅逐掉(Eviction)。

目前,Descheduler 不會決定驅逐的 Pod 應該排程到哪台機器,而是假定預設的排程器會做出正确的排程抉擇。也就是說,之是以 Pod 目前不合适,不是因為排程器的算法有問題,而是因為叢集的情況發生了變化。如果讓排程器重新選擇,排程器現在會把 Pod 放到合适的節點上。這種做法讓 Descheduler 邏輯比較簡單,而且避免了排程邏輯出現在兩個元件中。

Descheduler 執行的邏輯是可以配置的,目前有幾種場景:

RemoveDuplicates:RS、Deployment 中的 Pod 不能同時出現在一台機器上。

LowNodeUtilization:找到資源使用率比較低的 Node,然後驅逐其他資源使用率比較高節點上的 Pod,期望排程器能夠重新排程讓資源更均衡。

RemovePodsViolatingInterPodAntiAffinity:找到已經違反 Pod Anti Affinity 規則的 Pods 進行驅逐,可能是因為反親和是後面加上去的。

RemovePodsViolatingNodeAffinity:找到違反 Node Affinity 規則的 Pods 進行驅逐,可能是因為 Node 後面修改了 Label。

當然,為了保證應用的穩定性,Descheduler 并不會随意地驅逐 Pod,還是會尊重 Pod 運作的規則,包括 Pod 的優先級(不會驅逐 Critical Pod,并且按照優先級順序進行驅逐)和 PDB(如果違反了 PDB,則不會進行驅逐),并且不會驅逐沒有 Deployment、RS、Jobs 的 Pod 不會驅逐,Daemonset Pod 不會驅逐,有 Local storage 的 Pod 也不會驅逐。

Descheduler 不是一個常駐的任務,每次執行完之後會退出,是以推薦使用 CronJob 來運作。

總的來說,Descheduler 是對原生排程器的補充,用來解決原生排程器的排程決策随着時間會變得失效,或者不夠優化的缺陷。

資源動态調整

動态調整的思路:應用的實際流量會不斷變化,是以使用率也是不斷變化的,為了應對應用流量的變化,我們應用能夠自動調整應用的資源。比如線上商品應用在促銷的時候通路量會增加,我們應該自動增加 Pod 運算能力來應對;當促銷結束後,有需要自動降低 Pod 的運算能力防止浪費。

運算能力的增減有兩種方式:改變單個 Pod 的資源,以及增減 Pod 的數量。這兩種方式對應了 Kubernetes 的 HPA 和 VPA。

Horizontal Pod AutoScaling(橫向 Pod 自動擴充)

k8s管理linux程式,Kubernetes 資源管理概述

橫向 Pod 自動擴充的思路是這樣的:Kubernetes 會運作一個 Controller,周期性地監聽 Pod 的資源使用情況,當高于設定的門檻值時,會自動增加 Pod 的數量;當低于某個門檻值時,會自動減少 Pod 的數量。自然,這裡的門檻值以及 Pod 的上限和下限的數量都是需要使用者配置的。

上面這句話隐藏了一個重要的資訊:HPA 隻能和 RC、Deployment、RS 這些可以動态修改 Replicas 的對象一起使用,而無法用于單個 Pod、Daemonset(因為它控制的 Pod 數量不能随便修改)等對象。

目前官方的監控資料來源是 Metrics Server 項目,可以配置的資源隻有 CPU,但是使用者可以使用自定義的監控資料(比如:Prometheus)。其他資源(比如:Memory)的 HPA 支援也已經在路上了。

Vertical Pod AutoScaling

和 HPA 的思路相似,隻不過 VPA 調整的是單個 Pod 的 Request 值(包括 CPU 和 Memory)。VPA 包括三個元件:

Recommander:消費 Metrics Server 或者其他監控元件的資料,然後計算 Pod 的資源推薦值。

Updater:找到被 VPA 接管的 Pod 中和計算出來的推薦值差距過大的,對其做 Update 操作(目前是 Evict,建立的 Pod 在下面 Admission Controller 中會使用推薦的資源值作為 Request)。

Admission Controller:建立的 Pod 會經過該 Admission Controller,如果 Pod 是被 VPA 接管的,會使用 Recommander 計算出來的推薦值。

可以看到,這三個元件的功能是互相補充的,共同實作了動态修改 Pod 請求資源的功能。相對于 HPA,目前 VPA 還處于 Alpha,并且還沒有合并到官方的 Kubernetes Release 中,後續的接口和功能很可能會發生變化。

Cluster Auto Scaler

随着業務的發展,應用會逐漸增多,每個應用使用的資源也會增加,總會出現叢集資源不足的情況。為了動态地應對這一狀況,我們還需要 CLuster Auto Scaler,能夠根據整個叢集的資源使用情況來增減節點。

對于公有雲來說,Cluster Auto Scaler 就是監控這個叢集因為資源不足而 Pending 的 Pod,根據使用者配置的門檻值調用公有雲的接口來申請建立機器或者銷毀機器。對于私有雲,則需要對接内部的管理平台。

目前 HPA 和 VPA 不相容,隻能選擇一個使用,否則兩者會互相幹擾。而且 VPA 的調整需要重新開機 Pod,這是因為 Pod 資源的修改是比較大的變化,需要重新走一下 Apiserver、排程的流程,保證整個系統沒有問題。目前社群也有計劃在做原地更新,也就是說不通過殺死 Pod 再排程新 Pod 的方式,而是直接修改原有 Pod 來更新。

理論上 HPA 和 VPA 是可以共同工作的,HPA 負責瓶頸資源,VPA 負責其他資源。比如對于 CPU 密集型的應用,使用 HPA 監聽 CPU 使用率來調整 Pods 個數,然後用 VPA 監聽其他資源(Memory、IO)來動态擴充這些資源的 Request 大小即可。當然這隻是理想情況,

總結

從前面介紹的各種 Kubernetes 排程和資源管理方案可以看出來,提高應用的資源使用率、保證應用的正常運作、維護排程和叢集的公平性是件非常複雜的事情,Kubernetes 并沒有完美的方法,而是對各種可能的問題不斷提出一些針對性的方案。

叢集的資源使用并不是靜态的,而是随着時間不斷變化的,目前 Kubernetes 的排程決策都是基于排程時叢集的一個靜态資源切片進行的,動态地資源調整是通過 Kubelet 的驅逐程式進行的,HPA 和 VPA 等方案也不斷提出,相信後面會不斷完善這方面的功能,讓 Kubernetes 更加智能。

資源管理和排程、應用優先級、監控、鏡像中心等很多東西相關,是個非常複雜的領域。在具體的實施和操作的過程中,常常要考慮到企業内部的具體情況和需求,做出針對性的調整,并且需要開發者、系統管理者、SRE、監控團隊等不同小組一起合作。但是這種付出從整體來看是值得的,提升資源的使用率能有效地節約企業的成本,也能讓應用更好地發揮出作用。

參考文檔

Kubernetes 官方文檔:

其他文檔:

來源:Cizixs Writes Here

原文:http://t.cn/ReTAKl3

題圖:來自谷歌圖檔搜尋

版權:本文版權歸原作者所有