Cgroups

Linux Cgroups 的全称是 Linux Control Group,它最主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。Cgroups 还能够对进程进行优先级设置、审计,以及将进程挂起和恢复等操作。
在 Linux 中,Cgroups 给用户暴露出来的操作接口是文件系统,即它以文件和目录的方式组织在操作系统的 /sys/fs/cgroup 路径下。

  1. root@ubuntu-xenial:~# mount -t cgroup
  2. cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd)
  3. cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
  4. cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
  5. cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
  6. cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
  7. cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
  8. cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
  9. cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
  10. cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
  11. cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
  12. cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)

这些都是这台机器当前可以被 Cgroups 进行限制的资源种类。

而在子系统对应的资源种类下,你就可以看到该类资源具体可以被限制的方法。比如,对 CPU 子系统来说,我们就可以看到如下几个配置文件,这个指令是:

  1. [root@node1 ~]# ls /sys/fs/cgroup/cpu
  2. cgroup.clone_children cpuacct.stat cpuacct.usage_percpu cpuacct.usage_sys cpu.cfs_quota_us cpu.shares release_agent
  3. cgroup.procs cpuacct.usage cpuacct.usage_percpu_sys cpuacct.usage_user cpu.rt_period_us cpu.stat tasks
  4. cgroup.sane_behavior cpuacct.usage_all cpuacct.usage_percpu_user cpu.cfs_period_us cpu.rt_runtime_us notify_on_release

在对应的子系统下面创建一个目录,比如,我们现在进入 /sys/fs/cgroup/cpu 目录下:

  1. [root@node1 cpu]# mkdir container
  2. [root@node1 cpu]# cd container
  3. [root@node1 container]# ls
  4. cgroup.clone_children cpu.cfs_quota_us cpu.shares cpuacct.usage cpuacct.usage_percpu_sys cpuacct.usage_user
  5. cgroup.procs cpu.rt_period_us cpu.stat cpuacct.usage_all cpuacct.usage_percpu_user notify_on_release
  6. cpu.cfs_period_us cpu.rt_runtime_us cpuacct.stat cpuacct.usage_percpu cpuacct.usage_sys tasks

这个目录就称为一个“控制组”。操作系统会在新创建的 container 目录下,自动生成该子系统对应的资源限制文件。

cfs_quota_us实现对CPU的限制

后台执行这样一条脚本:

  1. $ while : ; do : ; done &[1]
  2. 226

它执行了一个死循环,可以把计算机的一个CPU核心 吃到 100%,根据它的输出,我们可以看到这个脚本在后台运行的进程号(PID)是 226。

  1. $ cat /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us
  2. -1
  3. $ cat /sys/fs/cgroup/cpu/container/cpu.cfs_period_us
  4. 100000

向 container 组里的 cfs_quota 文件写入 20 ms(20000 us)

  1. echo 20000 > /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us

把被限制的进程的 PID 写入 container 组里的 tasks 文件,上面的设置就会对该进程生效了。

  1. $ echo 226 > /sys/fs/cgroup/cpu/container/tasks

可写入多个PID rmdir container 可以执行成功,rm -rf container 会提示 Operation not permitted

除 CPU 子系统外,Cgroups 的每一个子系统都有其独有的资源限制能力,比如:

  • blkio,为块设备设定I/O 限制,一般用于磁盘等设备;
  • cpuset,为进程分配单独的 CPU 核和对应的内存节点;
  • memory,为进程设定内存使用的限制。

docker下手动自定义cgroup参数

对于 Docker 等 Linux 容器项目来说,它们只需要在每个子系统下面,为每个容器创建一个控制组(即创建一个新目录),然后在启动容器进程之后,把这个进程的 PID 填写到对应控制组的 tasks 文件中就可以了。
而至于在这些控制组下面的资源文件里填上什么值,就靠用户执行 docker run 时的参数指定了,比如:

  1. $ docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bas

启动这个容器后,可以通过查看 Cgroups 文件系统下,CPU 子系统中,“docker”这个控制组里的资源限制文件的内容来确认:

  1. $ cat /sys/fs/cgroup/cpu/docker/5d5c9f67d/cpu.cfs_period_us
  2. 100000
  3. $ cat /sys/fs/cgroup/cpu/docker/5d5c9f67d/cpu.cfs_quota_us
  4. 20000

/proc问题

Linux 下的 /proc 目录存储的是记录当前内核运行状态的一系列特殊文件,用户可以通过访问这些文件,查看系统以及当前正在运行的进程的信息,这些文件也是 top 指令查看系统信息的主要数据来源。但是,如果在容器里执行 top 指令,、它显示的信息居然是宿主机的 CPU 和内存数据,而不是当前容器的数据。
造成这个问题的原因就是,/proc 文件系统并不知道用户通过 Cgroups 给这个容器做了什么样的资源限制,即:/proc 文件系统不了解 Cgroups 限制的存在。
在生产环境中,这个问题必须进行修正,否则应用程序在容器里读取到的 CPU 核数、可用内存等信息都是宿主机上的数据。

lxcfs

补充

  1. windows docker技术的原理又是什么样的

—> 从官网上看,也是依赖hyper-v的虚拟机化技术

  1. 如何实现不同容器不同操作系统?
  2. 容器磁盘无法限制(IOPS与大小)?
  3. 容器的文件在宿主机的文件目录?

docker info | grep -w “Root Dir”

  1. 为什么k8s 在创建pod 时候 只有对cpu 和内存的限制 没有网络和磁盘io限制的选项?

网络和IO限制比较难实施

  1. 容器里调用 settimeofday 修改时间,会造成宿主机修改时间。 前提是容器要有相关的权限—privileged 或者 —cap-add SYS_TIME
  2. systemd 或者 supervisord 这样的软件来代替应用本身作为容器的启动进程。

—> 更好的是在前台运行。

  1. 容器内部还能再做namespace和cgroup实现docker的嵌套。
  2. 容器和宿主机共用内核。

因为容器和宿主机共用内核,那么内核调用的方法应该是统一的;所以这里的lib应该是指内核调用方法。文件系统中看到的lib是这些调用方法的实现,不同操作系统不能共用。我是这么理解的,不知道是否正确。

  1. 容器是一个“单进程”模型。这个特点 是K8s用Pod管理容器运行时的主要原因
  2. 容器和应用能够同生命周期