先級)
上一篇【第29篇】污點(diǎn)和容忍——K8s的“拒之門外“機(jī)制下一篇【第31篇】LimitRange——給你的Namespace畫個圈摘要上篇咱們聊了資源請求requests和限制limits怎么配但你有沒有想過一個問題K8s集群資源緊張的時候殺誰不殺誰不是隨機(jī)砍的——K8s有一套嚴(yán)格的等級制度叫QoSQuality of Service把Pod分成三等Guaranteed皇親國戚requestslimits全設(shè)且相等、Burstable中產(chǎn)階級設(shè)了requests但對不上limits、BestEffort底層打工人啥都沒設(shè)。等級不同待遇天差地別——OOM Score從最低的-998Guaranteed基本不死到最高的1000BestEffort首選開刀驅(qū)逐順序也是從BestEffort開始一層層清退。本文就把這三六九等的規(guī)則掰碎告訴你QoS等級怎么判定、OOM Score怎么打分、生產(chǎn)環(huán)境怎么用Guaranteed保護(hù)核心服務(wù)——讓你的金牌Pod永遠(yuǎn)不會被誤殺。一、三種QoS等級怎么判定——“你是不是親生的”1.1 判定規(guī)則——一張流程圖搞定【QoS 等級判定流程——你是哪一級】 開始 │ ▼ ┌─────────────────────────────┐ │ 每個容器都設(shè)置了 │ │ requests 和 limits │──No──┐ └─────────────┬───────────────┘ │ │ Yes │ ▼ ▼ ┌─────────────────────────────┐ ┌─────────────────────────────┐ │ 每個容器都 │ │ 至少有一個容器沒設(shè) │ │ requests.cpu limits.cpu │ │ requests 或 limits │ │ AND │ │ │ │ requests.mem limits.mem│ │ → BestEffort底層打工人 │ │ │ │ 誰都可以壓縮、最先被驅(qū)逐 │ └─────────────┬───────────────┘ └─────────────────────────────┘ │ ┌────────┴────────┐ │ Yes │ No ▼ ▼ ┌───────────┐ ┌───────────┐ │Guaranteed│ │ Burstable │ │皇親國戚│ │中產(chǎn)階級│ │requests │ │設(shè)了requests│ │limits │ │但不等limits│ └───────────┘ └───────────┘1.2 三個等級的YAML實(shí)例# # 等級1GuaranteedVIP# # 條件所有容器的 requests limitsCPU和內(nèi)存都要相等apiVersion:v1kind:Podmetadata:name:guaranteed-podspec:containers:-name:appimage:nginxresources:requests:cpu:500m# ← 相等memory:512Mi# ← 相等limits:cpu:500m# ← 相等memory:512Mi# ← 相等# ? 單容器requestslimits → Guaranteed---# # 等級2Burstable中產(chǎn)階級——最常見# # 條件至少一個容器設(shè)了requests或limits但不滿足GuaranteedapiVersion:v1kind:Podmetadata:name:burstable-podspec:containers:-name:appimage:nginxresources:requests:cpu:200m# 設(shè)了requestsmemory:256Milimits:cpu:1000m# limits和requests不相等memory:512Mi# limits和requests不相等# ? 設(shè)了requests但不等limits → Burstable# 這也是最常見的配置——大部分生產(chǎn)Pod都是Burstable---# # 等級3BestEffort底層打工人# # 條件沒有任何容器設(shè)置requests或limitsapiVersion:v1kind:Podmetadata:name:besteffort-podspec:containers:-name:appimage:nginx# 沒有 resources 字段# ? 沒設(shè)任何資源 → BestEffort# 這個Pod在資源緊張時是第一個被驅(qū)逐的1.3 容易搞錯的判定細(xì)節(jié)【QoS判定中的陷阱】 場景1多容器Pod——一個容器滿足Guaranteed不算數(shù) ┌─────────────────────────────────────────────────┐ │ containers: │ │ - name: app │ │ resources: │ │ requests: {cpu:500m, mem:512Mi} │ │ limits: {cpu:500m, mem:512Mi} ← Guaranteed條件 │ │ - name: sidecar │ │ resources: │ │ requests: {cpu:100m, mem:128Mi} │ │ limits: {cpu:200m, mem:256Mi} ← 不等 │ │ │ │ 判定Burstable不是Guaranteed │ │ 原因sidecar的requests≠limits拖了后腿 │ └─────────────────────────────────────────────────┘ 場景2只設(shè)了limits沒設(shè)requests ┌─────────────────────────────────────────────────┐ │ containers: │ │ - name: app │ │ resources: │ │ limits: │ │ cpu: 500m │ │ memory: 512Mi │ │ # 沒設(shè)requests │ │ │ │ 判定Burstable │ │ 原因K8s自動把requestslimits至少有一個 │ │ 容器設(shè)了requests或limits就算Burstable │ │ 注意自動補(bǔ)的requests和limits是相等的 │ │ 但QoS判定只看你顯式設(shè)的 │ └─────────────────────────────────────────────────┘ 場景3只設(shè)requests不設(shè)limits ┌─────────────────────────────────────────────────┐ │ containers: │ │ - name: app │ │ resources: │ │ requests: │ │ cpu: 200m │ │ memory: 256Mi │ │ # 沒設(shè)limits │ │ │ │ 判定Burstable │ │ 原因至少一個資源requests設(shè)了 │ │ 效果可以用到Node上所有剩余資源 │ │ 但OOM時不會像Guaranteed那樣被保護(hù) │ └─────────────────────────────────────────────────┘配置情況QoS等級特征所有容器 requestslimitsCPU和內(nèi)存都等Guaranteed最高保護(hù)級別至少一個容器設(shè)了requests或limits但不滿足GuaranteedBurstable最常見的等級所有容器都沒設(shè)requests和limitsBestEffort最低保護(hù)級別要點(diǎn)QoS是Pod級別的——哪怕你有一個容器配得完美滿足Guaranteed只要另一個容器拉了后腿整個Pod就降級。這也是為什么Istio/Envoy這類Sidecar注入要特別小心——它給你的Pod加了個沒設(shè)資源的Sidecar容器直接把你的Guaranteed拉成了BestEffort二、OOM Score——“你的生存分是多少”2.1 三級QoS的OOM Score差異【OOM Score 計(jì)分——Linux內(nèi)核的生死簿】 OOM Score 計(jì)算公式簡化 ┌─────────────────────────────────────────────────────────┐ │ │ │ oom_score (進(jìn)程內(nèi)存占用 / 系統(tǒng)總內(nèi)存) × 1000 │ │ oom_score_adj │ │ │ │ K8s設(shè)置的 oom_score_adj │ │ │ │ Guaranteed Pod │ │ ┌──────────────────────────────────────────────────┐ │ │ │ oom_score_adj -998 │ │ │ │ (即使node OOM基本也不會被殺除非整個內(nèi)存炸了) │ │ │ │ oom_score 范圍-998 ~ -900 │ │ │ │ 生存概率★★★★★ 接近100% │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ Burstable Pod │ │ ┌──────────────────────────────────────────────────┐ │ │ │ oom_score_adj min(max(2, │ │ │ │ 1000 - 1000 × (request/limit) ), 999) │ │ │ │ │ │ │ │ 例mem request256Mi, limit512Mi │ │ │ │ score_adj 1000 - 1000×0.5 500 │ │ │ │ oom_score 范圍500 ~ 1500 │ │ │ │ 生存概率★★★☆☆ 中等 │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ BestEffort Pod │ │ ┌──────────────────────────────────────────────────┐ │ │ │ oom_score_adj 1000 │ │ │ │ oom_score 范圍1000 ~ 2000 │ │ │ │ 生存概率★☆☆☆☆ 隨時可能被砍 │ │ │ └──────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘# 查看Pod的OOM Score# 方法1進(jìn)入Node查看進(jìn)程的oom_score_adjkubectl get pod guaranteed-pod-owide# NODE: worker-1sshworker-1# 找到容器進(jìn)程PIDcat/proc/$(dockerinspect-f{{.State.Pid}}container_id)/oom_score_adj# -998 ← Guaranteed Pod# 500 ← Burstable Pod# 1000 ← BestEffort Pod# 方法2用kubectl describe查看QoS等級kubectl describe pod guaranteed-pod|grepQoS Class# QoS Class: Guaranteedkubectl describe pod burstable-pod|grepQoS Class# QoS Class: Burstablekubectl describe pod besteffort-pod|grepQoS Class# QoS Class: BestEffort2.2 為什么BestEffort第一個被殺【驅(qū)逐鏈路——資源緊張時的選擇性犧牲】 時刻1Node內(nèi)存開始緊張 ┌─────────────────────────────────────────────────────────┐ │ Node-1: 8Gi 內(nèi)存 │ │ ┌────────────────────────────────────────────────┐ │ │ │ ████████████████████████████??????????????????│ │ │ │ 已用 6.5Gi 剩余 1.5Gi │ │ │ └────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ 時刻2內(nèi)存持續(xù)增長觸發(fā)Eviction閾值 ┌─────────────────────────────────────────────────────────┐ │ Node-1: 已用 7.2Gi (90%)超過 eviction-hard 閾值 │ │ │ │ kubelet 驅(qū)逐決策 │ │ ┌────────────────────────────────────────────┐ │ │ │ 第1波驅(qū)逐所有 BestEffort Pod → 直接殺掉 │ │ │ │ 第2波驅(qū)逐B(yǎng)urstable中超出request最多的Pod │ │ │ │ 第3波驅(qū)逐剩余Burstable Pod │ │ │ │ 第4波驅(qū)逐理論上Guaranteed Pod │ │ │ │ 實(shí)際上系統(tǒng)和kubelet會死保它們 │ │ │ └────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ 時刻3內(nèi)存恢復(fù)安全水位 ┌─────────────────────────────────────────────────────────┐ │ Node-1: 已用 5.8Gi (72%) │ │ Scheduler 在別的Node上重建被驅(qū)逐的Pod │ └─────────────────────────────────────────────────────────┘要點(diǎn)驅(qū)逐是排隊(duì)槍斃式的——BestEffort打頭陣Burstable按超出request的比例排第二梯隊(duì)Guaranteed在最后面。注意kubelet驅(qū)逐的是整個Pod不是單個容器按Pod級別的資源使用量排序。即使你的BackEffort Pod只用了10Mi內(nèi)存只要Node內(nèi)存緊張它也會被優(yōu)先驅(qū)逐。三、驅(qū)逐機(jī)制詳解——“kubelet的水位線”3.1 kubelet的驅(qū)逐閾值【Eviction 閾值——kubelet 的警戒水位線】 ┌─────────────────────────────────────────────────────────┐ │ Node 內(nèi)存狀態(tài) │ │ │ │ 100% ████████████████████████████████████████████████ │ │ │ │ │ 95% ├── eviction-hard 閾值內(nèi)存 100Mi → 開始驅(qū)逐 │ │ │ ┌───────────────────────────────────────┐ │ │ │ │ 觸發(fā)條件默認(rèn) │ │ │ │ │ ? memory.available 100Mi │ │ │ │ │ ? nodefs.available 10% │ │ │ │ │ ? imagefs.available 15% │ │ │ │ └───────────────────────────────────────┘ │ │ 85% ├── eviction-soft 閾值默認(rèn)不啟用 │ │ │ │ │ 70% ├── 安全水位——正常運(yùn)行 │ │ │ │ │ 50% │ │ │ │ │ │ 0% └─────────────────────────────────────────────────│ └─────────────────────────────────────────────────────────┘# 查看kubelet的驅(qū)逐配置kubectl describenodeworker-1|grep-A10Conditions:# 或者直接看kubelet配置cat/var/lib/kubelet/config.yaml|grep-A10eviction# evictionHard:# memory.available: 100Mi# nodefs.available: 10%# nodefs.inodesFree: 5%# imagefs.available: 15%# 自定義kubelet驅(qū)逐配置kubelet配置文件apiVersion:kubelet.config.k8s.io/v1beta1kind:KubeletConfigurationevictionHard:memory.available:200Mi# 提高到200Mi——更保守nodefs.available:10%imagefs.available:15%evictionSoft:memory.available:500Mi# 軟閾值到達(dá)500Mi時evictionSoftGracePeriod:memory.available:60s# 持續(xù)60秒后才觸發(fā)驅(qū)逐evictionMaxPodGracePeriod:120# 驅(qū)逐時最長優(yōu)雅關(guān)閉時間3.2 驅(qū)逐優(yōu)先級排序——“先殺誰”【Eviction 排序算法——排好隊(duì)一個個來】 排序因子 ┌─────────────────────────────────────────────────────────┐ │ 1. QoS等級權(quán)重最大 │ │ BestEffort Burstable Guaranteed │ │ │ │ 2. 同一QoS內(nèi)按超出部分占比排序 │ │ (Pod實(shí)際使用量 - Pod request) / Pod實(shí)際使用量 │ │ 這個比例越大的Pod越先被驅(qū)逐 │ │ 說明它多占了更多 │ │ │ │ 3. Priority優(yōu)先級 │ │ 低優(yōu)先級的Pod先驅(qū)逐 │ └─────────────────────────────────────────────────────────┘ 舉例——3個Burstable Pod的驅(qū)逐順序 ┌──────────┬──────────┬──────────┬──────────┬──────────┐ │ Pod │ Request │ Usage │ 超出量 │ 超出比例 │ 驅(qū)逐順序 │ ├──────────┼──────────┼──────────┼──────────┼──────────┤ │ Burst-A │ 256Mi │ 800Mi │ 544Mi │ 68% │ 第1個 │ │ Burst-B │ 512Mi │ 900Mi │ 388Mi │ 43% │ 第2個 │ │ Burst-C │ 512Mi │ 600Mi │ 88Mi │ 15% │ 第3個 │ └──────────┴──────────┴──────────┴──────────┴──────────┘3.3 驅(qū)逐過程——Pod是怎么被請走的# 查看Pod被驅(qū)逐的原因kubectl describe pod evicted-pod# Status: Failed# Reason: Evicted# Message: The node was low on resource: memory.# Threshold quantity: 100Mi, available: 80Mi# 被驅(qū)逐的Pod的狀態(tài)kubectl get pod evicted-pod# NAME READY STATUS RESTARTS AGE# evicted-pod 0/1 Evicted 0 5m# 被驅(qū)逐的Pod會在其他Node上重建如果由Deployment管理kubectl get pod-lappmy-app# NAME READY STATUS NODE# my-app-new-001 1/1 Running worker-2 ← 被驅(qū)逐了但在別的Node重建了要點(diǎn)驅(qū)逐不是殺掉再原地重啟——是被驅(qū)逐的Pod從當(dāng)前Node強(qiáng)行移除由Scheduler在別的Node上重新調(diào)度一個新的Pod。這就是為什么驅(qū)逐期間會有短暫的請求中斷——舊Pod被驅(qū)了新Pod還沒Ready。如果你的業(yè)務(wù)對可用性要求極高保證至少3個副本 用Guaranteed QoS 配好PodDisruptionBudget。四、生產(chǎn)環(huán)境QoS最佳實(shí)踐4.1 QoS等級選擇策略【按服務(wù)重要性選擇QoS等級】 Tier 1核心業(yè)務(wù)支付、訂單、用戶登錄 ┌─────────────────────────────────────────────────┐ │ QoS: Guaranteed │ │ requests limits相等 │ │ 原因絕不能因?yàn)橘Y源緊張被殺寧可少部署幾個 │ │ 代價(jià)資源預(yù)留較多彈性空間小 │ │ 適合對穩(wěn)定性要求極高的核心服務(wù) │ └─────────────────────────────────────────────────┘ Tier 2普通業(yè)務(wù)API服務(wù)、后臺任務(wù)、前端頁面 ┌─────────────────────────────────────────────────┐ │ QoS: Burstable │ │ requests limits不等 │ │ 原因平時用很少高峰可以多申請有一定保護(hù) │ │ 代價(jià)可能被驅(qū)逐但概率較低 │ │ 適合大部分Web服務(wù) │ └─────────────────────────────────────────────────┘ Tier 3可犧牲任務(wù)批處理、調(diào)試Pod、臨時測試 ┌─────────────────────────────────────────────────┐ │ QoS: BestEffort │ │ 不設(shè)requests和limits │ │ 原因用完就扔的任務(wù)被殺也不心疼 │ │ 適合CI/CD任務(wù)、臨時調(diào)試、一次性腳本 │ └─────────────────────────────────────────────────┘4.2 實(shí)戰(zhàn)給核心服務(wù)套上Guaranteed金鐘罩# 核心支付服務(wù)——Guaranteed QoSapiVersion:apps/v1kind:Deploymentmetadata:name:payment-servicespec:replicas:3selector:matchLabels:app:paymenttemplate:metadata:labels:app:paymentspec:# 高優(yōu)先級——配合QoS保護(hù)priorityClassName:high-priority# Pod反親和性——分散到不同Nodeaffinity:podAntiAffinity:requiredDuringSchedulingIgnoredDuringExecution:-labelSelector:matchExpressions:-key:appoperator:Invalues:-paymenttopologyKey:kubernetes.io/hostnamecontainers:-name:paymentimage:payment:v3.2resources:requests:cpu:2000m# ← 相等 → Guaranteedmemory:4Gi# ← 相等 → Guaranteedlimits:cpu:2000m# ← 相等memory:4Gi# ← 相等# 結(jié)果QoS Guaranteed, OOM Score -998# → 除非整個Node的內(nèi)存都被吃光了否則這個Pod不會死# 普通Web服務(wù)——Burstable QoS最常見的配置apiVersion:apps/v1kind:Deploymentmetadata:name:web-frontendspec:replicas:5selector:matchLabels:app:webtemplate:metadata:labels:app:webspec:containers:-name:nginximage:nginx:1.25resources:requests:cpu:200m# 保證200mmemory:256Mi# 保證256Milimits:cpu:1000m# 最多1000m不等于memory:512Mi# 最多512Mi不等于# 結(jié)果QoS Burstable# 好處平時用很少省資源高峰可以爆發(fā)到limit4.3 關(guān)于Sidecar容器的QoS陷阱——“隊(duì)友拖后腿”# 場景你給應(yīng)用配了完美的Guaranteed# 但I(xiàn)stio自動注入了一個Sidecar——QoS被拖累apiVersion:v1kind:Podmetadata:name:app-with-sidecarannotations:sidecar.istio.io/inject:true# Istio自動注入spec:containers:-name:appimage:my-app:v1resources:requests:cpu:500mmemory:512Milimits:cpu:500m# ← 完美Guaranteedmemory:512Mi# Istio自動注入的Sidecar容器——拖后腿-name:istio-proxy# ← 這個容器是自動加的image:istio/proxyv2# 注意這個Sidecar可能沒設(shè)resources或requests≠limits# → 整個Pod的QoS從Guaranteed降級為Burstable# 解決方案給Sidecar也配好resources---apiVersion:v1kind:Podmetadata:name:app-with-sidecar-fixedannotations:# Istio配置——給Sidecar設(shè)資源sidecar.istio.io/proxyCPU:100msidecar.istio.io/proxyCPULimit:100m# ← 相等sidecar.istio.io/proxyMemory:128Misidecar.istio.io/proxyMemoryLimit:128Mi# ← 相等spec:containers:-name:appimage:my-app:v1resources:requests:{cpu:500m,memory:512Mi}limits:{cpu:500m,memory:512Mi}# 現(xiàn)在app和istio-proxy都是requestslimits → Global QoS Guaranteed要點(diǎn)Service MeshIstio/Linkerd的Sidecar注入是Guaranteed QoS的隱形殺手——你辛辛苦苦配好Guaranteed結(jié)果Sidecar一來全給你拉成Burstable。解決方案(1) 給Sidecar也配requestslimits(2) 或者接受Burstable但至少保證Sidecar有足夠的requests。本篇小結(jié)QoS是K8s資源管理的等級制度決定了資源緊張時誰先被犧牲三種等級判斷Guaranteed所有容器requestslimits、Burstable有requests但不等于limits、BestEffort啥都沒設(shè)OOM Score是天差地別Guaranteed是-998接近免死Burstable在0-999之間BestEffort是1000首選開刀驅(qū)逐是排隊(duì)槍斃BestEffort先死→Burstable按超量比例排→Guaranteed最后基本不死核心服務(wù)用Guaranteed——雖然多占點(diǎn)資源但換來的是OOM保護(hù)很值Sidecar是QoS殺手——Istio/Envoy注入后如果沒配resources會把你的Guaranteed拖成Burstable甚至BestEffortQoS是Pod級別的保護(hù)但如果你管理著一個多團(tuán)隊(duì)共享的集群光靠QoS不夠——還得用LimitRange給每個Namespace畫個圈強(qiáng)制約束Pod的資源聲明。下一篇咱們聊LimitRange——給你的Namespace立規(guī)矩。上一篇【第29篇】污點(diǎn)和容忍——K8s的“拒之門外“機(jī)制下一篇【第31篇】LimitRange——給你的Namespace畫個圈