Rancher k8s 资源管理
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 |
目前 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 |
根 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 子系统为例
通过执行
docker run -tid --memory 1G alpine
命令运行一个容器;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容器的组成
从宿主机角度看,一个运行的完整容器是以进程形式存在。运行一个容器后通过
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)。
容器与 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 中。进入
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在
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#可以看到结果是一个很大的值,表示不受限制。
查看内存限制值
进入 容器 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。验证当前 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 中。
总结
在使用 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 资源限制
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与原生 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可以确定,Pod 相关的进程会全部绑定到 kubepods cgroup 组中。
kubepods cgroup 又分为两个子 cgroup:burstable 和 besteffort。
根据 Pod 配置的资源限制参数的不同,将自动将 Pod 中的进程绑定到不同的子 cgroup 中(在 QoS 服务质量管理部分将说明 Pod 绑定子 cgroup 的逻辑)。
下文中说到的 K8S 集群资源预留,就是通过限制 docker cgroup 和 kubepods cgroup 的资源来达到整体的资源平衡。
K8S 集群资源预留
为了保证节点可以正常稳定运行,需要对节点资源进行合理的功能性划分与限制。
node-capacity (节点总资源) |
根据以上表格,可以大致把节点总的资源划分为四小块。
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 |
参数说明
--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
支持三种类型进程的资源限制:pods
,kube-reserved
,system-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-reserved
和system-reserve
的值较小,当集群负载上去之后因为资源被限制导致 K8S 基础组件运行出现异常。如果kube-reserved
和system-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 介绍 |
---|---|
BestEffort | Pod 中的所有容器都没有指定 CPU 和内存的 requests 和 limits,那么这个 Pod 的 QoS 就是 BestEffort 级别 |
Burstable | Pod 中只要有一个容器,这个容器 requests 和 limits 的设置同其他容器设置的不一致,那么这个 Pod 的 QoS 就是 Burstable 级别 |
Guaranteed | Pod 中所有容器都必须统一设置了 limits,并且设置参数都一致,如果有一个容器要设置 requests,那么所有容器都要设置,并设置参数同 limits 一致,那么这个 Pod 的 QoS 就是 Guaranteed 级别 |
资源回收策略
当 kubernetes 集群中某个节点上可用资源比较小时,kubernetes 提供了资源回收策略保证被调度到该节点 pod 服务正常运行。当节点上的内存或者 CPU 资源耗尽时,可能会造成该节点上正在运行的 pod 服务不稳定。Kubernetes 通过 kubelet 来进行回收策略控制,保证节点上 pod 在节点资源比较小时可以稳定运行。
可压缩资源:CPU
当 Pod 使用的 CPU 超过设置的
limits
值,Pod 中进程使用 CPU 会被限制,但不会被 kill。不可压缩资源:memory、storage
Kubernetes 通过 cgroup 设置 Pod QoS 级别,当资源不足时先 kill 优先级低的 Pod,在实际使用过程中,通过 OOM 分数值来实现,OOM 分数值从 0-1000。
OOM 分数值根据
OOM_ADJ
参数计算得出:Name OOM_ADJ sshd 等系统进程(sshd/dmevented / systemd-udevd) -1000 K8S 管理进程(kubelet/docker/ journalctl) -999 Guaranteed Pod -998 其它进程(内核 init 进程等) 0 Burstable Pod min(max(2, 1000 –
(1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999)BestEffort Pod 1000 OOM_ADJ 参数值越大,计算出来 OOM 分数越高,表明该 Pod 优先级就越低,当出现资源竞争时会越早被 kill 掉。对于 OOM_ADJ 参数是
-1000
的,表示永远不会因为 OOM 而被 kill 掉。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 Version | Priority and Preemption State | 默认启用 |
---|---|---|
1.8 | alpha | no |
1.9 | alpha | no |
1.10 | alpha | no |
1.11 | beta | yes |
1.14 | stable | yes |
实践
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: |
配置 Pod 优先级
PriorityClass 是一个不受命名空间约束的对象,它定义了优先级类名与优先级整数值的映射。它的名称通过 PriorityClass 对象 metadata 中的 name 字段指定。value 值越大,优先级越高。
PriorityClass 对象的值可以是小于或者等于 10 亿的 32 位整数值。更大的数值被保留给那些通常不应该取代或者驱逐的关键的系统级 Pod 使用。集群管理员应该为它们想要的每个此类映射创建一个 PriorityClass 对象。
注意: 优先级配置需要在集群基础组件配置完成后再执行。
配置 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 时使用。
设置 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 -