mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-11-01 14:22:17 +00:00
dedupe pod resource request calculation
This commit is contained in:
@@ -24,54 +24,127 @@ import (
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
|
||||
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
|
||||
)
|
||||
|
||||
// PodRequestsAndLimits returns a dictionary of all defined resources summed up for all
|
||||
// containers of the pod. Pod overhead is added to the
|
||||
// total container resource requests and to the total container limits which have a
|
||||
// non-zero quantity.
|
||||
func PodRequestsAndLimits(pod *v1.Pod) (reqs, limits v1.ResourceList) {
|
||||
return PodRequestsAndLimitsReuse(pod, nil, nil)
|
||||
// PodResourcesOptions controls the behavior of PodRequests and PodLimits.
|
||||
type PodResourcesOptions struct {
|
||||
// Reuse, if provided will be reused to accumulate resources and returned by the PodRequests or PodLimits
|
||||
// functions. All existing values in Reuse will be lost.
|
||||
Reuse v1.ResourceList
|
||||
// InPlacePodVerticalScalingEnabled indicates that the in-place pod vertical scaling feature gate is enabled.
|
||||
InPlacePodVerticalScalingEnabled bool
|
||||
// ExcludeOverhead controls if pod overhead is excluded from the calculation.
|
||||
ExcludeOverhead bool
|
||||
// ContainerFn is called with the effective resources required for each container within the pod.
|
||||
ContainerFn func(res v1.ResourceList, containerType podutil.ContainerType)
|
||||
// NonMissingContainerRequests if provided will replace any missing container level requests for the specified resources
|
||||
// with the given values. If the requests for those resources are explicitly set, even if zero, they will not be modified.
|
||||
NonMissingContainerRequests v1.ResourceList
|
||||
}
|
||||
|
||||
// PodRequestsAndLimitsWithoutOverhead will create a dictionary of all defined resources summed up for all
|
||||
// containers of the pod.
|
||||
func PodRequestsAndLimitsWithoutOverhead(pod *v1.Pod) (reqs, limits v1.ResourceList) {
|
||||
reqs = make(v1.ResourceList, 4)
|
||||
limits = make(v1.ResourceList, 4)
|
||||
podRequestsAndLimitsWithoutOverhead(pod, reqs, limits)
|
||||
// PodRequests computes the pod requests per the PodResourcesOptions supplied. If PodResourcesOptions is nil, then
|
||||
// the requests are returned including pod overhead. The computation is part of the API and must be reviewed
|
||||
// as an API change.
|
||||
func PodRequests(pod *v1.Pod, opts PodResourcesOptions) v1.ResourceList {
|
||||
// attempt to reuse the maps if passed, or allocate otherwise
|
||||
reqs := reuseOrClearResourceList(opts.Reuse)
|
||||
|
||||
return reqs, limits
|
||||
}
|
||||
var containerStatuses map[string]*v1.ContainerStatus
|
||||
if opts.InPlacePodVerticalScalingEnabled {
|
||||
containerStatuses = map[string]*v1.ContainerStatus{}
|
||||
for i := range pod.Status.ContainerStatuses {
|
||||
containerStatuses[pod.Status.ContainerStatuses[i].Name] = &pod.Status.ContainerStatuses[i]
|
||||
}
|
||||
}
|
||||
|
||||
func podRequestsAndLimitsWithoutOverhead(pod *v1.Pod, reqs, limits v1.ResourceList) {
|
||||
for _, container := range pod.Spec.Containers {
|
||||
addResourceList(reqs, container.Resources.Requests)
|
||||
containerReqs := container.Resources.Requests
|
||||
if opts.InPlacePodVerticalScalingEnabled {
|
||||
cs, found := containerStatuses[container.Name]
|
||||
if found {
|
||||
if pod.Status.Resize == v1.PodResizeStatusInfeasible {
|
||||
containerReqs = cs.ResourcesAllocated
|
||||
} else {
|
||||
containerReqs = max(container.Resources.Requests, cs.ResourcesAllocated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts.NonMissingContainerRequests) > 0 {
|
||||
containerReqs = applyNonMissing(containerReqs, opts.NonMissingContainerRequests)
|
||||
}
|
||||
|
||||
if opts.ContainerFn != nil {
|
||||
opts.ContainerFn(containerReqs, podutil.Containers)
|
||||
}
|
||||
|
||||
addResourceList(reqs, containerReqs)
|
||||
}
|
||||
|
||||
// init containers define the minimum of any resource
|
||||
// Note: In-place resize is not allowed for InitContainers, so no need to check for ResizeStatus value
|
||||
for _, container := range pod.Spec.InitContainers {
|
||||
containerReqs := container.Resources.Requests
|
||||
if len(opts.NonMissingContainerRequests) > 0 {
|
||||
containerReqs = applyNonMissing(containerReqs, opts.NonMissingContainerRequests)
|
||||
}
|
||||
|
||||
if opts.ContainerFn != nil {
|
||||
opts.ContainerFn(containerReqs, podutil.InitContainers)
|
||||
}
|
||||
maxResourceList(reqs, containerReqs)
|
||||
}
|
||||
|
||||
// Add overhead for running a pod to the sum of requests if requested:
|
||||
if !opts.ExcludeOverhead && pod.Spec.Overhead != nil {
|
||||
addResourceList(reqs, pod.Spec.Overhead)
|
||||
}
|
||||
|
||||
return reqs
|
||||
}
|
||||
|
||||
// applyNonMissing will return a copy of the given resource list with any missing values replaced by the nonMissing values
|
||||
func applyNonMissing(reqs v1.ResourceList, nonMissing v1.ResourceList) v1.ResourceList {
|
||||
cp := v1.ResourceList{}
|
||||
for k, v := range reqs {
|
||||
cp[k] = v.DeepCopy()
|
||||
}
|
||||
|
||||
for k, v := range nonMissing {
|
||||
if _, found := reqs[k]; !found {
|
||||
rk := cp[k]
|
||||
rk.Add(v)
|
||||
cp[k] = rk
|
||||
}
|
||||
}
|
||||
return cp
|
||||
}
|
||||
|
||||
// PodLimits computes the pod limits per the PodResourcesOptions supplied. If PodResourcesOptions is nil, then
|
||||
// the limits are returned including pod overhead for any non-zero limits. The computation is part of the API and must be reviewed
|
||||
// as an API change.
|
||||
func PodLimits(pod *v1.Pod, opts PodResourcesOptions) v1.ResourceList {
|
||||
// attempt to reuse the maps if passed, or allocate otherwise
|
||||
limits := reuseOrClearResourceList(opts.Reuse)
|
||||
|
||||
for _, container := range pod.Spec.Containers {
|
||||
if opts.ContainerFn != nil {
|
||||
opts.ContainerFn(container.Resources.Limits, podutil.Containers)
|
||||
}
|
||||
addResourceList(limits, container.Resources.Limits)
|
||||
}
|
||||
// init containers define the minimum of any resource
|
||||
for _, container := range pod.Spec.InitContainers {
|
||||
maxResourceList(reqs, container.Resources.Requests)
|
||||
if opts.ContainerFn != nil {
|
||||
opts.ContainerFn(container.Resources.Limits, podutil.InitContainers)
|
||||
}
|
||||
maxResourceList(limits, container.Resources.Limits)
|
||||
}
|
||||
}
|
||||
|
||||
// PodRequestsAndLimitsReuse returns a dictionary of all defined resources summed up for all
|
||||
// containers of the pod. Pod overhead is added to the
|
||||
// total container resource requests and to the total container limits which have a
|
||||
// non-zero quantity. The caller may avoid allocations of resource lists by passing
|
||||
// a requests and limits list to the function, which will be cleared before use.
|
||||
func PodRequestsAndLimitsReuse(pod *v1.Pod, reuseReqs, reuseLimits v1.ResourceList) (reqs, limits v1.ResourceList) {
|
||||
// attempt to reuse the maps if passed, or allocate otherwise
|
||||
reqs, limits = reuseOrClearResourceList(reuseReqs), reuseOrClearResourceList(reuseLimits)
|
||||
|
||||
podRequestsAndLimitsWithoutOverhead(pod, reqs, limits)
|
||||
|
||||
// Add overhead for running a pod
|
||||
// to the sum of requests and to non-zero limits:
|
||||
if pod.Spec.Overhead != nil {
|
||||
addResourceList(reqs, pod.Spec.Overhead)
|
||||
|
||||
// Add overhead to non-zero limits if requested:
|
||||
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)
|
||||
@@ -80,19 +153,7 @@ func PodRequestsAndLimitsReuse(pod *v1.Pod, reuseReqs, reuseLimits v1.ResourceLi
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// reuseOrClearResourceList is a helper for avoiding excessive allocations of
|
||||
// resource lists within the inner loop of resource calculations.
|
||||
func reuseOrClearResourceList(reuse v1.ResourceList) v1.ResourceList {
|
||||
if reuse == nil {
|
||||
return make(v1.ResourceList, 4)
|
||||
}
|
||||
for k := range reuse {
|
||||
delete(reuse, k)
|
||||
}
|
||||
return reuse
|
||||
return limits
|
||||
}
|
||||
|
||||
// addResourceList adds the resources in newList to list.
|
||||
@@ -116,6 +177,39 @@ func maxResourceList(list, newList v1.ResourceList) {
|
||||
}
|
||||
}
|
||||
|
||||
// max returns the result of max(a, b) for each named resource and is only used if we can't
|
||||
// accumulate into an existing resource list
|
||||
func max(a v1.ResourceList, b v1.ResourceList) v1.ResourceList {
|
||||
result := v1.ResourceList{}
|
||||
for key, value := range a {
|
||||
if other, found := b[key]; found {
|
||||
if value.Cmp(other) <= 0 {
|
||||
result[key] = other.DeepCopy()
|
||||
continue
|
||||
}
|
||||
}
|
||||
result[key] = value.DeepCopy()
|
||||
}
|
||||
for key, value := range b {
|
||||
if _, found := result[key]; !found {
|
||||
result[key] = value.DeepCopy()
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// reuseOrClearResourceList is a helper for avoiding excessive allocations of
|
||||
// resource lists within the inner loop of resource calculations.
|
||||
func reuseOrClearResourceList(reuse v1.ResourceList) v1.ResourceList {
|
||||
if reuse == nil {
|
||||
return make(v1.ResourceList, 4)
|
||||
}
|
||||
for k := range reuse {
|
||||
delete(reuse, k)
|
||||
}
|
||||
return reuse
|
||||
}
|
||||
|
||||
// GetResourceRequestQuantity finds and returns the request quantity for a specific resource.
|
||||
func GetResourceRequestQuantity(pod *v1.Pod, resourceName v1.ResourceName) resource.Quantity {
|
||||
requestQuantity := resource.Quantity{}
|
||||
|
||||
Reference in New Issue
Block a user