cgroup 简介

控制群组 (control group)(简称cgroup) 是 Linux kernel 的一项功能。从使用的角度看,cgroup 是一个目录树结构,目录中可以创建多层子目录,这些目录称为**cgroup 目录**。在一些场景中为了体现层级关系,还会称为**cgroup 子目录**

通过 cgroup 可对 CPU 时间片、系统内存、磁盘 IO、网络带宽等资源进行精细化控制,以便硬件资源可以在应用程序和用户间智能分配,从而增加整体效率。

通过将 cgroup 层级与 systemd 单位树绑定,可以把资源管理设置从进程级别转换至应用程序级别。因此,可以使用systemctl指令或通过修改 systemd 服务配置文件来管理系统资源。更多关于 systemd 相关配置请查阅附件文档。

Linux Kernel 的 cgroup 资源管控器

cgroup 资源管控器也称为 cgroup 子系统,代表一种单一资源:如 CPU 时间片或者内存。

Linux kernel 提供一系列资源管控器,由 systemd 自动挂载。如需了解目前已挂载的资源管控器列表,可通过查看文件: /proc/cgroups,或使用 lssubsys 工具查看。

centos7+、Redhat7+、Ubuntu16+ 等 以 systemd 作为进程初始化工具的系统中,默认 cgroup 由 systemd 自动挂载到 /sys/fs/cgroup 目录下。在 /sys/fs/cgroup 目录下将自动创建以下 cgroup 子系统:

root@alihost01:/sys/fs/cgroup# ll
total 0
drwxr-xr-x 15 root root 380 Jun 21 14:45 ./
drwxr-xr-x 11 root root 0 Jul 1 18:07 ../
dr-xr-xr-x 7 root root 0 Jul 1 18:07 blkio/
lrwxrwxrwx 1 root root 11 Jun 21 14:45 cpu -> cpu,cpuacct/
lrwxrwxrwx 1 root root 11 Jun 21 14:45 cpuacct -> cpu,cpuacct/
drwxr-xr-x 2 root root 40 Jun 21 14:45 cpuacct,cpu/
dr-xr-xr-x 7 root root 0 Jul 1 18:07 cpu,cpuacct/
dr-xr-xr-x 4 root root 0 Jul 1 18:07 cpuset/
dr-xr-xr-x 7 root root 0 Jul 1 18:07 devices/
dr-xr-xr-x 4 root root 0 Jul 1 18:07 freezer/
dr-xr-xr-x 4 root root 0 Jul 1 18:07 hugetlb/
dr-xr-xr-x 7 root root 0 Jun 21 18:14 memory/
lrwxrwxrwx 1 root root 16 Jun 21 14:45 net_cls -> net_cls,net_prio/
dr-xr-xr-x 4 root root 0 Jul 1 18:07 net_cls,net_prio/
lrwxrwxrwx 1 root root 16 Jun 21 14:45 net_prio -> net_cls,net_prio/
drwxr-xr-x 2 root root 40 Jun 21 14:45 net_prio,net_cls/
dr-xr-xr-x 4 root root 0 Jul 1 18:07 perf_event/
dr-xr-xr-x 7 root root 0 Jul 1 18:07 pids/
dr-xr-xr-x 7 root root 0 Jul 1 18:07 systemd/

目前 Linux 支持下面 12 种常用的 cgroup 子系统:

  • cpu (since Linux 2.6.24; CONFIG_cgroup_SCHED)
    用来限制 cgroup 的 CPU 使用率。
  • cpuacct (since Linux 2.6.24; CONFIG_cgroup_CPUACCT)
    统计 cgroup 的 CPU 的使用率。
  • cpuset (since Linux 2.6.24; CONFIG_CPUSETS)
    绑定 cgroup 到指定 CPUs 和 NUMA 节点。
  • memory (since Linux 2.6.25; CONFIG_MEMCG)
    统计和限制 cgroup 的内存的使用率,包括 process memory, kernel memory, 和 swap。
  • devices (since Linux 2.6.26; CONFIG_cgroup_DEVICE)
    限制 cgroup 创建(mknod)和访问设备的权限。
  • freezer (since Linux 2.6.28; CONFIG_cgroup_FREEZER)
    suspend 和 restore 一个 cgroup 中的所有进程。
  • net_cls (since Linux 2.6.29; CONFIG_cgroup_NET_CLASSID)
    将一个 cgroup 中进程创建的所有网络包加上一个 classid 标记,用于tc和 iptables。 只对发出去的网络包生效,对收到的网络包不起作用。
  • blkio (since Linux 2.6.33; CONFIG_BLK_cgroup)
    限制 cgroup 访问块设备的 IO 速度。
  • perf_event (since Linux 2.6.39; CONFIG_cgroup_PERF)
    对 cgroup 进行性能监控
  • net_prio (since Linux 3.3; CONFIG_cgroup_NET_PRIO)
    针对每个网络接口设置 cgroup 的访问优先级。
  • hugetlb (since Linux 3.5; CONFIG_cgroup_HUGETLB)
    限制 cgroup 的 huge pages 的使用量。
  • pids (since Linux 4.3; CONFIG_cgroup_PIDS)
    限制一个 cgroup 及其子 cgroup 中的总进程数。

注意: /sys/fs/cgroup/systemd 目录非 cgroup 子系统,是 systemd 维护的自己使用的的层级结构。

cgroup 层级结构

以 memory 子系统为例,其他子系统类似。

进入 /sys/fs/cgroup/memory 目录,可以看到以下内容:

root@ubuntu1:/sys/fs/cgroup/memory# ll
total 0
dr-xr-xr-x 5 root root 0 Jul 2 09:01 ./
drwxr-xr-x 13 root root 340 Jul 2 08:59 ../
-rw-r--r-- 1 root root 0 Jul 2 09:01 cgroup.clone_children
--w--w--w- 1 root root 0 Jul 2 09:01 cgroup.event_control
-rw-r--r-- 1 root root 0 Jul 2 09:01 cgroup.procs
-r--r--r-- 1 root root 0 Jul 2 09:01 cgroup.sane_behavior
drwxr-xr-x 2 root root 0 Jul 2 08:59 init.scope/
-rw-r--r-- 1 root root 0 Jul 2 09:01 memory.failcnt
--w------- 1 root root 0 Jul 2 09:01 memory.force_empty
-rw-r--r-- 1 root root 0 Jul 2 09:01 memory.kmem.failcnt
-rw-r--r-- 1 root root 0 Jul 2 09:01 memory.kmem.limit_in_bytes
-rw-r--r-- 1 root root 0 Jul 2 09:01 memory.kmem.max_usage_in_bytes
-r--r--r-- 1 root root 0 Jul 2 09:01 memory.kmem.slabinfo
-rw-r--r-- 1 root root 0 Jul 2 09:01 memory.kmem.tcp.failcnt
-rw-r--r-- 1 root root 0 Jul 2 09:01 memory.kmem.tcp.limit_in_bytes
-rw-r--r-- 1 root root 0 Jul 2 09:01 memory.kmem.tcp.max_usage_in_bytes
-r--r--r-- 1 root root 0 Jul 2 09:01 memory.kmem.tcp.usage_in_bytes
-r--r--r-- 1 root root 0 Jul 2 09:01 memory.kmem.usage_in_bytes
-rw-r--r-- 1 root root 0 Jul 2 09:01 memory.limit_in_bytes
-rw-r--r-- 1 root root 0 Jul 2 09:01 memory.max_usage_in_bytes
-rw-r--r-- 1 root root 0 Jul 2 09:01 memory.memsw.failcnt
-rw-r--r-- 1 root root 0 Jul 2 09:01 memory.memsw.limit_in_bytes
-rw-r--r-- 1 root root 0 Jul 2 09:01 memory.memsw.max_usage_in_bytes
-r--r--r-- 1 root root 0 Jul 2 09:01 memory.memsw.usage_in_bytes
-rw-r--r-- 1 root root 0 Jul 2 09:01 memory.move_charge_at_immigrate
-r--r--r-- 1 root root 0 Jul 2 09:01 memory.numa_stat
-rw-r--r-- 1 root root 0 Jul 2 09:01 memory.oom_control
---------- 1 root root 0 Jul 2 09:01 memory.pressure_level
-rw-r--r-- 1 root root 0 Jul 2 09:01 memory.soft_limit_in_bytes
-r--r--r-- 1 root root 0 Jul 2 09:01 memory.stat
-rw-r--r-- 1 root root 0 Jul 2 09:01 memory.swappiness
-r--r--r-- 1 root root 0 Jul 2 09:01 memory.usage_in_bytes
-rw-r--r-- 1 root root 0 Jul 2 08:59 memory.use_hierarchy
-rw-r--r-- 1 root root 0 Jul 2 09:01 notify_on_release
-rw-r--r-- 1 root root 0 Jul 2 09:01 release_agent
drwxr-xr-x 64 root root 0 Jul 2 08:59 system.slice/
-rw-r--r-- 1 root root 0 Jul 2 09:01 tasks
drwxr-xr-x 3 root root 0 Jul 2 08:59 user.slice/
  • 根 cgroup

    虽然 /sys/fs/cgroup/memory 属于 cgroup 子系统,但它也是当前子系统的 根 cgroup,所以在 /sys/fs/cgroup/memory 中可以看到相应的配置文件,并且根 cgroup 不支持资源限制。

  • 子 cgroup

    /sys/fs/cgroup/memory 目录(根 cgroup)中看到的文件夹,比如 system.slice,叫做 子 cgroup

    进入 system.slice 目录可以发现,子 cgroup 与根 cgroup 拥有相同的配置文件。如果要限制内存最大使用量,可通过配置子 cgroup 的 memory.limit_in_bytes 进行限制,默认为-1不做限制。子 cgroup 中还可以创建子 cgroup,达到资源的更细化控制。

  • 创建子 cgroup

    可以在 /sys/fs/cgroup/memory 目录中,通过 mkdir 来创建文件夹,从而创建子 cgroup,子 cgroup 中配置文件将会自动生成。通过 mkdir 创建的子 cgroup 是临时的,重启主机后子 cgroup 将会丢失

  • 实践

    不建议对顶级 cgroup 做资源限制,这样会导致其他子 cgroup 资源限制受影响。建议根据应用类型创建不同的子 cgroup,把应用绑定在不同的子 cgroup 中。

  • 配置文件说明

    • cgroup.clone_children
      这个文件只对 cpuset 子系统有影响,当该文件的内容为 1 时,新创建的 cgroup 将会继承父 cgroup 的配置,即从父 cgroup 里面拷贝配置文件来初始化新 cgroup,可以参考这里

    • cgroup.procs
      当前 cgroup 中的所有进程 PID,可以手动把进程 PID 添加到当前 cgroup.procs 中,以实现进程与 cgroup 绑定。 系统不保证进程 PID 是顺序排列的,且进程 PID 有可能重复

    • cgroup.sane_behavior
      具体功能不详,可以参考这里

    • notify_on_release
      该文件的内容为 1 时,当 cgroup 退出时(不再包含任何进程和子 cgroup),将调用 release_agent 里面配置的命令。新 cgroup 被创建时将默认继承父 cgroup 的这项配置。

    • release_agent
      里面包含了 cgroup 退出时将会执行的命令,系统调用该命令时会将相应 cgroup 的相对路径当作参数传进去。 注意:这个文件只会存在于 root cgroup 下面,其他 cgroup 里面不会有这个文件。

    • tasks
      当前 cgroup 中的所有线程 ID,当 PID 被添加到当前的 cgroup.procs 时,会自动把对应的线程添加到当前 tasks 中。系统不保证线程 ID 是顺序排列的。

      更多文件说明可以查看附件文档的附录 A。

原生 Docker 容器资源限制

注意: 以下内容均以 memory 子系统为例

  1. 通过执行 docker run -tid --memory 1G alpine 命令运行一个容器;

  2. docker 在创建容器时,会在每个 cgroup 子系统的根 cgroup 目录下自动创建 docker cgroup 目录。比如:

    root@ubuntu1:/sys/fs/cgroup/memory# ll
    total 0
    dr-xr-xr-x 6 root root 0 Jul 2 09:08 ./
    drwxr-xr-x 13 root root 340 Jul 2 08:59 ../
    -rw-r--r-- 1 root root 0 Jul 2 09:01 cgroup.clone_children
    --w--w--w- 1 root root 0 Jul 2 09:01 cgroup.event_control
    -rw-r--r-- 1 root root 0 Jul 2 09:01 cgroup.procs
    -r--r--r-- 1 root root 0 Jul 2 09:01 cgroup.sane_behavior
    drwxr-xr-x 3 root root 0 Jul 2 09:08 docker/
    drwxr-xr-x 2 root root 0 Jul 2 09:01 init.scope/
    -rw-r--r-- 1 root root 0 Jul 2 09:01 memory.failcnt
    --w------- 1 root root 0 Jul 2 09:01 memory.force_empty
    -rw-r--r-- 1 root root 0 Jul 2 09:01 memory.kmem.failcnt
  3. 容器的组成

    从宿主机角度看,一个运行的完整容器是以进程形式存在。运行一个容器后通过 ps -ef 可以查看进程相互依赖关系:

    root       1311   1230  0 09:01 pts/0    00:00:00 -bash
    root 1406 1 0 09:08 ? 00:00:06 /usr/bin/dockerd -H fd://
    root 1414 1406 0 09:08 ? 00:00:12 docker-containerd --config /var/run/docker/containerd/containerd.toml
    root 2727 1414 0 09:30 ? 00:00:00 docker-containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/ 47cc04ac55619ec7358123c71b89a5a5ba77dd9e584e8c1af58e4cc12e0f47a9 -addr
    root 2762 2727 0 09:30 pts/0 00:00:00 /bin/sh
    root@ubuntu1:/sys/fs/cgroup/memory/docker#

    2762 为容器中的进程,其父进程 2727 为 docker-containerd-shim 进程,docker-containerd-shim 由 docker-containerd 管理,其父进程为 1414(docker-containerd)。

  4. 容器与 cgroup 的绑定关系

    通过执行 cd /sys/fs/cgroup/memory; systemd-cgls 可以查看到主机上所有应用进程与 cgroup 的关系。

    ├─memory
    │ ├─docker
    │ │ └─47cc04ac55619ec7358123c71b89a5a5ba77dd9e584e8c1af58e4cc12e0f47a9
    │ │ └─2762 /bin/sh
    │ ├─system.slice
    │ │ ├─mdadm.service
    │ │ ├─rsyslog.service
    │ │ │ └─920 /usr/sbin/rsyslogd -n
    │ │ ├─docker.service
    │ │ │ ├─1406 /usr/bin/dockerd -H fd://
    │ │ │ ├─1414 docker-containerd --config /var/run/docker/containerd/containerd.toml
    │ │ │ └─2727 docker-containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/47cc04ac55619ec7358123c71b89a5a5ba77dd9e584e8c1af58e4cc12e0f47a9 -address /var/run/docker/containerd/docker
    │ │ ├─lxcfs.service
    │ │ │ └─892 /usr/bin/lxcfs /var/lib/lxcfs/
    │ │ └─acpid.service
    │ │ └─905 /usr/sbin/acpid
    │ └─user.slice

    通过 systemd-cgls 可以发现,docker-containerd-shim 被绑定在 system.slice cgroup 中,可以理解为它是属于 dcoker 系统级的进程,而容器中应用进程则绑定到 docker cgroup 中。

  5. 进入 docker cgroup 目录,可以看到以容器 ID 为名创建的 cgroup 目录和其他配置文件;

    root@ubuntu1:/sys/fs/cgroup/memory/docker# ll
    total 0
    drwxr-xr-x 3 root root 0 Jul 2 09:30 ./
    dr-xr-xr-x 6 root root 0 Jul 2 09:08 ../
    drwxr-xr-x 2 root root 0 Jul 2 09:40 47cc04ac55619ec7358123c71b89a5a5ba77dd9e584e8c1af58e4cc12e0f47a9/
    -rw-r--r-- 1 root root 0 Jul 2 09:11 cgroup.clone_children
    --w--w--w- 1 root root 0 Jul 2 09:11 cgroup.event_control
    -rw-r--r-- 1 root root 0 Jul 2 09:11 cgroup.procs
    -rw-r--r-- 1 root root 0 Jul 2 09:11 memory.failcnt
  6. docker cgroup 目录中,执行 cat memory.limit_in_bytes

    root@ubuntu1:/sys/fs/cgroup/memory/docker# cat memory.limit_in_bytes
    9223372036854771712
    root@ubuntu1:/sys/fs/cgroup/memory/docker#

    可以看到结果是一个很大的值,表示不受限制。

  7. 查看内存限制值

    进入 容器 ID 命名的 cgroup 目录,执行 cat memory.limit_in_bytes

    root@ubuntu1:/sys/fs/cgroup/memory/docker/47cc04ac55619ec7358123c71b89a5a5ba77dd9e584e8c1af58e4cc12e0f47a9# cat memory.limit_in_bytes
    1073741824
    root@ubuntu1:/sys/fs/cgroup/memory/docker/47cc04ac55619ec7358123c71b89a5a5ba77dd9e584e8c1af58e4cc12e0f47a9#

    因为在启动容器的时候有添加内存限制参数 --memory 1G,所以这里的值正好是 1G。

  8. 验证当前 cgroup 绑定的进程 PID

    • 验证应用进程 PID

      在当前子 cgroup 目录下,执行 cat cgroup.procs

      root@ubuntu1:/sys/fs/cgroup/memory/docker/47cc04ac55619ec7358123c71b89a5a5ba77dd9e584e8c1af58e4cc12e0f47a9# cat cgroup.procs
      2762
      root@ubuntu1:/sys/fs/cgroup/memory/docker/47cc04ac55619ec7358123c71b89a5a5ba77dd9e584e8c1af58e4cc12e0f47a9#

      得到的进程 PID 正好是容器中应用进程的 PID,从而验证了第 4 步 容器与 cgroup 的绑定关系 中返回的结果。

    • 验证 docker-containerd-shim PID

      多运行几个容器,然后通过 ps -ef 确定 docker-containerd-shim 的 PID 号。

      接着执行 cat /sys/fs/cgroup/memory/system.slice/docker.service/cgroup.procs 查看 PID 号。

      可以发现 docker-containerd-shim 进程 PID 全部被绑定到 docker.service 子 cgroup 中。

  9. 总结

    • 在使用 docker 创建容器时,会自动在每个 cgroup 子系统 中创建 docker cgroup 目录,docker cgroup 目录默认不做资源限制。然后会以容器 ID 为名称,在 docker cgroup 目录下创建 子 cgroup 目录,假设docker run 的时候添加了内存限制参数(–memory ),那么会 自动修改以容器 ID 命名的 cgroup 目录 下的memory.limit_in_bytes 文件,这样就实现了对单个容器最大内存使用的限制。

    • 因为是以容器 ID 为名称创建的子 cgroup 目录,所以所有的子 cgroup 不会冲突,并且对一个子 cgroup 做资源限制,不会影响其他子 cgroup。每个容器中的所有进程将会绑定在以当前容器 ID 命名的子 cgroup 组中,容器 docker-containerd-shim 进程 PID 将会统一绑定在 /sys/fs/cgroup/memory/system.slice/docker.service cgroup 组中。

    • 根据上面的逻辑,如果想控制所有容器进程内存使用量不超过预期值,那么只需要配置 docker cgroup 资源使用量即可。

      cd /sys/fs/cgroup/memory/docker
      echo '10G' > memory.limit_in_bytes

Kubernets Pod 资源限制

  1. kubelet 在创建 Pod 时,如果没有通过参数 --cgroup-root(参数使用后续讲解)指定顶级 cgroup 组,那么会自动在 cgroup 子系统根 cgroup 中创建 kubepods cgroup 目录。

    root@ubuntu1:/sys/fs/cgroup/memory# ll
    total 0
    dr-xr-xr-x 7 root root 0 Jul 2 16:29 ./
    drwxr-xr-x 15 root root 380 Jul 2 16:29 ../
    -rw-r--r-- 1 root root 0 Jul 2 09:01 cgroup.clone_children
    --w--w--w- 1 root root 0 Jul 2 09:01 cgroup.event_control
    -rw-r--r-- 1 root root 0 Jul 2 09:01 cgroup.procs
    -r--r--r-- 1 root root 0 Jul 2 09:01 cgroup.sane_behavior
    drwxr-xr-x 8 root root 0 Jul 2 16:29 docker/
    drwxr-xr-x 2 root root 0 Jul 2 09:01 init.scope/
    drwxr-xr-x 4 root root 0 Jul 2 16:30 kubepods/
    -rw-r--r-- 1 root root 0 Jul 2 09:01 memory.failcnt
    --w------- 1 root root 0 Jul 2 09:01 memory.force_empty
    -rw-r--r-- 1 root root 0 Jul 2 09:01 memory.kmem.failcnt
  2. 与原生 Docker 容器相似,通过执行 cd /sys/fs/cgroup/memory; systemd-cgls 查询 Pod 与 cgroup 绑定关系.

    root@ubuntu1:/sys/fs/cgroup/memory# cd /sys/fs/cgroup/memory; systemd-cgls
    Working directory /sys/fs/cgroup/memory:
    ├─docker
    │ ├─8f2e3c82eb801f692c889b9d6b84b1a7c245c3d782798ef60e1e23f58e5ed130
    │ │ └─5188 /usr/local/bin/etcd --peer-client-cert-auth --client-cert-auth --advertise-client-urls=https://1.1.1.128:2379,https://1.1.1.128:4001 --listen-client-urls=https://0.0.0.0:2379 --trusted-ca-file=/etc/kubernetes/ssl/kube-ca.pem --
    │ ├─38241e69344a759d5e1c54dc2f74c5d2323d123f896f03fdb80fb971d060ce17
    │ │ └─6291 kubelet --serialize-image-pulls=false --registry-qps=0 --allow-privileged=true --authentication-token-webhook=true --read-only-port=0 --cluster-domain=cluster.local --kube-reserved=cpu=0.25,memory=2000Mi --cni-conf-dir=/etc/cni
    │ ├─0ada8f8d47dc91a9e07e4e533bab36f83eabd3d427c89b65be91e7302fbc30a9
    │ │ └─kube-proxy
    │ │ └─6818 kube-proxy --hostname-override=1.1.1.128 --kubeconfig=/etc/kubernetes/ssl/kubecfg-kube-proxy.yaml --v=2 --healthz-bind-address=127.0.0.1 --cluster-cidr=10.42.0.0/16
    ├─system.slice
    │ ├─mdadm.service
    │ │ └─975 /sbin/mdadm --monitor --pid-file /run/mdadm/monitor.pid --daemonise --scan --syslog
    │ ├─rsyslog.service
    │ │ └─920 /usr/sbin/rsyslogd -n
    │ ├─docker.service
    │ │ ├─3465 /usr/bin/dockerd -H fd://
    │ │ ├─3474 docker-containerd --config /var/run/docker/containerd/containerd.toml
    │ │ ├─5170 docker-containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/8f2e3c82eb801f692c889b9d6b84b1a7c245c3d782798ef60e1e23f58e5ed130 -address /var/run/docker/containerd/docker-c
    │ │ ├─5414 docker-containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/bb8280628aca338887460df574cb947f6045bc31dd8bbf107722aa7534f2c07d -address /var/run/docker/containerd/docker-c
    │ │ ├─5699 docker-containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/3d71cb60793053da0456db1882b87290877f477a6b2f4dd58244ed9c53b4a30a -address /var/run/docker/containerd/docker-c
    │ │ ├─5989 docker-containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/d58f02925fac52add81d765e2c34ea54ed59ddfc2cacc545e029a790e5344d76 -address /var/run/docker/containerd/docker-c
    │ │ ├─6274 docker-containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/38241e69344a759d5e1c54dc2f74c5d2323d123f896f03fdb80fb971d060ce17 -address /var/run/docker/containerd/docker-c
    │ │ ├─6800 docker-containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/0ada8f8d47dc91a9e07e4e533bab36f83eabd3d427c89b65be91e7302fbc30a9 -address /var/run/docker/containerd/docker-c
    └─kubepods
    ├─burstable
    │ ├─pod87a169c0-9ca3-11e9-a530-000c29fe6663
    │ │ ├─57dcc6eb699c8a3ffbbe79ff5500a165dd200b7be6faec8f4298c4bea11a00db
    │ │ │ └─8133 /pause
    │ │ ├─082df6b4c1e5bc1778755061a0d9d3fac044c51720bdadd6b1218acb749ce7d0
    │ │ │ └─8459 /kube-dns --domain=cluster.local. --dns-port=10053 --config-dir=/kube-dns-config --v=2
    │ │ ├─2d398ff9b51acf2abca20490c77650236b1b7960d7f8bb3f565d22b53aa45d40
    │ │ │ ├─8555 /dnsmasq-nanny -v=2 -logtostderr -configDir=/etc/k8s/dns/dnsmasq-nanny -restartDnsmasq=true -- -k --cache-size=1000 --log-facility=- --server=/cluster.local/127.0.0.1#10053 --server=/in-addr.arpa/127.0.0.1#10053 --server=/i
    │ │ │ └─8643 /usr/sbin/dnsmasq -k --cache-size=1000 --log-facility=- --server=/cluster.local/127.0.0.1#10053 --server=/in-addr.arpa/127.0.0.1#10053 --server=/ip6.arpa/127.0.0.1#10053
    │ │ └─c97d22a0f7d7ae03139f9409ae4e9f81d2a9731c3c2622fdebe1474109d6e7ed
    │ │ └─8620 /sidecar --v=2 --logtostderr --probe=kubedns,127.0.0.1:10053,kubernetes.default.svc.cluster.local,5,A --probe=dnsmasq,127.0.0.1:53,kubernetes.default.svc.cluster.local,5,A
    └─besteffort
    └─pod8b3e50ed-9ca3-11e9-a530-000c29fe6663
    ├─6ee56058fb4fd0a4a4aaa184af867eb51b053f26587e8e5614512f23c518fd3c
    │ └─8942 /metrics-server --kubelet-insecure-tls --kubelet-preferred-address-types=InternalIP --logtostderr
    └─5586cf42f50a251536af28e58e540b4192780063e1f6eacb7c9efbe0e0ca9856
    └─8740 /pause
  3. 可以确定,Pod 相关的进程会全部绑定到 kubepods cgroup 组中。

  4. kubepods cgroup 又分为两个子 cgroup:burstable 和 besteffort。

    根据 Pod 配置的资源限制参数的不同,将自动将 Pod 中的进程绑定到不同的子 cgroup 中(在 QoS 服务质量管理部分将说明 Pod 绑定子 cgroup 的逻辑)。

    下文中说到的 K8S 集群资源预留,就是通过限制 docker cgroupkubepods cgroup 的资源来达到整体的资源平衡。

K8S 集群资源预留

为了保证节点可以正常稳定运行,需要对节点资源进行合理的功能性划分与限制。

      node-capacity (节点总资源)
--------------------------------------------
| kube-reserved (kube 组件预留资源) |
|-----------------------------------------|
| system-reserved (系统服务预留资源) |
|-----------------------------------------|
| eviction-threshold (驱逐阈值) |
|-----------------------------------------|
| allocatable (Pod 可分配) |
--------------------------------------------

根据以上表格,可以大致把节点总的资源划分为四小块。

  • Node-Capacity

    节点总的资源。

  • Kube-Reserved

    给 k8s 系统组件预留的资源(包括 kubelet、kube-apiserver、kube-scheduler 等)。

  • System-Reserved

    给 Linux 系统进程(kernel、sshd、Dockerd 等)预留的资源。

  • Eviction-Threshold

    硬驱逐阈值,当节点可用内存值低于此值时,kubelet 会进行 Pod 的驱逐。

  • Allocatable

    真正可供节点上 Pod 使用的资源总容量,kube-scheduler调度 Pod 时参考此值(kubectl describe node 可查看),节点上所有 Pods 的资源请求值(request)不超过 Allocatable。

    可通过一个公式计算可供 Pod 使用资源总量:

    [Allocatable] = [Node-Capacity] - [Kube-Reserved] - [System-Reserved] - [Eviction-Threshold]

    从公式可以看出,如果不设置kube-reserved、system-reserved、Hard-Eviction-Threshold,节点上可以让 Pod 使用的资源总量等于节点的总资源量。如果不做资源划分与限制,Pod 与宿主机系统进程以及 k8s 系统组件争抢资源,导致主机资源耗尽出现异常,例如常见的 Docker 运行卡顿、ssh 无法连接、K8S 节点未就绪 (NotReady)。

配置参数

kubelet 的启动参数中涉及资源预留的主要有以下几个:

--cgroups-per-qos
--cgroup-driver
--cgroup-root
--enforce-node-allocatable
--kube-reserved
--kube-reserved-cgroup
--system-reserved
--system-reserved-cgroup
--eviction-hard
--eviction-soft
--eviction-soft-grace-period
--eviction-max-pod-grace-period
--eviction-pressure-transition-period

参数说明

  • --cgroups-per-qos

    默认开启(true)

    开启这个参数后,所有 Pod 的 cgroup 都将挂载到 kubelet 管理的 cgroup 目录下。要想启用节点资源限制,必须开启此参数。

  • --cgroup-driver

    指定 kubelet 使用的 cgroup driver,默认 cgroupfs,可以选择 systemd,这个值需要与 Docker Runtime 所使用的 cgroup Driver 保持一致。rke1 创建的集群,因为是以容器运行的 kubelet 无法调用 systemd ,所以这个值一定要为 cgroupfs

  • --cgroup-root

    指定 Pod 使用的顶级 cgroup,默认为空,即把 Pod cgroup 挂载到根 cgroup 下,建议默认为空。这个 cgroup 组就是前面说到的 kubepods 组,默认 kubelet 会自动创建。如果不想使用默认的 cgroup,则需要先手动创建 cgroup,不然 kubelet 无法启动。

  • --kube-reserved

    为 kube 系统组件预留的资源值,这个值只是用于调度计算,并不是实际限制。

    示例配置:--kube-reserved=cpu=1,memory=1Gi,ephemeral-storage=10Gi

  • --kube-reserved-cgroup

    用于 kube 系统组件资源限制的 cgroup 组,如果要对 kube 系统组件做资源限制则需要配置这个 cgroup 组。rke 集群环境中,K8S 系统核心组件均以原生 docker 容器运行,那么其绑定的 cgroup 组为 docker。所以,如果是 rancher 创建的集群或者 RKE 创建的集群,这个参数需要配置为 /docker

    注意,这里指定的 cgroup 及其子系统需要预先创建好,kubelet 不会自动创建。如果配置为 /docker,docker 已经自动创建 docker cgroup,则不需要再手动创建。

  • --system-reserved

    为宿主机系统组件预留的资源值,这个值只是用于调度计算,并不是实际限制。

    示例配置:--system-reserved=cpu=1,memory=1Gi,ephemeral-storage=10Gi

  • --system-reserved-cgroup

    用于宿主机系统组件资源限制的 cgroup 组,如果要对宿主机系统组件做资源限制则需要配置这个 cgroup 组。建议不配置这个参数,它会使用默认的 cgroup 组。

    注意,这里指定的 cgroup 及其子系统需要预先创建好,kubelet 不会自动创建。

  • --enforce-node-allocatable

    这个参数可以理解为资源限制的开关。

    前面说到的 --kube-reserved--system-reserved 仅用于调度计算,当配置了这两个参数后,也就告诉 调度器 kube 系统组件和宿主机系统服务已经预留了一部分资源,调度器 会根据 Allocatable 计算公式计算出可供 Pod 调度的实际资源值。

    但在未做 资源限制 的情况下,Pod 实际使用的资源是可以超过 Pod 可调度的资源值。如果要保证 Pod 实际使用不会超过 Allocatable 计算的实际可调度的资源,则需要通过 --enforce-node-allocatable 开启资源限制功能。

    --enforce-node-allocatable 支持三种类型进程的资源限制:podskube-reservedsystem-reserve

    这三种类型可以同时选择或者只选择其中一种或者多种。资源限制 功能通过宿主机的 cgroup 来实现,不管选择哪一种类型,都需要指定对应的 cgroup 组,并且 cgroup 组需要预先创建好,kubelet 不会自动创建,如果配置的 cgroup 组不存在,则 kubelet 启动会报错。

    假设设置 --enforce-node-allocatable=pod,kube-reserved,system-reserved,参数配置好之后 kubelet 将会把 --kube-reserved--system-reserved 配置的预留值写入 --kube-reserved-cgroup--system-reserved-cgroup 对应 cgroup 组的 memory.limit_in_bytes 文件中。Pod 进程对应的 cgroup 组默认为 kubepods,执行 cat /sys/fs/cgroup/memory/kubepods/memory.limit_in_bytes 可以发现限制的值正好为 node-capacity - kube-reserved - system-reserved

    根据以上配置就把整个节点资源精确划分为三部分来使用。但是实际应用中发现,如果设置 kube-reservedsystem-reserve 的值较小,当集群负载上去之后因为资源被限制导致 K8S 基础组件运行出现异常。如果 kube-reservedsystem-reserve 设置的值较大,相应的 Pod 能使用的资源又会较少,而 K8S 系统组件也不会一直使用那么多资源,从而造成不必要的资源浪费。

    在实际应用中,建议只限制 Pod 的资源,即配置 --enforce-node-allocatable=pod。这样就不会把 --kube-reserved--system-reserved 配置的预留值写入 --kube-reserved-cgroup--system-reserved-cgroup 对应 cgroup 组的 memory.limit_in_bytes 文件中。

  • --eviction-soft

    软驱逐阈值。

    为了避免资源压力导致系统不稳定,当 节点总资源 - kube 系统组件预留 - 宿主机系统服务预留 - Pod 实际使用资源 小于软驱逐阈值的时候,kubelet 触发驱逐 Pod 的信号。

    • --eviction-soft-grace-period

      超过软驱逐阈值时并不会立即执行驱逐,它会等待 --eviction-soft-grace-period 配置的时间。在这段时间内,kubelet 每 10s 会重新获取监控数据,如果最后一次获取的数据仍然触发了驱逐阈值,最后才会执行 Pod 驱逐。

    • --eviction-max-pod-grace-period

      强制驱逐 Pod 宽限期。

      驱逐 Pod 时会先发送 SIGTERM 信号给 Pod,然后 Pod 再发送 SIGTERM 信号给容器并等待容器停止运行,默认等待30s。如果在这段时间内容器没有退出,则 kubelet 会发送SIGKILL信号强制删除 Pod,通过--eviction-max-pod-grace-period 可以指定 Pod 终止的宽限时间。我们也可以通过 pod.Spec.TerminationGracePeriodSeconds 配置 Pod 终止的宽限时间,Rancher 部署的应用默认为 30S。如果配置了 pod.Spec.TerminationGracePeriodSeconds--eviction-max-pod-grace-period,将会取两者最小值作为 Pod 最终终止时间。

  • --eviction-hard

    硬驱逐阈值。

    硬驱逐阈值与软驱逐阈值类似,硬驱逐阈值没有缓冲时间,当 节点总资源 - kube 系统组件预留 - 宿主机系统服务预留 - Pod 实际使用资源 小于硬驱逐阈值的时候将会立即执行驱逐,没有等待时间,强制执行 KILL Pod。

    (Pods 驱逐顺序下文会继续说明)

Pod QoS 服务质量管理

QoS 的英文全称为 Quality of Service ,中文名为”服务质量”。QOS 实现资源有效调度和分配,从而提高资源利用率。kubernetes 针对不同服务的预期资源要求,通过 QoS(Quality of Service)来对 Pod 进行服务质量管理。

对于 Pod 来说,服务质量体现在两个指标上:一个指标是 CPU,另一个指标是内存。

如果未对资源进行限制,一些以 Pod 运行的关键服务进程,可能因为内存资源紧张触发 OOM 而被系统 kill 掉,或者被限制 CPU 使用导致进程被暂停。在 kubernetes 中,每个 Pod 都有个 QoS 标记,通过这个 Qos 标记来对 Pod 进行服务质量管理。在实际运行过程中,当节点资源紧张的时候,kubernetes 根据 Pod 具有的不同 QoS 标记,采取不同的处理策略。

已知问题: QOS 目前不支持 swap,所有 QoS 策略基于 swap 禁止的基础上。

QOS 级别

QoS 级别QoS 介绍
BestEffortPod 中的所有容器都没有指定 CPU 和内存的 requests 和 limits,那么这个 Pod 的 QoS 就是 BestEffort 级别
BurstablePod 中只要有一个容器,这个容器 requests 和 limits 的设置同其他容器设置的不一致,那么这个 Pod 的 QoS 就是 Burstable 级别
GuaranteedPod 中所有容器都必须统一设置了 limits,并且设置参数都一致,如果有一个容器要设置 requests,那么所有容器都要设置,并设置参数同 limits 一致,那么这个 Pod 的 QoS 就是 Guaranteed 级别

资源回收策略

当 kubernetes 集群中某个节点上可用资源比较小时,kubernetes 提供了资源回收策略保证被调度到该节点 pod 服务正常运行。当节点上的内存或者 CPU 资源耗尽时,可能会造成该节点上正在运行的 pod 服务不稳定。Kubernetes 通过 kubelet 来进行回收策略控制,保证节点上 pod 在节点资源比较小时可以稳定运行。

  1. 可压缩资源:CPU

    当 Pod 使用的 CPU 超过设置的 limits 值,Pod 中进程使用 CPU 会被限制,但不会被 kill。

  2. 不可压缩资源:memory、storage

    Kubernetes 通过 cgroup 设置 Pod QoS 级别,当资源不足时先 kill 优先级低的 Pod,在实际使用过程中,通过 OOM 分数值来实现,OOM 分数值从 0-1000。

    OOM 分数值根据 OOM_ADJ 参数计算得出:

    NameOOM_ADJ
    sshd 等系统进程(sshd/dmevented / systemd-udevd)-1000
    K8S 管理进程(kubelet/docker/ journalctl)-999
    Guaranteed Pod-998
    其它进程(内核 init 进程等)0
    Burstable Podmin(max(2, 1000 –
    (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999)
    BestEffort Pod1000

    OOM_ADJ 参数值越大,计算出来 OOM 分数越高,表明该 Pod 优先级就越低,当出现资源竞争时会越早被 kill 掉。对于 OOM_ADJ 参数是-1000的,表示永远不会因为 OOM 而被 kill 掉。

  3. QoS Pods 驱逐顺序

    如果节点资源不足要驱逐 Pod 或 OOM Kill 进程,将按以下顺序进行驱逐:

    • Best-Effort 类型:该类型 Pods 会最先被驱逐或者被 Kill;
    • Burstable 类型:在没有 Best-Effort Pod 可以被驱逐时,该类型 Pods 会被驱逐或者 kill 掉(其中较大预留但资源使用较少的 Pod 会最后被驱逐或者 Kill)。
    • Guaranteed 类型:系统用完了全部内存、且没有 Burstable 与 Best-Effort container 可以被 kill,该类型的 Pods 会被 kill 掉。

    注:如果 Pod 进程因使用超过 limites 值而非 Node 资源紧张导致的 Kill,系统倾向于在原节点上重启该 Container,或在原节点或者其他节点重新创建一个 Pod。

Pod 优先级

RKE-Kubernets 集群核心组件以原生 docker 容器运行,因为主机资源固定,那么可以通过kubepods cgroup限制应用 Pod 进程使用的资源最大量,从而保证 RKE-Kubernets 集群核心组件和宿主机系统服务不受资源不足的影响。

但是有一些 Kubernets 系统组件,比如 DNS,它们也是以 Pod 方式运行并绑定在kubepods cgroup中。如果其他应用 Pod 进程使用了kubepods cgroup限制的最大内存资源,将会触发系统OOM或者因为资源紧张导致服务运行不正常。

根据 Pod QoS 服务质量 的特性,在节点资源不足时,会先驱逐优先级最低的 Pod。因此,为了保证 Kubernets 系统组件的正常运行防止被驱逐,需要提升 Kubernets 系统组件的优先级。

Kubernets 具有提高 Pod 优先级的功能,从 1.8 到 1.10 版本,默认没有开启 Pod 优先级和抢占。为了启用该功能,需要在 API server 和 scheduler 的启动参数中设置:

--feature-gates=PodPriority=true

在 API server 中还需要设置如下启动参数:

--runtime-config=scheduling.k8s.io/v1alpha1=true

Pod 优先级指明 pod 的相对重要程度。在 1.9 之前的版本中,如果 pod 因为资源问题无法调度,则 kubernetes 尝试抢占低优先级 pod 资源,将它们排挤掉,为高优先级 pod 提供运行条件。

在 1.9 及之后的版本中,pod 优先级会影响 pod 的调度顺序及当节点资源不足时的驱逐顺序。即调度时优先部署高优先级 pod,当节点资源不足时先行驱逐低优先级 pod。

在 1.11 之前的版本中,pod 优先级是 alpha 特性,在 1.11 版本中变成 beta 特性,并保证在后续版本继续支持。alpha 版本中默认禁止,需要明确打开,beta 版本默认打开,关系如下表:

Kubernetes VersionPriority and Preemption State默认启用
1.8alphano
1.9alphano
1.10alphano
1.11betayes
1.14stableyes

实践

docker.service 配置

对于 CentOS 系统,docker.service 默认位于/usr/lib/systemd/system/docker.service

对于 Ubuntu 系统,docker.service 默认位于/lib/systemd/system/docker.service

编辑docker.service,添加以下参数。

  • 防止 docker 服务被 OOM KILL

    docker 服务属于整个容器平台的核心基础服务。在宿主机系统内存不足时会触发 OOM KILL,docker 服务不是系统服务,因此 docker 服务进程很可能会被系统 KILL。为了防止 docker 进程被 KILL,可以在docker.service中配置 OOMScoreAdjust=-1000以禁止被 OOM KILL。

  • 防止 docker 服务内存溢出

    docker 服务有时候出现异常,会出现占用很多内存资源的情况。为了防止 docker 服务占用整个节点资源,需要对服务做内存限制。在 docker.service 中添加 MemoryLimit=xxG以限制 docker 服务使用最大内存。

  • 开启 iptables 转发链

    因为目前是通过 iptables 进行转发通信,而 iptables FORWARD 链默认是丢弃模式(Chain FORWARD (policy DROP)。为了保证通信正常,在启动 docker 前自动把iptables FORWARD链打开。

    ExecStartPost=/usr/sbin/iptables -P FORWARD ACCEPT (centos)

    ExecStartPost=/sbin/iptables -P FORWARD ACCEPT (ubuntu)

  • docker 推荐配置

    mkdir -p /etc/docker/
    touch /etc/docker/daemon.json

    cat > /etc/docker/daemon.json <<EOF
    {
    "log-driver": "json-file",
    "log-opts": {
    "max-size": "100m",
    "max-file": "10"
    },
    "oom-score-adjust": -1000,
    "max-concurrent-downloads": 10,
    "max-concurrent-uploads": 10,
    "registry-mirrors": ["https://7bezldxe.mirror.aliyuncs.com"],
    "storage-driver": "overlay2",
    "storage-opts":["overlay2.override_kernel_check=true"]
    }
    EOF

    systemctl daemon-reload && systemctl restart docker

RKE 配置 Kubernetes 集群资源预留

rke 参考配置文件,rke 版本大于等于 v0.2.4

nodes:
- address: <节点 IP>
user: <user>
role:
- controlplane
- etcd
- worker

ignore_docker_version: false
ssh_key_path: <修改为实际路径>
ssh_agent_auth: false

#private_registries:
# - url: registry.cn-shanghai.aliyuncs.com
## user:
## password:
# is_default: true

cluster_name: demo

services:
etcd:
## rke 版本大于等于 0.2.x 或 rancher 版本大于等于 2.2.0 时使用
backup_config:
enabled: true
interval_hours: 12
retention: 6
quota-backend-bytes: '4294967296'
auto-compaction-retention: 240 #(单位小时)
kube-api:
service_cluster_ip_range: 10.43.0.0/16
service_node_port_range: 30000-32767
pod_security_policy: false
extra_args:
audit-log-path: "-"
delete-collection-workers: 3
v: 4
kube-controller:
cluster_cidr: 10.42.0.0/16
service_cluster_ip_range: 10.43.0.0/16
extra_args:
## 控制器定时与节点通信以检查通信是否正常,周期默认 5s
node-monitor-period: '5s'
## 当节点通信失败后,再等一段时间 kubernetes 判定节点为 notready 状态。
## 这个时间段必须是 kubelet 的 nodeStatusUpdateFrequency(默认 10s)的 N 倍,
## 其中 N 表示允许 kubelet 同步节点状态的重试次数,默认 40s。
node-monitor-grace-period: '20s'
## 再持续通信失败一段时间后,kubernetes 判定节点为 unhealthy 状态,默认 1m0s。
node-startup-grace-period: '30s'
## 再持续失联一段时间,kubernetes 开始迁移失联节点的 Pod,默认 5m0s。
pod-eviction-timeout: '1m'
kubelet:
cluster_domain: cluster.local
cluster_dns_server: 10.43.0.10
fail_swap_on: false
extra_args:
## 修改节点最大 Pod 数量
max-pods: "250"
## 密文和配置映射同步时间,默认 1 分钟
sync-frequency: '3s'
## Kubelet 进程可以打开的文件数(默认 1000000),根据节点配置情况调整
max-open-files: '2000000'
## 与 apiserver 会话时的并发数,默认是 10
kube-api-burst: '30'
## 与 apiserver 会话时的 QPS,默认是 5
kube-api-qps: '15'
## kubelet 默认一次拉取一个镜像,设置为 false 可以同时拉取多个镜像,
## 前提是存储驱动要为 overlay2,对应的 Dokcer 也需要增加下载并发数
serialize-image-pulls: 'false'
## 拉取镜像的最大并发数,registry-burst 不能超过 registry-qps ,
## 仅当 registry-qps 大于 0(零)时生效,(默认 10)。如果 registry-qps 为 0 则不限制(默认 5)。
registry-burst: '10'
registry-qps: '0'

cgroups-per-qos: 'true'
# 这里一定要为 cgroupfs
cgroup-driver: 'cgroupfs'

enforce-node-allocatable: 'pods,kube-reserved' # 'pods,kube-reserved,system-reserved'
system-reserved: 'cpu=1,memory=500Mi'
# 根据实际资源调整
kube-reserved: 'cpu=1,memory=1Gi'
# RKE 集群需要设置为`/docker`
kube-reserved-cgroup: '/docker'

# 设置进行 pod 驱逐的阈值,这个参数只支持内存和磁盘。通过--eviction-hard 标志预留一些内存后,当 Allocatable 可用内存降至保留值以下时,kubelet 将会对 pod 进行驱逐。
## 硬阈值,当值小于 100M 就成触发驱逐
eviction-hard: 'memory.available<100Mi'
# 软阈值,当可用值小于 500Mi,
eviction-soft: 'memory.available<500Mi'
eviction-soft-grace-period: 'memory.available=1m30s'

eviction-max-pod-grace-period: '30'
eviction-pressure-transition-period: '30s'

extra_binds:
- "/usr/libexec/kubernetes/kubelet-plugins:/usr/libexec/kubernetes/kubelet-plugins"

# Currently, only authentication strategy supported is x509.
# You can optionally create additional SANs (hostnames or IPs) to add to
# the API server PKI certificate.
# This is useful if you want to use a load balancer for the control plane servers.
authentication:
strategy: "x509|webhook"
sans:
# 此处配置备用域名或 IP,当主域名或者 IP 无法访问时,可通过备用域名或 IP 访问
- "192.168.1.100"
- "www.test.com"

# Kubernetes 认证模式
## Use `mode: rbac` 启用 RBAC
## Use `mode: none` 禁用 认证
authorization:
mode: rbac

# Add-ons are deployed using kubernetes jobs. RKE will give up on trying to get the job status after this timeout in seconds..
addon_job_timeout: 30

# 有几个网络插件可以选择:`flannel、canal、calico`,Rancher2 默认 canal
network:
plugin: canal
options:
canal_iface: eth0
flannel_backend_type: "vxlan"

# 目前只支持 nginx ingress controller
## 可以设置`provider: none`来禁用 ingress controller

ingress:
provider: nginx
node_selector:
app: ingress
# 配置 dns 上游 dns 服务器
## 可用 rke 版本 v0.2.0
dns:
provider: kube-dns
upstreamnameservers:
- 114.114.114.114
- 1.2.4.8

配置 Pod 优先级

PriorityClass 是一个不受命名空间约束的对象,它定义了优先级类名与优先级整数值的映射。它的名称通过 PriorityClass 对象 metadata 中的 name 字段指定。value 值越大,优先级越高。

PriorityClass 对象的值可以是小于或者等于 10 亿的 32 位整数值。更大的数值被保留给那些通常不应该取代或者驱逐的关键的系统级 Pod 使用。集群管理员应该为它们想要的每个此类映射创建一个 PriorityClass 对象。

注意: 优先级配置需要在集群基础组件配置完成后再执行。

  1. 配置 PriorityClass

    apiVersion: scheduling.k8s.io/v1beta1
    kind: PriorityClass
    metadata:
    name: high-priority-system-pod
    value: 1000000000
    globalDefault: false
    preemptionPolicy: PreemptLowerPriority
    description: "This priority class should be used for XYZ service pods only."

    metadata.name:名称
    value:小于或者等于 10 亿的 32 位任意整数值,数字越大优先级越高,超过一亿的数字被系统保留,用于指派给系统组件。
    globalDefault:是否应用于全局 pod 策略
    description:描述信息
    preemptionPolicy: 抢占功能。设置为 Never 表示不抢占,默认为 PreemptLowerPriority,1.15 之后默认禁用抢占功能。
    • 如果升级现有集群启用此功能,那些已经存在系统里面的 Pod 的优先级将会设置为 0。
    • 此外,将一个PriorityClass的 globalDefault 设置为 true,不会改变系统中已经存在的 Pod 的优先级。也就是说,PriorityClass 的值只能用于在 PriorityClass 添加之后创建的那些 Pod 。
    • 如果您删除一个 PriorityClass,那些使用了该 PriorityClass 的 Pod 将会保持不变,但是,该 PriorityClass 的名称不能在新创建 Pod 时使用。
  2. 设置 Pod priority

    在配置 Pod 时,设置其priorityClass Name字段为可用 PriorityClass 名称,则在创建 Pod 时由允入控制器将名称转换成对应数字。如果没有找到相应的 PriorityClass,Pod 将会被拒绝创建。示例如下:

    APP_NS=kube-system

    APP=canal
    WORKLOAD_TYPE=daemonsets
    kubectl -n $APP_NS get $WORKLOAD_TYPE $APP -o json |jq '.spec.template.spec += {"priorityClassName": "high-priority-system-pod"}' | kubectl -n $APP_NS apply -f -

    APP=kube-dns
    WORKLOAD_TYPE=deployments
    kubectl -n $APP_NS get $WORKLOAD_TYPE $APP -o json |jq '.spec.template.spec += {"priorityClassName": "high-priority-system-pod"}' | kubectl -n $APP_NS apply -f -

    APP=kube-dns-autoscaler
    WORKLOAD_TYPE=deployments
    kubectl -n $APP_NS get $WORKLOAD_TYPE $APP -o json |jq '.spec.template.spec += {"priorityClassName": "high-priority-system-pod"}' | kubectl -n $APP_NS apply -f -

    APP=metrics-server
    WORKLOAD_TYPE=deployments
    kubectl -n $APP_NS get $WORKLOAD_TYPE $APP -o json |jq '.spec.template.spec += {"priorityClassName": "high-priority-system-pod"}' | kubectl -n $APP_NS apply -f -

    ##################
    APP_NS=cattle-system

    APP=rancher # 仅 local 集群执行
    WORKLOAD_TYPE=deployments
    kubectl -n $APP_NS get $WORKLOAD_TYPE $APP -o json |jq '.spec.template.spec += {"priorityClassName": "high-priority-system-pod"}' | kubectl -n $APP_NS apply -f -

    APP=cattle-cluster-agent
    WORKLOAD_TYPE=deployments
    kubectl -n $APP_NS get $WORKLOAD_TYPE $APP -o json |jq '.spec.template.spec += {"priorityClassName": "high-priority-system-pod"}' | kubectl -n $APP_NS apply -f -

    APP=cattle-node-agent
    WORKLOAD_TYPE=daemonsets
    kubectl -n $APP_NS get $WORKLOAD_TYPE $APP -o json |jq '.spec.template.spec += {"priorityClassName": "high-priority-system-pod"}' | kubectl -n $APP_NS apply -f -

    ##################
    APP_NS=ingress-nginx

    APP=nginx-ingress-controller
    WORKLOAD_TYPE=daemonsets
    kubectl -n $APP_NS get $WORKLOAD_TYPE $APP -o json |jq '.spec.template.spec += {"priorityClassName": "high-priority-system-pod"}' | kubectl -n $APP_NS apply -f -

    ##################
    # 启用集群监控和告警的集群,执行力以下命令
    APP_NS=cattle-prometheus

    APP=prometheus-cluster-monitoring
    WORKLOAD_TYPE=statefulsets
    kubectl -n $APP_NS get $WORKLOAD_TYPE $APP -o json |jq '.spec.template.spec += {"priorityClassName": "high-priority-system-pod"}' | kubectl -n $APP_NS apply -f -

    APP=alertmanager-cluster-alerting
    WORKLOAD_TYPE=statefulsets
    kubectl -n $APP_NS get $WORKLOAD_TYPE $APP -o json |jq '.spec.template.spec += {"priorityClassName": "high-priority-system-pod"}' | kubectl -n $APP_NS apply -f -