目錄
一、Pod啟動典型創建過程
二、調度流程
三、指定調度節點
1.使用nodeName字段指定調度節點
2.使用nodeSelector指定調度節點
2.1給對應的node節點添加標簽
2.2修改為nodeSelector調度方式
3.通過親和性來指定調度節點
3.1節點親和性
3.2Pod親和性與反親和性
3.2.1使用Pod親和性調度
?3.2.2使用Pod反親和性調度
4.使用污點(Taint) 和 容忍(Tolerations)指定調度節點
4.1污點(Taint)?
4.2容忍(Tolerations)
四、cordon 和 drain
五、Pod詳解
1.Pod啟動階段(相位 phase)
2.?Pod常見狀態
3.?如何刪除 Unknown 狀態的 Pod ?
4.?故障排除步驟
一、Pod啟動典型創建過程
Kubernetes 是通過 List-Watch 的機制進行每個組件的協作,保持數據同步的,每個組件之間的設計實現了解耦。
用戶是通過 kubectl 根據配置文件,向 APIServer 發送命令,在 Node 節點上面建立 Pod 和 Container。
APIServer 經過 API 調用,權限控制,調用資源和存儲資源的過程,實際上還沒有真正開始部署應用。這里?? ?需要 Controller Manager、Scheduler 和 kubelet 的協助才能完成整個部署過程。
在 Kubernetes 中,所有部署的信息都會寫到 etcd 中保存。實際上 etcd 在存儲部署信息的時候,會發送 Create 事件給 APIServer,而 APIServer 會通過監聽(Watch)etcd 發過來的事件。其他組件也會監聽(Watch)APIServer 發出來的事件。
Pod 是 Kubernetes 的基礎單元,Pod 啟動典型創建過程如下:
- 這里有三個 List-Watch,分別是 Controller Manager(運行在 Master),Scheduler(運行在 Master),kubelet(運行在 Node)。 他們在進程已啟動就會監聽(Watch)APIServer 發出來的事件。
- 用戶通過 kubectl 或其他 API 客戶端提交請求給 APIServer 來建立一個 Pod 對象副本。
- APIServer 嘗試著將 Pod 對象的相關元信息存入 etcd 中,待寫入操作執行完成,APIServer 即會返回確認信息至客戶端。
- 當 etcd 接受創建 Pod 信息以后,會發送一個 Create 事件給 APIServer。
- 由于 Controller Manager 一直在監聽(Watch,通過https的6443端口)APIServer 中的事件。此時 APIServer 接受到了 Create 事件,又會發送給 Controller Manager。
- Controller Manager 在接到 Create 事件以后,調用其中的 Replication Controller 來保證 Node 上面需要創建的副本數量。一旦副本數量少于 RC 中定義的數量,RC 會自動創建副本。總之它是保證副本數量的 Controller(PS:擴容縮容的擔當)。
- 在 Controller Manager 創建 Pod 副本以后,APIServer 會在 etcd 中記錄這個 Pod 的詳細信息。例如 Pod 的副本數,Container 的內容是什么。
- 同樣的 etcd 會將創建 Pod 的信息通過事件發送給 APIServer。
- 由于 Scheduler 在監聽(Watch)APIServer,并且它在系統中起到了“承上啟下”的作用,“承上”是指它負責接收創建的 Pod 事件,為其安排 Node;“啟下”是指安置工作完成后,Node 上的 kubelet 進程會接管后繼工作,負責 Pod 生命周期中的“下半生”。 換句話說,Scheduler 的作用是將待調度的 Pod 按照調度算法和策略綁定到集群中 Node 上。
- Scheduler 調度完畢以后會更新 Pod 的信息,此時的信息更加豐富了。除了知道 Pod 的副本數量,副本內容。還知道部署到哪個 Node 上面了。并將上面的 Pod 信息更新至 API Server,由 APIServer 更新至 etcd 中,保存起來。
- etcd 將更新成功的事件發送給 APIServer,APIServer 也開始反映此 Pod 對象的調度結果。
- kubelet 是在 Node 上面運行的進程,它也通過 List-Watch 的方式監聽(Watch,通過https的6443端口)APIServer 發送的 Pod 更新的事件。kubelet 會嘗試在當前節點上調用 Docker 啟動容器,并將 Pod 以及容器的結果狀態回送至 APIServer。
- APIServer 將 Pod 狀態信息存入 etcd 中。在 etcd 確認寫入操作成功完成后,APIServer將確認信息發送至相關的 kubelet,事件將通過它被接受。
#注意:在創建 Pod 的工作就已經完成了后,為什么 kubelet 還要一直監聽呢?原因很簡單,假設這個時候 kubectl 發命令,要擴充 Pod 副本數量,那么上面的流程又會觸發一遍,kubelet 會根據最新的 Pod 的部署情況調整 Node 的資源。又或者 Pod 副本數量沒有發生變化,但是其中的鏡像文件升級了,kubelet 也會自動獲取最新的鏡像文件并且加載。
二、調度流程
Scheduler 是 kubernetes 的調度器,主要的任務是把定義的 pod 分配到集群的節點上。其主要考慮的問題如下:
- 公平:如何保證每個節點都能被分配資源
- 資源高效利用:集群所有資源最大化被使用
- 效率:調度的性能要好,能夠盡快地對大批量的 pod 完成調度工作
- 靈活:允許用戶根據自己的需求控制調度的邏輯
Sheduler 是作為單獨的程序運行的,啟動之后會一直監聽 APIServer,獲取 spec.nodeName 為空的 pod,對每個 pod 都會創建一個 binding,表明該 pod 應該放到哪個節點上。
調度分為幾個部分:首先是過濾掉不滿足條件的節點,這個過程稱為預算策略(predicate);然后對通過的節點按照優先級排序,這個是優選策略(priorities);最后從中選擇優先級最高的節點。如果中間任何一步驟有錯誤,就直接返回錯誤。
Predicate 有一系列的常見的算法可以使用:
- PodFitsResources:節點上剩余的資源是否大于 pod 請求的資源。
- PodFitsHost:如果 pod 指定了 NodeName,檢查節點名稱是否和 NodeName 匹配。
- PodFitsHostPorts:節點上已經使用的 port 是否和 pod 申請的 port 沖突。
- PodSelectorMatches:過濾掉和 pod 指定的 label 不匹配的節點。
- NoDiskConflict:已經 mount 的 volume 和 pod 指定的 volume 不沖突,除非它們都是只讀。
如果在 predicate 過程中沒有合適的節點,pod 會一直在 pending 狀態,不斷重試調度,直到有節點滿足條件。 經過這個步驟,如果有多個節點滿足條件,就繼續 priorities 過程:按照優先級大小對節點排序。
優先級由一系列鍵值對組成,鍵是該優先級項的名稱,值是它的權重(該項的重要性)。有一系列的常見的優先級選項包括:
- LeastRequestedPriority:通過計算CPU和Memory的使用率來決定權重,使用率越低權重越高。也就是說,這個優先級指標傾向于資源使用比例更低的節點。
- BalancedResourceAllocation:節點上 CPU 和 Memory 使用率越接近,權重越高。這個一般和上面的一起使用,不單獨使用。比如 node01 的 CPU 和 Memory 使用率 20:60,node02 的 CPU 和 Memory 使用率 50:50,雖然 node01 的總使用率比 node02 低,但 node02 的 CPU 和 Memory 使用率更接近,從而調度時會優選 node02。
- ImageLocalityPriority:傾向于已經有要使用鏡像的節點,鏡像總大小值越大,權重越高。
通過算法對所有的優先級項目和權重進行計算,得出最終的結果。
三、指定調度節點
1.使用nodeName字段指定調度節點
nodeName 將 Pod 直接調度到指定的 Node 節點上,會跳過 Scheduler 的調度策略,該匹配規則是強制匹配
apiVersion: apps/v1
kind: Deployment #創建deployment控制器
metadata:name: myapp #deployment控制器名稱:myapp
spec: #定義模板文件replicas: 3 #副本集為3個selector: #標簽選擇器matchLabels: #匹配的標簽為 app: myapp1app: myapp1template: #定義pod模板metadata:labels: #pod的標簽app: myapp1spec: nodeName: node01 #pod創建后所在的node節點名稱containers: #定義pod中的容器- name: myapp #pod容器中的名稱image: nginx #容器使用的鏡像ports: - containerPort: 80kubectl apply -f demo1.yamlkubectl get pods --show-labels -owide
#查看詳細事件(發現未經過 scheduler 調度分配)
kubectl describe pod myapp-6bc58d7775-6wlpp
2.使用nodeSelector指定調度節點
通過 kubernetes 的 label-selector 機制選擇節點,由調度器調度策略匹配 label,然后調度 Pod 到目標節點,該匹配規則屬于強制約束
2.1給對應的node節點添加標簽
kubectl label nodes node01 app=akubectl label nodes node02 app=b#查看標簽
kubectl get nodes --show-labels注:
#修改一個 label 的值,需要加上 --overwrite 參數
kubectl label nodes node02 app=a --overwrite#刪除一個 label,只需在命令行最后指定 label 的 key 名并與一個減號相連即可:
kubectl label nodes node02 app-
或
kubectl label nodes node02 app=b-#指定標簽查詢 node 節點
kubectl get node -l app=a
2.2修改為nodeSelector調度方式
vim myapp1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:name: myapp1
spec:replicas: 3selector:matchLabels:run: myapp1template:metadata:labels:run: myapp1spec:nodeSelector:app: acontainers:- name: myapp1image: nginx:1.14ports:- containerPort: 80kubectl apply -f myapp1.yaml
kubectl get pods -owide
#查看詳細事件(通過事件可以發現要先經過 scheduler 調度分配)
kubectl describe pod myapp1-7cc594f786-jfs97
3.通過親和性來指定調度節點
將 Pod 指派給節點 | Kubernetes
3.1節點親和性
pod.spec.nodeAffinity
- preferredDuringSchedulingIgnoredDuringExecution:軟策略
- requiredDuringSchedulingIgnoredDuringExecution:硬策略
注:
- 硬策略就是Pod必須要去指定條件的node節點,不去不行
- 軟策略表示Pod最優先去指定node節點,如果沒有符合條件的node節點,也可以選擇其它node節點
鍵值運算關系
- In:label 的值在某個列表中
- NotIn:label 的值不在某個列表中
- Gt:label 的值大于某個值
- Lt:label 的值小于某個值
- Exists:某個 label 存在
- DoesNotExist:某個 label 不存在?
nodeAffinity硬策略和軟策略示例
apiVersion: v1
kind: Pod
metadata:name: affinitylabels:app: node-affinity-pod
spec:containers:- name: with-node-affinityimage: soscscs/myapp:v1affinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution: #先滿足硬策略,排除有kubernetes.io/hostname=node02標簽的節點nodeSelectorTerms:- matchExpressions:- key: kubernetes.io/hostnameoperator: NotInvalues:- node02preferredDuringSchedulingIgnoredDuringExecution: #再滿足軟策略,優先選擇有app=a標簽的節點- weight: 1 #如果有多個軟策略,權重越大,優先級越大preference:matchExpressions:- key: appoperator: Invalues:- a只有硬策略的情況下,如果硬策略不滿足條件,Pod 狀態一直會處于 Pending 狀態。
如果把硬策略和軟策略合在一起使用,則要先滿足硬策略之后才會滿足軟策略
3.2Pod親和性與反親和性
pod.spec.affinity.podAffinity/podAntiAffinity
- preferredDuringSchedulingIgnoredDuringExecution:軟策略
- requiredDuringSchedulingIgnoredDuringExecution:硬策略?
調度策略 | 匹配標簽 | 操作符 | 拓撲域支持 | 調度目標 |
nodeAffinity | 主機 | In, NotIn, Exists,DoesNotExist, Gt, Lt | 否 | ?指定主機 |
podAffinity | Pod | In, NotIn, Exists,DoesNotExist | 是 | Pod與指定Pod同一拓撲域 |
podAntiAffinity | Pod | In, NotIn, Exists,DoesNotExist | 是 | Pod與指定Pod不在同一拓撲域 |
3.2.1使用Pod親和性調度
apiVersion: v1
kind: Pod
metadata:name: myapplabels: #定義該Pod的標簽app: myapp
spec:containers:- name: myapp01image: nginxaffinity:podAffinity: #定義pod親和性requiredDuringSchedulingIgnoredDuringExecution: #pod親和性的硬策略- labelSelector: #定義硬策略標簽選擇器的信息matchExpressions: #定義標簽選擇器選擇的信息- key: app #定義鍵名operator: In #定義鍵值之間的運算關系values: #定義鍵值- myapptopologyKey: run #定義節點標簽的鍵,判斷是否在同一拓撲域中topologykey: run表示如果node節點都包含有標簽為run=鍵值,
當node節點的鍵值相同時,則表示node節點在同一拓撲域中;
當run鍵的值不相同時,則表示node節點不在同一拓撲域中。
注:
- 僅當節點上至少包含一個已運行且 app=myapp 的標簽 的 Pod 處于拓撲域 a 時,才可以將該 Pod 調度到拓撲域 a 上的節點。 (更確切的說,新建pod分配node節點,會選擇有標簽為app=myapp的pod運行的node節點或該node節點所處拓撲域上其它node節點。)
- topologyKey 是節點標簽的鍵。如果兩個節點使用此鍵標記并且具有相同的標簽值,則調度器會將這兩個節點視為處于同一拓撲域中。 調度器試圖在每個拓撲域中放置數量均衡的 Pod。
- 如果 app 對應的值不一樣就是不同的拓撲域。比如 Pod1 在 app=a?的 Node 上,Pod2 在 app=b?的 Node 上,Pod3 在 app=a?的 Node 上,則 Pod2 和 Pod1、Pod3 不在同一個拓撲域,而Pod1 和 Pod3在同一個拓撲域。
?3.2.2使用Pod反親和性調度
apiVersion: v1
kind: Pod
metadata:name: myapplabels:app: myapp
spec:containers:- name: myappimage: nginxaffinity:podAntiAffinity:requiredDuringSchedulingIgnoredDuringExecution:- labelSelector:matchExpressions:- key: appoperator: Invalues:- atopologyKey: run如果節點處于 Pod 所在的同一拓撲域且具有鍵“app”和值“a”的標簽, 則該 pod 不能將其調度到該節點上。
(如果 topologyKey 為 run,則意味著當節點和具有鍵 “app”和值“a”的 Pod 處于相同的拓撲域,Pod 不能被調度到該節點上。)
4.使用污點(Taint) 和 容忍(Tolerations)指定調度節點
4.1污點(Taint)?
節點親和性,是Pod的一種屬性(偏好或硬性要求),它使Pod被吸引到一類特定的節點。Taint 則相反,它使節點能夠排斥一類特定的 Pod。
Taint 和 Toleration 相互配合,可以用來避免 Pod 被分配到不合適的節點上。每個節點上都可以應用一個或多個 taint ,這表示對于那些不能容忍這些 taint 的 Pod,是不會被該節點接受的。如果將 toleration 應用于 Pod 上,則表示這些 Pod 可以(但不一定)被調度到具有匹配 taint 的節點上。
使用 kubectl taint 命令可以給某個 Node 節點設置污點,Node 被設置上污點之后就和 Pod 之間存在了一種相斥的關系,可以讓 Node 拒絕 Pod 的調度執行,甚至將 Node 已經存在的 Pod 驅逐出去。污點的組成格式如下:
key=value:effect每個污點有一個 key 和 value 作為污點的標簽,其中 value 可以為空,effect 描述污點的作用。當前 taint effect 支持如下三個選項:
●NoSchedule:表示 k8s 將不會將 Pod 調度到具有該污點的 Node 上
●PreferNoSchedule:表示 k8s 將盡量避免將 Pod 調度到具有該污點的 Node 上
●NoExecute:表示 k8s 將不會將 Pod 調度到具有該污點的 Node 上,同時會將 Node 上已經存在的 Pod 驅逐出去#設置污點
kubectl taint node node01 key1=value1:NoSchedule#節點說明中,查找 Taints 字段
kubectl describe node node01 #去除污點
kubectl taint node node01 key1:NoSchedule-
4.2容忍(Tolerations)
設置了污點的 Node 將根據 taint 的 effect:NoSchedule、PreferNoSchedule、NoExecute 和 Pod 之間產生互斥的關系,Pod 將在一定程度上不會被調度到 Node 上。但我們可以在 Pod 上設置容忍(Tolerations),意思是設置了容忍的 Pod 將可以容忍污點的存在,可以被調度到存在污點的 Node 上。
apiVersion: v1
kind: Pod
metadata:name: myapp01labels:app: myapp01
spec:containers:- name: with-node-affinityimage: soscscs/myapp:v1tolerations:- key: "check" #定義污點鍵名operator: "Equal" value: "mycheck" #定義污點鍵值effect: "NoExecute" #定義污點的作用tolerationSeconds: 3600#其中的 key、vaule、effect 都要與 Node 上設置的 taint 保持一致
#operator 的值為 Exists 將會忽略 value 值,即存在即可
#tolerationSeconds 用于描述當 Pod 需要被驅逐時可以在 Node 上繼續保留運行的時間
注意事項:
(1)當不指定 key 值時,表示容忍所有的污點 keytolerations:- operator: "Exists"(2)當不指定 effect 值時,表示容忍所有的污點作用tolerations:- key: "key"operator: "Exists"(3)有多個 Master 存在時,防止資源浪費,可以如下設置
kubectl taint node Master-Name node-role.kubernetes.io/master=:PreferNoSchedule//如果某個 Node 更新升級系統組件,為了防止業務長時間中斷,可以先在該 Node 設置 NoExecute 污點,把該 Node 上的 Pod 都驅逐出去
kubectl taint node node01 check=mycheck:NoExecute//此時如果別的 Node 資源不夠用,可臨時給 Master 設置 PreferNoSchedule 污點,讓 Pod 可在 Master 上臨時創建
kubectl taint node master node-role.kubernetes.io/master=:PreferNoSchedule//待所有 Node 的更新操作都完成后,再去除污點
kubectl taint node node01 check=mycheck:NoExecute-
四、cordon 和 drain
對節點執行維護操作:
kubectl get nodes//將 Node 標記為不可調度的狀態,這樣就不會讓新創建的 Pod 在此 Node 上運行
kubectl cordon <NODE_NAME> #該node將會變為SchedulingDisabled狀態//kubectl drain 可以讓 Node 節點開始釋放所有 pod,并且不接收新的 pod 進程。drain 本意排水,意思是將出問題的 Node 下的 Pod 轉移到其它 Node 下運行
kubectl drain <NODE_NAME> --ignore-daemonsets --delete-emptydir-data --force--ignore-daemonsets:無視 DaemonSet 管理下的 Pod。
--delete-emptydir-data:如果有 mount local volume 的 pod,會強制殺掉該 pod。
--force:強制釋放不是控制器管理的 Pod。注:執行 drain 命令,會自動做了兩件事情:
(1)設定此 node 為不可調度狀態(cordon)
(2)evict(驅逐)了 Pod//kubectl uncordon 將 Node 標記為可調度的狀態
kubectl uncordon <NODE_NAME>
五、Pod詳解
1.Pod啟動階段(相位 phase)
Pod 創建完之后,一直到持久運行起來,中間有很多步驟,也就有很多出錯的可能,因此會有很多不同的狀態。
一般來說,pod 這個過程包含以下幾個步驟:
- 調度到某臺 node 上。kubernetes 根據一定的優先級算法選擇一臺 node 節點將其作為 Pod 運行的 node
- 拉取鏡像
- 掛載存儲配置等
- 容器運行起來。如果有健康檢查,會根據檢查的結果來設置其狀態。
2.?Pod常見狀態
- Pending:表示APIServer創建了Pod資源對象并已經存入了etcd中,但是它并未被調度完成(比如還沒有調度到某臺node上),或者仍然處于從倉庫下載鏡像的過程中。
- Running:Pod已經被調度到某節點之上,并且Pod中所有容器都已經被kubelet創建。至少有一個容器正在運行,或者正處于啟動或者重啟狀態(也就是說Running狀態下的Pod不一定能被正常訪問)。
- Succeeded:有些pod不是長久運行的,比如job、cronjob,一段時間后Pod中的所有容器都被成功終止,并且不會再重啟。需要反饋任務執行的結果。
- Failed:Pod中的所有容器都已終止了,并且至少有一個容器是因為失敗終止。也就是說,容器以非0狀態退出或者被系統終止,比如 command 寫的有問題。
- Unknown:表示無法讀取 Pod 狀態,通常是 kube-controller-manager 無法與 Pod 通信。Pod 所在的 Node 出了問題或失聯,從而導致 Pod 的狀態為 Unknow
3.?如何刪除 Unknown 狀態的 Pod ?
- 從集群中刪除有問題的 Node。使用公有云時,kube-controller-manager 會在 VM 刪除后自動刪除對應的 Node。 而在物理機部署的集群中,需要管理員手動刪除 Node(kubectl delete node <node_name>)。
- 被動等待 Node 恢復正常,Kubelet 會重新跟 kube-apiserver 通信確認這些 Pod 的期待狀態,進而再決定刪除或者繼續運行這些 Pod。
- 主動刪除 Pod,通過執行 kubectl delete pod <pod_name> --grace-period=0 --force 強制刪除 Pod。但是這里需要注意的是,除非明確知道 Pod 的確處于停止狀態(比如 Node 所在 VM 或物理機已經關機),否則不建議使用該方法。特別是 StatefulSet 管理的 Pod,強制刪除容易導致腦裂或者數據丟失等問題。
4.?故障排除步驟
//查看Pod事件
kubectl describe TYPE NAME_PREFIX //查看Pod日志(Failed狀態下)
kubectl logs <POD_NAME> [-c Container_NAME]//進入Pod(狀態為running,但是服務沒有提供)
kubectl exec –it <POD_NAME> bash//查看集群信息
kubectl get nodes//發現集群狀態正常
kubectl cluster-info//查看kubelet日志發現
journalctl -xefu kubelet