网站建设服务哪家好,中文网站制作,帝国网站免费模板,营销网站制作公司推荐本文为从零开始写 Docker 系列第十九篇#xff0c;添加对 cgroup v2 的支持。 完整代码见#xff1a;https://github.com/lixd/mydocker 欢迎 Star 推荐阅读以下文章对 docker 基本实现有一个大致认识#xff1a;
核心原理#xff1a;深入理解 Docker 核心原理#xff1a…
本文为从零开始写 Docker 系列第十九篇添加对 cgroup v2 的支持。 完整代码见https://github.com/lixd/mydocker 欢迎 Star 推荐阅读以下文章对 docker 基本实现有一个大致认识
核心原理深入理解 Docker 核心原理Namespace、Cgroups 和 Rootfs基于 namespace 的视图隔离探索 Linux NamespaceDocker 隔离的神奇背后基于 cgroups 的资源限制 初探 Linux Cgroups资源控制的奇妙世界深入剖析 Linux Cgroups 子系统资源精细管理Docker 与 Linux Cgroups资源隔离的魔法之旅 基于 overlayfs 的文件系统Docker 魔法解密探索 UnionFS 与 OverlayFS基于 veth pair、bridge、iptables 等等技术的 Docker 网络揭秘 Docker 网络手动实现 Docker 桥接网络 开发环境如下
rootmydocker:~# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 20.04.2 LTS
Release: 20.04
Codename: focal
rootmydocker:~# uname -r
5.4.0-74-generic注意需要使用 root 用户 1. 概述
本篇主要添加对 cgroup v2 的支持,自动识别当前系统 cgroup 版本。
2. 实现
判断 cgroup 版本
通过下面这条命令来查看当前系统使用的 Cgroups V1 还是 V2
stat -fc %T /sys/fs/cgroup/如果输出是cgroup2fs 那就是 V2就像这样
roottezn:~# stat -fc %T /sys/fs/cgroup/
cgroup2fs如果输出是tmpfs 那就是 V1就像这样
[rootdocker cgroup]# stat -fc %T /sys/fs/cgroup/
tmpfsGo 实现如下
const (unifiedMountpoint /sys/fs/cgroup
)var (isUnifiedOnce sync.OnceisUnified bool
)// IsCgroup2UnifiedMode returns whether we are running in cgroup v2 unified mode.
func IsCgroup2UnifiedMode() bool {isUnifiedOnce.Do(func() {var st unix.Statfs_terr : unix.Statfs(unifiedMountpoint, st)if err ! nil os.IsNotExist(err) {// For rootless containers, sweep it under the rug.isUnified falsereturn}isUnified st.Type unix.CGROUP2_SUPER_MAGIC})return isUnified
}
cgroup v2 支持
使用 cgroup v2 过程和 v1 基本一致
1创建子 cgroup2配置 cpu、memory 等 Subsystem3配置需要限制的进程
创建子 cgroup
创建子 cgroup,则是在 cgroup 根目录下创建子目录即可对 cgroup v2 来说根目录就是 /sys/fs/cgroup
const UnifiedMountpoint /sys/fs/cgroup// getCgroupPath 找到cgroup在文件系统中的绝对路径
/*
实际就是将根目录和cgroup名称拼接成一个路径。
如果指定了自动创建就先检测一下是否存在如果对应的目录不存在则说明cgroup不存在这里就给创建一个
*/
func getCgroupPath(cgroupPath string, autoCreate bool) (string, error) {// 不需要自动创建就直接返回cgroupRoot : UnifiedMountpointabsPath : 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}return absPath, errors.Wrap(err, create cgroup)
}配置 Subsystem
以 cpu 为例只需要在 cpu.max 中添加具体限制即可就像这样
echo 5000 10000 cpu.max含义是在10000的CPU时间周期内有5000是分配给本cgroup的也就是本cgroup管理的进程在单核CPU上的使用率不会超过50%
具体实现如下
func (s *CpuSubSystem) Set(cgroupPath string, res *resource.ResourceConfig) error {if res.CpuCfsQuota 0 {return nil}subCgroupPath, err : getCgroupPath(cgroupPath, true)if err ! nil {return err}// cpu.cfs_period_us cpu.cfs_quota_us 控制的是CPU使用时间单位是微秒比如每1秒钟这个进程只能使用200ms相当于只能用20%的CPU// v2 中直接将 cpu.cfs_period_us cpu.cfs_quota_us 统一记录到 cpu.max 中比如 5000 10000 这样就是限制使用 50% cpuif res.CpuCfsQuota ! 0 {// cpu.cfs_quota_us 则根据用户传递的参数来控制比如参数为20就是限制为20%CPU所以把cpu.cfs_quota_us设置为cpu.cfs_period_us的20%就行// 这里只是简单的计算了下并没有处理一些特殊情况比如负数什么的if err os.WriteFile(path.Join(subCgroupPath, cpu.max), []byte(fmt.Sprintf(%s %s, strconv.Itoa(PeriodDefault/Percent*res.CpuCfsQuota), PeriodDefault)), constant.Perm0644); err ! nil {return fmt.Errorf(set cgroup cpu share fail %v, err)}}return nil
}配置需要限制的进程
只需要将 pid 写入 cgroup.procs 即可
echo 1033 cgroup.procsGo 实现如下
func (s *CpuSubSystem) Apply(cgroupPath string, pid int) error {return applyCgroup(pid, cgroupPath)
}func applyCgroup(pid int, cgroupPath string) error {subCgroupPath, err : getCgroupPath(cgroupPath, true)if err ! nil {return errors.Wrapf(err, get cgroup %s, cgroupPath)}if err os.WriteFile(path.Join(subCgroupPath, cgroup.procs), []byte(strconv.Itoa(pid)),constant.Perm0644); err ! nil {return fmt.Errorf(set cgroup proc fail %v, err)}return nil
}移除
删除 cgroup 下的子目录即可移除
func (s *CpuSubSystem) Remove(cgroupPath string) error {subCgroupPath, err : getCgroupPath(cgroupPath, false)if err ! nil {return err}return os.RemoveAll(subCgroupPath)
}兼容V1和V2
只需要在创建 CgroupManager 时判断当前系统 cgroup 版本即可
func NewCgroupManager(path string) CgroupManager {if IsCgroup2UnifiedMode() {log.Infof(use cgroup v2)return NewCgroupManagerV2(path)}log.Infof(use cgroup v1)return NewCgroupManagerV1(path)
}3. 测试
cgroup v1
到 cgroup v1 环境进行测试
rootmydocker:~/mydocker# ./mydocker run -mem 10m -cpu 10 -it -name cgroupv1 busybox /bin/sh
{level:info,msg:createTty true,time:2024-04-14T13:23:1908:00}
{level:info,msg:resConf:\u0026{10m 10 },time:2024-04-14T13:23:1908:00}
{level:info,msg:lower:/var/lib/mydocker/overlay2/3845479957/lower image.tar:/var/lib/mydocker/image/busybox.tar,time:2024-04-14T13:23:1908:00}
{level:info,msg:mount overlayfs: [/usr/bin/mount -t overlay overlay -o lowerdir/var/lib/mydocker/overlay2/3845479957/lower,upperdir/var/lib/mydocker/overlay2/3845479957/upper,workdir/var/lib/mydocker/overlay2/3845479957/work /var/lib/mydocker/overlay2/3845479957/merged],time:2024-04-14T13:23:1908:00}
{level:info,msg:use cgroup v1,time:2024-04-14T13:23:1908:00}
{level:error,msg:apply subsystem:cpuset err:set cgroup proc fail write /sys/fs/cgroup/cpuset/mydocker-cgroup/tasks: no space left on device,time:2024-04-14T13:23:1908:00}
{level:info,msg:command all is /bin/sh,time:2024-04-14T13:23:1908:00}
{level:info,msg:init come on,time:2024-04-14T13:23:1908:00}
{level:info,msg:Current location is /var/lib/mydocker/overlay2/3845479957/merged,time:2024-04-14T13:23:1908:00}
{level:info,msg:Find path /bin/sh,time:2024-04-14T13:23:1908:00}根据日志可知当前使用的时 cgroup v1
{level:info,msg:use cgroup v1,time:2024-04-14T13:23:1908:00}执行以下命令测试memory分配
yes /dev/null可以看到过会就被 OOM Kill 了
/ # yes /dev/null
Killed执行以下命令 跑满 cpu
while : ; do : ; done 确实被限制到 10%了
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND
1212 root 20 0 1332 68 4 R 9.9 0.0 0:02.30 sh cgroup v2
到 cgroup v2 环境进行测试或者参考以下步骤切换到 v2 版本。
切换到 cgroup v2
你还可以通过修改内核 cmdline 引导参数在你的 Linux 发行版上手动启用 cgroup v2。
如果你的发行版使用 GRUB则应在 /etc/default/grub 下的 GRUB_CMDLINE_LINUX 中添加 systemd.unified_cgroup_hierarchy1 然后执行 sudo update-grub。
编辑 grub 配置
vi /etc/default/grub内容大概是这样的
GRUB_DEFAULT0
GRUB_TIMEOUT_STYLEhidden
GRUB_TIMEOUT0
GRUB_DISTRIBUTORlsb_release -i -s 2 /dev/null || echo Debian
GRUB_CMDLINE_LINUX_DEFAULTquiet splash
GRUB_CMDLINE_LINUX对最后一行GRUB_CMDLINE_LINUX进行修改
GRUB_CMDLINE_LINUXquiet splash systemd.unified_cgroup_hierarchy1然后执行以下命令更新 GRUB 配置
sudo update-grub最后查看一下启动参数确认配置修改上了
cat /boot/grub/grub.cfg | grep systemd.unified_cgroup_hierarchy1然后就是重启
reboot重启后查看不出意外切换到 cgroups v2 了
rootcgroupv2:~# stat -fc %T /sys/fs/cgroup/
cgroup2fs测试
./mydocker run -mem 10m -cpu 10 -it -name cgroupv2 busybox /bin/shrootmydocker:~/mydocker# ./mydocker run -mem 10m -cpu 10 -it -name cgroupv2 busybox /bin/sh
{level:info,msg:createTty true,time:2024-04-14T13:26:3208:00}
{level:info,msg:resConf:\u0026{10m 10 },time:2024-04-14T13:26:3208:00}
{level:info,msg:lower:/var/lib/mydocker/overlay2/3526930704/lower image.tar:/var/lib/mydocker/image/busybox.tar,time:2024-04-14T13:26:3208:00}
{level:info,msg:mount overlayfs: [/usr/bin/mount -t overlay overlay -o lowerdir/var/lib/mydocker/overlay2/3526930704/lower,upperdir/var/lib/mydocker/overlay2/3526930704/upper,workdir/var/lib/mydocker/overlay2/3526930704/work /var/lib/mydocker/overlay2/3526930704/merged],time:2024-04-14T13:26:3208:00}
{level:info,msg:use cgroup v2,time:2024-04-14T13:26:3208:00}
{level:info,msg:init come on,time:2024-04-14T13:26:3208:00}
{level:info,msg:command all is /bin/sh,time:2024-04-14T13:26:3208:00}
{level:info,msg:Current location is /var/lib/mydocker/overlay2/3526930704/merged,time:2024-04-14T13:26:3208:00}
{level:info,msg:Find path /bin/sh,time:2024-04-14T13:26:3208:00}根据日志可知当前使用的时 cgroup v2
{level:info,msg:use cgroup v2,time:2024-04-14T13:26:3208:00}执行同样的测试效果一致说明 cgroup v2 使用正常。
执行以下命令测试memory分配
yes /dev/null可以看到过会就被 OOM Kill 了
/ # yes /dev/null
Killed执行以下命令 跑满 cpu
while : ; do : ; done 确实被限制到 10%了
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND
1212 root 20 0 1332 68 4 R 9.9 0.0 0:02.30 sh 4. 小结
本文主要为 mydocker 添加了 cgroup v2 的支持根据系统 cgroup 版本自适应切换。 完整代码见https://github.com/lixd/mydocker 欢迎关注~ **【从零开始写 Docker 系列】**持续更新中搜索公众号【探索云原生】订阅文章。 相关代码见 feat-cgroup-v2 分支,测试脚本如下 需要提前在 /var/lib/mydocker/image 目录准备好 busybox.tar 文件具体见第四篇第二节。 # 克隆代码
git clone -b feat-cgroup-v2 https://github.com/lixd/mydocker.git
cd mydocker
# 拉取依赖并编译
go mod tidy
go build .
# 测试
./mydocker run -mem 10m -cpu 10 -it -name cgroupv2 busybox /bin/sh