1. 程式人生 > >《自己動手寫Docker》書摘之二---Linux Cgroups介紹

《自己動手寫Docker》書摘之二---Linux Cgroups介紹

Linux Cgroups介紹

上面是構建Linux容器的namespace技術,它幫程序隔離出自己單獨的空間,但Docker又是怎麼限制每個空間的大小,保證他們不會互相爭搶呢?那麼就要用到Linux的Cgroups技術。

概念

Linux Cgroups(Control Groups) 提供了對一組程序及將來的子程序的資源的限制,控制和統計的能力,這些資源包括CPU,記憶體,儲存,網路等。通過Cgroups,可以方便的限制某個程序的資源佔用,並且可以實時的監控程序的監控和統計資訊。

Cgroups中的三個元件:

  • cgroup
    cgroup 是對程序分組管理的一種機制,一個cgroup包含一組程序,並可以在這個cgroup上增加Linux subsystem的各種引數的配置,將一組程序和一組subsystem的系統引數關聯起來。
  • subsystem
    subsystem 是一組資源控制的模組,一般包含有:

    • blkio 設定對塊裝置(比如硬碟)的輸入輸出的訪問控制
    • cpu 設定cgroup中的程序的CPU被排程的策略
    • cpuacct 可以統計cgroup中的程序的CPU佔用
    • cpuset 在多核機器上設定cgroup中的程序可以使用的CPU和記憶體(此處記憶體僅使用於NUMA架構)
    • devices 控制cgroup中程序對裝置的訪問
    • freezer 用於掛起(suspends)和恢復(resumes) cgroup中的程序
    • memory 用於控制cgroup中程序的記憶體佔用
    • net_cls 用於將cgroup中程序產生的網路包分類(classify),以便Linux的tc(traffic controller) 可以根據分類(classid)區分出來自某個cgroup的包並做限流或監控。
    • net_prio 設定cgroup中程序產生的網路流量的優先順序
    • ns 這個subsystem比較特殊,它的作用是cgroup中程序在新的namespace fork新程序(NEWNS)時,創建出一個新的cgroup,這個cgroup包含新的namespace中程序。

    每個subsystem會關聯到定義了相應限制的cgroup上,並對這個cgroup中的程序做相應的限制和控制,這些subsystem是逐步合併到核心中的,如何看到當前的核心支援哪些subsystem呢?可以安裝cgroup的命令列工具(apt-get install cgroup-bin),然後通過lssubsys看到kernel支援的subsystem。

    “`shell

/ lssubsys -a

cpuset
cpu,cpuacct
blkio
memory
devices
freezer
net_cls,net_prio
perf_event
hugetlb
pids


* hierarchy  
    hierarchy 的功能是把一組cgroup串成一個樹狀的結構,一個這樣的樹便是一個hierarchy,通過這種樹狀的結構,Cgroups可以做到繼承。比如我的系統對一組定時的任務程序通過cgroup1限制了CPU的使用率,然後其中有一個定時dump日誌的程序還需要限制磁碟IO,為了避免限制了影響到其他程序,就可以建立cgroup2繼承於cgroup1並限制磁碟的IO,這樣cgroup2便繼承了cgroup1中的CPU的限制,並且又增加了磁碟IO的限制而不影響到cgroup1中的其他程序。

### 三個元件相互的關係:
通過上面的元件的描述我們就不難看出,Cgroups的是靠這三個元件的相互協作實現的,那麼這三個元件是什麼關係呢?  

* 系統在建立新的hierarchy之後,系統中所有的程序都會加入到這個hierarchy的根cgroup節點中,這個cgroup根節點是hierarchy預設建立,後面在這個hierarchy中建立cgroup都是這個根cgroup節點的子節點。
* 一個subsystem只能附加到一個hierarchy上面
* 一個hierarchy可以附加多個subsystem
* 一個程序可以作為多個cgroup的成員,但是這些cgroup必須是在不同的hierarchy中
* 一個程序fork出子程序的時候,子程序是和父程序在同一個cgroup中的,也可以根據需要將其移動到其他的cgroup中。

_這幾句話現在不理解暫時沒關係,後面我們實際使用過程中會逐漸的瞭解到他們之間的聯絡的。_

### kernel介面:
上面介紹了那麼多的Cgroups的結構,那到底要怎麼呼叫kernel才能配置Cgroups呢?上面瞭解到Cgroups中的hierarchy是一種樹狀的組織結構,Kernel為了讓對Cgroups的配置更直觀,Cgroups通過一個虛擬的樹狀檔案系統去做配置的,通過層級的目錄虛擬出cgroup樹,下面我們就以一個配置的例子來了解下如何操作Cgroups。  

* 首先,我們要建立並掛載一個hierarchy(cgroup樹):

    ```
    ➜ ~ mkdir cgroup-test # 建立一個hierarchy掛載點
    ➜ ~ sudo mount -t cgroup -o none,name=cgroup-test cgroup-test ./cgroup-test # 掛載一個hierarchy
    ➜ ~ ls ./cgroup-test # 掛載後我們就可以看到系統在這個目錄下生成了一些預設檔案
    cgroup.clone_children  cgroup.procs  cgroup.sane_behavior  notify_on_release  release_agent  tasks
    ```     
    這些檔案就是這個hierarchy中根節點cgroup配置項了,上面這些檔案分別的意思是:
    - `cgroup.clone_children` cpuset的subsystem會讀取這個配置檔案,如果這個的值是1(預設是0),子cgroup才會繼承父cgroup的cpuset的配置。
    - `cgroup.procs`是樹中當前節點的cgroup中的程序組ID,現在我們在根節點,這個檔案中是會有現在系統中所有程序組ID。
    - `notify_on_release`和`release_agent`會一起使用,`notify_on_release`表示當這個cgroup最後一個程序退出的時候是否執行`release_agent`,`release_agent`則是一個路徑,通常用作程序退出之後自動清理掉不再使用的cgroup。
    - `tasks`也是表示該cgroup下面的程序ID,如果把一個程序ID寫到`tasks`文件中,便會將這個程序加入到這個cgroup中。

* 然後,我們建立在剛才建立的hierarchy的根cgroup中擴展出兩個子cgroup:

    ```
    ➜ cgroup-test sudo mkdir cgroup-1 # 建立子cgroup "cgroup-1"
    ➜ cgroup-test sudo mkdir cgroup-2 # 建立子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
    ```
    可以看到在一個cgroup的目錄下建立資料夾,kernel就會把資料夾標記會這個cgroup的子cgroup,他們會繼承父cgroup的屬性。

* 在cgroup中新增和移動程序:  
    一個程序在一個Cgroups的hierarchy中只能存在在一個cgroup節點上,系統的所有程序預設都會在根節點,可以將程序在cgroup節點間移動,只需要將程序ID寫到移動到的cgroup節點的tasks檔案中。

    ```
    ➜ cgroup-1 echo $$
	7475
	➜ cgroup-1 sudo sh -c "echo $$ >> tasks" # 將我所在的終端的程序移動到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
    ```
    可以看到我們當前的`7475`程序已經被加到了`cgroup-test:/cgroup-1`中。
* 通過subsystem限制cgroup中程序的資源  
    上面我們建立hierarchy的時候,但這個hierarchy並沒有關聯到任何subsystem,所以沒辦法通過那個hierarchy中的cgroup限制程序的資源佔用,其實系統預設就已經把每個subsystem建立了一個預設的hierarchy,比如memory的hierarchy:

    ```
    ➜  ~ mount | grep memory
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory,nsroot=/)
    ```
    可以看到,在`/sys/fs/cgroup/memory`目錄便是掛在了memory subsystem的hierarchy。下面我們就通過在這個hierarchy中建立cgroup,限制下佔用的程序佔用的記憶體:

    ```
    ➜ memory stress --vm-bytes 200m --vm-keep -m 1 # 首先,我們不做限制啟動一個佔用記憶體的stress程序
    ➜ memory sudo mkdir test-limit-memory && cd test-limit-memory # 建立一個cgroup
    ➜ test-limit-memory sudo sh -c "echo "100m" > memory.limit_in_bytes" sudo sh -c "echo "100m" > memory.limit_in_bytes" # 設定最大cgroup最大記憶體佔用為100m
    ➜ test-limit-memory sudo sh -c "echo $$ > tasks" # 將當前程序移動到這個cgroup中
    ➜ test-limit-memory stress --vm-bytes 200m --vm-keep -m 1 # 再次執行佔用記憶體200m的的stress程序
    ```
    執行結果如下(通過top監控):

    ```
    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 stress


    PID  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
    ```
    可以看到通過cgroup,我們成功的將stress程序的最大記憶體佔用限制到了100m。 
## Docker是如何使用Cgroups的:
    我們知道Docker是通過Cgroups去做的容器的資源限制和監控,我們下面就以一個實際的容器例項來看下Docker是如何配置Cgroups的:

    ```
    ➜ ~ # docker run -m 設定記憶體限制
    ➜ ~ sudo docker run -itd -m  128m ubuntu
    957459145e9092618837cf94a1cb356e206f2f0da560b40cb31035e442d3df11
    ➜ ~ # docker會為每個容器在系統的hierarchy中建立cgroup
    ➜ ~ cd /sys/fs/cgroup/memory/docker/957459145e9092618837cf94a1cb356e206f2f0da560b40cb31035e442d3df11 
    ➜ 957459145e9092618837cf94a1cb356e206f2f0da560b40cb31035e442d3df11 # 檢視cgroup的記憶體限制
    ➜ 957459145e9092618837cf94a1cb356e206f2f0da560b40cb31035e442d3df11 cat memory.limit_in_bytes
134217728957459145e9092618837cf94a1cb356e206f2f0da560b40cb31035e442d3df11 # 檢視cgroup中程序所使用的記憶體大小
    ➜ 957459145e9092618837cf94a1cb356e206f2f0da560b40cb31035e442d3df11 cat memory.usage_in_bytes
430080
    ```
    可以看到Docker通過為每個容器建立Cgroup並通過Cgroup去配置的資源限制和資源監控。


## 用go語言實現通過cgroup限制容器的資源
下面我們在上一節的容器的基礎上加上cgroup的限制,下面這個demo實現了限制容器的記憶體的功能:

package main

import (
“os/exec”
“path”
“os”
“fmt”
“io/ioutil”
“syscall”
“strconv”
)

const cgroupMemoryHierarchyMount = “/sys/fs/cgroup/memory”

func main() {
if os.Args[0] == “/proc/self/exe” {
//容器程序
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.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

    if 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.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

if err := cmd.Start(); err != nil {
    fmt.Println("ERROR", err)
    os.Exit(1)
} else {
    //得到fork出來程序對映在外部名稱空間的pid
    fmt.Printf("%v", cmd.Process.Pid)

    // 在系統預設建立掛載了memory subsystem的Hierarchy上建立cgroup
    os.Mkdir(path.Join(cgroupMemoryHierarchyMount, "testmemorylimit"), 0755)
    // 將容器程序加入到這個cgroup中
    ioutil.WriteFile(path.Join(cgroupMemoryHierarchyMount, "testmemorylimit", "tasks") , []byte(strconv.Itoa(cmd.Process.Pid)), 0644)
    // 限制cgroup程序使用
    ioutil.WriteFile(path.Join(cgroupMemoryHierarchyMount, "testmemorylimit", "memory.limit_in_bytes") , []byte("100m"), 0644)
}
cmd.Process.Wait()

}


通過對Cgroups虛擬檔案系統的配置,我們讓容器中的把stress程序的記憶體佔用限制到了`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
“`

小結

本節我們主要介紹了Linux Cgroups,通過Linux Cgroups的三種結構,可以隨意的定製對資源的限制和做資源的監控,最後用GO語言做了一個Cgroups的限制資源的demo,介紹了怎麼樣用GO語言去操控容器的Cgroups而限制容器的資源。

這裡寫圖片描述

相關推薦

自己動手Docker書摘---Linux Cgroups介紹

Linux Cgroups介紹 上面是構建Linux容器的namespace技術,它幫程序隔離出自己單獨的空間,但Docker又是怎麼限制每個空間的大小,保證他們不會互相爭搶呢?那麼就要用到Linux的Cgroups技術。 概念 Linux Cgrou

自己動手Docker書摘之一: Linux Namespace

Linux Namespace 介紹我們經常聽到說Docker 是一個使用了Linux Namespace 和 Cgroups 的虛擬化工具,但是什麼是Linux Namespace 它在Docker內是怎麼被使用的,說到這裡很多人就會迷茫,下面我們就先介紹一下

自己動手Web容器TomJetty四:靜態頁面起步

        上一節我們實現了將HTTP請求頭的內容解析後列印到控制檯上,讓HTTP請求頭的各個組成部分完全暴露在在我們面前。這個功能在IE瀏覽器的一款叫作HttpWatch的外掛中有類似的體現,相信很多讀者都用過它,利用HttpWatch檢視網頁請求和響應的日誌資訊功

自己動手資料結構:叉樹BinaryTree類模板C++實現(功能較全)

#ifndef MYBINARYTREE_H #define MYBINARYTREE_H template <class T> class BinaryTree { protected: struct TNode { T val; TNode*

自己動手Docker》PDF版

點選下載; 《自己動手寫Docker》 內容簡介 · · · · · · 本書在詳細分析Docker所依賴的技術棧的基礎上,一步一步地通過程式碼例項,讓讀者可以自己循序漸進地用Go語言構建出一個容器的引擎。不同於其他Docker原理介紹或程式碼剖析的

自己動手CPU第九階段(2)——載入存儲指令說明2(lwl、lwr)

上傳 open 送書 運算 ada 讀者 str ast base 將陸續上傳新書《自己動手寫CPU》。今天是第38篇,我盡量每周四篇,可是近期已經非常久沒有實現這個目標了。一直都有事,不好意思哈。 開展曬書評送書活動,在q=%E4%BA%9A%E9%A9%A

自己動手java虛擬機器》學習筆記()-----命令列工具(java)

專案地址:https://github.com/gongxianshengjiadexiaohuihui 首先是Cmd的類 /** * @ClassName Cmd * @Description TODO * @Author Mr.G * @Date 2018/10/9 9:40

Linux C語言自己動手日誌生成函式

        有時候需要自己把日誌資訊儲存到日誌檔案中,沒有找到現成的函式,只好自己動手寫一個,完成相關功能。 void LOG(const char* ms, ... ) { char wzLog[1024] = {0}; char buffer[1024] =

自己動手處理器第二階段(3)——Verilog HDL行為語句

將陸續上傳本人寫的新書《自己動手寫處理器》(尚未出版),今天是第七篇,我儘量每週四篇 2.6 Verilog HDL行為語句 2.6.1 過程語句       Verilog定義的模組一般包括有過程語句,過程語句有兩種:initial、always。其中initial常用於

自己動手ORM框架()-AdoHelper支援多資料庫操作的封裝(1)

主題:本節主要是底層的針對於資料庫的CRUD,包括對於不同型別資料庫的可擴充套件性。第一步:編寫AdoHelper類,用於封裝對資料庫的操作,能同時支援多個數據庫(目前支援SqlServer、Oracle、Access),可擴充套件支援任何型別的資料庫。

自己動手作業系統(

    系統啟動流程簡單來說就是下面的順序:    ===============================    1.BIOS:開機主動執行的韌體,會認識到第一個可開機的裝置    2.MBR:第一個可開機裝置的第一個扇區內的主引導分割槽塊,內包含引導載入程式  

自己動手作業系統1:bochs初步使用

0x00 前言 自己動手寫作業系統,自然需要一個承載程式的執行環境,這一章記錄我使用bochs遇到的坑,凌晨十二點半才填完坑。 這次記錄的是使用bochs完整實現一個最簡單的作業系統(其實根本不算作業系統,只是通過硬碟啟動個mbr程式)。 0x01 b

自己動手作業系統(編寫核心Hello World 教程)

   By EvilBinary 小E     本文用到的boot.s 和setup.s 兩個檔案見本blog MyOs  分類相關文章。     我們編寫start.s 來啟動初始化資訊,然後進入main.c核心程式碼,然後顯示Evilbinary os字樣 //star

自己動手CPU第五階段(1)——流水線資料相關問題

將陸續上傳本人寫的新書《自己動手寫CPU》(尚未出版),今天是第15篇,我儘量每週四篇       上一章建立了原始的OpenMIPS五級流水線結構,但是隻實現了一條ori指令,從本章開始,將逐步完善。本章首先討論了流水線資料相關問題,然後修改OpenMIPS以解決該問

自己動手CPU第九階段(2)——載入儲存指令說明2(lwl、lwr)

將陸續上傳新書《自己動手寫CPU》,今天是第38篇,我儘量每週四篇,但是最近已經很久沒有實現這個目標了,一直都有事,不好意思哈。 開展晒書評送書活動,在亞馬遜、京東、噹噹三大圖書網站上,發表《自己動手寫CPU》書評的前十名讀者,均可獲贈《步步驚芯——軟核處理器內部設計分析》

自己動手處理器第二階段(1)——可編程邏輯器件與PLD電路設計流程

Language 設有 tex 主體 algorithm 元器件 shee 數字 ltr 將陸續上傳本人寫的新書《自己動手寫處理器》(尚未出版),今

死磕 java同步系列自己動手一個鎖Lock

問題 (1)自己動手寫一個鎖需要哪些知識? (2)自己動手寫一個鎖到底有多簡單? (3)自己能不能寫出來一個完美的鎖? 簡介 本篇文章的目標一是自己動手寫一個鎖,這個鎖的功能很簡單,能進行正常的加鎖、解鎖操作。 本篇文章的目標二是通過自己動手寫一個鎖,能更好地理解後面章節將要學習的AQS及各種同步器實現的原理

第四章 自己動手比特幣錢包

概覽 生成錢包 錢包餘額 生成交易 使用錢包 測試體驗 小結 概覽 錢包的目的是為了給使用者建立更高層的抽象介面來對交易進行管理。 我們最終的目的是讓使用者可以方便的: 建立一個新錢包 檢視錢包的餘額 在錢包之間進行交易 以上這些生效後,使用者就不需要知道上一章節中描述的inputs和outpus這些

死磕 java執行緒系列自己動手一個執行緒池

歡迎關注我的公眾號“彤哥讀原始碼”,檢視更多原始碼系列文章, 與彤哥一起暢遊原始碼的海洋。 (手機橫屏看原始碼更方便) 問題 (1)自己動手寫一個執行緒池需要考慮哪些因素? (2)自己動手寫的執行緒池如何測試? 簡介 執行緒池是Java併發程式設計中經常使用到的技術,那麼自己如何動手寫一個執行緒池呢?本

死磕 java執行緒系列自己動手一個執行緒池(續)

(手機橫屏看原始碼更方便) 問題 (1)自己動手寫的執行緒池如何支援帶返回值的任務呢? (2)如果任務執行的過程中丟擲異常了該