首页 > 其他分享 >从零开始写 Docker(十九)---增加 cgroup v2 支持

从零开始写 Docker(十九)---增加 cgroup v2 支持

时间:2024-07-24 09:20:52浏览次数:8  
标签:err level mydocker --- v2 cgroup msg

feat-cgroup-v2.png

本文为从零开始写 Docker 系列第十九篇,添加对 cgroup v2 的支持。


完整代码见:https://github.com/lixd/mydocker
欢迎 Star

推荐阅读以下文章对 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. 概述

本篇主要添加对 cgroup v2 的支持,自动识别当前系统 cgroup 版本。

2. 实现

判断 cgroup 版本

通过下面这条命令来查看当前系统使用的 Cgroups V1 还是 V2

stat -fc %T /sys/fs/cgroup/

如果输出是cgroup2fs 那就是 V2,就像这样

root@tezn:~# stat -fc %T /sys/fs/cgroup/
cgroup2fs

如果输出是tmpfs 那就是 V1,就像这样

[root@docker cgroup]# stat -fc %T /sys/fs/cgroup/
tmpfs

Go 实现如下:

const (
	unifiedMountpoint = "/sys/fs/cgroup"
)

var (
	isUnifiedOnce sync.Once
	isUnified     bool
)

// IsCgroup2UnifiedMode returns whether we are running in cgroup v2 unified mode.
func IsCgroup2UnifiedMode() bool {
	isUnifiedOnce.Do(func() {
		var st unix.Statfs_t
		err := unix.Statfs(unifiedMountpoint, &st)
		if err != nil && os.IsNotExist(err) {
			// For rootless containers, sweep it under the rug.
			isUnified = false
			return
		}
		isUnified = st.Type == unix.CGROUP2_SUPER_MAGIC
	})
	return isUnified
}

cgroup v2 支持

使用 cgroup v2 过程和 v1 基本一致

  • 1)创建子 cgroup
  • 2)配置 cpu、memory 等 Subsystem
  • 3)配置需要限制的进程

创建子 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 := UnifiedMountpoint
	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
	}
	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% cpu
	if 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.procs

Go 实现如下:

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 环境进行测试

root@mydocker:~/mydocker# ./mydocker run -mem 10m -cpu 10 -it -name cgroupv1 busybox /bin/sh
{"level":"info","msg":"createTty true","time":"2024-04-14T13:23:19+08:00"}
{"level":"info","msg":"resConf:\u0026{10m 10 }","time":"2024-04-14T13:23:19+08:00"}
{"level":"info","msg":"lower:/var/lib/mydocker/overlay2/3845479957/lower image.tar:/var/lib/mydocker/image/busybox.tar","time":"2024-04-14T13:23:19+08: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:19+08:00"}
{"level":"info","msg":"use cgroup v1","time":"2024-04-14T13:23:19+08: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:19+08:00"}
{"level":"info","msg":"command all is /bin/sh","time":"2024-04-14T13:23:19+08:00"}
{"level":"info","msg":"init come on","time":"2024-04-14T13:23:19+08:00"}
{"level":"info","msg":"Current location is /var/lib/mydocker/overlay2/3845479957/merged","time":"2024-04-14T13:23:19+08:00"}
{"level":"info","msg":"Find path /bin/sh","time":"2024-04-14T13:23:19+08:00"}

根据日志可知,当前使用的时 cgroup v1

{"level":"info","msg":"use cgroup v1","time":"2024-04-14T13:23:19+08: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_hierarchy=1, 然后执行 sudo update-grub

编辑 grub 配置

vi /etc/default/grub

内容大概是这样的:

GRUB_DEFAULT=0
GRUB_TIMEOUT_STYLE=hidden
GRUB_TIMEOUT=0
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
GRUB_CMDLINE_LINUX=""

对最后一行GRUB_CMDLINE_LINUX进行修改

GRUB_CMDLINE_LINUX="quiet splash systemd.unified_cgroup_hierarchy=1"

然后执行以下命令更新 GRUB 配置

sudo update-grub

最后查看一下启动参数,确认配置修改上了

cat /boot/grub/grub.cfg | grep "systemd.unified_cgroup_hierarchy=1"

然后就是重启

reboot

重启后查看,不出意外切换到 cgroups v2 了

root@cgroupv2:~# stat -fc %T /sys/fs/cgroup/
cgroup2fs

测试

./mydocker run -mem 10m -cpu 10 -it -name cgroupv2 busybox /bin/sh
root@mydocker:~/mydocker# ./mydocker run -mem 10m -cpu 10 -it -name cgroupv2 busybox /bin/sh
{"level":"info","msg":"createTty true","time":"2024-04-14T13:26:32+08:00"}
{"level":"info","msg":"resConf:\u0026{10m 10 }","time":"2024-04-14T13:26:32+08:00"}
{"level":"info","msg":"lower:/var/lib/mydocker/overlay2/3526930704/lower image.tar:/var/lib/mydocker/image/busybox.tar","time":"2024-04-14T13:26:32+08: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:32+08:00"}
{"level":"info","msg":"use cgroup v2","time":"2024-04-14T13:26:32+08:00"}
{"level":"info","msg":"init come on","time":"2024-04-14T13:26:32+08:00"}
{"level":"info","msg":"command all is /bin/sh","time":"2024-04-14T13:26:32+08:00"}
{"level":"info","msg":"Current location is /var/lib/mydocker/overlay2/3526930704/merged","time":"2024-04-14T13:26:32+08:00"}
{"level":"info","msg":"Find path /bin/sh","time":"2024-04-14T13:26:32+08:00"}

根据日志可知,当前使用的时 cgroup v2

{"level":"info","msg":"use cgroup v2","time":"2024-04-14T13:26:32+08: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

标签:err,level,mydocker,---,v2,cgroup,msg
From: https://www.cnblogs.com/KubeExplorer/p/18320066

相关文章

  • VMware vCenter Server 6.5 U3w (安全更新) - ESXi 集中管理软件
    VMwarevCenterServer6.5U3w(安全更新)-ESXi集中管理软件集中式控制vSphere环境请访问原文链接:https://sysin.org/blog/vmware-vcenter-6-5/,查看最新版。原创作品,转载请保留出处。作者主页:sysin.orgVMwarevCenterServer是一款高级服务器管理软件,提供了一个集中式......
  • VMware vCenter Server 6.7 U3u (安全更新) - ESXi 集中管理软件
    VMwarevCenterServer6.7U3u(安全更新)-ESXi集中管理软件集中式控制vSphere环境请访问原文链接:https://sysin.org/blog/vmware-vcenter-6-7/,查看最新版。原创作品,转载请保留出处。作者主页:sysin.orgVMwarevCenterServer是一款高级服务器管理软件,提供了一个集中式......
  • Python 检测 USB 设备 - IDLE 和 CMD 解释器之间的不同结果
    我正在尝试解决VDI解决方案中智能卡设备的USB重定向问题。我正在使用pyscard模块作为智能卡。对于进一步的上下文,主要问题是当浏览器插件调用用于处理智能卡的python脚本时,未检测到读卡器。关于问题,当我从CMD解释器运行此代码片段时,我收到空列表,表示系统上未找......
  • cv2.stereoCalibrate 是否提供了合并初始估计的方法
    这是我的代码:defstereo_calibrate(img,correspondences_left,correspondences_right,camera_matrix_L,camera_matrix_R,dist_coeffs=None):ifdist_coeffsisNone:dist_coeffs=np.zeros((4,1))#Extractimagepointsandobjectpointsfromco......
  • 在spyder-python上随机出现的这些奇怪的亮点是什么
    在此处输入图像描述每次我单击此按钮或进行任何更改时,都会创建奇怪的突出显示,当我最小化功能时更是如此。有什么建议如何摆脱这些或可能的原因是什么?谢谢!我尝试更改外观首选项中的设置,但无法影响问题。很抱歉,我无法直接查看或与Spyder界面交互。我是一个AI......
  • SQL 命令在手动运行时工作正常(SQL Developer),但在 Python 的 oracledb 模块中给出 ORA-
    我正在使用OracleSQL数据库,并且我想运行该命令ALTERSESSIONSETNLS_DATE_FORMAT='YYYY-MM-DD';当我从SQLDeveloper应用程序手动运行它时,它工作正常。但是,当我使用oracledb模块从Python运行它时,出现以下错误:ErrorrunningSQLscript:ORA-00922:mi......
  • .py-script 仅通过终端运行,而不是通过 Pycharm 的“运行”按钮运行
    我使用虚拟环境在PyCharm中创建了一个新的Python项目。这是我第一次使用虚拟环境,而且我还是Python和PyCharm的初学者。由于某种原因,Python无法再找到任何包,即使我确信我在虚拟环境中安装了scikit-learn。使用终端时,只要我使用源代码激活虚拟环境,一切都会正常工作........
  • 使用 AES-GCM 分块加密文件
    我想编写一个生成器,以给定大小的块来加密文件并一一返回块。我还想验证有效负载,因此我为此选择了AES-GCM。为什么我要分块加密而不是一次性加密整个文件?我通过网络发送这些块,因此我不是加密整个(可能很大)文件,将其存储在其他地方,然后在进行网络传输时再次对其进行分块,而是加密......
  • 使用 Python-PlexAPI 获取 plex 上所有好友的关注列表
    有关如何接收我的plex服务器上所有用户的监视列表的任何提示。我正在根据一些规则创建自动删除,其中一个规则是,如果电影位于用户观看列表中,则不应删除该电影。我遇到了麻烦,因为所有与观看列表相关的内容都在MyPlexAccount上。lexapi.myplex.MyPlexAccount具有我的用......
  • 使用 RNN 生成 PyTorch 路径 - 与输入、输出、隐藏和批量大小混淆
    我遵循了关于使用RNN生成句子的教程,并且尝试修改它以生成位置序列,但是我在定义正确的模型参数(例如input_size、output_size、hidden_​​dim、batch_size)时遇到了麻烦。背景:我有596个x,y位置序列,每个序列看起来像[[x1,y1],[x2,y2],...,[xn,yn]]。每个序列代表车......