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