500行代码手写docker-实现硬件资源限制cgroups

这篇具有很好参考价值的文章主要介绍了500行代码手写docker-实现硬件资源限制cgroups。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

(5)500行代码手写docker-实现硬件资源限制cgroups

本系列教程主要是为了弄清楚容器化的原理,纸上得来终觉浅,绝知此事要躬行,理论始终不及动手实践来的深刻,所以这个系列会用go语言实现一个类似docker的容器化功能,最终能够容器化的运行一个进程。

本章的源码已经上传到github,地址如下:

https://github.com/HobbyBear/tinydocker/tree/chapter5

之前我们对容器的网络命名空间,文件系统命名空间都进行了配置,说到底这些都是为了资源更好的隔离,但是他们无法办到对硬件资源使用的隔离,比如,cpu,内存,带宽,而今天要介绍的cgroups技术便能够对硬件资源的使用产生隔离。

cgroups技术简介

cgroups技术是内核提供的功能,可以通过虚拟文件系统接口对其进行访问和更改。mount 命令可以查看cgroups在虚拟文件系统下的挂载目录。

root@ecs-295280:~# mount | grep  cgroup
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
cgroup2 on /sys/fs/cgroup/unified type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
root@ecs-295280:~#

一般默认的挂载目录是在/sys/fs/cgroup 目录下,系统内核在开机时,会默认挂载cgroup目录。这样便能通过访问文件的方式对cgroup功能进行使用。

在/sys/fs/cgroup/ 目录下,我们看到的每个目录例如cpu,blkio被称作subsystem子系统,每个子系统下可以设置各自要管理的进程id。

root@ecs-295280:~# ls /sys/fs/cgroup/
blkio    cpu,cpuacct  freezer  net_cls           perf_event  systemd
cpu      cpuset       hugetlb  net_cls,net_prio  pids        unified
cpuacct  devices      memory   net_prio          rdma

拿cpu这个目录下的文件举例

root@ecs-295280:/sys/fs/cgroup/cpu# ls
cgroup.clone_children  cpuacct.usage_percpu_sys   cpu.stat
cgroup.procs           cpuacct.usage_percpu_user  ebpf-agent
cgroup.sane_behavior   cpuacct.usage_sys          hostguard
cpuacct.stat           cpuacct.usage_user         notify_on_release
cpuacct.usage          cpu.cfs_period_us          release_agent
cpuacct.usage_all      cpu.cfs_quota_us           tasks
cpuacct.usage_percpu   cpu.shares
root@ecs-295280:/sys/fs/cgroup/cpu# ll -l

在cpu子系统这个目录下,有两个文件cgroup.procs,tasks文件,它们都是用来管理cgroup中的进程。但是,它们的使用方式略有不同:

cgroup.procs文件用于向cgroup中添加或删除进程,只需要将进程的task id写入该文件即可。

tasks文件则是用于将整个进程组添加到cgroup中。如果将一个进程组的pid写入tasks文件,则该进程组中的所有进程都会被添加到cgroup中。

进程被加入到这个cgroup组以后,其使用的cpu带宽将会受到cpu.cfs_quota_us和cpu.cfs_period_us的影响。通过shell命令查看他们的内容。

root@ecs-295280:/sys/fs/cgroup/cpu/test# cat cpu.cfs_period_us
100000
root@ecs-295280:/sys/fs/cgroup/cpu/test# cat cpu.cfs_quota_us
-1

默认情况下,cpu.cfs_period_us是100000,单位是微秒,cpu.cfs_period_us代表了cpu运行一个周期的时长,100000代表了100ms,cpu.cfs_quota_us代表进程所占用的周期时长,-1代表不限制进程使用cpu周期时长,如果cpu.cfs_quota_us是50000(50ms)则代表在cpu一个调度周期内,该cgroup下的进程最多只能运行半个周期,如果达到了运行周期的限制,那么它必须等待下一个时间片才能继续运行了。

命名行实践下cgroups隔离特性

我们来实验下:

对cpu使用率进行限制

在cpu的一级目录下,是包含了当前系统所有进程,为了不影响它们,我们在cpu的一级目录下创建一个test目录,然后单独的在test目录中的tasks文件加入进程id。

📢📢 ❗️cgroup的每个子系统是分级的,这个级别体现在目录层级上,默认子目录会继承父目录的属性,子目录也可以通过修改子目录下的文件,来覆盖掉父目录的属性。

root@ecs-295280:/sys/fs/cgroup/cpu/test# pwd
/sys/fs/cgroup/cpu/test

设置cpu.cfs_quota_us为一个时间片的一半,设置tasks,把当前进程加入到cgroup中

root@ecs-295280:/sys/fs/cgroup/cpu/test# cat cpu.cfs_quota_us
50000
root@ecs-295280:/sys/fs/cgroup/cpu/test# sh -c "echo $$ > tasks"
root@ecs-295280:/sys/fs/cgroup/cpu/test# cat tasks
65961
66314

在当前shell 界面,通过stress对cpu进行压力测试。我的虚拟机是一个核,我这里直接通过stress对这一个cpu核进行压测。

root@ecs-295280:/sys/fs/cgroup/cpu/test# stress --cpu 1 --timeout 60

启动另一个终端,查看cpu占用情况

Tasks:  94 total,   2 running,  92 sleeping,   0 stopped,   0 zombie
%Cpu(s): 51.9 us,  0.0 sy,  0.0 ni, 48.1 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   1982.9 total,    451.3 free,    193.4 used,   1338.2 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.   1597.9 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  66333 root      20   0    3856    100      0 R  50.3   0.0   0:06.00 stress
      1 root      20   0  102780  12420   8236 S   0.0   0.6   0:05.93 systemd

可以看到的是cpu占用率在达到百分之50时就不上去了,这正是由于stress进程是bash进程的子进程,继承了bash进程的cgroup,所以cpu使用率受到了限制。

对内存使用率进行限制

再来看看如何通过cgroup对内存进行限制,这次我们就应该进入到memory这个子系统的目录了,同样我们在其下面创建一个test目录。

root@ecs-295280:/sys/fs/cgroup/memory# mkdir test
root@ecs-295280:/sys/fs/cgroup/memory# cd test/
root@ecs-295280:/sys/fs/cgroup/memory/test# pwd
/sys/fs/cgroup/memory/test

然后把当前进程加进去

root@ecs-295280:/sys/fs/cgroup/memory/test# sh -c "echo $$ > tasks"
root@ecs-295280:/sys/fs/cgroup/memory/test# cat tasks
65961
66476

设置最大使用内存,memory目录下限制最大使用内存需要设置memory.limit_in_bytes 这个文件,默认情况下,它是一个大的离谱的值,我们将它改为100M

root@ecs-295280:/sys/fs/cgroup/memory/test# cat memory.limit_in_bytes
9223372036854771712
root@ecs-295280:/sys/fs/cgroup/memory/test# vim memory.limit_in_bytes
root@ecs-295280:/sys/fs/cgroup/memory/test# cat memory.limit_in_bytes
104857600

这个时候通过stress 对内存进行压力测试,我们限制了100M,但是如果stress要求分配200M内存,看看能正常分配吗?

root@ecs-295280:/sys/fs/cgroup/memory/test# stress --vm-bytes 200m --vm-keep  -m 1
stress: info: [66533] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: FAIL: [66533] (415) <-- worker 66534 got signal 9
stress: WARN: [66533] (417) now reaping child worker processes
stress: FAIL: [66533] (451) failed run completed in 0s

可以看到的是,程序崩溃了,原因则是由于发生了oom,因为内存已经被我们限制到了100M,通过test目录下的memory.oom_control文件可以看到发生oom的次数。

oom_kill_disable 0
under_oom 0
oom_kill 1

oom_kill 为1代表发生oom后,进程被kill掉的次数。

在简单看完cgroup如何对cpu和内存进行限制以后,看看golang代码如何实现。

golang代码实现cgroups配置

在用代码对cgroup的操作本质上就是对cgroup的文件进行操作。

cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		err = cmd.Start()
		if err != nil {
			fmt.Println(err)
		}

		containerName := os.Args[2]
		if err := cgroups.ConfigDefaultCgroups(cmd.Process.Pid, containerName); err != nil {
			log.Error("config cgroups fail %s", err)
		}

		if err := network.ConfigDefaultNetworkInNewNet(cmd.Process.Pid); err != nil {
			log.Error("config network fail %s", err)
		}
		cmd.Wait()
		cgroups.CleanCgroupsPath(containerName)

在前面代码的基础上,启动子进程后,父进程把子进程pid添加到一个新的cgroup中,cgroups.ConfigDefaultCgroups方法用于实现对cgroup的控制,以容器名作为cgroup子系统的目录,然后当子进程容器执行完毕后,通过cgroups.CleanCgroupsPath去对cgroup相关目录进行清理。

func CleanCgroupsPath(containerName string) error {
	output, err := exec.Command("cgdelete", "-r", fmt.Sprintf("memory:%s/%s", dockerName, containerName)).Output()
	if err != nil {
		log.Error("cgdelete fail err=%s output=%s", err, string(output))
	}
	output, err = exec.Command("cgdelete", "-r", fmt.Sprintf("cpu:%s/%s", dockerName, containerName)).Output()
	if err != nil {
		log.Error("cgdelete fail err=%s output=%s", err, string(output))
	}
	return nil
}

清理cgroup的方式我用了cgdelete 命令 删除掉容器cgroup的配置,直接remove删除会出现删除失败情况。

总结

这也是我对于手写容器系列的终章,算是对容器原理的一个入门级讲解,其实后续还可以针对它做很多优化,比如实现不同主机上的容器互联,实现容器日志的功能,实现端口映射,实现卷映射功能,这些功能其实都是建立在我们讲的容器原理之上的,懂了原理便能一通百通,希望能给你带来启发。文章来源地址https://www.toymoban.com/news/detail-463089.html

到了这里,关于500行代码手写docker-实现硬件资源限制cgroups的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 500行代码代码手写docker-将rootfs设置为只读镜像

    500行代码代码手写docker-将rootfs设置为只读镜像

    本系列教程主要是为了弄清楚容器化的原理,纸上得来终觉浅,绝知此事要躬行,理论始终不及动手实践来的深刻,所以这个系列会用go语言实现一个类似docker的容器化功能,最终能够容器化的运行一个进程。 本章的源码已经上传到github,地址如下: 前文提到,如果仅仅将u

    2024年02月06日
    浏览(13)
  • 500行代码手写docker-以新命名空间运行程序

    500行代码手写docker-以新命名空间运行程序

    本系列教程主要是为了弄清楚容器化的原理,纸上得来终觉浅,绝知此事要躬行,理论始终不及动手实践来的深刻,所以这个系列会用go语言实现一个类似docker的容器化功能,最终能够容器化的运行一个进程。 本章的源码已经上传到github,地址如下: 本章要完成的任务则是g

    2024年02月05日
    浏览(15)
  • 500行代码手写docker开篇-goland远程编译环境配置

    500行代码手写docker开篇-goland远程编译环境配置

    本系列教程主要是为了弄清楚容器化的原理,纸上得来终觉浅,绝知此事要躬行,理论始终不及动手实践来的深刻,所以这个系列会用go语言实现一个类似docker的容器化功能,最终能够容器化的运行一个进程。 代码最终运行效果 本系列源码已经上传到github,地址如下: 在开始

    2024年02月05日
    浏览(16)
  • 使用cgroup工具对服务器某些/全部用户进行计算资源限制

    使用cgroup工具对服务器某些/全部用户进行计算资源限制

    主要介绍,如何对指定/所有用户进行资源限定(这里主要介绍cpu和内存占用限制),防止某些用户大量占用服务器计算资源,影响和挤占他人正常使用服务器。 安装 cgroup 管理工具 使用 mount -t cgroup 命令检查验证 可以通过编写 /etc/cgconfig.conf 和 /etc/cgrules.conf 文件进行计算资

    2024年02月10日
    浏览(11)
  • Docker资源控制cgroups

    Docker资源控制cgroups

    Docker 通过 Cgroup 来控制容器使用的资源配额,包括 CPU、内存、磁盘三大方面, 基本覆盖了常见的资源配额和使用量控制。 Cgroup 是 ControlGroups 的缩写,是 Linux 内核提供的一种可以限制、记录、隔离进程组所使用的物理资源(如 CPU、内存、磁盘 IO 等等) 的机制,被 LXC、docker 等

    2024年02月10日
    浏览(9)
  • 【云原生】Docker Cgroups资源控制管理

    【云原生】Docker Cgroups资源控制管理

    目录 一、cgroups简介 cgroups有四大功能: 二、cpu时间片的概念 三、对CPU使用的限制 3.1 设置CPU使用率上限 (1)查看容器的默认CPU使用限制 (2)进行压力测试 (3)创建容器时设置CPU使用时间限制 (4)对已存在的容器进行CPU限制 3.2 设置CPU资源占用比(设置多个容器时才有效

    2024年02月12日
    浏览(13)
  • 十七、Docker之Cgroup资源配置

    其实在日常的工作中,我们一般都没有对docker容器进行资源限制,也就是默认情况下,可以使用宿主机的所有资源。但是如果你运行的服务有问题,就有可能对宿主机和宿主机上的其他业务造成影响,这还是有一定的风险。那么本文会给大家介绍一下如何对容器进行资源配置

    2024年02月16日
    浏览(11)
  • docker cgroup资源占用及docker的镜像创建

    docker cgroup资源占用及docker的镜像创建

    基本复写了常见的资源配额和使用量控制 cgroup是controlgroup的缩写 设置cpu使用率的上限 linux通过cfs(完全公平调度器)来调度各个进程对cpu的使用,cfs默认的调度周期是100ms 我们可以设置每个容器进程的调度周期,以及再这个周期内各个容器最多能使用cpu时间。 cpu分多少时间

    2024年02月08日
    浏览(12)
  • 【云原生】Docker网络及Cgroup资源控制

    【云原生】Docker网络及Cgroup资源控制

    Docker使用Linux桥接,在宿主机虚拟一个Docker容器网桥(docker0),Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址,称为Container-IP,同时Docker网桥是每个容器的默认网关。因为在同一宿主机内的容器都接入同一个网桥,这样容器之间就能够通过容器的 Container-IP 直

    2024年02月16日
    浏览(16)
  • linux 内核资源配置--cgroups详解以及在docker中的应用

    linux 内核资源配置--cgroups详解以及在docker中的应用

    1.1、cgroups 是什么 Linux cgroup (Control Groups)是 Linux 内核提供的一种机制, 用于限制进程组使用的资源(如 CPU、内存、磁盘 I/O 等) 。通过将进程组划分为层次结构,并将资源限制应用于不同层次的组,可以实现对系统资源的统一管理和限制。 cgroup 提供了一套 API,用于创建

    2024年02月16日
    浏览(14)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包