ubernetes 的节点可以按照节点的资源容量进行调度,默认情况下 Pod 能够使用节点全部可用容量。这样就会造成一个问题,因为节点自己通常运行了不少驱动 OS 和 Kubernetes 的系统守护进程。除非为这些系统守护进程留出资源,否则它们将与 Pod 争夺资源并导致节点资源短缺问题。

当我们在线上使用 Kubernetes 集群的时候,如果没有对节点配置正确的资源预留,我们可以考虑一个场景,由于某个应用无限制的使用节点的 CPU 资源,导致节点上 CPU 使用持续100%运行,而且压榨到了 kubelet 组件的 CPU 使用,这样就会导致 kubelet 和 apiserver 的心跳出问题,节点就会出现 Not Ready 状况了。默认情况下节点 Not Ready 过后,5分钟后会驱逐应用到其他节点,当这个应用跑到其他节点上的时候同样100%的使用 CPU,是不是也会把这个节点搞挂掉,同样的情况继续下去,也就导致了整个集群的雪崩,集群内的节点一个一个的 Not Ready 了,后果是非常严重的,或多或少的人遇到过 Kubernetes 集群雪崩的情况,这个问题也是面试的时候镜像询问的问题。

要解决这个问题就需要为 Kubernetes 集群配置资源预留,kubelet 暴露了一个名为 Node Allocatable 的特性,有助于为系统守护进程预留计算资源,Kubernetes 也是推荐集群管理员按照每个节点上的工作负载来配置 Node Allocatable。

本文的操作环境为 Kubernetes V1.17.11 版本,Docker 和 Kubelet 采用的 cgroup 驱动为 systemd。

Node Allocatable

Kubernetes 节点上的 Allocatable 被定义为 Pod 可用计算资源量,调度器不会超额申请 Allocatable,目前支持 CPU, memory 和 ephemeral-storage 这几个参数。

我们可以通过 kubectl describe node 命令查看节点可分配资源的数据:

$ kubectl describe node ydzs-node4
......
Capacity:
cpu: 4
ephemeral-storage: 17921Mi
hugepages-2Mi: 0
memory: 8008820Ki
pods: 110
Allocatable:
cpu: 4
ephemeral-storage: 16912377419
hugepages-2Mi: 0
memory: 7906420Ki
pods: 110
......

可以看到其中有 Capacity 与 Allocatable 两项内容,其中的 Allocatable 就是节点可被分配的资源,我们这里没有配置资源预留,所以默认情况下 Capacity 与 Allocatable 的值基本上是一致的。下图显示了可分配资源和资源预留之间的关系:

  • Kubelet Node Allocatable 用来为 Kube 组件和 System 进程预留资源,从而保证当节点出现满负荷时也能保证 Kube 和 System 进程有足够的资源。
  • 目前支持 cpu, memory, ephemeral-storage 三种资源预留。
  • Node Capacity 是节点的所有硬件资源,kube-reserved 是给 kube 组件预留的资源,system-reserved 是给系统进程预留的资源,eviction-threshold 是 kubelet 驱逐的阈值设定,allocatable 才是真正调度器调度 Pod 时的参考值(保证节点上所有 Pods 的 request 资源不超过Allocatable)。

节点可分配资源的计算方式为:

Node Allocatable Resource = Node Capacity - Kube-reserved - system-reserved - eviction-threshold

配置资源预留

Kube 预留值

首先我们来配置 Kube 预留值,kube-reserved 是为了给诸如 kubelet、容器运行时、node problem detector 等 kubernetes 系统守护进程争取资源预留。要配置 Kube 预留,需要把 kubelet 的 --kube-reserved-cgroup 标志的值设置为 kube 守护进程的父控制组。

不过需要注意,如果 --kube-reserved-cgroup 不存在,Kubelet 不会创建它,启动 Kubelet 将会失败。

比如我们这里修改 node-ydzs4 节点的 Kube 资源预留,我们可以直接修改 /var/lib/kubelet/config.yaml 文件来动态配置 kubelet,添加如下所示的资源预留配置:

apiVersion: kubelet.config.k8s.io/v1beta1
......
enforceNodeAllocatable:
- pods
- kube-reserved # 开启 kube 资源预留
kubeReserved:
cpu: 500m
memory: 1Gi
ephemeral-storage: 1Gi
kubeReservedCgroup: /kubelet.slice # 指定 kube 资源预留的 cgroup

修改完成后,重启 kubelet,如果没有创建上面的 kubelet 的 cgroup,启动会失败:

$ systemctl restart kubelet
$ journalctl -u kubelet -f
......
Aug 11 15:04:13 ydzs-node4 kubelet[28843]: F0811 15:04:13.653476 28843 kubelet.go:1380] Failed to start ContainerManager Failed to enforce Kube Reserved Cgroup Limits on "/kubelet.slice": ["kubelet"] cgroup does not exist

上面的提示信息很明显,我们指定的 kubelet 这个 cgroup 不存在,但是由于子系统较多,具体是哪一个子系统不存在不好定位,我们可以将 kubelet 的日志级别调整为 v=4,就可以看到具体丢失的 cgroup 路径:

$ vi /var/lib/kubelet/kubeadm-flags.env
KUBELET_KUBEADM_ARGS="--v=4 --cgroup-driver=systemd --network-plugin=cni"

然后再次重启 kubelet:

$ systemctl daemon-reload
$ systemctl restart kubelet

再次查看 kubelet 日志:

$ journalctl -u kubelet -f
......
Sep 09 17:57:36 ydzs-node4 kubelet[20427]: I0909 17:57:36.382811 20427 cgroup_manager_linux.go:273] The Cgroup [kubelet] has some missing paths: [/sys/fs/cgroup/cpu,cpuacct/kubelet.slice /sys/fs/cgroup/memory/kubelet.slice /sys/fs/cgroup/systemd/kubelet.slice /sys/fs/cgroup/pids/kubelet.slice /sys/fs/cgroup/cpu,cpuacct/kubelet.slice /sys/fs/cgroup/cpuset/kubelet.slice]
Sep 09 17:57:36 ydzs-node4 kubelet[20427]: I0909 17:57:36.383002 20427 factory.go:170] Factory "systemd" can handle container "/system.slice/run-docker-netns-db100461211c.mount", but ignoring.
Sep 09 17:57:36 ydzs-node4 kubelet[20427]: I0909 17:57:36.383025 20427 manager.go:908] ignoring container "/system.slice/run-docker-netns-db100461211c.mount"
Sep 09 17:57:36 ydzs-node4 kubelet[20427]: F0909 17:57:36.383046 20427 kubelet.go:1381] Failed to start ContainerManager Failed to enforce Kube Reserved Cgroup Limits on "/kubelet.slice": ["kubelet"] cgroup does not exist

注意:systemd 的 cgroup 驱动对应的 cgroup 名称是以 .slice 结尾的,比如如果你把 cgroup 名称配置成kubelet.service,那么对应的创建的 cgroup 名称应该为kubelet.service.slice`。如果你配置的是 cgroupfs 的驱动,则用配置的值即可。无论哪种方式,通过查看错误日志都是排查问题最好的方式。

现在可以看到具体的 cgroup 不存在的路径信息了:

The Cgroup [kubelet] has some missing paths: [/sys/fs/cgroup/cpu,cpuacct/kubelet.slice /sys/fs/cgroup/memory/kubelet.slice /sys/fs/cgroup/systemd/kubelet.slice /sys/fs/cgroup/pids/kubelet.slice /sys/fs/cgroup/cpu,cpuacct/kubelet.slice /sys/fs/cgroup/cpuset/kubelet.slice]

所以要解决这个问题也很简单,我们只需要创建上面的几个路径即可:

$ mkdir -p /sys/fs/cgroup/cpu,cpuacct/kubelet.slice
$ mkdir -p /sys/fs/cgroup/memory/kubelet.slice
$ mkdir -p /sys/fs/cgroup/systemd/kubelet.slice
$ mkdir -p /sys/fs/cgroup/pids/kubelet.slice
$ mkdir -p /sys/fs/cgroup/cpu,cpuacct/kubelet.slice
$ mkdir -p /sys/fs/cgroup/cpuset/kubelet.slice

创建完成后,再次重启:

$ systemctl restart kubelet
$ journalctl -u kubelet -f
......
Sep 09 17:59:41 ydzs-node4 kubelet[21462]: F0909 17:59:41.291957 21462 kubelet.go:1381] Failed to start ContainerManager Failed to enforce Kube Reserved Cgroup Limits on "/kubelet.slice": failed to set supported cgroup subsystems for cgroup [kubelet]: failed to set config for supported subsystems : failed to write 0 to hugetlb.2MB.limit_in_bytes: open /sys/fs/cgroup/hugetlb/kubelet.slice/hugetlb.2MB.limit_in_bytes: no such file or directory

可以看到还有一个 hugetlb 的 cgroup 路径不存在,所以继续创建这个路径:

$ mkdir -p /sys/fs/cgroup/hugetlb/kubelet.slice
$ systemctl restart kubelet

重启完成后就可以正常启动了,启动完成后我们可以通过查看 cgroup 里面的限制信息校验是否配置成功,比如我们查看内存的限制信息:

$ cat /sys/fs/cgroup/memory/kubelet.slice/memory.limit_in_bytes
1073741824 # 1Gi

现在再次查看节点的信息:

$ kubectl describe node ydzs-node4
......
Addresses:
InternalIP: 10.151.30.59
Hostname: ydzs-node4
Capacity:
cpu: 4
ephemeral-storage: 17921Mi
hugepages-2Mi: 0
memory: 8008820Ki
pods: 110
Allocatable:
cpu: 3500m
ephemeral-storage: 15838635595
hugepages-2Mi: 0
memory: 6857844Ki
pods: 110
......

可以看到可以分配的 Allocatable 值就变成了 Kube 预留过后的值了,证明我们的 Kube 预留成功了。

系统预留值

我们也可以用同样的方式为系统配置预留值,system-reserved 用于为诸如 sshd、udev 等系统守护进程争取资源预留,system-reserved 也应该为 kernel 预留 内存,因为目前 kernel 使用的内存并不记在 Kubernetes 的 pod 上。但是在执行 system-reserved 预留操作时请加倍小心,因为它可能导致节点上的关键系统服务 CPU 资源短缺或因为内存不足而被终止,所以如果不是自己非常清楚如何配置,可以不用配置系统预留值。

同样通过 kubelet 的参数 --system-reserved 配置系统预留值,但是也需要配置 --system-reserved-cgroup 参数为系统进程设置 cgroup。

请注意,如果 --system-reserved-cgroup 不存在,Kubelet 不会创建它,kubelet 会启动失败。

驱逐阈值

上面我们还提到可分配的资源还和 kubelet 驱逐的阈值有关。节点级别的内存压力将导致系统内存不足,这将影响到整个节点及其上运行的所有 Pod,节点可以暂时离线直到内存已经回收为止,我们可以通过配置 kubelet 驱逐阈值来防止系统内存不足。驱逐操作只支持内存和 ephemeral-storage 两种不可压缩资源。当出现内存不足时,调度器不会调度新的 Best-Effort QoS Pods 到此节点,当出现磁盘压力时,调度器不会调度任何新 Pods 到此节点。

我们这里为 ydzs-node4 节点配置如下所示的硬驱逐阈值:

# /var/lib/kubelet/config.yaml
......
evictionHard: # 配置硬驱逐阈值
memory.available: "300Mi"
nodefs.available: "10%"
enforceNodeAllocatable:
- pods
- kube-reserved
kubeReserved:
cpu: 500m
memory: 1Gi
ephemeral-storage: 1Gi
kubeReservedCgroup: /kubelet.slice
......

我们通过 --eviction-hard 预留一些内存后,当节点上的可用内存降至保留值以下时,kubelet 将尝试驱逐 Pod,

$ kubectl describe node ydzs-node4
......
Addresses:
InternalIP: 10.151.30.59
Hostname: ydzs-node4
Capacity:
cpu: 4
ephemeral-storage: 17921Mi
hugepages-2Mi: 0
memory: 8008820Ki
pods: 110
Allocatable:
cpu: 3500m
ephemeral-storage: 15838635595
hugepages-2Mi: 0
memory: 6653044Ki
pods: 110
......

配置生效后再次查看节点可分配的资源可以看到内存减少了,临时存储没有变化是因为硬驱逐的默认值就是 10%。也是符合可分配资源的计算公式的:

Node Allocatable Resource = Node Capacity - Kube-reserved - system-reserved - eviction-threshold

到这里我们就完成了 Kubernetes 资源预留的配置。

Kubernetes实践技巧:资源预留的更多相关文章

  1. Kubernetes实践技巧:Windows 系统最佳实践

    有部分同学是使用的 Windows 系统,我们的直播课程也是在 Windows 系统下面进行的,然后通过 SSH 方式连接到 服务器上面操作 Kubernetes,由于对 vim 不是很熟悉,所以又通 ...

  2. Kubernetes实践技巧:集群升级k8s版本

    更新证书 使用 kubeadm 安装 kubernetes 集群非常方便,但是也有一个比较烦人的问题就是默认的证书有效期只有一年时间,所以需要考虑证书升级的问题,本文的演示集群版本为 v1.16.2 ...

  3. Kubernetes实践技巧:升级为集群

    高可用 前面我们课程中的集群是单 master 的集群,对于生产环境风险太大了,非常有必要做一个高可用的集群,这里的高可用主要是针对控制面板来说的,比如 kube-apiserver.etcd.kub ...

  4. Kubelet资源预留

    目录 Kubelet Node Allocatable 配置参数 配置示例 Kubelet Node Allocatable Kubelet Node Allocatable用来为Kube组件和Sys ...

  5. 卓越管理的实践技巧(2)成功的委派任务 Setup for Successful Delegation

    Setup for Successful Delegation 前文卓越管理的秘密(Behind Closed Doors)最后一部分提到了总结的13条卓越管理的实践技巧并列出了所有实践技巧名称的索引 ...

  6. kubernetes调度之资源配额

    系列目录 当多个用户或者开发团队共享一个有固定节点的的kubernetes集群时,一个团队或者一个用户使用的资源超过他应当使用的资源是需要关注的问题,资源配额是管理员用来解决这个问题的一个工具. 资源 ...

  7. kubernets资源预留

    一.  Kubelet Node Allocatable Kubelet Node Allocatable用来为Kube组件和System进程预留资源,从而保证当节点出现满负荷时也能保证Kube和Sy ...

  8. [转载]DW数据仓库建模与ETL的实践技巧

    一.Data仓库的架构 Data仓库(Data Warehouse DW)是为了便于多维分析和多角度展现而将Data按特定的模式进行存储所建立起来的关系型Datcbase,它的Data基于OLTP源S ...

  9. 数据仓库建模与ETL实践技巧

    数据分析系统的总体架构分为四个部分 —— 源系统.数据仓库.多维数据库.客户端(图一:pic1.bmp) 其中,数据仓库(DW)起到了数据大集中的作用.通过数据抽取,把数据从源系统源源不断地抽取出来, ...

随机推荐

  1. Pytorch从0开始实现YOLO V3指南 part2——搭建网络结构层

    本节翻译自:https://blog.paperspace.com/how-to-implement-a-yolo-v3-object-detector-from-scratch-in-pytorch ...

  2. Java中运算符和方法的区别

    1.多数情况下,运算符是程序语言里固有的.比如+,-,*,/.可以直接被编译为机器语言而无需再调用其它方法编译. 2.运算符在被定义时会被规定运算的优先级.如4+3*3,会得到13.而不是21. 3. ...

  3. static 和 final(java)

    static: 1.1static修饰成员变量: 使用static修饰的成员变量不在对象的数据结构,而是类的基本信息(参数) 使用static修饰的成员可以直接使用,类名.成员的方式访问,而不需要再n ...

  4. 树莓派实战:微信机器人(itchat实现)

    背景 楼主有一台树莓派4B开发板(8G内存版),是目前的顶配机型.这一年来的业余时间,除了写Java.架构方面的文章,也陆续折腾了不少树莓派上的好玩小项目,在此新开一个树莓派实战的文章系列,分享给粉丝 ...

  5. 01 开发App真机调试问题

    逍遥安卓模拟器 :https://juejin.cn/post/7062922018710093831 HBuilderX真机调试插上手机却提示"未检测到手机或浏览器"的问题:ht ...

  6. 【定时功能】消息的定时发送-基于RocketMQ

    一.功能介绍 要实现一个消息的定时发送功能,也就是让消息可以在某一天某一个时间具体节点进行发送.而我们公司的业务场景是类似短信的业务,而且数量不小,用户会进行号码.消息内容.定时发送时间等信息的提交. ...

  7. EB和Varuxn的单字聊天

    持续更新! 本文已经征得\(Varuxn\)同意,仅当做记录网课的趣事和"深厚"的友情 原标题<ErB和Varuxn的单字聊天> 原标题来源: 这个想法来源是 \(Va ...

  8. 技术分享 | ARM下中标麒麟系统ky10使用Xtrabackup-8.0.25

    欢迎来到 GreatSQL社区分享的MySQL技术文章,如有疑问或想学习的内容,可以在下方评论区留言,看到后会进行解答 一.需求背景 查询Percona官方手册,Xtrabackup 8.0可以备份M ...

  9. 利用图像二维熵实现视频信号丢失检测(Signal Loss Detection)

    1 图像二维熵 图像二维熵作为一种特征评价尺度能够反映出整个图像所含平均信息量的高低,熵值(H)越大则代表图像所包含的信息越多,反之熵值(H)越小,则图像包含的信息越少.对于图像信息量,可以简单地认为 ...

  10. Spring源码 12 IOC refresh方法7

    本文章基于 Spring 5.3.15 Spring IOC 的核心是 AbstractApplicationContext 的 refresh 方法. 其中一共有 13 个主要方法,这里分析第 7 ...