浅析Kubernetes资源管理-资源预留

1. 情况描述

Kubernetes的系统资源分为可压缩资源(CPU)和不可压缩资源(memory、storage)。可压缩资源(比如CPU)在系统满负荷时会划分时间片分时运行进程,通常情况下系统整体会变慢;不可压缩资源(如Memory)在系统满负荷时,严重时会导致某些进程被系统OOM killer机制杀掉。

默认情况下,kubelet没有做资源预留限制,这样节点上的所有资源都能被Pod使用。若节点上的pod负载较大,这些pod可能会与节点上的系统守护进程和k8s组件争夺资源,严重时甚至会引发系统OOM而杀掉一些进程。若被杀掉的进程是系统进程或K8S组件,可能导致更严重的问题,甚至会导致集群的雪崩。比如:集群内某一节点跑的pod没做resource limit使得占用资源过大,进而导致kubelet缺乏资源挂掉,此时该节点变为NotReady状态,kubernetes会将这个节点上所有的pod驱逐到其他正常的节点上重建,进而将另外正常的节点压跨,以此循环下去,直至集群内所有节点都会NotReady。

针对这种问题,kubernetes提供了kubelet的Node Allocatable特性,为系统进程和k8s组件预留资源(官方文档)

2. 资源预留简介

2.1 Node Allocatable

kubelet的启动配置中有一个Node Allocatable特性,来为系统守护进程和k8s组件预留计算资源,使得即使节点满负载运行时,也不至于出现pod去和系统守护进程以及k8s组件争抢资源,导致节点挂掉的情况。目前支持对CPU, memory, ephemeral-storage三种资源进行预留。kubernetes官方建议根据各个节点的负载情况来具体配置相关参数。

节点计算资源的分配如下图所示:

 Node Capacity
---------------------------
|     kube-reserved       |
|-------------------------|
|     system-reserved     |
|-------------------------|
|    eviction-threshold   |
|-------------------------|
|                         |
|      allocatable        |
|   (available for pods)  |
|                         |
---------------------------

其中各个部分的含义如下:

  • Node Capacity:Node的硬件资源总量;
  • kube-reserved:为k8s系统进程预留的资源(包括kubelet、container runtime等,不包括以pod形式的资源);
  • system-reserved:为linux系统守护进程预留的资源;
  • eviction-threshold:通过--eviction-hard参数为节点预留内存;
  • allocatable:可供节点上Pod使用的容量,kube-scheduler调度Pod时的参考此值。

节点可供Pod使用资源总量的计算公式:

allocatable = NodeCapacity - [kube-reserved] - [system-reserved] - [eviction-threshold]

从公式可以看出,默认情况下(不设置kube-reserved、system-reserved、eviction-threshold)节点上提供给Pod使用的资源总量等于节点的总容量。

2.2 参数含义及配置

Kubelet Node Allocatable的代码比较简单,主要在pkg/kubelet/cm/node_container_manager.go,感兴趣的同学可以看一下。以下是相关配置参数:

  • --enforce-node-allocatable,默认为pods(默认情况下,kubelet会为所有pod的总cgroup做资源限制,限制为公式计算出的allocatable的大小)。要为kube组件和System进程预留资源,则需要设置为pods,kube-reserved,system-reserve,同时还要分别加上--kube-reserved-cgroup和--system-reserved-cgroup以指定分别限制在哪个cgroup里;

  • --cgroups-per-qos,Enabling QoS and Pod level cgroups,默认开启。开启后,kubelet会将管理所有workload Pods的cgroups;

  • --cgroup-driver,默认为cgroupfs,另一可选项为systemd。取决于容器运行时使用的cgroup driver,kubelet与其保持一致;

  • --kube-reserved,用于配置为kube组件(kubelet,kube-proxy,dockerd等)预留的资源量,比如—kube-reserved=cpu=2000m,memory=8Gi,ephemeral-storage=16Gi;

  • --kube-reserved-cgroup,如果设置了--kube-reserved,需设置对应的cgroup,且该cgroup目录要事先创建好,否则kubelet将不会自动创建导致kubelet启动失败。比如设置为kube-reserved-cgroup=/kubelet.service;

  • --system-reserved,用于配置为System进程预留的资源量,比如—system-reserved=cpu=2000m,memory=4Gi,ephemeral-storage=8Gi;

  • --system-reserved-cgroup,如果设置了--system-reserved,需设置对应的cgroup,且该cgroup目录要事先创建好,否则kubelet将不会自动创建导致kubelet启动失败。比如设置为system-reserved-cgroup=/system.slice。

  • --eviction-hard,用来配置kubelet的hard eviction条件,只支持memory和ephemeral-storage两种不可压缩资源。当出现MemoryPressure时,Scheduler不会调度新的Best-Effort QoS Pods到此节点。当出现DiskPressure时,Scheduler不会调度任何新Pods到此节点。

3. 配置与验证

针对pod、system、kube均做cgroup级别限制,需要进行以下配置:

  1. 在kubelet的启动参数中添加:
--enforce-node-allocatable=pods,kube-reserved,system-reserved \
--cgroup-driver=cgroupfs \
--kube-reserved=cpu=1,memory=1Gi,ephemeral-storage=10Gi \
--kube-reserved-cgroup=/system.slice/kubelet.service \
--system-reserved cpu=1,memory=2Gi,ephemeral-storage=10Gi \
--system-reserved-cgroup=/system.slice \

设置cgroup结构可参考官方建议

  1. 为system.slice、kubelet.service创建cpuset子系统:
    未创建前system.slice这个cgroup是没有cpuset子系统的,而kubelet(1.9)启动时会去查看这些cgroup子系统是否存在,如果不存在会报相应的cgroup错误。
// Exists checks if all subsystem cgroups already exist
func (m *cgroupManagerImpl) Exists(name CgroupName) bool {
	// Get map of all cgroup paths on the system for the particular cgroup
	cgroupPaths := m.buildCgroupPaths(name)

	// the presence of alternative control groups not known to runc confuses
	// the kubelet existence checks.
	// ideally, we would have a mechanism in runc to support Exists() logic
	// scoped to the set control groups it understands.  this is being discussed
	// in https://github.com/opencontainers/runc/issues/1440
	// once resolved, we can remove this code.
	whitelistControllers := sets.NewString("cpu", "cpuacct", "cpuset", "memory", "systemd")

	// If even one cgroup path doesn't exist, then the cgroup doesn't exist.
	for controller, path := range cgroupPaths {
		// ignore mounts we don't care about
		if !whitelistControllers.Has(controller) {
			continue
		}
		if !libcontainercgroups.PathExists(path) {
			return false
		}
	}

	return true
}

所以需要手工创建相应cpuset子系统:

$ sudo mkdir -p /sys/fs/cgroup/cpuset/system.slice
$ sudo mkdir -p /sys/fs/cgroup/cpuset/system.slice/kubelet.service
  1. 重启kubelet后,可以验证(以内存为例):通过公式计算、节点实际capacity及allocatable的值(kubectl describe node xxx)、kubepods控制组中对内存的限制值(/sys/fs/cgroup/memory/kubepods/memory.limit_in_bytes)均与预期相符。

并且,system.slice(/sys/fs/cgroup/memory/system.slice/memory.limit_in_bytes)、kubelet.service(/sys/fs/cgroup/memory/system.slice/kubelet.service/memory.limit_in_bytes)控制组对内存的限制值也与预期相符。

4. 最佳实践

  1. 生产环境中,建议同时限制pod、k8s系统组件、linux system进程资源,以免任一类资源负载过高影响其他组件,甚至造成雪崩;
  2. 针对daemonset创建出来的系统级别pod,建议为其配置Guaranteed的服务质量等级

相关文章

Reserve Compute Resources for System Daemons
Node Allocatable Resources
Kubernetes资源管理之—资源预留
从一次集群雪崩看Kubelet资源预留的正确姿势
Kubernetes 服务质量 Qos 解析

posted @ 2019-09-17 20:28  wessonli  阅读(1085)  评论(0编辑  收藏  举报