防僞碼:乘風破浪會有時,直掛雲帆濟滄海
一、本文將介紹 cgroup 如何做到內存,cpu 和 io 速率的隔離
本文用腳本運行示例進程,來驗證 Cgroups 關於 cpu、內存、io 這三部分的隔離效果。
測試機器環境
執行 mount 命令查看 cgroup 的掛載點
從上圖可以看到 cgroup 掛載在/sys/fs/cgroup 目錄
groups 可以限制 blkio、cpu、cpuacct、cpuset、devices、freezer、memory、net_cls、ns 等系
統的資源,以下是主要子系統的說明:
blkio 這個子系統設置限制每個塊設備的輸入輸出控制。例如:磁盤,光盤以及 usb 等等。
cpu 這個子系統使用調度程序爲 cgroup 任務提供 cpu 的訪問。
cpuacct 產生 cgroup 任務的 cpu 資源報告。
cpuset 如果是多核心的 cpu,這個子系統會爲cgroup 任務分配單獨的 cpu 和內存。
devices 允許或拒絕 cgroup 任務對設備的訪問。
freezer 暫停和恢復 cgroup 任務。
memory 設置每個 cgroup 的內存限制以及產生內存資源報告。
net_cls 標記每個網絡包以供 cgroup 方便使用,它通過使用等級識別符(classid)標記網絡數
據包,從而允許 Linux 流量控制程序(TC:Traffic Controller)識別從具體 cgroup 中生成
的數據包。
ns:命名空間子系統
cgroups 管理進程 cpu 資源
我們先看一個限制 cpu 資源的例子:
跑一個耗 cpu 的腳本
運行一個容器,在容器內創建腳本並運行腳本,腳本內容:
將容器切換到後臺運行
在宿主機上 top 可以看到這個腳本基本佔了 90%多的 cpu 資源
下面用 cgroups 控制這個進程的 cpu 資源
對於 centos7 來說,通過 systemd-cgls 來查看系統 cgroups tree:
#systemd-cgls
注:4239 就是我們所運行的容器 pid
將 cpu.cfs_quota_us 設爲 50000,相對於 cpu.cfs_period_us 的 100000 是 50%
進入容器,再次執行腳本,打開宿主機的另一個終端執行 top 命令
然後 top 的實時統計數據如下,cpu 佔用率將近 50%,看來 cgroups 關於 cpu 的控制起了效果
CPU 資源控制
CPU 資源的控制也有兩種策略,一種是完全公平調度 (CFS:CompletelyFair Scheduler)
策略,提供了限額和按比例分配兩種方式進行資源控制;另一種是實時調度(Real-Time
Scheduler)策略,針對實時進程按週期分配固定的運行時間。配置時間都以微秒(μs)爲
單位,文件名中用 us 表示。
CFS 調度策略下的配置
按權重比例設定 CPU 的分配
docker 提供了–cpu-shares 參數,在創建容器時指定容器所使用的 CPU 份額值。例如:
使用命令 docker run -tid –cpu-shares 100 鏡像,創建容器,則最終生成的 cgroup 的 cpu 份額
配置可以下面的文件中找到:
cpu-shares 的值不能保證可以獲得 1 個 vcpu 或者多少 GHz 的 CPU 資源,僅僅只是一個加權
值。
該加權值是一個整數(必須大於等於 2)表示相對權重,最後除以權重總和算出相對比例,
按比例分配 CPU 時間。
默認情況下,每個 docker 容器的 cpu 份額都是 1024。單獨一個容器的份額是沒有意義的,
只有在同時運行多個容器時,容器的 cpu 加權的效果才能體現出來。例如,兩個容器 A、B
的 cpu 份額分別爲 1000 和 500,在 cpu 進行時間片分配的時候,容器 A 比容器 B 多一倍的
機會獲得 CPU 的時間片。如果容器 A 的進程一直是空閒的,那麼容器 B 是可以獲取比容器
A 更多的 CPU 時間片的。極端情況下,比如說主機上只運行了一個容器,即使它的 cpu 份額
只有 50,它也可以獨佔整個主機的 cpu 資源。
cgroups 只在容器分配的資源緊缺時,也就是說在需要對容器使用的資源進行限制時,纔會
生效。因此,無法單純根據某個容器的 cpu 份額來確定有多少 cpu 資源分配給它,資源分配
結果取決於同時運行的其他容器的 cpu 分配和容器中進程運行情況。
cpu-shares 演示案例:
先刪除 docker 主機上運行的容器
Docker 通過--cpu-shares 指定 CPU 份額
運行一個容器指定 cpu 份額爲 1024
注:
--cpu-shares 指定 CPU 份額,默認就是 1024
--cpuset-cpus 可以綁定 CPU。例如,指定容器在--cpuset-cpus0,1 或--cpuset-cpus 0-3
--cpu 是 stress 命令的選項表示產生 n 個進程 每個進程都反覆不停的計算隨機數的平方根
stress 命令是 linux 下的一個壓力測試工具。
在 docker 宿主機上打開一個 terminal 執行 top
查看 top 的現實結果
可以看到 container1 的 CPU 佔比爲 1024/(1024+512)=2/3,container2 的 CPU 佔比爲
512/(1024+512)=1/3
將 container1 的 cpu.shares 改爲 512,
#echo “512” >/sys/fs/cgroup/cpu/system.slice/docker-<容器的完整長ID>/cpu.shares
可以看到兩個容器的 CPU 佔比趨於平均
設定 CPU 使用週期使用時間上限
cgroups 裏,可以用cpu.cfs_period_us 和 cpu.cfs_quota_us 來限制該組中的所有進程在單
位時間裏可以使用的 cpu 時間。cpu.cfs_period_us 就是時間週期,默認爲 100000,即百毫
秒。cpu.cfs_quota_us 就是在這期間內可使用的 cpu 時間,默認 -1,即無限制。
cpu.cfs_period_us:設定時間週期(單位爲微秒(μs)),必須與 cfs_quota_us 配合使用。
cpu.cfs_quota_us :設定週期內最多可使用的時間(單位爲微秒(μs))。這裏的配置指 task
對單個 cpu 的使用上限。
舉個例子,如果容器進程需要每 1 秒使用單個 CPU 的 0.2 秒時間,可以將 cpu-period 設置爲
1000000(即 1 秒),cpu-quota 設置爲 200000(0.2 秒)。
當然,在多核情況下,若 cfs_quota_us 是 cfs_period_us 的兩倍,就表示在兩個核上
完全使用 CPU,例如如果允許容器進程需要完全佔用兩個 CPU,則可以將 cpu-period 設置爲
100000(即 0.1 秒),cpu-quota 設置爲 200000(0.2 秒)。
使用示例:
使用命令 docker run 創建容器
在宿主機上執行 top
從上圖可以看到基本佔了 100%的 cpu 資源
則最終生成的 cgroup 的 cpu 週期配置可以下面的目錄中找到:
/sys/fs/cgroup/cpu/system.slice/docker-<容器的完整長ID>/
修改容器的 cpu.cfs_period_us 和 cpu.cfs_quota_us 值
執行 top 查看 cpu 資源
從上圖可以看到基本佔了 50%的 cpu 資源
RT 調度策略下的配置 實時調度策略與公平調度策略中的按週期分配時間的方法類似,也是
在週期內分配一個固定的運行時間。
cpu.rt_period_us :設定週期時間。
cpu.rt_runtime_us:設定週期中的運行時間。
cpuset - CPU 綁定
對多核 CPU 的服務器,docker 還可以控制容器運行限定使用哪些 cpu 內核和內存節點,即
使用–cpuset-cpus 和–cpuset-mems 參數。對具有 NUMA 拓撲(具有多 CPU、多內存節點)的
服務器尤其有用,可以對需要高性能計算的容器進行性能最優的配置。如果服務器只有一個
內存節點,則–cpuset-mems 的配置基本上不會有明顯效果
注:
現在的機器上都是有多個 CPU 和多個內存塊的。以前我們都是將內存塊看成是一大塊內存,
所有 CPU 到這個共享內存的訪問消息是一樣的。但是隨着處理器的增加,共享內存可能會
導致內存訪問衝突越來越厲害,且如果內存訪問達到瓶頸的時候,性能就不能隨之增加。
NUMA(Non-Uniform MemoryAccess)就是這樣的環境下引入的一個模型。比如一臺機
器是有2個處理器,有4個內存塊。我們將1個處理器和兩個內存塊合起來,稱爲一個NUMAnode,這樣這個機器就會有兩個 NUMAnode。在物理分佈上,NUMA node 的處理器和內
存塊的物理距離更小,因此訪問也更快。比如這臺機器會分左右兩個處理器(cpu1, cpu2),
在每個處理器兩邊放兩個內存塊(memory1.1, memory1.2, memory2.1,memory2.2),這樣
NUMA node1 的 cpu1 訪問 memory1.1 和 memory1.2 就比訪問memory2.1 和 memory2.2
更快。所以使用 NUMA 的模式如果能儘量保證本 node 內的 CPU 只訪問本 node 內的內存
塊,那這樣的效率就是最高的。
使用示例:
表示創建的容器只能用 0、1、2 這三個內核。最終生成的 cgroup 的 cpu 內核配置如下:
cpuset.cpus:在這個文件中填寫 cgroup 可使用的 CPU 編號,如 0-2,16 代表 0、1、2 和 16
這 4 個 CPU。
cpuset.mems:與 CPU 類似,表示 cgroup 可使用的 memorynode,格式同上
通過 docker exec <容器 ID> taskset -c -p 1(容器內部第一個進程編號一般爲 1),可以看到容器
中進程與 CPU 內核的綁定關係,可以認爲達到了綁定 CPU 內核的目的。
總結:
CPU 配額控制參數的混合使用
當上面這些參數中時,cpu-shares 控制只發生在容器競爭同一個內核的時間片時,如果通過
cpuset-cpus 指定容器 A 使用內核 0,容器 B 只是用內核 1,在主機上只有這兩個容器使用對
應內核的情況,它們各自佔用全部的內核資源,cpu-shares 沒有明顯效果。
cpu-period、cpu-quota 這兩個參數一般聯合使用,在單核情況或者通過 cpuset-cpus 強制容
器使用一個 cpu 內核的情況下,即使 cpu-quota 超過 cpu-period,也不會使容器使用更多的
CPU 資源。
cpuset-cpus、cpuset-mems 只在多核、多內存節點上的服務器上有效,並且必須與實際的物
理配置匹配,否則也無法達到資源控制的目的。
在系統具有多個 CPU 內核的情況下,需要通過 cpuset-cpus 爲容器 CPU 內核才能比較方便地
進行測試。
內存配額控制
和 CPU 控制一樣,docker 也提供了若干參數來控制容器的內存使用配額,可以控制容器的
swap 大小、可用內存大小等各種內存方面的控制。主要有以下參數:
Docker 提供參數-m,--memory=""限制容器的內存使用量,如果不設置-m,則默認容器內存
是不設限的,容器可以使用主機上的所有空閒內存
內存配額控制使用示例
設置容器的內存上限,參考命令如下所示
#docker run -dit --memory 128m 鏡像
默認情況下,除了–memory 指定的內存大小以外,docker 還爲容器分配了同樣大小的 swap
分區,也就是說,上面的命令創建出的容器實際上最多可以使用 256MB 內存,而不是 128MB內存。如果需要自定義 swap 分區大小,則可以通過聯合使用–memory–swap 參數來實現控
制。
可以發現,使用 256MB 進行壓力測試時,由於超過了內存上限(128MB 內存+128MB swap),
進程被 OOM(out of memory)殺死。
使用 250MB 進行壓力測試時,進程可以正常運行。
通過 docker stats 可以查看到容器的內存已經滿負載了。
#docker stats test2
對上面的命令創建的容器,可以查看到在 cgroups 的配置文件中,查看到容器的內存大小爲
128MB (128×1024×1024=134217728B),內 存 和 swap 加 起 來 大 小 爲 256MB
(256×1024×1024=268435456B)。
#cat/sys/fs/cgroup/memory/system.slice/docker-<容器的完整ID>/memory.limit_in_bytes
134217728
#cat /sys/fs/cgroup/memory/system.slice/docker-< 容 器 的 完 整
ID>/memory.memsw.limit_in_bytes
268435456
磁盤 IO 配額控制
主要包括以下參數:
--device-read-bps:限制此設備上的讀速度(bytesper second),單位可以是 kb、mb 或者 gb。
--device-read-iops:通過每秒讀 IO 次數來限制指定設備的讀速度。
--device-write-bps :限制此設備上的寫速度(bytesper second),單位可以是 kb、mb 或者 gb。
--device-write-iops:通過每秒寫 IO 次數來限制指定設備的寫速度。
--blkio-weight:容器默認磁盤 IO 的加權值,有效值範圍爲10-1000。
--blkio-weight-device: 針對特定設備的 IO 加權控制。其格式爲DEVICE_NAME:WEIGHT
磁盤 IO 配額控制示例
blkio-weight
使用下面的命令創建兩個–blkio-weight 值不同的容器:
在容器中同時執行下面的 dd 命令,進行測試
注:oflag=direct 規避掉文件系統的 cache,把寫請求直接封裝成 io 指令發到硬盤