節(jié)點(diǎn):調(diào)度機(jī)制、實(shí)操與Pending排查全解析)
把 Kuboard 里跑著的服務(wù)從一臺(tái)節(jié)點(diǎn)遷到另一臺(tái)節(jié)點(diǎn)聽起來(lái)就是在界面上改個(gè)選項(xiàng)的事實(shí)際做起來(lái)卻有一堆隱藏的坑。我對(duì)這件事的印象特別深是因?yàn)橛幸淮尉€上中間件集群磁盤告警我以為加個(gè)節(jié)點(diǎn)名就能搞定結(jié)果部署改完以后工作負(fù)載一直 Pending業(yè)務(wù)請(qǐng)求直接飄紅。后來(lái)把節(jié)點(diǎn)選擇、親和性、容忍這些調(diào)度機(jī)制重新捋了一遍才真正搞懂 Kuboard 里那個(gè)調(diào)度選項(xiàng)背后到底在干什么。下面就把 Kuboard 里更改服務(wù)運(yùn)行節(jié)點(diǎn)的幾種方式講清楚包括每種方式背后的調(diào)度邏輯、表單和 YAML 怎么填、改完以后怎么驗(yàn)證以及 Pending 之后從哪下手排查。適合正在用 Kuboard 管理 Kubernetes 集群的運(yùn)維同學(xué)也適合剛接觸容器調(diào)度、只知道概念還沒(méi)實(shí)操過(guò)的新手。1. 服務(wù)運(yùn)行節(jié)點(diǎn)是怎么被決定的先理解調(diào)度這張網(wǎng)不先把調(diào)度機(jī)制的大框架搭起來(lái)光在 Kuboard 里點(diǎn)來(lái)點(diǎn)去出了問(wèn)題很容易蒙圈。Kubernetes 默認(rèn)會(huì)把 Pod 調(diào)度到某個(gè)節(jié)點(diǎn)上這個(gè)決定由控制面里的調(diào)度器做出。調(diào)度器會(huì)綜合每個(gè)節(jié)點(diǎn)的資源余量、污點(diǎn)設(shè)置、Pod 自身的親和性要求等一系列條件給可用節(jié)點(diǎn)打分選一個(gè)最合適的。咱們?cè)?Kuboard 里所見即所得的“改節(jié)點(diǎn)”本質(zhì)上就是在修改 Pod 的調(diào)度條件讓調(diào)度器只在這些條件下選節(jié)點(diǎn)。Kubernetes 里控制“Pod 跑到哪個(gè)節(jié)點(diǎn)”的機(jī)制主要有四類nodeName、nodeSelector、節(jié)點(diǎn)親和性、污點(diǎn)與容忍。它們的力度和適用場(chǎng)景完全不同。方式作用對(duì)象力度典型場(chǎng)景nodeName直接指定某臺(tái)節(jié)點(diǎn)強(qiáng)制執(zhí)行繞過(guò)調(diào)度器緊急遷移、排查單節(jié)點(diǎn)問(wèn)題nodeSelector通過(guò)節(jié)點(diǎn)標(biāo)簽匹配一組節(jié)點(diǎn)強(qiáng)制只有滿足標(biāo)簽的節(jié)點(diǎn)可用按環(huán)境、機(jī)型、磁盤類型分組調(diào)度節(jié)點(diǎn)親和性通過(guò)標(biāo)簽表達(dá)式匹配節(jié)點(diǎn)支持強(qiáng)制和偏好兩種復(fù)雜條件組合、軟性優(yōu)先調(diào)度污點(diǎn)與容忍節(jié)點(diǎn)排斥 PodPod 聲明容忍節(jié)點(diǎn)主動(dòng)拒絕獨(dú)占節(jié)點(diǎn)、隔離特殊工作負(fù)載用一個(gè)生活類比的例子默認(rèn)調(diào)度器就像一個(gè)網(wǎng)約車平臺(tái)它幫你自動(dòng)派單選一輛離你近、價(jià)格合適的車。但如果你備注“不要拼車必須 SUV”平臺(tái)就只能在符合條件的那部分車?yán)镞x。nodeSelector、親和性、容忍就是這類備注而 nodeName 相當(dāng)于你直接指定車牌號(hào)讓平臺(tái)不要管其他條件必須派這輛車。直接指定車牌號(hào)的限制當(dāng)然最死平時(shí)盡量別用。還有一個(gè)一定要建立的概念對(duì)于 Deployment、StatefulSet 這類工作負(fù)載節(jié)點(diǎn)選擇是寫在 Pod 模板template里的。也就是說(shuō)Kuboard 里的“改節(jié)點(diǎn)”并不是直接把某個(gè)正在運(yùn)行的 Pod 從一臺(tái)機(jī)器挪到另一臺(tái)而是修改了模板然后控制器會(huì)按照這個(gè)新模板去創(chuàng)建新的 Pod替換掉舊的。這也是為什么改完以后會(huì)看到 Pod 重啟一遍這是正?,F(xiàn)象不是故障。2. Kuboard 里改節(jié)點(diǎn)界面上你該認(rèn)識(shí)的四個(gè)配置入口在 Kuboard 的工作負(fù)載編輯頁(yè)面里不同小版本的菜單位置和命名會(huì)有一點(diǎn)差別有的版本叫“調(diào)度策略”有的版本在“高級(jí)設(shè)置”里還有的版本直接放在 YAML 編輯模式里。但不管界面長(zhǎng)什么樣核心的配置入口就是下面幾個(gè)。2.1 節(jié)點(diǎn)選擇nodeSelector最基礎(chǔ)、最常用Kuboard 表單里通常會(huì)提供一個(gè)“節(jié)點(diǎn)選擇”或“NodeSelector”的鍵值對(duì)列表。你填的每一對(duì)鍵值都會(huì)被寫入 Pod 模板的 nodeSelector 字段。調(diào)度器會(huì)忽略所有不含這些標(biāo)簽的節(jié)點(diǎn)只在你指定的那一批節(jié)點(diǎn)里挑。例如想讓服務(wù)只跑在擁有diskssd標(biāo)簽的節(jié)點(diǎn)上表單里就填diskssd對(duì)應(yīng)到 YAML 里是這個(gè)樣子spec: template: spec: nodeSelector: disk: ssd使用 nodeSelector 的關(guān)鍵是節(jié)點(diǎn)標(biāo)簽必須完全匹配鍵和值缺一不可。如果節(jié)點(diǎn)上只有diskssd而沒(méi)有envprod但你同時(shí)填了兩組鍵值那這個(gè) Pod 照樣會(huì) Pending。2.2 節(jié)點(diǎn)親和性支持 In、NotIn、Exists 等更復(fù)雜的條件當(dāng)你的條件不是簡(jiǎn)單的一組鍵值對(duì)而是“我要 env 為 prod 或 staging 的節(jié)點(diǎn)但不要 disk 為 hdd 的”nodeSelector 就不夠用了。這時(shí)候要用節(jié)點(diǎn)親和性nodeAffinity。Kuboard 的調(diào)度策略區(qū)域通常能看到“節(jié)點(diǎn)親和性”的添加按鈕點(diǎn)開后可以填寫 requiredDuringSchedulingIgnoredDuringExecution 和 preferredDuringSchedulingIgnoredDuringExecution 兩類規(guī)則。前者是硬性要求不滿足就沒(méi)得商量后者是軟性偏好實(shí)在沒(méi)有符合條件的節(jié)點(diǎn)調(diào)度器也會(huì)往其他節(jié)點(diǎn)放。常見的強(qiáng)制表達(dá)式配置是affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: env operator: In values: - prod - staging這段 YAML 的意思是節(jié)點(diǎn)必須帶有一個(gè)名為 env 的標(biāo)簽且標(biāo)簽值必須是 prod 或 staging。Kuboard 表單里可能做不到這么細(xì)的表達(dá)式像 operator 的取值、多個(gè)表達(dá)式之間的與或關(guān)系很多場(chǎng)景下直接切到 YAML 編輯模式會(huì)更順手。2.3 容忍Toleration一定要和污點(diǎn)一起看節(jié)點(diǎn)親和性與 nodeSelector 是從 Pod 的角度表達(dá)“我想去哪些節(jié)點(diǎn)”而污點(diǎn)是從節(jié)點(diǎn)的角度表達(dá)“我不歡迎哪些 Pod”。如果某個(gè)節(jié)點(diǎn)被打上了污點(diǎn)默認(rèn)情況下所有不聲明容忍的 Pod 都不會(huì)被調(diào)度上去。常見的單機(jī)獨(dú)享場(chǎng)景是這么用的給節(jié)點(diǎn)打一個(gè)dedicateddb:NoSchedule的污點(diǎn)這時(shí)候只有聲明了對(duì)這個(gè)污點(diǎn)有容忍的 Pod 才能調(diào)度上去相當(dāng)于給數(shù)據(jù)庫(kù)服務(wù)劃了一塊專屬地盤。在 Kuboard 表單里添加容忍時(shí)一般需要填 key、value、operator 和 effect。如果只需要匹配 key 而不關(guān)心值可以選 Exists 操作符并且不填 value。對(duì)應(yīng) YAMLtolerations: - key: dedicated operator: Equal value: db effect: NoSchedule2.4 nodeName在 YAML 編輯模式里直接指定單節(jié)點(diǎn)nodeName 屬于最暴力的強(qiáng)制指定調(diào)度器會(huì)直接忽略其他條件。Kuboard 表單里通常不會(huì)專門給這個(gè)字段留一個(gè)輸入框但你可以在 YAML 編輯模式下直接加到 Pod 模板里spec: template: spec: nodeName: node-02我對(duì) nodeName 的態(tài)度一直比較謹(jǐn)慎。它適合排障或者臨時(shí)遷移時(shí)快速定位但不適合作為長(zhǎng)期調(diào)度策略。一旦這臺(tái)節(jié)點(diǎn)宕機(jī)或者維護(hù)服務(wù)既不會(huì)自動(dòng)遷移到別的節(jié)點(diǎn)也沒(méi)法換一臺(tái)機(jī)器所有 Pod 都會(huì)卡在 Pending 狀態(tài)。除非你能明確承擔(dān)這個(gè)后果否則不要在生產(chǎn)環(huán)境長(zhǎng)期用。3. 實(shí)操把服務(wù)從磁盤告警節(jié)點(diǎn)遷移到目標(biāo)節(jié)點(diǎn)前面原理說(shuō)了一大堆現(xiàn)在進(jìn)入完整的實(shí)操環(huán)節(jié)。我把一個(gè)真實(shí)的遷移場(chǎng)景拆開帶你一步步走完順帶把過(guò)程中該看的、該想的地方都指出來(lái)。場(chǎng)景是這樣的某公司的一套 Kubernetes 集群有四臺(tái)節(jié)點(diǎn)分別叫 node-01、node-02、node-03、node-04。其中 node-01 最近磁盤使用率到了 90% 以上上面跑著一個(gè)名叫 order-service 的微服務(wù)日志和本地緩存都在這臺(tái)機(jī)器上磁盤告警以后 IO 明顯變慢。運(yùn)維打算把這個(gè)服務(wù)遷移到 node-02node-02 的標(biāo)簽比較干凈而且之前就規(guī)劃了envprod和diskssd。3.1 先確認(rèn)目標(biāo)節(jié)點(diǎn)的標(biāo)簽不要憑想象直接填標(biāo)簽先看目標(biāo)節(jié)點(diǎn)到底有什么標(biāo)簽。在 Kuboard 的集群節(jié)點(diǎn)頁(yè)面可以看到每個(gè)節(jié)點(diǎn)的 Labels也可以直接用 kubectlkubectl get nodes node-02 --show-labels假設(shè)輸出里有下面這兩行標(biāo)簽diskssd envprod那我們的 nodeSelector 就有據(jù)可依了。如果目標(biāo)節(jié)點(diǎn)連一個(gè)規(guī)劃好的標(biāo)簽都沒(méi)有那就先給節(jié)點(diǎn)補(bǔ)標(biāo)簽補(bǔ)完再繼續(xù)。在 Kuboard 里可以打開節(jié)點(diǎn)詳情在標(biāo)簽區(qū)域直接添加kubectl 的方法是kubectl label nodes node-02 diskssd envprod這一步不要跳過(guò)去。我在實(shí)際操練中見過(guò)太多人直接在界面里填disk: ssd但節(jié)點(diǎn)上標(biāo)簽叫disk-type: sata結(jié)果 Pod 就永遠(yuǎn) Pending 了。一切以節(jié)點(diǎn)上的真實(shí)標(biāo)簽為準(zhǔn)。3.2 檢查當(dāng)前 Pod 跑在哪個(gè)節(jié)點(diǎn)改之前至少要留個(gè)底知道現(xiàn)在服務(wù)跑在哪、Pod 名是什么。用 kubectl 看kubectl get pods -n production -l apporder-service -o wide-o wide會(huì)多顯示一列 NODE記錄下舊 Pod 所在節(jié)點(diǎn)。這一步主要是為了遷移完成以后做對(duì)比也方便回滾。3.3 在 Kuboard 中修改工作負(fù)載的調(diào)度策略進(jìn)入 Kuboard找到 production 命名空間下面的 order-service 工作負(fù)載點(diǎn)“編輯”。在調(diào)度策略區(qū)域選擇“節(jié)點(diǎn)選擇”添加兩組鍵值env proddisk ssd如果界面上沒(méi)有明顯的節(jié)點(diǎn)選擇區(qū)塊就切到 YAML 編輯模式。Kuboard 的 YAML 編輯器會(huì)把工作負(fù)載的完整定義顯示出來(lái)找到spec.template.spec這一層添加nodeSelector: env: prod disk: ssd保存后 Kuboard 會(huì)提交這次更改Deployment 控制器檢測(cè)到 Pod 模板發(fā)生了變化就會(huì)觸發(fā)滾動(dòng)更新創(chuàng)建新的 ReplicaSet。3.4 調(diào)整更新策略盡量別中斷服務(wù)如果 order-service 有多個(gè)副本先看一眼工作負(fù)載的更新策略。在 Kuboard 的編輯頁(yè)面里找到“更新策略”或strategy相關(guān)配置推薦把滾動(dòng)更新參數(shù)設(shè)置成strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0解釋一下這兩個(gè)參數(shù)。maxSurge 表示滾動(dòng)更新時(shí)允許超出期望副本數(shù)多少個(gè) PodmaxUnavailable 表示允許有多少個(gè) Pod 暫時(shí)不可用。maxSurge1, maxUnavailable0的含義是先多建一個(gè) Pod等新的 Pod 就緒以后再關(guān)掉一個(gè)舊 Pod。這樣服務(wù)在整個(gè)遷移過(guò)程中始終有可用副本是線上業(yè)務(wù)最穩(wěn)妥的組合。但如果這個(gè)服務(wù)只有 1 個(gè)副本那 maxUnavailable0 會(huì)有一個(gè)問(wèn)題新 Pod 必須成功起來(lái)舊的才會(huì)被回收而新 Pod 需要空閑資源。如果集群資源已經(jīng)被占滿新 Pod 會(huì)一直 Pending滾動(dòng)更新就卡住了。對(duì)單副本服務(wù)比較實(shí)際的做法有兩種要么先臨時(shí)把副本數(shù)提到 2遷移完再縮回 1要么接受短暫的不可用設(shè)置 maxUnavailable1允許先關(guān)舊 Pod 再開新 Pod。我在遷移核心服務(wù)的時(shí)候如果資源充足一定先用前者。3.5 保存并觀察滾動(dòng)更新點(diǎn)擊保存以后不要看一眼界面就離開。Kuboard 的工作負(fù)載詳情頁(yè)一般會(huì)實(shí)時(shí)刷出 Pod 列表你會(huì)看到一個(gè)新 Pending 的 Pod 被創(chuàng)建。點(diǎn)擊進(jìn)入它的詳情看“事件”那一欄如果調(diào)度成功了事件里通常不會(huì)有 ErrorPod 會(huì)從 Pending 變成 ContainerCreating最后變成 Running。同時(shí)在終端也可以交叉驗(yàn)證kubectl get pods -n production -l apporder-service -o wide看到新 Pod 的 NODE 列顯示 node-02并且舊 Pod 逐個(gè)被 Terminating最后只剩新的遷移就完成了。如果此時(shí)想看這個(gè) Deployment 最終的調(diào)度約束可以用kubectl get deployment -n production order-service -o jsonpath{.spec.template.spec.nodeSelector}輸出里應(yīng)該能看到{disk:ssd,env:prod}這樣的結(jié)構(gòu)。3.6 遷移完成后想回滾怎么辦遷移后如果發(fā)現(xiàn)新節(jié)點(diǎn)有問(wèn)題想立刻回到之前的節(jié)點(diǎn)只需要在 Kuboard 里把 nodeSelector 改回原來(lái)的標(biāo)簽組合或者直接恢復(fù)到修改前的 YAML 版本再保存一次Deployment 又會(huì)觸發(fā)一輪新更新把 Pod 遷回去。如果你用 nodeName 做的遷移回滾同樣是把 nodeName 刪掉換成原來(lái)的 nodeSelector。這里記得不要把舊的 Pod 手動(dòng)刪掉來(lái)“觸發(fā)”回滾直接改模板就夠了。手動(dòng)刪除 Pod 在工作負(fù)載的 ReplicaSet 管理下會(huì)再拉起一個(gè)但你改的模板沒(méi)變拉起來(lái)還是在新節(jié)點(diǎn)白忙一場(chǎng)。4. Pending 是常態(tài)常見問(wèn)題與排查技巧實(shí)錄改完節(jié)點(diǎn)配置以后最有代表性的報(bào)錯(cuò)就是 Pod 卡在 Pending。有時(shí)候一眼就能看出原因有時(shí)候折騰半天還不知道哪一步出了問(wèn)題。我把平時(shí)排查過(guò)的高頻問(wèn)題整理成一個(gè)速查表里面全是實(shí)際踩過(guò)的坑?,F(xiàn)象常見原因排查方法解決辦法Pod 一直 Pending事件提示 0/1 nodes are available節(jié)點(diǎn)標(biāo)簽不匹配或節(jié)點(diǎn)有污點(diǎn)沒(méi)被容忍kubectl describe pod 看 Events 里的提示核對(duì)節(jié)點(diǎn)真實(shí)標(biāo)簽補(bǔ)標(biāo)簽或補(bǔ)容忍多個(gè)節(jié)點(diǎn)可用但新 Pod 就是起不來(lái)資源不足比如 CPU、內(nèi)存、端口被占滿kubectl describe node 看 Allocated resources擴(kuò)節(jié)點(diǎn)、縮其他工作負(fù)載、清緩存改完 nodeSelector舊 Pod 沒(méi)被替換改的位置不對(duì)可能只改了副本數(shù)或舊 ReplicaSetkubectl get rs 看是否有新 ReplicaSet確認(rèn)改的是 template.spec 里的調(diào)度配置事件提示 node(s) had taint無(wú)法調(diào)度目標(biāo)節(jié)點(diǎn)有 NoSchedule 污點(diǎn)查看節(jié)點(diǎn) Taints給 Pod 加對(duì)應(yīng)容忍或移除污點(diǎn)新 Pod 反復(fù) CrashLoopBackOff新節(jié)點(diǎn)缺少數(shù)據(jù)、配置文件或本地路徑不存在kubectl logs 看容器啟動(dòng)報(bào)錯(cuò)確認(rèn)新節(jié)點(diǎn)的掛載、目錄權(quán)限、配置都齊全滾動(dòng)更新卡住舊 Pod 不退出maxUnavailable0 且新 Pod 未就緒查看 ReplicaSet 的 Desired/Created 數(shù)調(diào)整 maxSurge/maxUnavailable或手動(dòng)擴(kuò)容副本有狀態(tài)服務(wù)遷走后數(shù)據(jù)查詢異常用了 LocalPV 或 EmptyDir數(shù)據(jù)只留在舊節(jié)點(diǎn)查看 PVC 的 PV 是否綁定具體節(jié)點(diǎn)改用共享存儲(chǔ)或遷移前先備份恢復(fù)排查的時(shí)候最核心的命令就是kubectl describe pod。它會(huì)在 Events 部分直接告訴你調(diào)度器到底因?yàn)槭裁礇](méi)有調(diào)度成功比如kubectl describe pod -n production order-service-xxxxx如果看到類似node(s) didnt match Pods node affinity/selector問(wèn)題基本鎖定在標(biāo)簽不匹配上看到node(s) had taint就是缺容忍或污點(diǎn)太硬看到Insufficient cpu/memory就是資源不夠和節(jié)點(diǎn)選擇沒(méi)關(guān)系。還有一個(gè)小技巧用 kubectl 直接看 Pod 當(dāng)前的調(diào)度約束長(zhǎng)什么樣可以快速確認(rèn)改動(dòng)有沒(méi)有生效。kubectl get pod -n production -o jsonpath{.spec.nodeSelector} kubectl get pod -n production -o jsonpath{.spec.affinity}如果發(fā)現(xiàn) nodeSelector 根本沒(méi)出現(xiàn)在你已經(jīng)改好的 Pod 里說(shuō)明你改的可能不是當(dāng)前正在用的模板。比如有的 Deployment 底下掛著多個(gè) ReplicaSet你編輯的時(shí)候不小心選了這個(gè) ReplicaSet 而不是工作負(fù)載本身保存以后只是改了一個(gè)孤立的歷史版本沒(méi)有產(chǎn)生新的滾動(dòng)更新。遇到這種情況去 Kuboard 的版本歷史或kubectl get rs里看是不是新 ReplicaSet 已經(jīng)被創(chuàng)建了。5. 比改一次更重要的節(jié)點(diǎn)標(biāo)簽和污點(diǎn)規(guī)范化設(shè)計(jì)如果你只是臨時(shí)救火那前面幾節(jié)已經(jīng)夠用了。但如果你在一個(gè)稍微正式點(diǎn)的環(huán)境里長(zhǎng)期負(fù)責(zé)集群我強(qiáng)烈建議不要每次都手動(dòng)去填標(biāo)簽而是先把節(jié)點(diǎn)標(biāo)簽和污點(diǎn)的規(guī)范定下來(lái)。做一次遷移很簡(jiǎn)單可要是每個(gè)服務(wù)遷移都靠臨時(shí)拼標(biāo)簽?zāi)愕募哼t早會(huì)變成一個(gè)誰(shuí)都不敢碰的黑盒子。先想清楚你這套集群里節(jié)點(diǎn)有哪些差異。常見維度有這么幾個(gè)環(huán)境prod、staging、dev、機(jī)型cpu 強(qiáng)、內(nèi)存大、GPU 卡、磁盤ssd、hdd、本地盤、地域或機(jī)柜zone-a、zone-b。我見過(guò)比較順手的做法是統(tǒng)一用role或node-type這一類前綴清晰的標(biāo)簽不要臨時(shí)起名。用途標(biāo)簽鍵標(biāo)簽值示例環(huán)境隔離envprod、staging、dev機(jī)型分類machine-typestandard、highcpu、memory、gpu磁盤類型disk-typessd、hdd歸屬區(qū)域zonezone-a、zone-b標(biāo)簽定好以后再用 nodeSelector 或者節(jié)點(diǎn)親和性把服務(wù)歸類。比如凡是需要 GPU 的訓(xùn)練任務(wù)統(tǒng)一配machine-type: gpu而不是某一次遷移臨時(shí)指定到 node-03。這樣以后 node-03 出問(wèn)題你可以加一臺(tái)同樣標(biāo)簽的新節(jié)點(diǎn)把這個(gè)標(biāo)簽打上Pod 會(huì)自動(dòng)流過(guò)去不需要改任何工作負(fù)載。如果說(shuō)標(biāo)簽做的是“引導(dǎo)”污點(diǎn)做的則是“隔離”。舉個(gè)例子數(shù)據(jù)庫(kù)服務(wù)如果和其他業(yè)務(wù)混跑很容易因?yàn)猷従?Pod 的流量波動(dòng)或者資源搶占影響穩(wěn)定性。你可以給數(shù)據(jù)庫(kù)專用節(jié)點(diǎn)打污點(diǎn)kubectl taint nodes node-03 dedicateddb:NoSchedule這樣默認(rèn)所有 Pod 都不會(huì)調(diào)度到這個(gè)節(jié)點(diǎn)。只有數(shù)據(jù)庫(kù)自己的工作負(fù)載在 Kuboard 里添加了對(duì)dedicateddb:NoSchedule的容忍別的服務(wù)即使加了 nodeSelector 指定這臺(tái)節(jié)點(diǎn)也上不去因?yàn)椴粷M足污點(diǎn)條件。這一個(gè)組合拳的效果比單純加標(biāo)簽要好得多。還有一點(diǎn)屬于經(jīng)驗(yàn)之談不要在 YAML 里長(zhǎng)期保留nodeName。為了臨時(shí)遷移指定一兩次 nodeName 沒(méi)問(wèn)題但保留在長(zhǎng)期運(yùn)行的配置里就意味著你放棄了調(diào)度器的自動(dòng)容錯(cuò)能力。節(jié)點(diǎn)維護(hù)、宕機(jī)、擴(kuò)容縮容任何一個(gè)變動(dòng)都會(huì)讓這個(gè)服務(wù)極其被動(dòng)。我見過(guò)一個(gè)案例某服務(wù)綁死了 nodeName后來(lái)那臺(tái)機(jī)器要下線檢修運(yùn)維整整折騰了一晚上因?yàn)橐蕾囘@臺(tái)機(jī)器的服務(wù)不是一個(gè)而是十幾個(gè)。所以能寫進(jìn)規(guī)范的就不要硬編碼能打標(biāo)簽的就不要寫單一節(jié)點(diǎn)名。6. 滾動(dòng)更新參數(shù)與遷移時(shí)服務(wù)不中斷最后一層保護(hù)網(wǎng)前面這么一頓操作服務(wù)的節(jié)點(diǎn)選擇條件已經(jīng)生效新 Pod 會(huì)逐步替換舊 Pod。但如果滾動(dòng)更新參數(shù)配置得不合理整個(gè)遷移過(guò)程依然可能把服務(wù)打斷。這一節(jié)我把滾動(dòng)更新的幾個(gè)關(guān)鍵參數(shù)講透算是遷移節(jié)點(diǎn)的最后一層保護(hù)網(wǎng)。滾動(dòng)更新有兩種極端情況需要考慮。第一種是把maxUnavailable設(shè)置得太大比如maxUnavailable: 1而副本總數(shù)只有 1 個(gè)那更新開始的一瞬間舊 Pod 就會(huì)被殺掉新 Pod 還在創(chuàng)建這段時(shí)間服務(wù)整體不可用。第二種是maxUnavailable: 0, maxSurge: 0這種配置語(yǔ)義上不允許任何額外 Pod 和任何暫時(shí)不可用等于禁止?jié)L動(dòng)更新控制器會(huì)卡住因?yàn)樾?Pod 起不來(lái)的話舊 Pod 永遠(yuǎn)不能被淘汰。對(duì)多副本的線上服務(wù)我通常用這個(gè)配置strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0它的含義是新 Pod 先起來(lái)一個(gè)等它 Ready 之后再淘汰一個(gè)舊 Pod以此類推。整個(gè)遷移過(guò)程里可用的副本數(shù)始終保持在原值服務(wù)不會(huì)出現(xiàn)空窗期。對(duì)單副本服務(wù)這個(gè)配置就不太合適了。要么臨時(shí)把副本數(shù)提升到 2讓滾動(dòng)更新有喘息空間遷移完再手動(dòng)縮回 1要么接受短暫中斷把maxUnavailable設(shè)為 1先把舊 Pod 釋放掉釋放出來(lái)的資源再起新 Pod。如果你服務(wù)的啟動(dòng)時(shí)間特別長(zhǎng)比如要預(yù)加載緩存、做健康檢查我建議用臨時(shí)擴(kuò)容的方式否則即使一個(gè) Pod 可以中斷業(yè)務(wù)方也會(huì)抱怨那幾秒的閃斷。還有一類服務(wù)在遷移時(shí)要格外小心StatefulSet。StatefulSet 的滾動(dòng)更新是順序進(jìn)行的Pod 名里的序號(hào)從 0 開始挨個(gè)替換而且每個(gè) Pod 使用的 PVC 和節(jié)點(diǎn)位置有強(qiáng)關(guān)聯(lián)。如果你用的是節(jié)點(diǎn)上的本地盤做存儲(chǔ)遷移 StatefulSet 特別容易把數(shù)據(jù)留在舊節(jié)點(diǎn)上新 Pod 起來(lái)以后找不到數(shù)據(jù)。這種情況一定要先關(guān)注存儲(chǔ)層把 PVC 背后的 PV 存儲(chǔ)遷移到共享存儲(chǔ)或新的持久化方案再談節(jié)點(diǎn)調(diào)度。否則只把 Pod 調(diào)度到新節(jié)點(diǎn)存儲(chǔ)還留在舊節(jié)點(diǎn)反而是更大的事故。遷移過(guò)程中還可以盯著 Kuboard 里的工作負(fù)載事件。如果你看到更新進(jìn)度卡住多半就是 RollingUpdate 策略和資源余量之間的矛盾。不要急著去手動(dòng)刪除某個(gè) Pod先把更新策略調(diào)寬松一點(diǎn)比如臨時(shí)把 maxSurge 改成 2等新 Pod 都起來(lái)了再改回來(lái)。我個(gè)人在實(shí)際操作中的體會(huì)是改服務(wù)運(yùn)行節(jié)點(diǎn)這件事技術(shù)上不難難的是調(diào)度條件是不是有依據(jù)、滾動(dòng)更新會(huì)不會(huì)打斷業(yè)務(wù)、以及有沒(méi)有預(yù)留回滾的余地。所以我現(xiàn)在每次遷移前都會(huì)先在腦子里過(guò)一遍這三個(gè)問(wèn)題目標(biāo)節(jié)點(diǎn)的標(biāo)簽確認(rèn)了嗎更新策略和服務(wù)副本數(shù)是否匹配如果新節(jié)點(diǎn)有問(wèn)題原來(lái)的配置怎么恢復(fù)把這三件事想明白Kuboard 里那幾次點(diǎn)擊就只是最后執(zhí)行的動(dòng)作而已。如果你也經(jīng)常要在集群里做這類運(yùn)維操作建議順手把工作負(fù)載的更新策略和節(jié)點(diǎn)標(biāo)簽規(guī)范一起檢查一遍別等到需要遷移的時(shí)候才發(fā)現(xiàn)標(biāo)簽沒(méi)規(guī)劃、策略是默認(rèn)值。這些平時(shí)不起眼的配置恰恰是線上變更最可靠的保障。