前言
在生产环境中,通常需要通过配置资源配额(Resource Quota)来限制一个命名空间(namespace)能使用的资源量。在资源紧张的情况下,常常需要调整工作负载(workload)的请求值(requests)和限制值(limits),以确保工作负载能够顺利部署。本文将从Kubernetes源码的角度,简要分析Kubernetes如何计算Pod中的限制值(limits)对资源配额(Resource Quota)限制的影响。
源代码地址
kubernetes/pkg/api/v1/resource/helpers.go at master · kubernetes/kubernetes · GitHub
源码解析
container分类
这里以Pod limits为例,计算给定Pod的资源限制(limits),包括普通容器和初始化容器的资源限制。
这里实际上把容器分成了3类
- 普通容器
- 初始化容器
- 可重启初始化容器(RestartPolicy=Always)
- 不可重启初始化容器
但实际上目前initcontainer RestartPolicy只能设置为Always,因此在实际环境中,把容器分为了2类
源码
// PodLimits 计算根据提供的PodResourcesOptions选项计算Pod的资源限制(limits)。
// 如果PodResourcesOptions为nil,则返回的限制包含任何非零限制的Pod开销。
// 这个计算是API的一部分,必须作为API更改来审查。
func PodLimits(pod *v1.Pod, opts PodResourcesOptions) v1.ResourceList {
// 尝试重用传递的maps,如果没有传递则分配新的
limits := reuseOrClearResourceList(opts.Reuse)
// 遍历Pod中的每个容器
for _, container := range pod.Spec.Containers {
// 如果提供了ContainerFn函数,则调用它处理容器资源限制
if opts.ContainerFn != nil {
opts.ContainerFn(container.Resources.Limits, podutil.Containers)
}
// 将容器的资源限制添加到总的limits中
addResourceList(limits, container.Resources.Limits)
}
restartableInitContainerLimits := v1.ResourceList{}
initContainerLimits := v1.ResourceList{}
// 初始化容器定义了任何资源的最小值
//
// 假设 `InitContainerUse(i)` 是第i个初始化容器初始化时的资源需求,
// 则 `InitContainerUse(i) = sum(重启初始化容器的资源,索引 < i) + 第i个初始化容器的资源`。
//
// 详细信息见 https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/753-sidecar-containers#exposing-pod-resource-requirements
for _, container := range pod.Spec.InitContainers {
containerLimits := container.Resources.Limits
// 检查初始化容器是否标记为可重启的初始化容器,实际上目前initcontainer必须设置为可重启
if container.RestartPolicy != nil && *container.RestartPolicy == v1.ContainerRestartPolicyAlways {
// 将容器限制添加到总的limits中
addResourceList(limits, containerLimits)
// 跟踪累积的重启初始化容器资源
addResourceList(restartableInitContainerLimits, containerLimits)
containerLimits = restartableInitContainerLimits
} else {
// 创建一个临时的资源列表,包含当前初始化容器和累积的重启初始化容器资源
tmp := v1.ResourceList{}
addResourceList(tmp, containerLimits)
addResourceList(tmp, restartableInitContainerLimits)
containerLimits = tmp
}
// 如果提供了ContainerFn函数,则调用它处理初始化容器资源限制
if opts.ContainerFn != nil {
opts.ContainerFn(containerLimits, podutil.InitContainers)
}
// 取initContainerLimits和containerLimits的最大值
maxResourceList(initContainerLimits, containerLimits)
}
// 取limits和initContainerLimits的最大值
maxResourceList(limits, initContainerLimits)
// 如果不排除开销,并且Pod的Spec中有开销,则添加开销到非零限制中
if !opts.ExcludeOverhead && pod.Spec.Overhead != nil {
for name, quantity := range pod.Spec.Overhead {
if value, ok := limits[name]; ok && !value.IsZero() {
value.Add(quantity)
limits[name] = value
}
}
}
// 返回计算的资源限制
return limits
}
重要概念
在上述代码中,有以下几点需要理解
- 需要取一个满足任何情况的最大limits计算
- initcontainer如果有3个initcontainer RestartPolicy都设置为Always,1个initcontainer为不可重启,如果这3个中有2个initcontainer重启了,则有可能会与非可重启的initcontainer同时运行,在这种情况下使用的mem=1号可重启initcontainer mem + 2号可重启initcontainer mem + 3号不可重启initcontainer mem
- 如果有普通容器和initcontainer,则先运行完initcontainer再运行普通容器
Pod limits计算规则
理解了以上几点,那么Pod limits的计算规则就很容易得出了:
-
普通容器资源需求:计算Pod中所有普通容器的资源限制总和。
-
初始化容器资源需求:
- 可重启初始化容器(RestartPolicy: Always):累加所有可重启初始化容器的资源需求。
- 不可重启初始化容器:每个不可重启初始化容器的资源需求需要加上之前所有可重启初始化容器的资源需求,然后取其中最大值。
-
计算公式:
- 初始化容器资源需求 = max(初始化容器1 + 初始化容器2 + ... + 初始化容器N)
- Pod资源总需求 = max(普通容器资源需求, 初始化容器资源需求)
简化公式
典型情况
情况1
- 主容器资源需求:CPU:50m,内存:100Mi
- 初始化容器:
- 初始化容器1(可重启):CPU:50m,内存:110Mi
- 初始化容器2(可重启):CPU:60m,内存:120Mi
- 初始化容器3(不可重启):CPU:100m,内存:200Mi
资源配额计算:
resourcequota limit
= max(主容器需求, 初始化容器总需求)- 初始化容器总需求 = 初1 + 初2 + 初3 = 110 + 120 + 200 = 430Mi
- 计算结果:430Mi > 100Mi(主容器需求)
最终结果:resourcequota limit
= 430Mi
情况2
- 主容器资源需求:CPU:50m,内存:100Mi
- 初始化容器:
- 初始化容器1(可重启):CPU:50m,内存:110Mi
- 初始化容器2(可重启):CPU:50m,内存:120Mi
- 初始化容器3(不可重启):CPU:60m,内存:200Mi
- 初始化容器4(不可重启):CPU:100m,内存:220Mi
资源配额计算:
resourcequota limit
= max(主容器需求, 初始化容器总需求)- 初始化容器总需求 = 初1 + 初2 + 初4 = 110 + 120 + 220 = 450Mi
- 计算结果:450Mi > 100Mi(主容器需求)
最终结果:resourcequota limit
= 450Mi
情况3
- 主容器资源需求:CPU:50m,内存:500Mi
- 初始化容器:
- 初始化容器1(可重启):CPU:50m,内存:110Mi
- 初始化容器2(可重启):CPU:50m,内存:120Mi
- 初始化容器3(不可重启):CPU:60m,内存:200Mi
- 初始化容器4(不可重启):CPU:100m,内存:220Mi
资源配额计算:
resourcequota limit
= max(主容器需求, 初始化容器总需求)- 初始化容器总需求 = 初1 + 初2 + 初4 = 110 + 120 + 220 = 450Mi
- 计算结果:500Mi(主容器需求) > 450Mi
最终结果:resourcequota limit
= 500Mi