Docker之Linux Cgroups
Linux Cgroups介紹
上面是構(gòu)建Linux容器的namespace技術(shù),它幫進(jìn)程隔離出自己?jiǎn)为?dú)的空間,但Docker又是怎么限制每個(gè)空間的大小,保證他們不會(huì)互相爭(zhēng)搶呢?那么就要用到Linux的Cgroups技術(shù)。
概念
Linux Cgroups(Control Groups) 提供了對(duì)一組進(jìn)程及將來(lái)的子進(jìn)程的資源的限制,控制和統(tǒng)計(jì)的能力,這些資源包括CPU,內(nèi)存,存儲(chǔ),網(wǎng)絡(luò)等。通過(guò)Cgroups,可以方便的限制某個(gè)進(jìn)程的資源占用,并且可以實(shí)時(shí)的監(jiān)控進(jìn)程的監(jiān)控和統(tǒng)計(jì)信息。?
Cgroups中的三個(gè)組件:
- cgroup
cgroup 是對(duì)進(jìn)程分組管理的一種機(jī)制,一個(gè)cgroup包含一組進(jìn)程,并可以在這個(gè)cgroup上增加Linux subsystem的各種參數(shù)的配置,將一組進(jìn)程和一組subsystem的系統(tǒng)參數(shù)關(guān)聯(lián)起來(lái)。 -  
subsystem
subsystem 是一組資源控制的模塊,一般包含有:- blkio 設(shè)置對(duì)塊設(shè)備(比如硬盤)的輸入輸出的訪問(wèn)控制
 - cpu 設(shè)置cgroup中的進(jìn)程的CPU被調(diào)度的策略
 - cpuacct 可以統(tǒng)計(jì)cgroup中的進(jìn)程的CPU占用
 - cpuset 在多核機(jī)器上設(shè)置cgroup中的進(jìn)程可以使用的CPU和內(nèi)存(此處內(nèi)存僅使用于NUMA架構(gòu))
 - devices 控制cgroup中進(jìn)程對(duì)設(shè)備的訪問(wèn)
 - freezer 用于掛起(suspends)和恢復(fù)(resumes) cgroup中的進(jìn)程
 - memory 用于控制cgroup中進(jìn)程的內(nèi)存占用
 - net_cls 用于將cgroup中進(jìn)程產(chǎn)生的網(wǎng)絡(luò)包分類(classify),以便Linux的tc(traffic controller) 可以根據(jù)分類(classid)區(qū)分出來(lái)自某個(gè)cgroup的包并做限流或監(jiān)控。
 - net_prio 設(shè)置cgroup中進(jìn)程產(chǎn)生的網(wǎng)絡(luò)流量的優(yōu)先級(jí)
 - ns 這個(gè)subsystem比較特殊,它的作用是cgroup中進(jìn)程在新的namespace fork新進(jìn)程(NEWNS)時(shí),創(chuàng)建出一個(gè)新的cgroup,這個(gè)cgroup包含新的namespace中進(jìn)程。
 
每個(gè)subsystem會(huì)關(guān)聯(lián)到定義了相應(yīng)限制的cgroup上,并對(duì)這個(gè)cgroup中的進(jìn)程做相應(yīng)的限制和控制,這些subsystem是逐步合并到內(nèi)核中的,如何看到當(dāng)前的內(nèi)核支持哪些subsystem呢?可以安裝cgroup的命令行工具(apt-get install cgroup-bin),然后通過(guò)lssubsys看到kernel支持的subsystem。?
# / lssubsys -a cpuset cpu,cpuacct blkio memory devices freezer net_cls,net_prio perf_event hugetlb pids -  
hierarchy
hierarchy 的功能是把一組cgroup串成一個(gè)樹(shù)狀的結(jié)構(gòu),一個(gè)這樣的樹(shù)便是一個(gè)hierarchy,通過(guò)這種樹(shù)狀的結(jié)構(gòu),Cgroups可以做到繼承。比如我的系統(tǒng)對(duì)一組定時(shí)的任務(wù)進(jìn)程通過(guò)cgroup1限制了CPU的使用率,然后其中有一個(gè)定時(shí)dump日志的進(jìn)程還需要限制磁盤IO,為了避免限制了影響到其他進(jìn)程,就可以創(chuàng)建cgroup2繼承于cgroup1并限制磁盤的IO,這樣cgroup2便繼承了cgroup1中的CPU的限制,并且又增加了磁盤IO的限制而不影響到cgroup1中的其他進(jìn)程。 
三個(gè)組件相互的關(guān)系:
通過(guò)上面的組件的描述我們就不難看出,Cgroups的是靠這三個(gè)組件的相互協(xié)作實(shí)現(xiàn)的,那么這三個(gè)組件是什么關(guān)系呢??
- 系統(tǒng)在創(chuàng)建新的hierarchy之后,系統(tǒng)中所有的進(jìn)程都會(huì)加入到這個(gè)hierarchy的根cgroup節(jié)點(diǎn)中,這個(gè)cgroup根節(jié)點(diǎn)是hierarchy默認(rèn)創(chuàng)建,后面在這個(gè)hierarchy中創(chuàng)建cgroup都是這個(gè)根cgroup節(jié)點(diǎn)的子節(jié)點(diǎn)。
 - 一個(gè)subsystem只能附加到一個(gè)hierarchy上面
 - 一個(gè)hierarchy可以附加多個(gè)subsystem
 - 一個(gè)進(jìn)程可以作為多個(gè)cgroup的成員,但是這些cgroup必須是在不同的hierarchy中
 - 一個(gè)進(jìn)程fork出子進(jìn)程的時(shí)候,子進(jìn)程是和父進(jìn)程在同一個(gè)cgroup中的,也可以根據(jù)需要將其移動(dòng)到其他的cgroup中。
 
這幾句話現(xiàn)在不理解暫時(shí)沒(méi)關(guān)系,后面我們實(shí)際使用過(guò)程中會(huì)逐漸的了解到他們之間的聯(lián)系的。
kernel接口:
上面介紹了那么多的Cgroups的結(jié)構(gòu),那到底要怎么調(diào)用kernel才能配置Cgroups呢?上面了解到Cgroups中的hierarchy是一種樹(shù)狀的組織結(jié)構(gòu),Kernel為了讓對(duì)Cgroups的配置更直觀,Cgroups通過(guò)一個(gè)虛擬的樹(shù)狀文件系統(tǒng)去做配置的,通過(guò)層級(jí)的目錄虛擬出cgroup樹(shù),下面我們就以一個(gè)配置的例子來(lái)了解下如何操作Cgroups。?
-  
首先,我們要?jiǎng)?chuàng)建并掛載一個(gè)hierarchy(cgroup樹(shù)):
~ mkdir cgroup-test # 創(chuàng)建一個(gè)hierarchy掛載點(diǎn)~ sudo mount -t cgroup -o none,name=cgroup-test cgroup-test ./cgroup-test # 掛載一個(gè)hierarchy~ ls ./cgroup-test # 掛載后我們就可以看到系統(tǒng)在這個(gè)目錄下生成了一些默認(rèn)文件 cgroup.clone_children cgroup.procs cgroup.sane_behavior notify_on_release release_agent tasks這些文件就是這個(gè)hierarchy中根節(jié)點(diǎn)cgroup配置項(xiàng)了,上面這些文件分別的意思是:
- cgroup.clone_children?cpuset的subsystem會(huì)讀取這個(gè)配置文件,如果這個(gè)的值是1(默認(rèn)是0),子cgroup才會(huì)繼承父cgroup的cpuset的配置。
 - cgroup.procs是樹(shù)中當(dāng)前節(jié)點(diǎn)的cgroup中的進(jìn)程組ID,現(xiàn)在我們?cè)诟?jié)點(diǎn),這個(gè)文件中是會(huì)有現(xiàn)在系統(tǒng)中所有進(jìn)程組ID。
 - notify_on_release和release_agent會(huì)一起使用,notify_on_release表示當(dāng)這個(gè)cgroup最后一個(gè)進(jìn)程退出的時(shí)候是否執(zhí)行release_agent,release_agent則是一個(gè)路徑,通常用作進(jìn)程退出之后自動(dòng)清理掉不再使用的cgroup。
 - tasks也是表示該cgroup下面的進(jìn)程ID,如果把一個(gè)進(jìn)程ID寫到tasks文件中,便會(huì)將這個(gè)進(jìn)程加入到這個(gè)cgroup中。
 
 -  
然后,我們創(chuàng)建在剛才創(chuàng)建的hierarchy的根cgroup中擴(kuò)展出兩個(gè)子cgroup:
cgroup-test sudo mkdir cgroup-1 # 創(chuàng)建子cgroup "cgroup-1"cgroup-test sudo mkdir cgroup-2 # 創(chuàng)建子cgroup "cgroup-1"cgroup-test tree . |-- cgroup-1 | |-- cgroup.clone_children | |-- cgroup.procs | |-- notify_on_release | `-- tasks |-- cgroup-2 | |-- cgroup.clone_children | |-- cgroup.procs | |-- notify_on_release | `-- tasks |-- cgroup.clone_children |-- cgroup.procs |-- cgroup.sane_behavior |-- notify_on_release |-- release_agent `-- tasks可以看到在一個(gè)cgroup的目錄下創(chuàng)建文件夾,kernel就會(huì)把文件夾標(biāo)記會(huì)這個(gè)cgroup的子cgroup,他們會(huì)繼承父cgroup的屬性。
 -  
在cgroup中添加和移動(dòng)進(jìn)程:
cgroup-1 echo $$ 7475cgroup-1 sudo sh -c "echo $$ >> tasks" # 將我所在的終端的進(jìn)程移動(dòng)到cgroup-1中cgroup-1 cat /proc/7475/cgroup 13:name=cgroup-test:/cgroup-1 11:perf_event:/ 10:cpu,cpuacct:/user.slice 9:freezer:/ 8:blkio:/user.slice 7:devices:/user.slice 6:cpuset:/ 5:hugetlb:/ 4:pids:/user.slice/user-1000.slice 3:memory:/user.slice 2:net_cls,net_prio:/ 1:name=systemd:/user.slice/user-1000.slice/session-19.scope
一個(gè)進(jìn)程在一個(gè)Cgroups的hierarchy中只能存在在一個(gè)cgroup節(jié)點(diǎn)上,系統(tǒng)的所有進(jìn)程默認(rèn)都會(huì)在根節(jié)點(diǎn),可以將進(jìn)程在cgroup節(jié)點(diǎn)間移動(dòng),只需要將進(jìn)程ID寫到移動(dòng)到的cgroup節(jié)點(diǎn)的tasks文件中。可以看到我們當(dāng)前的7475進(jìn)程已經(jīng)被加到了cgroup-test:/cgroup-1中。
 -  
通過(guò)subsystem限制cgroup中進(jìn)程的資源
~ mount | grep memory cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory,nsroot=/)
上面我們創(chuàng)建hierarchy的時(shí)候,但這個(gè)hierarchy并沒(méi)有關(guān)聯(lián)到任何subsystem,所以沒(méi)辦法通過(guò)那個(gè)hierarchy中的cgroup限制進(jìn)程的資源占用,其實(shí)系統(tǒng)默認(rèn)就已經(jīng)把每個(gè)subsystem創(chuàng)建了一個(gè)默認(rèn)的hierarchy,比如memory的hierarchy:可以看到,在/sys/fs/cgroup/memory目錄便是掛在了memory subsystem的hierarchy。下面我們就通過(guò)在這個(gè)hierarchy中創(chuàng)建cgroup,限制下占用的進(jìn)程占用的內(nèi)存:
memory stress --vm-bytes 200m --vm-keep -m 1 # 首先,我們不做限制啟動(dòng)一個(gè)占用內(nèi)存的stress進(jìn)程memory sudo mkdir test-limit-memory && cd test-limit-memory # 創(chuàng)建一個(gè)cgrouptest-limit-memory sudo sh -c "echo "100m" > memory.limit_in_bytes" sudo sh -c "echo "100m" > memory.limit_in_bytes" # 設(shè)置最大cgroup最大內(nèi)存占用為100mtest-limit-memory sudo sh -c "echo $$ > tasks" # 將當(dāng)前進(jìn)程移動(dòng)到這個(gè)cgroup中test-limit-memory stress --vm-bytes 200m --vm-keep -m 1 # 再次運(yùn)行占用內(nèi)存200m的的stress進(jìn)程運(yùn)行結(jié)果如下(通過(guò)top監(jiān)控):
PID PPID TIME+ %CPU %MEM PR NI S VIRT RES UID COMMAND 8336 8335 0:08.23 99.0 10.0 20 0 R 212284 205060 1000 stress 8335 7475 0:00.00 0.0 0.0 20 0 S 7480 876 1000 stressPID PPID TIME+ %CPU %MEM PR NI S VIRT RES UID COMMAND 8310 8309 0:01.17 7.6 5.0 20 0 R 212284 102056 1000 stress 8309 7475 0:00.00 0.0 0.0 20 0 S 7480 796 1000 stress可以看到通過(guò)cgroup,我們成功的將stress進(jìn)程的最大內(nèi)存占用限制到了100m。?
Docker是如何使用Cgroups的:
我們知道Docker是通過(guò)Cgroups去做的容器的資源限制和監(jiān)控,我們下面就以一個(gè)實(shí)際的容器實(shí)例來(lái)看下Docker是如何配置Cgroups的:
~ # docker run -m 設(shè)置內(nèi)存限制~ sudo docker run -itd -m 128m ubuntu 957459145e9092618837cf94a1cb356e206f2f0da560b40cb31035e442d3df11~ # docker會(huì)為每個(gè)容器在系統(tǒng)的hierarchy中創(chuàng)建cgroup~ cd /sys/fs/cgroup/memory/docker/957459145e9092618837cf94a1cb356e206f2f0da560b40cb31035e442d3df11 957459145e9092618837cf94a1cb356e206f2f0da560b40cb31035e442d3df11 # 查看cgroup的內(nèi)存限制957459145e9092618837cf94a1cb356e206f2f0da560b40cb31035e442d3df11 cat memory.limit_in_bytes 134217728957459145e9092618837cf94a1cb356e206f2f0da560b40cb31035e442d3df11 # 查看cgroup中進(jìn)程所使用的內(nèi)存大小957459145e9092618837cf94a1cb356e206f2f0da560b40cb31035e442d3df11 cat memory.usage_in_bytes 430080可以看到Docker通過(guò)為每個(gè)容器創(chuàng)建Cgroup并通過(guò)Cgroup去配置的資源限制和資源監(jiān)控。
 
用go語(yǔ)言實(shí)現(xiàn)通過(guò)cgroup限制容器的資源
下面我們?cè)谏弦还?jié)的容器的基礎(chǔ)上加上cgroup的限制,下面這個(gè)demo實(shí)現(xiàn)了限制容器的內(nèi)存的功能:
package mainimport ("os/exec""path""os""fmt""io/ioutil""syscall""strconv" )const cgroupMemoryHierarchyMount = "/sys/fs/cgroup/memory"func main() {if os.Args[0] == "/proc/self/exe" {//容器進(jìn)程fmt.Printf("current pid %d", syscall.Getpid())fmt.Println()cmd := exec.Command("sh", "-c", `stress --vm-bytes 200m --vm-keep -m 1`)cmd.SysProcAttr = &syscall.SysProcAttr{}cmd.Stdin = os.Stdincmd.Stdout = os.Stdoutcmd.Stderr = os.Stderrif err := cmd.Run(); err != nil {fmt.Println(err)os.Exit(1)}}cmd := exec.Command("/proc/self/exe")cmd.SysProcAttr = &syscall.SysProcAttr{Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS,}cmd.Stdin = os.Stdincmd.Stdout = os.Stdoutcmd.Stderr = os.Stderrif err := cmd.Start(); err != nil {fmt.Println("ERROR", err)os.Exit(1)} else {//得到fork出來(lái)進(jìn)程映射在外部命名空間的pidfmt.Printf("%v", cmd.Process.Pid)// 在系統(tǒng)默認(rèn)創(chuàng)建掛載了memory subsystem的Hierarchy上創(chuàng)建cgroupos.Mkdir(path.Join(cgroupMemoryHierarchyMount, "testmemorylimit"), 0755)// 將容器進(jìn)程加入到這個(gè)cgroup中ioutil.WriteFile(path.Join(cgroupMemoryHierarchyMount, "testmemorylimit", "tasks") , []byte(strconv.Itoa(cmd.Process.Pid)), 0644)// 限制cgroup進(jìn)程使用ioutil.WriteFile(path.Join(cgroupMemoryHierarchyMount, "testmemorylimit", "memory.limit_in_bytes") , []byte("100m"), 0644)}cmd.Process.Wait() }通過(guò)對(duì)Cgroups虛擬文件系統(tǒng)的配置,我們讓容器中的把stress進(jìn)程的內(nèi)存占用限制到了100m。
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 10861 root 20 0 212284 102464 212 R 6.2 5.0 0:01.13 stress總結(jié)
以上是生活随笔為你收集整理的Docker之Linux Cgroups的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
                            
                        - 上一篇: recursive
 - 下一篇: 使用ssh config配置文件来管理s