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。 

    # / 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_releaserelease_agent會一起使用,notify_on_release表示當這個cgroup最后一個進程退出的時候是否執行release_agentrelease_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
    134217728
     957459145e9092618837cf94a1cb356e206f2f0da560b40cb31035e442d3df11 # 查看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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM