一文帶你徹底搞懂Docker中的cgroup的具體使用
前言
進程在系統中使用CPU、內存、磁盤等計算資源或者存儲資源還是比較隨心所欲的,我們希望對進程資源利用進行限制,對進程資源的使用進行追蹤。這就讓cgroup的出現成為瞭可能,它用來統一將進程進行分組,並在分組的基礎上對進程進行監控和資源控制管理。
什麼是cgroup
Linux CGroup(Linux Contral Group),它其實是Linux內核的一個功能,它是Linux下的一種將進程按組進行管理的機制。最開始是由Google工程師Paul Menage和Rohit Seth於2006年發起的,最早起名叫進程容器。在2007之後隨著容器得提出,為瞭避免混亂重命名為cgroup,並且被合並到瞭內核2.6.24版本中去瞭。
在用戶層看來,cgroup技術就是把系統中的所有進程組織成一顆一顆獨立的樹,每棵樹都包含系統的所有進程,樹的每個節點是一個進程組,而每顆樹又和一個或者多個subsystem關聯。樹主要用來將進程進行分組,而subsystem用來對這些組進行操作。
cgroup的組成
cgroup主要包含以下兩個部分
- subsystem: 一個subsystem就是一個內核模塊,它被關聯到一顆cgroup樹之後,就會在樹節點進行具體的操作。subsystem經常被稱作”resource controller”,因為它主要被用來調度或者限制每個進程組的資源,但是這個說法不完全準確,因為有時我們將進程分組隻是為瞭做一些監控,觀察一下他們的狀態,比如perf_event subsystem。
- hierarchy:一個hierarchy可以理解為一棵cgroup樹,樹的每個節點就是一個進程組,每棵樹都會與多個subsystem關聯。在一顆樹裡面,會包含Linux系統中的所有進程,但每個進程隻能屬於一個節點(進程組)。系統中可以有很多顆cgroup樹,每棵樹都和不同的subsystem關聯,一個進程可以屬於多顆樹,即一個進程可以屬於多個進程組,這些進程組和不同的subsystem關聯。
可以通過查看/proc/cgroup目錄查看當前系統支持哪些subsystem關聯
第一列:表示subsystem名
第二列:表示關聯到的cgroup樹的ID,如果多個subsystem關聯到同一顆cgroup樹,那麼它們的這個字段將一樣。比如圖中的cpuset、cpu和cpuacct。
第三列:表示subsystem所關聯的cgroup樹中進程組的個數,即樹上節點的個數。
cgroup提供的功能
它提供瞭如下功能
- Resource limitation:資源使用限制
- Prioritization:優先級控制
- Accounting:一些審計或者統計
- Control:掛起進程,恢復執行進程
一般我們可以用cgroup做以下事情
- 隔離一個進程集合(比如MySQL的所有進程),限定他們所占用的資源,比如綁定的核限制
- 為這組進程分配內存
- 為這組進程的分配足夠的帶寬及進行存儲限制
- 限制訪問某些設備
cgroup在Linux中表現為一個文件系統,運行如下命令
mount成功後,可以看到,在/sys/fs下有個cgroup目錄,這個目錄下有很多子系統。比如cpu、cpuset、blkio等。
然後在/sys/fs/cgroup/cpu目錄下建個子目錄test,這個時候會發現在該目錄下多瞭很多文件
限制cgroup中的CPU
在cgroup裡面,跟CPU相關的子系統有cpusets、cpuacct和cpu。
其中cpuset主要用於設置CPU的親和性,可以限制cgroup中的進程隻能在指定的CPU上運行,或者不能在指定的CPU上運行,同時cpuset還能設置內存的親和性。cpuacct包含當前cgroup所使用的CPU的統計信息。這裡我們隻說以下cpu。
然後我們在/sys/fs/cgroup/cpu下創建一個子group, 該目錄下文件列表
cpu.cfs_period_us用來配置時間周期長度,cpu.cfs_quota_us用來配置當前cgroup在設置的周期長度內所能使用的CPU時間數,兩個文件配合起來設置CPU的使用上限。兩個文件的單位都是微秒(us),cpu.cfs_period_us的取值范圍為1毫秒(ms)到1秒(s),cpu.cfs_quota_us的取值大於1ms即可。
下面來舉個例子講解如何使用cpu限制
假如我們寫瞭一個死循環
運行起來用top查看下占用率達到瞭100%
我們執行如下命令對cfs_quota_us進行設置
echo 20000 > /sys/fs/cgroup/cpu/test/cpu.cfs_quota_us
這條命令表示把進程的CPU利用率下降20%,然後把進程PID加入到cgroup中
再執行top可以看到cpu利用率下降瞭
限制cgroup中的內存
代碼如果有bug,比如內存泄露等會榨幹系統內存,讓其它程序由於分配不瞭足夠的內存而出現異常,如果系統配置瞭交換分區,會導致系統大量使用交換分區,從而系統運行很慢。
而cgroup對進程內存控制主要控制如下:
- 限制cgroup中所有進程使用的內存總量
- 限制cgroup中所有進程使用的物理內容+swap交換總量
- 限制cgroup中所有進程所能使用的內核內存總量及其它一些內核資源(CONFIG_MEMCG_KMEM)。
這裡限制內核內存就是限制cgroup當前所使用的內核資源,包括當前進程的內核占空間,socket所占用的內存空間等。當內存吃緊時,可以阻止當前cgroup繼續創建進程以及向內核申請分配更多的內核資源。
下面通過一個例子帶大傢理解cgroup做內存控制的
#include <iostream> #include <sys/types.h> #include <cstdlib> #include <cstdio> #include <string.h> #include <unistd.h> #define CHUNK_SIZE 512 int main() { int size = 0; char *p = nullptr; while(1) { if((p = (char*)malloc(CHUNK_SIZE))==nullptr) { break; } memset(p, 0, CHUNK_SIZE); printf("[%u]-- [%d]MB is allocated ", getpid(), ++size); sleep(1); } return 0; }
首先,在/sys/fs/cgroup/memory下創建一個子目錄即創建瞭一個子cgroup,比如這裡我們創建瞭一個test目錄
$mkdir /sys/fs/cgroup/memory/test
test目錄包含以下文件
每個文件的作用大概介紹下:
文件 | 說明 |
---|---|
cgroup.event_control | 用於eventfd的接口 |
memory.usage_in_bytes | 顯示當前已用的內存 |
memory.limit_in_bytes | 設置/顯示當前限制的內存額度 |
memory.failcnt | 顯示內存使用量達到限制值的次數 |
memory.max_usage_in_bytes | 歷史內存最大使用量 |
memory.soft_limit_in_bytes | 設置/顯示當前限制的內存軟額度 |
memory.stat | 顯示當前cgroup的內存使用情況 |
memory.use_hierarchy | 設置/顯示是否將子cgroup的內存使用情況統計到當前cgroup裡面 |
memory.force_empty | 觸發系統立即盡可能的回收當前cgroup中可以回收的內存 |
memory.pressure_level | 設置內存壓力的通知事件,配合cgroup.event_control一起使用 |
memory.swappiness | 設置和顯示當前的swappiness |
memory.move_charge_at_immigrate | 設置當進程移動到其他cgroup中時,它所占用的內存是否也隨著移動過去 |
memory.oom_control | 設置/顯示oom controls相關的配置 |
memory.numa_stat | 顯示numa相關的內存 |
然後通過寫文件memory.limit_in_bytes來設置限額。這裡設置5M的限制,如下圖所示
把上面示例進程加入這個cgroup,如下圖所示
為瞭避免受swap空間的影響,設置swappiness為0來禁止當前cgroup使用swap,如下圖所示
當物理內存達到上限後,系統的默認行為是kill掉cgroup中繼續申請內存的進程。那麼怎麼控制這個行為呢?那就是配置memory.oom_control。這個文件裡面包含瞭一個控制是否為當前cgroup啟動OOM-killer的標識。如果寫0到這個文件,將啟動OOM-killer,當內核無法給進程分配足夠的內存時,將會直接kill掉該進程;如果寫1到這個文件,表示不啟動OOM-killer,當內核無法給進程分配足夠的內存時,將會暫停該進程直到有空餘的內存之後再繼續運行;同時,memory.oom_control還包含一個隻讀的under_oom字段,用來表示當前是否已經進入oom狀態,也即是否有進程被暫停瞭。還有一個隻讀的killed_oom字段,用來表示當前是否有進程被kill掉瞭。
限制cgoup的進程數
cgroup中有一個subsystem叫pids,功能是限制cgroup及其所有子孫cgroup裡面能創建的總的task數量。這裡的task指通過fork和clone函數創建的進程,由於clone函數也能創建線程,所以這裡的task也包含線程。
之前cgroup樹是已經掛載好的,這裡就直接創建子cgroup,取名為test。命令如下圖所示
再來看看test目錄下的文件
其中pids.current表示當前cgroup和其所有孫子cgroup現有的總的進程數量。
pids.max 當前cgroup和其所有孫子cgroup所允許創建的最大進程數量。
下面我們做個實驗,將pids.max設置為1
然後將當前bash進程加入到該cgroup中
隨便運行一個命令,由於在當前窗口pids.current已經等於pids.max瞭,所以創建進程失敗
當前cgroup中的pids.current和pids.max代表瞭當前cgroup及所有子孫cgroup的所有進程,所以子孫cgroup中的pids.max大小不能超過父cgroup中的大小,如果超過瞭會怎麼樣?我們把pids.max設置為3
當前進程數為2
重新打開一個shell窗口,創建個孫子cgroup,將其中的pids.max設置為5
講當前shell的bash進程寫入croup.procs
回到原來的shell窗口隨便執行一條命令可以看到執行失敗
可以看到,子cgroup中的進程數不僅受制與自己的pids.max,還受制於祖先cgroup的pids.max
到此這篇關於一文帶你徹底搞懂Docker中的cgroup的具體使用的文章就介紹到這瞭,更多相關Docker cgroup內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Docker核心原理之 Cgroup詳解
- 如何使用docker對容器資源進行限制
- docker資源控制管理Cgroup的實現
- Redis swap空間(虛擬內存)的使用詳解
- docker 內存監控與壓測方式