k8s架構簡介
Kubernetes是一項容器編排技術。它是容器集群管理系統(tǒng),是一個開源的平臺,可以實現(xiàn)容器集群的自動化部署、自動擴縮容、維護等功能。
架構圖:

Kubernetes屬于主從分布式架構,主要由Master節(jié)點和Node節(jié)點組成。Master節(jié)點作為控制節(jié)點,對集群進行調度管理;Node節(jié)點作為真正的工作節(jié)點,運行容器。
Master節(jié)點
Kube-Apiserver : 所有服務訪問的統(tǒng)一入口,各組件的協(xié)調者
Kube-controller-manager : 控制器,主要維護k8s資源對象
Kube-scheduler: 調度器,為 Pod 選擇一個 Node 節(jié)點
Etcd: 分布式數(shù)據(jù)庫來儲存集群中的重要信息,比如 pod、service 等對象信息
Node節(jié)點
pod:pod是一個非常重要的概念。它是k8s的最小的服務單位,可以理解為把一組關系緊密的容器放在一起,它們共享了同一個 Network Namespace,并且可以聲明共享一個 Volume。所以,Pod其實是一組共享了某些資源的容器。
Kubelet: 主要來執(zhí)行關于資源操作的指令,負責pod的維護。比如創(chuàng)建容器、Pod掛載數(shù)據(jù)卷、獲取節(jié)點信息和狀態(tài)等工作。
Kube-proxy: 負責代理服務,在多個pod之間做負載均衡。
調度器
Scheduler 是 kubernetes 的調度器,它的主要任務是為新創(chuàng)建出來的pod尋找一個最佳node 節(jié)點,聽起來非常簡單,但有很多要考慮的問題:
- 公平:如何保證每個節(jié)點都能被分配資源
- 資源高效利用:集群資源能被最大化的使用
- 效率:調度的性能要好,能夠盡快地對大批量的pod完成調度工作
- 靈活:允許用戶根據(jù)自己的需求控制調度的邏輯
Scheduler 是作為單獨的程序運行的,它啟動之后會一直監(jiān)聽 API server ,獲取Spec.NodeName為空的 pod。調度執(zhí)行完之后,調度器會將 Pod 對象的 NodeName 字段的值,修改為 Node 的名字,表明 pod 應該放在哪個節(jié)點上,這個步驟在 k8s中稱為Bind。
調度過程
調度分為幾個部分:首先是過濾掉不滿足條件的節(jié)點,這個過程稱為predicate;然后對通過的節(jié)點按照優(yōu)先級排序,這個是priority;最后從中選出優(yōu)先級最高的節(jié)點作為最終的結果。
Predicate 有一系列算法可以使用,比如:
-
PodFitResources:節(jié)點上剩余的資源是否大于 pod 請求的資源 -
PodFitHostPorts:節(jié)點上已經使用的 port 是否和 pod 申請的 port 沖突 -
PodSelectorMatches:過濾掉和 pod 指定的 label 不匹配的節(jié)點
如果在 predicate 過程中沒有合適的節(jié)點,pod 會一直在 pending狀態(tài),不斷重試調度,直到有節(jié)點滿足條件;經過這個步驟,如果有多個節(jié)點滿足條件,就繼續(xù) priority過程。
priority 由一系列鍵值對組成,鍵是優(yōu)先級項的名稱,值是它的權重。這些優(yōu)先級包括:
-
LeastRequestedPriority:通過計算 CPU 和 Memory 的使用率來決定它的權重,換句話說就是選擇空閑資源最多的節(jié)點 -
BalancedResourceAllocation:節(jié)點上的 CPU 和 Memory 使用率越接近,權重越高 -
ImageLocalityPriority:傾向于已經有要使用鏡像的節(jié)點,鏡像總大小值越大,權重越高
調度的具體過程,如下圖

可以看到,Kubernetes 調度器的核心,實際上就是兩個相互獨立的控制循環(huán)。
第一個控制循環(huán)稱為Imformer Path,它主要目的是啟動一系列imformer,來監(jiān)聽 Etcd 中 Pod、Node、Service 等與調度相關的對象的變化。比如當一個待調度 Pod(即:它的 nodeName 字段是空的)被創(chuàng)建出來之后,調度器就會通過 Pod Informer 的 Handler,將這個待調度 Pod 添加進調度隊列。
同時,調度器的 informer 還要負責對調度器緩存( scheduler cache)進行更新。Kubernetes 調度部分進行性能優(yōu)化的一個最根本原則,就是盡最大可能將集群信息 Cache 化,以便從根本上提高 Predicate 和 Priority 調度算法的執(zhí)行效率。
第二個控制循環(huán)稱為Scheduling Path,是調度器負責 pod 調度的主循環(huán)。這部分的主要邏輯,就是不斷地從調度隊列里出隊一個 Pod。然后,調用 Predicates 算法進行“過濾”。這一步“過濾”得到的一組 Node,就是所有可以運行這個 Pod 的宿主機列表。
接下來,調度器就會再調用 Priorities 算法為上述列表里的 Node 打分,分數(shù)從 0 到 10。得分最高的 Node,就會作為這次調度的結果。
當然,上面兩個調度算法所需的 node 數(shù)據(jù),都是從 Scheduler Cache 里直接拿到的,這是調度器保證算法執(zhí)行效率的主要手段。
算法執(zhí)行完后,調度器會執(zhí)行 Bind 操作,將 Pod 對象的 nodeName 字段的值,修改為上述 Node 的名字。但是,為了不在這個關鍵調度步驟中遠程訪問 API server,在 Bind 階段,調度器只會更新 Scheduler Cache 里的 Pod 和 Node 信息。這種基于“樂觀”假設的 API 對象更新方式,在 Kubernetes 里被稱作** Assume**。
等 Assume 之后,調度器才會創(chuàng)建一個 Goroutine 來異步向 API server 發(fā)起更新 Pod 的請求,來完成真正的 Bind 操作。對應節(jié)點的 kubelet 會進行一個 Admit 的操作,再次確認該 pod 能否運行在該節(jié)點上。
除了上面所說的,k8s 調度器還有一個重要設計,那就是無鎖化。
在 Scheduling Path 上,調度器會啟動多個 Goroutine 并發(fā)執(zhí)行 Predicates 算法,從而提高這一階段的執(zhí)行效率。而與之類似的,Priorities 算法也會以 MapReduce 的方式并行計算然后再進行匯總。而在這些所有需要并發(fā)的路徑上,調度器會避免設置任何全局的競爭資源,從而免去了使用鎖進行同步帶來的巨大的性能損耗。
總結一下,上面介紹了調度的具體過程,以及提升調度效率的三個方法:Cache化、樂觀假設、無鎖化。
親和性調度
節(jié)點親和性
pod.spec.nodeAffinity分為軟策略和硬策略
-
preferredDuringSchedulingIgnoredDuringExecution:軟策略 -
requredDuringSchedulingIgnoredDuringExecution:硬策略
當硬親和性規(guī)則不滿足時,Pod會置于Pending狀態(tài),軟親和性規(guī)則不滿足時,會選擇一個不匹配的節(jié)點。
當節(jié)點標簽改變而不再符合此節(jié)點親和性規(guī)則時,不會將Pod從該節(jié)點移出,僅對新建的Pod對象生效。
對于軟策略,會使用權重 weight 定義優(yōu)先級,1~100 值越大優(yōu)先級越高。
apiVersion: v1
kind: Pod
metadata:
name: node-affinity
namespace: test
spec:
containers:
- name: nginx-pod
image: nginx:latest
affinity:
# 節(jié)點親和
nodeAffinity:
# 硬策略,條件必須成立
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
# 要調度到node的標簽是kubernetes.io/hostname=node01的節(jié)點上
- matchExpressions:
- key: kubernetes.io/hostname
operator: NotIn
values:
- node01
# 軟策略,條件盡量要成立
preferredDuringSchedulingIgnoredDuringExecution:
# 多個軟策略的權重,范圍在1-100內,越大計算的得分越高
- weight: 1
preference:
matchExpressions:
# 要調度到node的標簽是area=beijing的節(jié)點上
- key: area
operator: NotIn
values:
- beijing
鍵值運算關系:
- In:label 的值在某個列表中
- NotIn:label 的值不在某個列表中
- Gt:label 的值大于某個值
- Lt:label 的值小于某個值
- Exists:某個 label 存在
- DoesNotExist:某個 label 不存在
pod親和性
pod 親和性主要解決 pod 可以和哪些 pod 部署在同一個拓撲域中的問題(其中拓撲域用主機標簽實現(xiàn),可以是單個主機,也可以是多個主機組成的 cluster、zone 等等),而 pod 反親和性主要是解決 pod 不能和哪些 pod 部署在同一個拓撲域中的問題,它們都是處理的 pod 與 pod 之間的關系。
同理,pod.spec.affinity.podAffinity/podAntiAffinity也分為軟策略和硬策略。
舉例:
apiVersion: v1
kind: Pod
metadata:
name: pod-affinity
namespace: test
spec:
containers:
- name: nginx-pod
image: nginx:latest
affinity:
# pod親和 要求與指定的pod在同一個拓撲域
podAffinity:
# 硬策略
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx01
# 拓撲域(node上的標簽key)
topologyKey: kubernetes.io/hostname
# pod親和 要求與指定的pod不在同一個拓撲域
podAntiAffinity:
# 軟策略
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx02
# 拓撲域(node上的標簽key)
topologyKey: kubernetes.io/hostname
污點和容忍度
上面說的節(jié)點親和性,可以理解為 pod 的一種偏好(或者是硬性的)屬性,它使得 pod 被吸引到一類特定的節(jié)點。Taint(污點)則相反,它使得節(jié)點能排斥一類特定的 pod。
Taint 和 Toleration 相互配合,可以用來避免 pod 被分配到不合適的節(jié)點上。每個節(jié)點都可以有一個或多個污點,而對于不能容忍這些污點的pod,是不會被該節(jié)點接受的。如果將 Toleration 應用于 pod 上,則表示這些 pod 可以(但不要求)被調度到具有匹配 taint 的節(jié)點上。
污點(Taint)
通過kubctl taint命令可以給某一個node節(jié)點設置污點,node上設置了污點之后,pod可以拒絕 node 的調度,甚至可以將node上已經存在的pod驅逐出去。
污點可以用于集群節(jié)點遷移準備工作,通過打污點來使當前節(jié)點上的pod遷移出去。k8s 的master節(jié)點自帶污點。
污點的組成為:
key = value : effect
每個污點有一個 key 和 value 作為污點的標簽,其中 value 可以為空,effect 描述污點的作用。當前 taint 的 effect 支持如下三個選項:
-
NoSchedule:k8s不會把pod調度到該節(jié)點上 -
PreferNoSchedule:k8s盡量不會把pod調度到該節(jié)點上 -
NoExecute:k8s不會把pod調度到該節(jié)點上,同時會把已有節(jié)點驅逐出去
# 設置污點
kubectl taint nodes node1 key1=value:NoSchedule
# 去除污點
Kubectl taint nodes node1 key1:NoSchedule-
容忍(Toleration)
pod 可以設置容忍,即使 node 有污點,也可以分配。
pod.spec.toleration:
tolerations:
- key: "key1"
operator: " Equal"
value: "value1"
effect: "NoSchedule"
tolerationSeconds: 3600
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoExecute"
- key: "key2"
operator: "Exists"
effect: "NoSchedule"
- 其中key,vaule,effect要與Node中設置的taint保持一致
- tolerationSeconds 用于描述當 Pod 需要被驅逐時可以在 Pod 上繼續(xù)保留運行的時間
k8s的namespace可以提供資源的邏輯隔離,但是無法提供物理隔離。物理隔離可以通過污點與容忍來實現(xiàn)。
比如想隔離不同產品線的服務,可以提前給 node 打上不同的污點,不同的產品線的pod容忍對應的污點即可。
調度器的可擴展設計
默認調度器的可擴展機制,在 Kubernetes 里面叫作 Scheduler Framework。這個設計的主要目的,就是在調度器生命周期的各個關鍵點上,為用戶暴露出可以進行擴展和實現(xiàn)的接口,從而實現(xiàn)由用戶自定義調度器的能力。

每一個綠色的箭頭都是一個可以插入自定義邏輯的接口.比如,上面的 Queue 部分,就意味著你可以在這一部分提供一個自己的調度隊列的實現(xiàn),從而控制每個Pod 開始被調度(出隊)的時機。
Predicates部分,則意味著你可以提供自己的過濾算法實現(xiàn),根據(jù)自己的需求,來決定選擇哪些機器
上述這些可插拔式邏輯,都是標準的Go語言插件機制(Go plugin 機制),也就是說,你需要在編譯的時候選擇把哪些插件編譯進去。
總結
以上主要介紹了 k8s 集群調度的主要流程、影響調度器調度的幾個因素,以及自定義調度器的可拓展設計。