Manjusaka

Manjusaka

容器 CPU 和 Memory 限制行為簡述

這篇是給之前沒啥容器經驗的選手準備的一篇文章,主要是講一下容器的 CPU 和 Memory 限制行為。

CPU 限制#

首先 Mac 或者是 Windows 選手在使用 Docker Desktop 的時候,會設置 Docker Desktop 的 CPU 限制,默認是 1,也就是說 Docker Desktop 只能使用 1 個 CPU。這是因為 Docker Desktop 裹了一層虛擬機(Windows 下應該是 WSL2/Hyper-V,Mac 下可能是 QEMU)。這相當於我們在一個特定 CPU 數量的宿主機中跑 Docker

首先提到 CPU 限制,本質上是限制進程的 CPU 使用的時間片,在 Linux 下,進程存在三種調度優先級

  1. SCHED_NORMAL
  2. SCHED_FIFO
  3. SCHED_RR

1 用的是 Linux 中 CFS 調度器,而常見普通進程都是 SCHED_NORMAL 。OK 前提知識帶過

說回容器中的 CPU 限制,目前主流語境下,容器特指以 Docker 為代表的一系列的基於 Linux 中 CGroup 和 Namespace 進行隔離的技術方案。那麼在這個語境下,CPU 限制的實現利用了 Linux CGroup 中三個 CPU Subsystem。我們主要關心的如下四個參數

  1. cpu.cfs_period_us
  2. cpu.cfs_quota_us
  3. cpu.shares
  4. cpuset.cpus

現在分別來聊一下

首先說 cpu.shares,在 Docker 中的使用參數是 --cpu-shares,本質上是一個下限的軟限制,用來設定 CPU 的利用率權重。默認值是 1024。這裡對於相對值可能理解有點抽象。那麼我們來看個例子 假如一個 1core 的主機運行 3 個 container,其中一個 cpu-shares 設置為 1024,而其它 cpu-shares 被設置成 512。當 3 個容器中的進程嘗試使用 100% CPU 的時候(因為 cpu.shares 针對的是下限,只有使用 100% CPU 很重要,此時才可以體現設置值),則設置 1024 的容器會佔用 50% 的 CPU 時間。那再舉個例子,之前這個場景,其餘的兩個容器如果都沒有太多任務,那麼空餘出來的 CPU 時間,是可以繼續被第一個 1024 的容器繼續使用的

接下來聊一下 cpu.cfs_quota_us 和 cpu.cfs_period_us ,這兩個是需要組合使用才能生效,本質上含義是在 cpu.cfs_period_us 的單位時間內,進程最多可以利用 cpu.cfs_quota_us (單位都是 us),如果 quota 耗盡,那麼進程會被內核 throttle 。在 Docker 下,你可以利用 --cpu-period 和 --cpu-quota 這兩個值分別進行設置。也可以通過 --cpu 來進行設置,當我們設置 --cpu 為 2 的時候,容器會保證 cpu.cfs_quota_us 兩倍於 cpu.cfs_period_us,剩下的就以此類推了(Docker 默認的 cpu.cfs_period_us 的閾值是 100ms 即 10000us)

現在已經聊了三個參數了,那麼我們什麼時候該用什麼參數呢。通常來說,對於性能相對敏感的進程,我們可以使用 cpu.shares 來保證進程盡可能多的使用 CPU),業務進程可以利用 cpu.cfs_quota_us 和 cpu.cfs_period_us 來保證相對較好的公平分配。但是這樣也帶來一個問題,就是對於業務流量比較大的應用,可能會因為頻繁被 throtlle 導致我們的 RT 等指標出現毛刺。Linux 5.12 之後有了一個新功能,cpu.cfs_burst_us ,即進程可以在 CPU 利用率比較低的空閒時段積累一定的 credit,然後在密集使用的時候換取一定的 buffer,實現更少的 throttle 和更高的 CPU 利用率(當然這個特性還暫時沒有被主流容器所完全支持)

現在新的問題來了,無論 share 還是 cpu.cfs_quota_us 和 cpu.cfs_period_us 被 throttle 的概率都不少,如果我們想讓進程更好的利用 CPU 怎麼辦?答案就是 cpuset.cpus ,Docker 中的參數是 --cpuset-cpus,可以讓進程進行綁核處理

嗯,CPU 的部分就到這裡

Mem 限制#

還是前提科普

首先 Mac 或者是 Windows 選手在使用 Docker Desktop 的時候,會設置 Docker Desktop 的 Mem 限制,這相當於我們在一個特定 Mem 數量的宿主機中跑 Docker

然後在我們今天的語境下,Mem 資源的限制還是依托於 CGroup 的 Memory Subsystem,參數有很多,我們目前只需要關心

  1. memory.limit_in_bytes

含義即是容器的最大內存限制,如果設置為 -1,代表著無任何內存的限制。在 Docker 中的參數是 --memory。

行為的話分為這樣兩種情況

  1. 如果系統內存還有空餘,但是容器內存超過了 Limit, 那麼容器進程會被 OOMKiller Kill 掉
  2. 如果系統內存先於容器達到了內核閾值,那麼 OOMKiller 會在整個系統範圍內根據根據負載等多個因素計算一個 score,然後 rank 後從高到低進行 OOM Kill 的操作

當然實際上還有一種額外的情況。可以通過 --oom-kill-disable 參數設置 memory.oom_control 的值。如果設置為 1,那麼容器內存超過 Limit 就不會被 OOM Kill 掉而是會被暫停,如果設置為 0,那麼容器內存超過 Limit 就會被 OOM Kill 掉

嗯關於 Mem 的行為差不多就這些

總結#

差不多就這樣吧,純新手向的文章,水文一篇,大家別介意(

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。