什么是QoS(服务质量)?
QoS,Quality of service,即服务质量。它是对服务整体性能的描述或测量。特别是网络用户所看到的性能。为了定量测量服务质量,通常会考虑网络服务的以下几个方面,例如丢包、比特率、吞吐量、传输延迟、可用性、抖动等。
在计算机网络和其他分组交换电信网络领域,服务质量是指流量优先级和资源预留控制机制,而不是所实现的服务质量。服务质量是指为不同的应用程序、用户或数据流提供不同优先级的能力,或者保证数据流达到一定性能水平的能力。
Memory QoS在Kunernetes的历史
最早Memory QoS在Kubernetes v1.22添加,后来引入了memory.high公式等一些限制。这些限制在v1.27版本被解决。
2023.04月发布的Kubernetes v1.27对Memory QoS的更改,提高了Linux节点中的内存的管理能力。
如何开启Memory QoS
要求
- 操作系统发行版启用 cgroup v2
- Linux 内核为 5.8 或更高版本
- 容器运行时支持 cgroup v2。例如:
- containerd v1.4 和更高版本
- cri-o v1.20 和更高版本
- kubelet 和容器运行时被配置为使用 systemd cgroup 驱动
启用Memory QoS
验证是否支持cgroup V2
1 | stat -fc %T /sys/fs/cgroup/ |
- 对于 cgroup v2,输出为 cgroup2fs
- 对于 cgroup v1,输出为 tmpfs
linux发行版cgroup v2支持:
- Container-Optimized OS(从 M97 开始)
- Ubuntu(从 21.10 开始,推荐 22.04+)
- Debian GNU/Linux(从 Debian 11 Bullseye 开始)
- Fedora(从 31 开始)
- Arch Linux(从 2021 年 4 月开始)
- RHEL 和类似 RHEL 的发行版(从 9 开始)
要手动启动cgroup v2,如果发行版使用grub引导,则通过修改/etc/default/grub下的GRUB_CMDLINE_LINUX,在其中添加systemd.unified_cgroup_hierarchy=1,然后执行sudo update-grub。
Containerd版本 >= v1.6.0
在v1.6.0合并了该PR以支持在创建容器时支持对cgroup v2字段的解释
kubelet配置开启MemoryQoS=true
1 | apiVersion: kubelet.config.k8s.io/v1beta1 |
迁移到cgroup v2
- 升级到一个使用cgroup v2的内核版本。即Linux 内核为 5.8 或更高版本。
- 如果有应用基于cgroup v1直接访问cgroup文件系统,需要将这些应用更新到cgroup v2的版本。
- 一些第三方依赖和监控服务可能有此依赖
- 以独立的DaemonSet方式运行的cAdvisor 以监控 Pod 和容器,需要将其更新到v0.43.0版本及以上
- 部署Java应用程序,则最好完全支持cgroup v2以及更高的版本
- OpenJDK / HotSpot: jdk8u372、11.0.16、15 及更高的版本
- IBM Semeru Runtimes: jdk8u345-b01、11.0.16.0、17.0.4.0、18.0.2.0 及更高的版本
- IBM Java: 8.0.7.15 及更高的版本
- 正在使用 uber-go/automaxprocs 包,则确保版本 >= v1.5.1
背景
当Kubelet将容器作为Pod的一部分启动时,Kubelete将CPU和内存限制传递给容器运行时,容器运行时将CPU和内存分配给容器。如果有空余的CPU时间,则会为容器分配他们的request数量,容器使用的CPU不能超过限制。如果容器使用的CPU数量超过了限制,则容器的CPU使用率将收到限制。但是对于内存来讲,容器运行时仅使用内存limit,丢弃request,request仅用于影响调度。当容器使用的内存超过限制,则会调用Linux out of memory Kill。
Momory Request
对于Memory request来讲,它主要在kube-schedule在调度时使用。在cgroup v1中,没有任何控件来指定cgroup将始终保持最小的内存,因此,容器运行时将不使用Pod的内存request字段来设置内存的最小分配量。
而在Cgroup v2中,cgroup引入了一个memory.min设置,用于指定cgroup中的进程的最小内存使用量。Cgroup v2将始终保持内存分配大于memory.min,如果它无法保证,则会调用Linux OOM Kill杀死进程。即cgroup将会至少保证容器有memory.min的内存量可以使用,否则,它将会Kill掉进程(可能是在Cgroup之外),以保证内存可用。
Memory Limit
对于Memory limit来讲,memory指定内存限制。如果容器尝试分配更多的内存,超过了内存限制,Linux将通过OOM Kill来终止容器。如果终止的是容器的主进程,将导致容器退出。memory.limit_in_bytes 接口用于设置内存使用限制。然而,与CPU不同的是,它不可能应用内存限制:一旦容器超过内存限制,它就会被OOM杀死。
在cgroups v2中,memory.max类似于cgroupv1中的memory.limit_in_bytes。内存QoS将memory.max映射到spec.containers[].resources.limits.memory 以指定内存使用的硬限制。如果内存消耗超过此水平,内核将调用其 OOM Killer。
cgroups v2还添加了memory.high配置。内存QoS使用memory.high设置内存使用限制。如果违反memory.high限制,则有问题的cgroup会受到限制,并且内核会尝试回收内存,这可能会避免OOM终止。
Memory QoS怎么运行
Cgroups v2 内存控制器接口和 Kubernetes 容器资源映射
内存QoS使用cgroups v2的内存控制器来保证Kubernetes中的内存资源。此功能使用的 cgroupv2 接口有:
- memory.max
- memory.min
- memory.high
Momory QoS 级别
memory.max 映射到 Pod 规范中指定的 limits.memory 。 kubelet 和容器运行时在各自的 cgroup 中配置限制。内核强制执行限制以防止容器使用超过配置的资源限制。如果容器中的进程尝试消耗超过指定的限制,内核将终止进程并出现内存不足 (OOM) 错误。
memory.min 映射到 requests.memory ,这会导致保留内核永远不应回收的内存资源。这就是内存 QoS 确保 Kubernetes Pod 内存可用性的方式。如果没有不受保护的可回收内存可用,则会调用 OOM 杀手以提供更多可用内存。
对于内存保护,除了限制内存使用的原始方式之外,内存 QoS 还会限制接近其内存限制的工作负载,确保系统不会因内存使用的零星增加而不堪重负。当您启用 MemoryQoS 功能时,KubeletConfiguration 中将提供一个新字段 memoryThrottlingFactor 。默认设置为 0.9。 memory.high 映射到通过使用 memoryThrottlingFactor 、 requests.memory 和 limits.memory 计算得出的限制限制,如下式所示,并将值向下舍入为最接近的页面尺寸:
注意:如果容器没有指定内存限制,则 limits.memory 将替换节点可分配内存。
注意 memory.high 仅在容器级别 cgroup 上设置,而 memory.min 在容器、pod 和节点级别 cgroup 上设置。
支持的Pod QoS类别
Guaranteed pods
即resource request == limit。通过不设置memory.high,memory qos在这些Pod上会被禁用。这确保了Guaranteed Pod 可以充分利用其内存请求,达到设定的限制,并且不会遇到任何限制。
Burstable pods
根据QoS定义,Burstable pods要求Pod中至少有一个容器具有CPU或内存请求或限制。
- 当requests.memory和limits.memory设置时,公式按原样使用:
1 | memory.high = pod.spec.containers[i].resources.requests[memory] + MemoryThrottilngFactor * { (pod.spec.containers[i].resources.limits[memory]) - pod.spec.containers[i].resources.requests[memory] } |
- 当设置了 requests.memory 并且未设置 limit.memory 时,limits.memory 会替换公式中的节点可分配内存:
1 | memory.high = pod.spec.containers[i].resources.requests[memory] + MemoryThrottilngFactor * (Node AllocatableMemory) - pod.spec.containers[i].resources.:requests[memory] |
- 根据 QoS 定义,BestEffort 不需要任何内存或 CPU 限制或请求。对于这种情况,kubernetes 设置 requests.memory = 0 并用 Limits.memory 替换公式中的节点可分配内存:
1 | memory.high = MemoryThrottilngFactor * NodeAllocateableMemory |
摘要:只有 Burstable 和 BestEffort QoS 类别中的 Pod 才会设置 memory.high 。Guaranteed QoS Pod 不会设置 memory.high ,因为它们的内存是有保证的。