关于docker:从零开始写-Docker三基于-cgroups-实现资源限制

111次阅读

共计 13573 个字符,预计需要花费 34 分钟才能阅读完成。

本文为从零开始写 Docker 系列第三篇,在mydocker run 根底上基于 cgroups 实现容器的资源限度。

<!–more–>


残缺代码见:https://github.com/lixd/mydocker
欢送 Star



如果你对云原生技术充斥好奇,想要深刻理解更多相干的文章和资讯,欢送关注微信公众号。

搜寻公众号【摸索云原生】即可订阅


举荐浏览以下文章对 docker 根本实现有一个大抵意识:

  • 外围原理:深刻了解 Docker 外围原理:Namespace、Cgroups 和 Rootfs
  • 基于 namespace 的视图隔离:摸索 Linux Namespace:Docker 隔离的神奇背地
  • 基于 cgroups 的资源限度

    • 初探 Linux Cgroups:资源管制的微妙世界
    • 深刻分析 Linux Cgroups 子系统:资源精密治理
    • Docker 与 Linux Cgroups:资源隔离的魔法之旅
  • 基于 overlayfs 的文件系统:Docker 魔法解密:摸索 UnionFS 与 OverlayFS
  • 基于 veth pair、bridge、iptables 等等技术的 Docker 网络:揭秘 Docker 网络:手动实现 Docker 桥接网络


开发环境如下:

root@mydocker:~# lsb_release -a
No LSB modules are available.
Distributor ID:    Ubuntu
Description:    Ubuntu 20.04.2 LTS
Release:    20.04
Codename:    focal
root@mydocker:~# uname -r
5.4.0-74-generic

留神:须要应用 root 用户

1. 概述

上一节中,曾经能够通过命令行 mydocker run -it 的形式创立并启动容器。这一节,将 通过 cgroups 实现对容器资源进行限度

这一节中,将实现通过减少-mem-cpu 等 flag 来实现容器 cpu、内存资源限度,残缺命令如下:

./mydocker run -it -mem 10m  -cpu 20 /bin/sh

蕴含以下几个局部:

  • mydocker run 命令减少对应 flag
  • 实现对立 CgroupsManager
  • 实现各个 Subsystem
  • 容器创立、进行时调用对应办法配置 cgroup

2. 具体实现

2.1. Cli 减少 flags

run 命令减少memcpucpuset 这几个 flag,具体如下:

var runCommand = cli.Command{
    Name: "run",
    Usage: `Create a container with namespace and cgroups limit
            mydocker run -it [command]`,
    Flags: []cli.Flag{
        cli.BoolFlag{
            Name:  "it", // 简略起见,这里把 -i 和 -t 参数合并成一个
            Usage: "enable tty",
        },
        cli.StringFlag{
            Name:  "mem", // 限度过程内存使用量,为了防止和 stress 命令的 -m 参数抵触 这里应用 -mem, 到时候能够看下解决抵触的办法
            Usage: "memory limit,e.g.: -mem 100m",
        },
        cli.StringFlag{
            Name:  "cpu",
            Usage: "cpu quota,e.g.: -cpu 100", // 限度过程 cpu 使用率
        },
        cli.StringFlag{
            Name:  "cpuset",
            Usage: "cpuset limit,e.g.: -cpuset 2,4", // 限度过程 cpu 使用率
        },
    },
    /*
        这里是 run 命令执行的真正函数。1. 判断参数是否蕴含 command
        2. 获取用户指定的 command
        3. 调用 Run function 去筹备启动容器:
    */
    Action: func(context *cli.Context) error {if len(context.Args()) < 1 {return fmt.Errorf("missing container command")
        }

        var cmdArray []string
        for _, arg := range context.Args() {cmdArray = append(cmdArray, arg)
        }

        tty := context.Bool("it")
        resConf := &subsystems.ResourceConfig{MemoryLimit: context.String("mem"),
            CpuSet:      context.String("cpuset"),
            CpuCfsQuota: context.Int("cpu"),
        }
        Run(tty, cmdArray, resConf)
        return nil
    },
}

从命令行中解析对应参数并传给 subsystem 以配置 cgroups。

2.2. Subsystem 实现

核心思想就是依据传递过去的参数,创立对应的 cgroups 并配置 subsystem。

例如指定了 -m 100m 就创立 memory subsystem,限度只能应用 100m 内存

定义 Subsystem

首先定义 Subsystem,这里将其形象为一个接口以适应各种 subsystem:

// Subsystem 接口,每个 Subsystem 能够实现上面的 4 个接口,// 这里将 cgroup 形象成了 path, 起因是 cgroup 在 hierarchy 的门路,便是虚构文件系统中的虚构门路
type Subsystem interface {
    // Name 返回以后 Subsystem 的名称, 比方 cpu、memory
    Name() string
    // Set 设置某个 cgroup 在这个 Subsystem 中的资源限度
    Set(path string, res *ResourceConfig) error
    // Apply 将过程增加到某个 cgroup 中
    Apply(path string, pid int) error
    // Remove 移除某个 cgroup
    Remove(path string) error
}

// ResourceConfig 用于传递资源限度配置的构造体,蕴含内存限度,CPU 工夫片权重,CPU 外围数
type ResourceConfig struct {
    MemoryLimit string
    CpuCfsQuota int
    CpuShare    string
    CpuSet      string
}
  • Name:Subsystem 名称,例如 memory、cpu。
  • Set:依据 ResourceConfig 设置 cgroup,行将阈值写入对应配置文件。

    • 例如 memory subsystem 则需将配置写入 memory.limit_in_bytes 文件
    • cpu subsystem 则是写入 cpu.cfs_period_us 和 cpu.cfs_quota_us
  • Apply:将对应 pid 增加到指定 cgroup 里
  • Remove:删除指定 cgroup

不同 Subsystem 只须要实现该接口即可。

找到 cgroup 挂载门路

在实现 Subsystem 之前,咱们还须要先找到 cgroup 的挂载门路,这样能力对 cgroup 进行操作。


那么,是如何找到挂载了 subsystem 的 hierarchy 的挂载目录的呢?

先来相熟下 /proc/{pid}/mountinfo 文件,如下:

$ cat /proc/24334/mountinfo 
613 609 0:59 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs ro
620 613 0:61 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755
624 620 0:26 /docker/fa4b9c67d031dd83cedbad7b744b4ae64eb689c5089f77d0c95379bd3b66d791 /sys/fs/cgroup/systemd ro,nosuid,nodev,noexec,relatime master:5 - cgroup cgroup rw,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd
625 620 0:29 /docker/fa4b9c67d031dd83cedbad7b744b4ae64eb689c5089f77d0c95379bd3b66d791 /sys/fs/cgroup/perf_event ro,nosuid,nodev,noexec,relatime master:6 - cgroup cgroup rw,perf_event
627 620 0:30 /docker/fa4b9c67d031dd83cedbad7b744b4ae64eb689c5089f77d0c95379bd3b66d791 /sys/fs/cgroup/net_cls,net_prio ro,nosuid,nodev,noexec,relatime master:7 - cgroup cgroup rw,net_cls,net_prio
629 620 0:31 /docker/fa4b9c67d031dd83cedbad7b744b4ae64eb689c5089f77d0c95379bd3b66d791 /sys/fs/cgroup/cpu,cpuacct ro,nosuid,nodev,noexec,relatime master:8 - cgroup cgroup rw,cpu,cpuacct
631 620 0:32 /docker/fa4b9c67d031dd83cedbad7b744b4ae64eb689c5089f77d0c95379bd3b66d791 /sys/fs/cgroup/pids ro,nosuid,nodev,noexec,relatime master:9 - cgroup cgroup rw,pids
634 620 0:33 / /sys/fs/cgroup/rdma ro,nosuid,nodev,noexec,relatime master:10 - cgroup cgroup rw,rdma
641 620 0:38 /docker/fa4b9c67d031dd83cedbad7b744b4ae64eb689c5089f77d0c95379bd3b66d791 /sys/fs/cgroup/hugetlb ro,nosuid,nodev,noexec,relatime master:15 - cgroup cgroup rw,hugetlb
644 620 0:39 /docker/fa4b9c67d031dd83cedbad7b744b4ae64eb689c5089f77d0c95379bd3b66d791 /sys/fs/cgroup/freezer ro,nosuid,nodev,noexec,relatime master:16 - cgroup cgroup rw,freezer
646 611 0:52 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw
647 611 0:63 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k
648 609 253:1 /var/lib/docker/containers/fa4b9c67d031dd83cedbad7b744b4ae64eb689c5089f77d0c95379bd3b66d791/resolv.conf /etc/resolv.conf rw,relatime - xfs /dev/vda1 rw,attr2,inode64,noquota
649 609 253:1 /var/lib/docker/containers/fa4b9c67d031dd83cedbad7b744b4ae64eb689c5089f77d0c95379bd3b66d791/hostname /etc/hostname rw,relatime - xfs /dev/vda1 rw,attr2,inode64,noquota
650 609 253:1 /var/lib/docker/containers/fa4b9c67d031dd83cedbad7b744b4ae64eb689c5089f77d0c95379bd3b66d7
# ... 省略

通过/proc/self/mountinfo 能够找出 与以后过程相干的 mount 信息。


而 Cgroups 的 hierarchy 的虚构文件系统是以 cgroup 类型文件系统的 mount 挂载下来的,其中的 option 中加上 subsystem, 代表挂载的 subsystem 类型。

依据这个就能够在 mountinfo 中找到对应的 subsystem 的挂载目录了,比方 memory:

/sys/fs/cgroup/memory ro,nosuid,nodev,noexec,relatime master:11 - cgroup cgroup rw,memory

通过最初的 option 是 rw, memory 看出这一条挂载的 subsystem 是 memory。

那么在 /sys/fs/cgroup/memory 中创立文件夹对应创立的 cgroup, 就能够用来做内存的限度。

依据这个规定咱们就能够找到对应 cgroup 挂载地位了,具体代码实现如下。

上面的 getCgroupPath 函数是找到对应 subsystem 挂载的 hierarchy 相对路径对应的 cgroup 在虚构文件系统中的门路,而后通过这个目录的读写去操作 cgroup

const mountPointIndex = 4

// getCgroupPath 找到 cgroup 在文件系统中的绝对路径
/*
理论就是将根目录和 cgroup 名称拼接成一个门路。如果指定了主动创立,就先检测一下是否存在,如果对应的目录不存在,则阐明 cgroup 不存在,这里就给创立一个
*/
func getCgroupPath(subsystem string, cgroupPath string, autoCreate bool) (string, error) {
    // 不须要主动创立就间接返回
    cgroupRoot := findCgroupMountpoint(subsystem)
    absPath := path.Join(cgroupRoot, cgroupPath)
    if !autoCreate {return absPath, nil}
    // 指定主动创立时才判断是否存在
    _, err := os.Stat(absPath)
    // 只有不存在才创立
    if err != nil && os.IsNotExist(err) {err = os.Mkdir(absPath, constant.Perm0755)
        return absPath, err
    }
    // 其余谬误或者没有谬误都间接返回,如果 err=nil, 那么 errors.Wrap(err, "")也会是 nil
    return absPath, errors.Wrap(err, "create cgroup")
}

// findCgroupMountpoint 通过 /proc/self/mountinfo 找出挂载了某个 subsystem 的 hierarchy cgroup 根节点所在的目录
func findCgroupMountpoint(subsystem string) string {
    // /proc/self/mountinfo 为以后过程的 mountinfo 信息
    // 能够间接通过 cat /proc/self/mountinfo 命令查看
    f, err := os.Open("/proc/self/mountinfo")
    if err != nil {return ""}
    defer f.Close()
    // 这里次要依据各种字符串解决来找到指标地位
    scanner := bufio.NewScanner(f)
    for scanner.Scan() {
        // txt 大略是这样的:104 85 0:20 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,memory
        txt := scanner.Text()
        // 而后依照空格宰割
        fields := strings.Split(txt, " ")
        // 对最初一个元素按逗号进行宰割,这里的最初一个元素就是 rw,memory
        // 其中的的 memory 就示意这是一个 memory subsystem
        subsystems := strings.Split(fields[len(fields)-1], ",")
        for _, opt := range subsystems {
            if opt == subsystem {
                // 如果等于指定的 subsystem,那么就返回这个挂载点跟目录,就是第四个元素,// 这里就是 `/sys/fs/cgroup/memory`, 即咱们要找的根目录
                return fields[mountPointIndex]
            }
        }
    }

    if err = scanner.Err(); err != nil {log.Error("read err:", err)
        return ""
    }
    return ""
}

接下来咱们就能够实现各个 Subsystem 了。

memory subsystem

memory subsystem 实现如下:

type MemorySubSystem struct {
}

// Name 返回 cgroup 名字
func (s *MemorySubSystem) Name() string {return "memory"}

// Set 设置 cgroupPath 对应的 cgroup 的内存资源限度
func (s *MemorySubSystem) Set(cgroupPath string, res *ResourceConfig) error {
    if res.MemoryLimit == "" {return nil}
    subsysCgroupPath, err := getCgroupPath(s.Name(), cgroupPath, true)
    if err != nil {return err}
    // 设置这个 cgroup 的内存限度,行将限度写入到 cgroup 对应目录的 memory.limit_in_bytes 文件中。if err := os.WriteFile(path.Join(subsysCgroupPath, "memory.limit_in_bytes"), []byte(res.MemoryLimit), constant.Perm0644); err != nil {return fmt.Errorf("set cgroup memory fail %v", err)
    }
    return nil
}

// Apply 将 pid 退出到 cgroupPath 对应的 cgroup 中
func (s *MemorySubSystem) Apply(cgroupPath string, pid int, res *ResourceConfig) error {
    if res.MemoryLimit == "" {return nil}
    subsysCgroupPath, err := getCgroupPath(s.Name(), cgroupPath, false)
    if err != nil {return errors.Wrapf(err, "get cgroup %s", cgroupPath)
    }
    if err := os.WriteFile(path.Join(subsysCgroupPath, "tasks"), []byte(strconv.Itoa(pid)), constant.Perm0644); err != nil {return fmt.Errorf("set cgroup proc fail %v", err)
    }
    return nil
}

// Remove 删除 cgroupPath 对应的 cgroup
func (s *MemorySubSystem) Remove(cgroupPath string) error {subsysCgroupPath, err := getCgroupPath(s.Name(), cgroupPath, false)
    if err != nil {return err}
    return os.RemoveAll(subsysCgroupPath)
}

能够看到,其实就是将操作 cgroups 的命令换成了 Go 语法罢了:

  • 比方限度内存则是往 memory.limit_in_bytes 里写入指定值
  • 增加某个过程到 cgroup 中就是往对应的 tasks 文件中写入对应的 pid
  • 删除 cgroup 就是把对应目录删掉

另外的 cpu subsystem 就不再贴代码了,都是相似的操作。

2.3 CgroupManager

实现了各个 Subsystem 之后,咱们在往上形象一层,定义一个 CgroupManager 来对立治理各个 subsystem

这样调用方就能够不必管具体的底层的实现了,只须要调用 CgroupManager 即可。

实现也很简略,就是循环调用每个 subsystem 中的办法。

type CgroupManager struct {
    // cgroup 在 hierarchy 中的门路 相当于创立的 cgroup 目录绝对于 root cgroup 目录的门路
    Path string
    // 资源配置
    Resource *subsystems.ResourceConfig
}

func NewCgroupManager(path string) *CgroupManager {
    return &CgroupManager{Path: path,}
}

// Apply 将过程 pid 退出到这个 cgroup 中
func (c *CgroupManager) Apply(pid int) error {
    for _, subSysIns := range subsystems.SubsystemsIns {err := subSysIns.Apply(c.Path, pid)
        if err != nil {logrus.Errorf("apply subsystem:%s err:%s", subSysIns.Name(), err)
        }
    }
    return nil
}

// Set 设置 cgroup 资源限度
func (c *CgroupManager) Set(res *subsystems.ResourceConfig) error {
    for _, subSysIns := range subsystems.SubsystemsIns {err := subSysIns.Set(c.Path, res)
        if err != nil {logrus.Errorf("apply subsystem:%s err:%s", subSysIns.Name(), err)
        }
    }
    return nil
}

// Destroy 开释 cgroup
func (c *CgroupManager) Destroy() error {
    for _, subSysIns := range subsystems.SubsystemsIns {if err := subSysIns.Remove(c.Path); err != nil {logrus.Warnf("remove cgroup fail %v", err)
        }
    }
    return nil
}
  • Set:配置 cgroup,具体实现则是循环调用各个 Subsystem 的 Set 办法
  • Apply:将指定 pid 过程退出到 cgroup 中, 实现同上
  • Destroy:开释 cgroup,实现同上

通过 CgroupManager 将 Subsystem 进行封装,对下层暗藏了资源限度的配置,以及将过程挪动到 cgroup 中等操作细节。

具体 CgroupManager 和 Subsystem 的调用流程如下所示:

如图所示,CgroupManager 在配置容器资源限度时

  • 首先会初始化 Subsystem 实例;
  • 而后遍历调用 Subsystem 实例的 Set 办法,创立和配置不同 Subsystem 挂载的 hierarchy 中的 cgroup;
  • 接着再通过调用 Subsystem 实例的 Apply 办法将容器过程对应 PID 别离退出到那些 cgroup 中,以实现容器的资源限度;
  • 最初容器运行完结时则调用 Subsystem 实例的 Destory 销毁 cgroup。

到此,整个 Cgroups 的实现就算是实现了,后续剩下的就是在启动的时候,解析参数,并配置对应的 Cgroups 即可。

2.4. 实现 Cgroups 配置

至此,实现了参数解析以及 CgroupManager,剩下的只须要应用 flag 中解析的参数初始化 CgroupManager 实例并调用相干办法即可实现 Cgroups 配置。


后面讲解析的参数组装为 subsystems.ResourceConfig 对象,传递到 Run 办法中,

而后 Run 办法则依据后面解析好的 config 设置对应的 Cgroups。

func Run(tty bool, comArray []string, res *subsystems.ResourceConfig) {parent, writePipe := container.NewParentProcess(tty)
    if parent == nil {log.Errorf("New parent process error")
        return
    }
    if err := parent.Start(); err != nil {log.Errorf("Run parent.Start err:%v", err)
    }
    // 创立 cgroup manager, 并通过调用 set 和 apply 设置资源限度并使限度在容器上失效
    cgroupManager := cgroups.NewCgroupManager("mydocker-cgroup")
    defer cgroupManager.Destroy()
    _ = cgroupManager.Set(res)
    _ = cgroupManager.Apply(parent.Process.Pid)
    // 再子过程创立后能力通过匹配来发送参数
    sendInitCommand(comArray, writePipe)
    _ = parent.Wait()}

外围代码如下:

cgroupManager := cgroups.NewCgroupManager("mydocker-cgroup")
    defer cgroupManager.Destroy()
    _ = cgroupManager.Set(res)
    _ = cgroupManager.Apply(parent.Process.Pid)

创立一个 Cgroups,而后调用 Set 办法写入对应的配置信息,最初调用 Apply 将子过程 pid 退出到这个 Cgroups 中。

这样就实现了 Cgroups 全副配置,曾经能够限度子过程的资源应用了。

同时通过defer cgroupManager.Destroy() 指定了 Destory 办法,以实现在容器退出后主动销毁对应 cgroup。

3. 测试

这里次要通过跑 stress 命令来测试 Cgroups 限度是否失效。

宿主机上须要先装置 stress,因为此时还没有切换 rootfs,因而能够间接应用宿主机上安装的程序。

memory

memory subsystem 测试流程如下:

  • 启动容器并应用 -mem flag 限度内存
  • 应用程序占用大量内存,测试是否超过阈值

为了演示创立了上面这样一个程序,用来向零碎申请内存,它会每秒耗费 1M 的内存。

vi ~/mem-allocate.c

残缺内容如下:

cat > ~/mem-allocate.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define MB (1024 * 1024)

int main(int argc, char *argv[])
{
    char *p;
    int i = 0;
    while(1) {p = (char *)malloc(MB);
        memset(p, 0, MB);
        printf("%dM memory allocated\n", ++i);
        sleep(1);
    }

    return 0;
}
EOF

编译一下

gcc ~/mem-allocate.c -o ~/mem-allocate

而后启动容器

root@mydocker:~/feat-cgroups/mydocker# ./mydocker run -it -mem 10m /bin/sh
{"level":"info","msg":"command all is /bin/sh","time":"2024-01-08T13:19:37+08:00"}
{"level":"info","msg":"init come on","time":"2024-01-08T13:19:37+08:00"}
{"level":"info","msg":"Find path /bin/sh","time":"2024-01-08T13:19:37+08:00"}

并在容器中运行内存占用程序

# ~/mem-allocate
1M memory allocated
2M memory allocated
3M memory allocated
4M memory allocated
5M memory allocated
6M memory allocated
7M memory allocated
8M memory allocated
9M memory allocated
Killed

能够看到,在第 10 次申请时因为达到了内存下限(10M) 程序间接被 OOM Kill 了。

内存和 CPU 不同,如果过程超过了调配的内存阈值,Linux 内核会采取 OOM (Out of Memory) 机制,以保证系统不会因内存不足而解体。

阐明,咱们的内存限度是失效的

最初在看一下 mydocker 创立的 cgroup 在哪儿

查看 memory subsystem 中的内容:

root@mydocker:~# ls /sys/fs/cgroup/memory/
cgroup.clone_children           memory.kmem.tcp.failcnt             memory.soft_limit_in_bytes      mydocker-cgroup 
tasks user.slice

能够看到,有一个 mydocker-cgroup 目录,这就是为 mydocker 容器创立的 memory cgroup。

查看外部的具体内容:

root@mydocker:~# cat /sys/fs/cgroup/memory/mydocker-cgroup/memory.limit_in_bytes
10485760
root@mydocker:~# cat /sys/fs/cgroup/memory/mydocker-cgroup/tasks
116477

memory.limit_in_bytes 中的配置 10485760 转换下单位就是咱们启动时配置的 10m。

tasks 中的 116477 就是容器过程 PID。

最初,当咱们执行 exit,退出容器后 mydocker-cgroup 也会随之删除。

cpu

cpu subsystem 测试也相似:

  • 启动容器并应用 -cpu flag 限度 cpu
  • 应用程序占用大量 cpu,测试是否超过阈值

启动容器并限度只能占用 0.1 核 cpu:

root@mydocker:~/feat-cgroups/mydocker# ./mydocker run -it -cpu 10 /bin/sh
{"level":"info","msg":"init come on","time":"2024-01-08T13:25:03+08:00"}
{"level":"info","msg":"command all is /bin/sh","time":"2024-01-08T13:25:03+08:00"}
{"level":"info","msg":"Find path /bin/sh","time":"2024-01-08T13:25:03+08:00"}

而后在容器中启动一个 while 循环

# while : ; do : ; done &

实践上,该 while 循环会占用满一整个 cpu,然而该容器被限度了只能占用 0.1 核 cpu。

查看 CPU 占用状况:

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
 116464 root      20   0    2608     88      0 R  10.0   0.0   0:05.37 sh

稳稳的被限度在了 10%,阐明 cpu subsystem 也是失效的。

4. 小结

整个实现不算简单,不过只有了解了实现原理,再写就很简略了。

几个重要的点如下:

  • 1)找到 cgroup 挂载门路:依据 /proc/self/mountinfo 中的信息,按规定解析失去 cgroup 挂载门路
  • 2)各个 Subsystem 的实现:应用 Go 实现 Cgroups 配置
  • 3)应用匿名管道传递参数:在父过程和子过程间应用了匿名管道来传递参数。

具体流程就是:

  • 1)解析命令行参数,取到 Cgroups 相干参数

    • 比方 -mem 100m 示意把内存限度到 100m
    • 又比方 -cpu 20 示意把 cpu 限度在 20%
  • 2)依据参数,创立对应 Cgroups 并配置对应的 subsystem,最初把 fork 进去的子过程 pid 退出到这个 Cgroups 中即可。

    • 1)创立 Cgroups
    • 2)配置 subsystem
    • 3)fork 出子过程
    • 4)把子过程 pid 退出到 Cgroups 中
  • 3)子过程 (容器) 完结时删除对于的 Cgroup

如果你对云原生技术充斥好奇,想要深刻理解更多相干的文章和资讯,欢送关注微信公众号。

搜寻公众号【摸索云原生】即可订阅


最初再举荐下 cgroups 相干的几篇文章:

  • 基于 cgroups 的资源限度

    • 初探 Linux Cgroups:资源管制的微妙世界
    • 深刻分析 Linux Cgroups 子系统:资源精密治理
    • Docker 与 Linux Cgroups:资源隔离的魔法之旅

正文完
 0