Limit討論,K8s 使用 CPU Limit 后,服務響應變成龜速...


  你應當小心設定k8s中負載的CPU limit,太小的值會給你的程序帶來額外的、無意義的延遲,太大的值會帶來過大的爆炸半徑,削弱集群的整體穩定性。

 

1.request和limit

  k8s的一大好處就是資源隔離,通過設定負載的request和limit,我們可以方便地讓不同程序共存於合適的節點上。

  其中,request是給調度看的,調度會確保節點上所有負載的CPU request合計與內存request合計分別都不大於節點本身能夠提供的CPU和內存,limit是給節點(kubelet)看的,節點會保證負載在節點上只使用這么多CPU和內存。例如,下面配置意味着單個負載會調度到一個剩余CPU request大於0.1核,剩余request內存大於200MB的節點,並且負載運行時的CPU使用率不能高於0.4核(超過將被限流),內存使用不多余300MB(超過將被OOM Kill並重啟)。

resources:
  requests:
    memory: 200Mi
    cpu: "0.1"
  limits:
    memory: 300Mi
    cpu: "0.4"

 

2.CPU的利用率

  CPU和內存不一樣,它是量子化的,只有“使用中”和“空閑”兩個狀態。

  當我們說內存的使用率是60%時,我們是在說內存有60%在空間上已被使用,還有40%的空間可以放入負載。但是,當我們說CPU的某個核的使用率是60%時,我們是在說采樣時間段內,CPU的這個核在時間上有60%的時間在忙,40%的時間在睡大覺。

  你設定負載的CPU limit時,這個時空區別可能會帶來一個讓你意想不到的效果——過分的降速限流, 節點CPU明明不忙,但是節點故意不讓你的負載全速使用CPU,服務延時上升。

 

3.CPU限流

  k8s使用CFS(Completely Fair Scheduler,完全公平調度)限制負載的CPU使用率,CFS本身的機制比較復雜,但是k8s的文檔中給了一個簡明的解釋,要點如下:

  • CPU使用量的計量周期為100ms;
  • CPU limit決定每計量周期(100ms)內容器可以使用的CPU時間的上限;
  • 本周期內若容器的CPU時間用量達到上限,CPU限流開始,容器只能在下個周期繼續執行;
  • 1 CPU = 100ms CPU時間每計量周期,以此類推,0.2 CPU = 20ms CPU時間每計量周期,2.5 CPU = 250ms CPU時間每計量周期;
  • 如果程序用了多個核,CPU時間會累加統計。

  舉個例子,假設一個API服務在響應請求時需要使用A, B兩個線程(2個核),分別使用60ms和80ms,其中B線程晚觸發20ms,我們看到API服務在100ms后可給出響應:

  如果CPU limit被設為1核,即每100ms內最多使用100ms CPU時間,API服務的線程B會受到一次限流(灰色部分),服務在140ms后響應:

  如果CPU limit被設為0.6核,即每100ms內最多使用60ms CPU時間,API服務的線程A會受到一次限流(灰色部分),線程B受到兩次限流,服務在220ms后響應:

  注意,即使此時CPU沒有其他的工作要做,限流一樣會執行,這是個死板不通融的機制。

  這是一個比較誇張的例子,一般的API服務是IO密集型的,CPU時間使用量沒那么大(你在跑模型推理?當我沒說),但還是可以看到,限流會實打實地延伸API服務的延時。因此,對於延時敏感的服務,我們都應該盡量避免觸發k8s的限流機制。

  下面這張圖是我工作中一個API服務在pod級別的CPU使用率和CPU限流比率(CPU Throttling),我們看到,CPU限流的情況在一天內的大部分時候都存在,限流比例在10%上下浮動,這意味着服務的工作沒能全速完成,在速度上打了9折。值得一提,這時pod所在節點仍然有富余的CPU資源,節點的整體CPU使用率沒有超過50%.

  你可能注意到,監控圖表里的CPU使用率看上去沒有達到CPU limit(橙色橫線),這是由於CPU使用率的統計周期(1min)太長造成的信號混疊(Aliasing),如果它的統計統計周期和CFS的一樣(100ms),我們就能看到高過CPU limit的尖刺了。(這不是bug,這是feature)

  不過,內核版本低於4.18的Linux還真有個bug會造成不必要的CPU限流

 

4.避免CPU限流

  有的開發者傾向於完全棄用CPU limit,裸奔直接跑,特別是內核版本不夠有bug的時候

  我認為這么做還是太過放飛自我了,如果程序里有耗盡CPU的bug(例如死循環,我不幸地遇到過),整個節點及其負載都會陷入不可用的狀態,爆炸半徑太大,特別是在大號的節點上(16核及以上)。

  我有兩個建議:

1.監控一段時間應用的CPU利用率,基於利用率設定一個合適的CPU limit(例如,日常利用率的95分位 * 10),同時該limit不要占到節點CPU核數的太大比例(例如2/3),這樣可以達到性能和安全的一個平衡。

2.使用automaxprocs一類的工具讓程序適配CFS調度環境,各個語言應該都有類似的庫或者執行參數,根據CFS的特點調整后,程序更不容易遇到CPU限流

 

5.結語

  上面說到的信號混疊(采樣頻率不足)和Linux內核bug讓我困擾了一年多,現在想想,主要還是望文生義惹的禍,文檔還是應該好好讀,基礎概念還是要搞清,遂記此文章於錯而知新

  題外話,性能和資源利用率有時是相互矛盾的。對於延時不敏感的程序,CPU限流率控制在10%以內應該都是比較健康可接受的,量體裁衣,在線離線負載混合部署,可以提升硬件的資源利用率。有消息說騰訊雲研發投產了基於服務優先級的搶占式調度,這是一條更難但更有效的路,希望有朝一日在上游能看到他們的相關貢獻。

  

參考資料

https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#meaning-of-cpu

https://stackoverflow.com/questions/68846880/azure-kubernetes-cpu-multithreading

https://cloud.tencent.com/developer/article/1736729

引用鏈接

[1]CFS 本身的機制比較復雜: https://en.wikipedia.org/wiki/Completely_Fair_Scheduler

[2]簡明的解釋: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#how-pods-with-resource-limits-are-run

[3]信號混疊(Aliasing): https://en.wikipedia.org/wiki/Aliasing

[4]內核版本低於 4.18 的 Linux 還真有個 bug 會造成不必要的 CPU 限流: https://github.com/kubernetes/kubernetes/issues/67577#issuecomment-466609030

[5]完全棄用 CPU limit: https://amixr.io/blog/what-wed-do-to-save-from-the-well-known-k8s-incident/

[6]內核版本不夠有 bug 的時候: https://medium.com/omio-engineering/cpu-limits-and-aggressive-throttling-in-kubernetes-c5b20bd8a718

[7]automaxprocs: https://github.com/uber-go/automaxprocs

[8]程序更不容易遇到 CPU 限流: https://github.com/uber-go/automaxprocs/issues/12#issuecomment-405976401

[9]錯而知新: https://nanmu.me/zh-cn/categories/錯而知新/

[10]基於服務優先級的搶占式調度: https://cloud.tencent.com/developer/article/1876817


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM