第61节 节点资源管理、异常检测以及常见问题排查


❤️💕💕新时代拥抱云原生,云原生具有环境统一、按需付费、即开即用、稳定性强特点。Myblog:http://nsddd.topopen in new window


[TOC]

NUMA Node

Non-Uniform Memory Access 是一种内存访问方式,是为多处理器计算机设计的内存架构。

节点资源管理

  • 状态汇报
  • 资源预留
  • 防止节点资源耗尽的防御机制驱逐
  • 容器和系统资源的配置

状态上报

kubelet 周期性地向 API Server 进行汇报,并更新节点的相关健康和资源使用信息

  • 节点基础信息,包括IP地址、操作系统、内核、运行时、kubelet、 kube-proxy 版本信息。
  • 节点资源信息包括CPU、内存、HugePage、临时存储、GPU 等注册设备,以及这些资源中可以分配给容器使用的部分。
  • 调度器在为 Pod 选择节点时会将机器的状态信息作为依据。
状态意义
Ready节点是否健康
MemoryPressure节点是否存在内存压力
PIDPressure节点是否存在比较多的进程
DiskPressure节点是否存在磁盘压力
NetworkUnavailable节点网络配置是否正确

Lease

在早期版本 kubele t的状态上报直接更新 node 对象,而上报的信息包含状态信息和资源信息,因此需要 传输的数据包较大,给 APIServer 和 etcd 造成的压力较大。

后引入 Lease 对象用来保存健康信息,在默认 40s 的 nodeLeaseDurationSeconds 周期内,若 Lease 对 象没有被更新,则对应节点可以被判定为不健康。

将变化频率低的资源信息和状态信息分离。

资源预留

计算节点除用户容器外,还存在很多支撑系统运行的基础服务,譬如 systemd、journald、 sshd、dockerd、Containerd、 kubelet 等。

为了使服务进程能够正常运行,要确保它们在任何时候都可以获取足够的系统资源,所以我们要为这些系统进程预留资源。 kubelet 可以通过众多启动参数为系统预留 CPU、内存、PID等资源,比如 SystemReserved、KubeReserved等。

Capacity 和 Allocatable

容量资源(Capacity) 是指 kubelet 获取的计算节点当前的资源信息。

  • CPU是从 /proc/cpuinfo 文件中获取的节点CPU核数;
  • memory 是从/ proc/memoryinfo 中获取的节点内存大小;
  • ephemeral-storage 是指节点根分区的大小。

资源可分配额(Allocatable) 是用户 Pod 可用的资源,是资源容量减去分配给系统的资源的剩余部分。

节点资源总量= KubeReserved + SystemReserved + Eviction-threshold + Allocatable

节点磁盘管理

系统分区 nodefs

  • 工作目录和容器日志
  • kubelet 的数据目录,默认为 /var/lib/kubelet

容器运行时分区 imagefs

  • 用户镜像和容器可写层
  • 容器运行时分区是可选的,可以合并到系统分区中
  • 存放镜像的目录,containerd 在 /var/lib/containerd,docker 在 /var/lib/docker

驱逐管理

  • kubelet 会在系统资源不够时中止一些容器进程,以空出系统资源,保证节点的稳定性。
  • 但由 kubelet 发起的驱逐只停止 Pod 的所有容器进程,并不会直接删除 Pod,便于管理员发现问题
    • Pod 的 status.phase 会被标记为 Failed
    • status.reason 会被设置为 Evicted
    • status.message 则会记录被驱逐的原因

资源可用额监控

  • kubelet 依赖内嵌的开源软件 cAdvisoropen in new window,周期性检查节点资源使用情况
  • CPU 是可压缩资源,根据不同进程分配时间配额和权重,CPU 可被多个进程竞相使用
  • 驱逐策略是基于磁盘和内存资源用量进行的,因为两者属于不可压缩的资源,当此类资源使用耗尽时将无法再申请
检查类型说明
memory.avaliable节点当前可用内存
nodefs.avaliable节点根分区的可用磁盘大小
nodefs.inodesFree节点根分区的可使用 inode
imagefs.avaliable节点运行时分区的可用磁盘大小
imagefs.inodesFree节点运行时分区的可使用 inode

驱逐策略

kubelet 获得节点的可用额信息后,会结合节点的容量信息来判断当前节点运行的 Pod 是否满足驱逐条件。

驱逐条件可以是绝对值或百分比,当监控资源的可使用额少于设定的数值或百分比时,kubelet 就会发起驱逐操作。

kubelet 参数 evictionMinimumReclaim 可以设置每次回收的资源的最小值,以防止小资源的多次回收。

kubelet 参数分类驱逐方式
evictionSoft软驱逐当检测到当前资源达到软驱逐的阈值时,并不会立即启动驱逐操作,而是要等待一个宽限期。这个宽限期选取 EvictionSoftGracePeriod 和 Pod 指定的TerminationGracePeriodSeconds 中较小的值
evictionHard硬驱逐没有宽限期,一旦检测到满足硬驱逐的条件,就直接中止容器来释放紧张资源

基于内存压力的驱逐

memory.avaiable 表示当前系统的可用内存情况。

kubelet 默认设置了 memory.avaiable<100Mi 的硬驱逐条件。

当 kubelet 检测到当前节点可用内存资源紧张并满足驱逐条件时,会将节点的 MemoryPressure 状态设置为 True, 调度器会阻止 BestEffort Pod 调度到内存承压的节点。

kubelet 启动对内存不足的驱逐操作时,会依照如下的顺序选取目标 Pod:、

  • (1)判断 Pod 所有容器的内存使用量总和是否超出了请求的内存量,超出请求资源的 Pod 会成为备选目标。
  • (2)查询 Pod 的调度优先级,低优先级的 Pod 被优先驱逐。
  • (3)计算 Pod 所有容器的内存使用量和 Pod 请求的内存量的差值,差值越小,越不容易被驱逐。
    • Pod A 请求 100Mi,使用了 120Mi,比例为 1.2
    • Pod B 请求 200Mi,使用了 300Mi,比例为1.5
    • Pod B 会被优先驱逐

基于磁盘压力的驱逐

以下任何一项满足驱逐条件时,它会将节点的 DiskPressure 状态设置为 True,调度器不会再调度任何 Pod 到该节点上:

  • nodefs.available
  • nodefs.inodesFree
  • imagefs.available
  • imagefs.inodesFree

驱逐行为

  • 有容器运行时分区
    • nodefs 达到驱逐阈值, 那么 kubelet 删除已经退出的容器
    • Imagefs 达到驱逐阈值,那么 kubelet 删除所有未使用的镜像
  • 无容器运行时分区
    • kubelet 同时删除未运行的容器和未使用的镜像。

回收已经退出的容器和未使用的镜像后,如果节点依然满足驱逐条件,kubelet 就会开始驱逐正在运行的 Pod,进一步释放磁盘空间。

  • 判断 Pod 的磁盘使用量是否超过请求的大小,超出请求资源的 Pod 会成为备选目标。
  • 查询 Pod 的调度优先级,低优先级的 Pod 优先驱逐。
  • 根据磁盘使用超过请求的数量进行排序,差值越小,越不容易被驱逐。

OOM Killer 的行为

  • 系统的 OOM_Killer 可能会采取 OOM 的方式来中止某些容器的进程,进行必要的内存回收操作
  • 而系统根据进程的 oom_score 来进行优先级排序,选择待终止的进程,且进程的 oom_score 越高,越容易被终止
  • 进程的 oom_score 是根据当前进程使用的内存占节点总内存的比例值乘以 10,再加上 oom_score_adj 综合得到的
    • 比如占用内存 50%,那么计算得到的这部分占比分就是 500,如果是 60% 就是 600 分
  • 而容器进程的 oom_score_adj 正是 kubelet 根据 memory.request 进行设置的
Pod QoS 等级oom_score_adj
Guaranteed-998
BestEffort1000
Burstablemin(max(2,1000-(1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999)

查看具体 Pod 的 oom_score:

crictl ps|grep nginx
crictl inspect b2e7a8e64253d|grep pid
cat /proc/296290/oom_score
cat /proc/296290/oom_score_adj

查看全部进程的 oom_score

#!/bin/bash
printf 'PID\tOOM Score\tOOM Adj\tCommand\n'
while read -r pid comm; do [ -f /proc/$pid/oom_score ] && [ $(cat /proc/$pid/oom_score) != 0 ] && printf '%d\t%d\t\t%d\t%s\n' "$pid" "$(cat /proc/$pid/oom_score)" "$(cat /proc/$pid/oom_score_adj)" "$comm"; done < <(ps -e -o pid= -o comm=) | sort -k 2nr

测试对 cpu 的校验和准入行为

  • 定义一个 Pod,并将该 Pod 中的 nodeName 属性直接写成集群中的节点名
  • 将 Pod 的 CPU 的资源设置为超出计算节点的 CPU 的值
  • 创建该 Pod
  • 观察行为并思考

日志管理

节点上需要通过运行 logrotate 的定时任务对系统服务日志进行rotate清理,以防止系统服务日志占用大量的磁盘空间。

  • logrotate 的执行周期不能过长,以防日志短时间内大量增长。
  • 同时配置日志的 rotate 条件, 在日志不占用太多空间的情况下,保证有足够的日志可供查看。
  • Docker
    • 除了基于系统 logrotate 管理日志,还可以依赖 Docker 自带的日志管理功能来设置容器日志的数量和每个日志文件的大小。
    • Docker 写入数据之前会对日志大小进行检查和 rotate 操作,确保日志文件不会超过配置的数量和大小。
  • Containerd
    • 日志的管理是通过 kubelet 定期(默认为10s)执行 du 命令,来检查容器日志的数量和文件的大小的。
    • 每个容器日志的大小和可以保留的文件个数,可以通过 kubelet 的配置参数 container-log-max-size和container-log-max-files 进行调整。

Docker 卷管理

  • 在构建容器镜像时,可以在 Dockerfile 中通过 VOLUME 指令声明一个存储卷,目前 Kubernetes 并未将其纳入管控范围,不建议使用。
  • 如果容器进程在可写层或 emptyDir 卷进行大量读写操作,就会导致磁盘 I/O 过高,从而影响其他容器进程甚至系统进程。
  • Docker 和 Containerd 运行时都基于 CGroupv1。对于块设备,只支持对 Direct I/O 限速,而对于Buffer I/O 还不具备有效的支持。因此,针对设备限速的问题,目前还没有完美的解决方案,对于有特殊 I/O 需求的容器,建议使用独立的磁盘空间。

网络资源

由网络插件通过 Linux Traffic Control 为 Pod限制带宽

可利用 CNI社区提供的 bandwidth 插件

进程数

kubelet 默认不限制 Pod 可以创建的子进程数量,但可以通过启动参数 podPidsLimit 开启限制,还可以由reserved 参数为系统进程预留进程数。

  • kubelet 通过系统调用周期性地获取当前系统的 PID 的使用量,并读取 /proc/sys/kernel/pid_max, 获取系统支持的PID 上限。
  • 如果当前的可用进程数少于设定阈值,那么 kubelet 会将节点对象的 PIDPressure 标记为 True
  • kube-scheduler 在进行调度时, 会从备选节点中对处于 NodeUnderPIDPressure 状态的节点进行过滤。

Kubernetes 日志

与传统的日志收集相比,Kubernetes 日志收集具有以下不同之处:

  1. 分布式:Kubernetes 集群中运行的应用程序通常由多个容器组成,这些容器可能跨越多个节点。因此,日志收集需要跨节点和容器进行。
  2. 动态性:Kubernetes 集群中的 Pod 可以随时被创建、删除或重新启动。因此,日志收集需要动态地适应这些变化。
  3. 多样性:Kubernetes 集群中的应用程序可能使用多种编程语言和日志格式。因此,日志收集需要支持多种格式和语言。

为了解决这些问题,Kubernetes 提供了一些内置的日志收集机制,如 kubectl logs 命令和 Fluentd 插件。此外,还可以使用第三方工具,如 Elasticsearch、Fluentd 和 Kibana 等,来进行日志收集和分析。

前言

Pod 的生命周期很短, Pod 销毁后 日志也会一同被删除, Kubernetes 的日志系统在设计的时候,必须独立于节点和pod 的生命周期,且保证日志数据可以实时采集到服务端。

在 Kubernetes 集群中,需要进行日志收集的地方包括:

  • 容器日志:在容器中运行的应用程序输出的日志。
  • 节点日志:节点上的系统日志和容器日志。

除了以上两种方式外,还可以使用日志聚合工具,如 Elasticsearch、Fluentd 和 Kibana 等,来进行日志收集和分析。

在进行日志收集时,需要注意以下几点:

  • 确保节点和 Pod 的时间同步。
  • 将日志存储在持久存储中,避免数据丢失。
  • 对于大规模集群,需要考虑日志的压缩和转储,以减少存储空间的占用。

日志轮转

日志轮转是指在日志文件达到一定大小或一定时间后,将其重命名并创建一个新的日志文件,以避免日志文件过大或过旧而导致的性能问题或存储问题。

优点:

  • 避免单个日志文件过大,导致读取、写入、传输、存储等方面的性能问题。
  • 避免日志文件过旧,导致存储空间占用过多。

缺点:

  • 如果日志轮转设置不当,可能会导致日志文件不完整,或者日志文件没有及时轮转,导致存储空间占用过多。

END 链接