# In-place Update of Pod Resources
## Table of Contents
- [Release Signoff Checklist](#release-signoff-checklist)
- [Summary](#summary)
- [Motivation](#motivation)
- [Goals](#goals)
- [Non-Goals](#non-goals)
- [Proposal](#proposal)
- [API Changes](#api-changes)
- [Allocated Resources](#allocated-resources)
- [Subresource](#subresource)
- [Validation](#validation)
- [Container Resize Policy](#container-resize-policy)
- [Resize Status](#resize-status)
- [CRI Changes](#cri-changes)
- [Risks and Mitigations](#risks-and-mitigations)
- [Design Details](#design-details)
- [Resource States](#resource-states)
- [Priority of Resize Requests](#priority-of-resize-requests)
- [Kubelet-triggered eviction](#kubelet-triggered-eviction)
- [Kubelet and API Server Interaction](#kubelet-and-api-server-interaction)
- [Kubelet Restart Tolerance](#kubelet-restart-tolerance)
- [Scheduler and API Server Interaction](#scheduler-and-api-server-interaction)
- [Flow Control](#flow-control)
- [Container resource limit update ordering](#container-resource-limit-update-ordering)
- [Container resource limit update failure handling](#container-resource-limit-update-failure-handling)
- [CRI Changes Flow](#cri-changes-flow)
- [Kubelet Restart Analysis](#kubelet-restart-analysis)
- [Notes](#notes)
- [Lifecycle Nuances](#lifecycle-nuances)
- [Atomic Resizes](#atomic-resizes)
- [Actuating Resizes](#actuating-resizes)
- [Memory Limit Decreases](#memory-limit-decreases)
- [Swap](#swap)
- [Sidecars](#sidecars)
- [QOS Class](#qos-class)
- [Resource Quota](#resource-quota)
- [Affected Components](#affected-components)
- [Instrumentation](#instrumentation)
- [kubelet_container_requested_resizes_total](#kubelet_container_requested_resizes_total)
- [kubelet_pod_resize_duration_seconds](#kubelet_pod_resize_duration_seconds)
- [kubelet_pod_infeasible_resizes_total](#kubelet_pod_infeasible_resizes_total)
- [kubelet_pod_pending_resizes](#kubelet_pod_pending_resizes)
- [kubelet_pod_in_progress_resizes](#kubelet_pod_in_progress_resizes)
- [kubelet_pod_deferred_resize_accepted_total](#kubelet_pod_deferred_resize_accepted_total)
- [Static CPU & Memory Policy](#static-cpu--memory-policy)
- [Future Enhancements](#future-enhancements)
- [Mutable QOS Class "Shape"](#mutable-qos-class-shape)
- [Design Sketch: Workload resource resize](#design-sketch-workload-resource-resize)
- [Design Sketch: Explicit QOS Class](#design-sketch-explicit-qos-class)
- [Design Sktech: Pod-level Resources](#design-sktech-pod-level-resources)
- [Test Plan](#test-plan)
- [Prerequisite testing updates](#prerequisite-testing-updates)
- [Unit Tests](#unit-tests)
- [Allocation Manager](#allocation-manager)
- [Kuberuntime Manager](#kuberuntime-manager)
- [CRI uunit tests](#cri-uunit-tests)
- [Integration tests](#integration-tests)
- [Pod Resize E2E Tests](#pod-resize-e2e-tests)
- [How the tests perform verification](#how-the-tests-perform-verification)
- [Success test cases for Guaranteed Pods with one container](#success-test-cases-for-guaranteed-pods-with-one-container)
- [Success test cases for Guaranteed Pods with multiple containers](#success-test-cases-for-guaranteed-pods-with-multiple-containers)
- [Success test cases for Burstable Pods with one container](#success-test-cases-for-burstable-pods-with-one-container)
- [Other success test cases for Burstable Pods](#other-success-test-cases-for-burstable-pods)
- [Memory limit decrease](#memory-limit-decrease)
- [Patch error tests](#patch-error-tests)
- [Scheduler logic tests](#scheduler-logic-tests)
- [Retry of deferred resizes](#retry-of-deferred-resizes)
- [Resource Quota tests](#resource-quota-tests)
- [Limit Ranger tests](#limit-ranger-tests)
- [Coverage of the READ and REPLACE endpoints](#coverage-of-the-read-and-replace-endpoints)
- [Backward Compatibility and Negative Tests](#backward-compatibility-and-negative-tests)
- [Graduation Criteria](#graduation-criteria)
- [Alpha](#alpha)
- [Beta](#beta)
- [Stable](#stable)
- [Upgrade / Downgrade Strategy](#upgrade--downgrade-strategy)
- [Version Skew Strategy](#version-skew-strategy)
- [Production Readiness Review Questionnaire](#production-readiness-review-questionnaire)
- [Feature Enablement and Rollback](#feature-enablement-and-rollback)
- [Rollout, Upgrade and Rollback Planning](#rollout-upgrade-and-rollback-planning)
- [Monitoring Requirements](#monitoring-requirements)
- [Dependencies](#dependencies)
- [Scalability](#scalability)
- [Troubleshooting](#troubleshooting)
- [Implementation History](#implementation-history)
- [Drawbacks](#drawbacks)
- [Alternatives](#alternatives)
- [Allocated Resource Limits](#allocated-resource-limits)
## Release Signoff Checklist
Items marked with (R) are required *prior to targeting to a milestone / release*.
- [x] (R) Enhancement issue in release milestone, which links to KEP dir in [kubernetes/enhancements] (not the initial KEP PR)
- [x] (R) KEP approvers have approved the KEP status as `implementable`
- [x] (R) Design details are appropriately documented
- [x] (R) Test plan is in place, giving consideration to SIG Architecture and SIG Testing input (including test refactors)
- [x] e2e Tests for all Beta API Operations (endpoints)
- [x] (R) Ensure GA e2e tests for meet requirements for [Conformance Tests](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/conformance-tests.md)
- [x] (R) Minimum Two Week Window for GA e2e tests to prove flake free
- [x] (R) Graduation criteria is in place
- [x] (R) [all GA Endpoints](https://github.com/kubernetes/community/pull/1806) must be hit by [Conformance Tests](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/conformance-tests.md)
- [ ] (R) Production readiness review completed
- [ ] (R) Production readiness review approved
- [x] "Implementation History" section is up-to-date for milestone
- [x] User-facing documentation has been created in [kubernetes/website], for publication to [kubernetes.io]
- [x] Supporting documentation—e.g., additional design documents, links to mailing list discussions/SIG meetings, relevant PRs/issues, release notes
[kubernetes.io]: https://kubernetes.io/
[kubernetes/enhancements]: https://git.k8s.io/enhancements
[kubernetes/kubernetes]: https://git.k8s.io/kubernetes
[kubernetes/website]: https://git.k8s.io/website
## Summary
This proposal aims at allowing Pod resource requests & limits to be updated
in-place, without a need to restart the Pod or its Containers.
The **core idea** behind the proposal is to make PodSpec mutable with regards to
Resources, denoting **desired** resources. Additionally, PodStatus is extended to
provide information about **actual** resources applied to the Pod and its Containers.
This document builds upon [proposal for live and in-place vertical scaling][]
and [Vertical Resources Scaling in Kubernetes][].
[proposal for live and in-place vertical scaling]:
https://github.com/kubernetes/community/pull/1719
[Vertical Resources Scaling in Kubernetes]:
https://docs.google.com/document/d/18K-bl1EVsmJ04xeRq9o_vfY2GDgek6B6wmLjXw-kos4
This proposal also aims to improve the Container Runtime Interface (CRI) APIs for
managing a Container's CPU and memory resource configurations on the runtime.
It seeks to extend UpdateContainerResources CRI API such that it works for
Windows, and other future runtimes besides Linux. It also seeks to extend
ContainerStatus CRI API to allow Kubelet to discover the current resources
configured on a Container.
## Motivation
Resources allocated to a Pod's Container(s) can require a change for various
reasons:
* load handled by the Pod has increased significantly, and current resources
are not sufficient,
* load has decreased significantly, and allocated resources are unused,
* resources have simply been set improperly.
Currently, changing resource allocation requires the Pod to be recreated since
the PodSpec's Container Resources is immutable.
While many stateless workloads are designed to withstand such a disruption,
some are more sensitive, especially when using low number of Pod replicas.
Moreover, for stateful or batch workloads, Pod restart is a serious disruption,
resulting in lower availability or higher cost of running.
Allowing Resources to be changed without recreating the Pod or restarting the
Containers addresses this issue directly.
Additionally, In-Place Pod Vertical Scaling feature relies on Container Runtime
Interface (CRI) to update CPU and/or memory requests/limits for a Pod's Container(s).
The current CRI API set has a few drawbacks that need to be addressed:
1. UpdateContainerResources CRI API takes a parameter that describes Container
resources to update for Linux Containers, and this may not work for Windows
Containers or other potential non-Linux runtimes in the future.
1. There is no CRI mechanism that lets Kubelet query and discover the CPU and
memory limits configured on a Container from the Container runtime.
1. The expected behavior from a runtime that handles UpdateContainerResources
CRI API is not very well defined or documented.
### Goals
* Primary: allow to change container resource requests & limits without
necessarily restarting the container.
* Secondary: allow actors (users, VPA, StatefulSet, JobController) to decide
how to proceed if in-place resource resize is not possible.
* Secondary: allow users to specify which Containers can be resized without a
restart.
Additionally, this proposal has two goals for CRI:
- Modify UpdateContainerResources to allow it to work for Windows Containers,
as well as Containers managed by other runtimes besides Linux,
- Provide CRI API mechanism to query the Container runtime for CPU and memory
resource configurations that are currently applied to a Container.
An additional goal of this proposal is to better define and document the
expected behavior of a Container runtime when handling resource updates.
### Non-Goals
The explicit non-goal of this KEP is to avoid controlling full lifecycle of a
Pod which failed in-place resource resizing. This should be handled by actors
which initiated the resizing.
Other identified non-goals are:
* allow to change Pod QoS class
* to change resources of non-restartable InitContainers
* eviction of lower priority Pods to facilitate Pod resize
* updating extended resources or any other resource types besides CPU, memory
* support for CPU/memory manager policies besides the default 'None' policy
* resolving race conditions with the scheduler
Definition of expected behavior of a Container runtime when it handles CRI APIs
related to a Container's resources is intended to be a high level guide. It is
a non-goal of this proposal to define a detailed or specific way to implement
these functions. Implementation specifics are left to the runtime, within the
bounds of expected behavior.
## Proposal
### API Changes
Container resource requests & limits can now be mutated via the `/resize` pod subresource.
PodStatus is extended to show the resources applied to the Pod and its Containers.
* Pod.Spec.Containers[i].Resources becomes purely a declaration, denoting the
**desired** state of Pod resources
* Pod.Status.ContainerStatuses[i].Resources (new field, type
v1.ResourceRequirements) shows the **actual** resources held by the Pod and
its Containers for running containers, and the allocated resources for non-running containers.
* Pod.Status.ContainerStatuses[i].AllocatedResources (new field, type v1.ResourceList) reports the
allocated resource requests.
* Pod.Status.Conditions explain what is happening for a given resource on a given container (see
details [below](#resize-status)).
The actual resources are reported by the container runtime in some cases, and for all other
resources are copied from the latest snapshot of allocated resources. Currently, the resources
reported by the runtime are CPU Limit (translated from quota & period), CPU Request (translated from
shares), and Memory Limit.
Additionally, a new `Pod.Spec.Containers[i].ResizePolicy[]` field (type
`[]v1.ContainerResizePolicy`) governs whether containers need to be restarted on resize. See
[Container Resize Policy](#container-resize-policy) for more details.
#### Allocated Resources
When the Kubelet admits a pod initially or admits a resize, all resource requirements from the spec
are cached and checkpointed locally. When a container is (re)started, these are the requests and
limits used. Only the allocated requests are reported in the API, through the
`Pod.Status.ContainerStatuses[i].AllocatedResources` field.
The scheduler uses `max(spec...resources, status...allocatedResources, status...resources)` for fit
decisions, but since the actual resources are only relevant and reported for running containers, the
Kubelet sets `status...resources` equal to the allocated resources for non-running containers.
#### Subresource
Resource changes can only be made via the new `/resize` subresource, which accepts Update and Patch
verbs. The request & response types for this subresource are the full pod object, but only the
following fields are allowed to be modified:
* `.spec.containers[*].resources`
* `.spec.initContainers[*].resources` (only for sidecars)
* `.spec.resizePolicy`
#### Validation
Resource fields remain immutable via pod update (a change from the alpha behavior), but are mutable
via the new `/resize` subresource. The following API validation rules will be applied for updates via
the `/resize` subresource:
1. Resources & ResizePolicy must be valid under pod create validation.
2. Computed QOS class cannot change. See [QOS Class](#qos-class) for more details.
3. Running pods without the `Pod.Status.ContainerStatuses[i].Resources` field set cannot be resized.
See [Version Skew Strategy](#version-skew-strategy) for more details.
#### Container Resize Policy
To provide fine-grained user control, PodSpec.Containers is extended with
ResizeRestartPolicy - a list of named subobjects (new object) that supports
'cpu' and 'memory' as names. It supports the following restart policy values:
* `NotRequired` - default value; resize the Container without restart, if possible.
* `RestartContainer` - the container requires a restart to apply new resource values.
(e.g. Java process needs to change its Xmx flag) By using ResizePolicy, user
can mark Containers as safe (or unsafe) for in-place resource update. Kubelet
uses it to determine the required action.
Note: `NotRequired` restart policy for resize does not *guarantee* that a container won't be
restarted. If the runtime knows a resize will trigger a restart, it should return an error instead,
and the Kubelet will retry the resize on the next pod sync. The behavior when shrinking
memory limits is defined under [Memory Limit Decreases](#memory-limit-decreases) below.
Setting the flag to separately control CPU & memory is due to an observation
that usually CPU can be added/removed without much problem whereas changes to
available memory are more probable to require restarts.
If more than one resource type with different policies are updated at the same
time, then `RestartContainer` policy takes precedence over `NotRequired` policy.
If a pod's RestartPolicy is `Never`, the ResizePolicy fields must be set to
`NotRequired` to pass validation. That said, any in-place resize may result
in the container being stopped *and not restarted*, if the system can not
perform the resize in place.
The `ResizePolicy` field is immutable.
#### Resize Status
Resize status will be tracked via 2 new pod conditions: `PodResizePending` and `PodResizeInProgress`.
**PodResizePending** will track states where the spec has been resized, but the Kubelet has not yet
allocated the resources (desired resources != actuated resources). There are two reasons associated
with this condition:
* `Deferred` - the proposed resize is feasible in theory (it fits on this node)
but is not possible right now; it will be regularly reevaluated. This can happen
if the node does not have enough free resources at the moment, but might in the
future when other pods are removed or scaled down.
* `Infeasible` - the proposed resize is not feasible and is rejected; it will never
be re-evaluated. Today, the possible reasons for infeasible include:
* The requested resources exceed the node's total capacity.
* The pod is a static pod.
* In-place resize is not yet supported for containers with swap enabled.
* In-place resize is not yet supported for guaranteed pods alongside memory manager static policy.
* In-place resize is not yet supported for guaranteed pods alongside CPU manager static policy.
In either case, the condition's `message` will include details of why the resize has not been
admitted. `lastTransitionTime` will be populated with the time the condition was added. `status`
will always be `True` when the condition is present - if there is no longer a pending resized
(either the resize was allocated or reverted), the condition will be removed. `observedGeneration` will
reflect the `metadata.generation` of the pod when the resize was last attempted.
**PodResizeInProgress** will track in-progress resizes, and should be present whenever allocated resources
!= actuated resources (see [Resource States](#resource-states)). For successful synchronous
resizes, this condition should be short lived, and `reason` and `message` will be left blank. If an
error occurs while actuating the resize, the `reason` will be set to `Error`, and `message` will be
populated with the error message. In the future, this condition will also be used for long-running
resizing behaviors (see [Memory Limit Decreases](#memory-limit-decreases)). `observedGeneration` will
reflect the `metadata.generation` of the pod when the resize was initially requested.
Note that it is possible for both conditions to be present at the same time, for example if an error
is encountered while actuating a resize and a new resize comes in that gets deferred.
Prior to v1.33, the resize status was tracked by a dedicated `Pod.Status.Resize` field. This field
will be deprecated, and not graduate to beta.
#### CRI Changes
As of Kubernetes v1.20, the CRI has included support for in-place resizing of containers via the
`UpdateContainerResources` API, which is implemented by both containerd and CRI-O. Additionally, the
`ContainerStatus` message includes a `ContainerResources` field, which reports the current resource
configuration of the container. `UpdateContainerResources` must be idempotent, if called with the
same configuration multiple times.
Starting with Kubernetes v1.33, the contract on the `UpdateContainerResources` call will be updated
to specify that runtimes should not deliberately restart the container to adjust the resources. If a
restart is required to resize, the runtime should return an error instead. There may be edge-cases
where a restart can still be triggered (see [Memory Limit Decreases](#memory-limit-decreases)), so
this is a best-effort requirement. There is no enforcement of this behavior.
Even though pod-level cgroups are currently managed by the Kubelet, runtimes may rely need to be
notified when the resource configuration changes. For example, this information should be passed
through to NRI plugins. To this end, we will add a new `UpdatePodSandboxResources` API:
```proto
service RuntimeService {
...
// UpdatePodSandboxResources synchronously updates the PodSandboxConfig with
// the pod-level resource configuration. This method is called _after_ the
// Kubelet reconfigures the pod-level cgroups.
// This request is treated as best effort, and failure will not block the
// Kubelet with proceeding with a resize.
rpc UpdatePodSandboxResources(UpdatePodSandboxResourcesRequest) returns (UpdatePodSandboxResourcesResponse) {}
}
message UpdatePodSandboxResourcesRequest {
// ID of the PodSandbox to update.
string pod_sandbox_id = 1;
// Optional overhead represents the overheads associated with this sandbox
LinuxContainerResources overhead = 2;
// Optional resources represents the sum of container resources for this sandbox
LinuxContainerResources resources = 3;
}
message UpdatePodSandboxResourcesResponse {}
```
The Kubelet will call `UpdatePodSandboxResources` _after_ it has reconfigured the pod-level cgroups.
This ordering is consistent with pod creation, where the Kubelet configures the pod-level cgroups
before calling `RunPodSandbox`.
For now, the `UpdatePodSandboxResources` call will be treated as best-effort by the Kubelet. This
means that in the case of an error, Kubelet will log the the error but otherwise ignore it and
proceed with the resize.
Note: Windows resources are not included here since they are not present in the
WindowsPodSandboxConfig.
### Risks and Mitigations
1. Backward compatibility: When Pod.Spec.Containers[i].Resources becomes
representative of desired state, and Pod's actual resource configurations are
tracked in Pod.Status.ContainerStatuses[i].Resources, applications
that query PodSpec and rely on Resources in PodSpec to determine resource
configurations will see values that may not represent actual configurations. As a
mitigation, this change needs to be documented and highlighted in the
release notes, and in top-level Kubernetes documents.
1. Scheduler race condition: If a resize happens concurrently with the scheduler evaluating the node
where the pod is resized, it can result in a node being over-scheduled, which will cause the pod
to be rejected with an `OutOfCPU` or `OutOfMemory` error. Solving this race condition is out of
scope for this KEP, but a general solution may be considered in the future.
## Design Details
### Resource States
In-place pod resizing adds a lot of new resource states. These are detailed in other sections of
this KEP, but summarized here to help understand how they relate to each other.
The Kubelet now tracks 4 sets of resources for each pod/container:
1. Desired resources
- What the user (or controller) asked for
- Recorded in the API as the spec resources (`.spec.container[i].resources`)
2. Allocated resources
- The resources that the Kubelet admitted, and intends to actuate
- Reported in the API through the `.status.containerStatuses[i].allocatedResources` field
(allocated requests only)
- Persisted locally on the node (requests + limits) in a checkpoint file
3. Actuated resources
- The resource configuration that the Kubelet passed to the runtime to actuate
- Not reported in the API
- Persisted locally on the node in a checkpoint file
- See [Actuating Resizes](#actuating-resizes) for more details
4. Actual resources
- The actual resource configuration the containers are running with, reported by the runtime,
typically read directly from the cgroup configuration
- Reported in the API via the `.status.conatinerStatuses[i].resources` field
- _Note: for non-running contiainers `.status.conatinerStatuses[i].resources` will be the Allocated resources._
Changes are always propogated through these 4 resource states in order:
```
Desired --> Allocated --> Actuated --> Actual
```
### Priority of Resize Requests
Resize requests detected by the kubelet (in `HandlePodUpdates` and `HandlePodAdditions`)
will be added to a queue of pending resizes. Resize requests will be attempted according to
the following priority:
1. *Resource requests are not increasing*: Resizes that don't increase requests will be
prioritized first. These resizes are expected to always succeed and would not be marked as
pending.
2. *PriorityClass*: Pods with a higher PriorityClass.
3. *QoS Class*: Pods with a higher QoS class, where Guaranteed > Burstable. Best effort pods
do not have CPU or memory resources, so are excluded from the discussion here.
4. *Time since resize request*: If all else is the same, resizes that have been pending
longer will be retried first (leveraging LastTransitionTime on the PodResizePending condition).
These priorities are *only* used to indicate which resize requests will be attempted first.
Scheduler preemption/eviction to make room for pending resizes is not in scope.
A higher priority resize being marked as pending should not block the remaining pending resizes
from being attempted, i.e. we will try all remaining resizes in the queue even if one is unsuccessful.
Resizes that are deferred will be added back to the queue to be re-attempted later. Resizes that are
infeasible may never be retried.
Allocation will be attempted on the pods in the queue:
- At the end of `HandlePodUpdates`, `HandlePodRemoves`, and `HandlePodCleanups` when a change to the queue is detected.
- Upon completion of another resize request.
- Periodically, to catch any cases that we may have missed.
A successful allocation will trigger a pod sync, which will actuate the allocated resize and update the
pod status accordingly.
### Kubelet-triggered eviction
A pod can be marked as critical with the `priorityClassName` of `system-node-critical` or `system-cluster-critical` as
described in [Guaranteed Scheduling For Critical Add-On Pods](https://kubernetes.io/docs/tasks/administer-cluster/guaranteed-scheduling-critical-addon-pods/#marking-pod-as-critical). If the kubelet receives a resize request for a
critical pod and there is not enough space for the resize, it will evict a non-critical pod to make room.
### Kubelet and API Server Interaction
When a new Pod is created, Scheduler is responsible for selecting a suitable
Node that accommodates the Pod.
For a newly created Pod, `(Init)ContainerStatuses` will be nil until the Pod is
scheduled to a node. When Kubelet admits a Pod, it will record the admitted
requests & limits to its internal allocated resources checkpoint.
When a Pod resize is requested, Kubelet attempts to update the resources
allocated to the Pod and its Containers. Kubelet first checks if the new desired
resources can fit the Node allocable resources by computing the sum of resources
allocated for all Pods in the Node, except the Pod being resized. For the Pod
being resized, it adds the new desired resources (i.e
Spec.Containers[i].Resources.Requests) to the sum.
* If new desired resources fit, Kubelet accepts the resize, updates the allocated resources, and
adds the `PodResizeInProgress` condition. It then invokes the UpdateContainerResources CRI API to update
Container resource limits. Once all Containers are successfully updated, it updates
Status...Resources to reflect new resource values and removes the condition.
* If new desired resources don't fit, Kubelet will add the `PodResizePending` condition with type
`Infeasible` and a message explaining why.
* If new desired resources fit but are in-use at the moment, Kubelet will add the `PodResizePending`
condition with type `Deferred` and a message explaining why.
In addition to the above, kubelet will generate Events on the Pod whenever a
resize is accepted or rejected, and if possible at key steps during the resize
process. This will allow humans to know that progress is being made.
If multiple Pods need resizing, they are handled sequentially in an order
defined by the Kubelet (e.g. in order of arrivial).
Scheduler may, in parallel, assign a new Pod to the Node because it uses cached
Pods to compute Node allocable values. If this race condition occurs, Kubelet
resolves it by rejecting that new Pod if the Node has no room after Pod resize.
Note: After a Pod is rejected, the scheduler could try to reschedule the
replacement pod on the same node that just rejected it. This is a general
statement about Kubernetes and is outside the scope of this KEP.
#### Kubelet Restart Tolerance
If Kubelet were to restart amidst handling a Pod resize, then upon restart, all
Pods are re-admitted based on their current allocated resources (restored from
checkpoint). Pending resizes are handled after all existing Pods have been
added. This ensures that resizes don't affect previously admitted existing Pods.
### Scheduler and API Server Interaction
Scheduler continues to use Pod's Spec.Containers[i].Resources.Requests for
scheduling new Pods, and continues to watch Pod updates, and updates its cache.
To compute the Node resources allocated to Pods, pending resizes must be factored in.
The scheduler will use the maximum of:
1. Desired resources, computed from container requests in the pod spec, unless the resize is marked as `Infeasible`
1. Actual resources, computed from the `.status.containerStatuses[i].resources.requests`
1. Allocated resources, reported in `.status.containerStatuses[i].allocatedResources`
### Flow Control
The following steps denote the flow of a series of in-place resize operations
for a Pod with ResizePolicy set to NotRequired for all its Containers.
This is intentionally hitting various edge-cases for demonstration.
1. A new pod is created
- `spec.containers[0].resources.requests[cpu]` = 1
- `spec.containers[0].resizePolicy[cpu].restartPolicy` = `"NotRequired"`
- all status is unset
1. Pod is scheduled
- `spec.containers[0].resources.requests[cpu]` = 1
- status still mostly unset
1. kubelet runs the pod and updates the API
- `spec.containers[0].resources.requests[cpu]` = 1
- `status.containerStatuses[0].allocatedResources[cpu]` = 1
- `actuated[cpu]` = 1
- `status.containerStatuses[0].resources.requests[cpu]` = 1
- actual CPU shares = 1024
1. Resize #1: cpu = 1.5 (via PUT or PATCH to /resize)
- apiserver validates the request (e.g. `limits` are not below
`requests`, ResourceQuota not exceeded, etc) and accepts the operation
- `spec.containers[0].resources.requests[cpu]` = 1.5
- `status.containerStatuses[0].allocatedResources[cpu]` = 1
- `actuated[cpu]` = 1
- `status.containerStatuses[0].resources.requests[cpu]` = 1
- actual CPU shares = 1024
1. Kubelet Restarts!
- The allocated & actuated resources are read back from checkpoint
- Pods are resynced from the API server, but admitted based on the allocated resources
- `spec.containers[0].resources.requests[cpu]` = 1.5
- `status.containerStatuses[0].allocatedResources[cpu]` = 1
- `actuated[cpu]` = 1
- `status.containerStatuses[0].resources.requests[cpu]` = 1
- actual CPU shares = 1024
1. Kubelet syncs the pod, sees resize #1 and admits it
- `spec.containers[0].resources.requests[cpu]` = 1.5
- `status.containerStatuses[0].allocatedResources[cpu]` = 1.5
- `actuated[cpu]` = 1
- `status.containerStatuses[0].resources.requests[cpu]` = 1
- `status.conditions[type==PodResizeInProgress]` added
- actual CPU shares = 1024
1. Resize #2: cpu = 2
- apiserver validates the request and accepts the operation
- `spec.containers[0].resources.requests[cpu]` = 2
- `status.containerStatuses[0].allocatedResources[cpu]` = 1.5
- `status.containerStatuses[0].resources.requests[cpu]` = 1
- `status.conditions[type==PodResizeInProgress]`
- actual CPU shares = 1024
1. Container runtime applied cpu=1.5
- `spec.containers[0].resources.requests[cpu]` = 2
- `status.containerStatuses[0].allocatedResources[cpu]` = 1.5
- `actuated[cpu]` = 1.5
- `status.containerStatuses[0].resources.requests[cpu]` = 1
- `status.conditions[type==PodResizeInProgress]`
- actual CPU shares = 1536
1. kubelet syncs the pod, and sees resize #2 (cpu = 2)
- kubelet decides this is feasible, but currently insufficient available resources
- `spec.containers[0].resources.requests[cpu]` = 2
- `status.containerStatuses[0].allocatedResources[cpu]` = 1.5
- `actuated[cpu]` = 1.5
- `status.containerStatuses[0].resources.requests[cpu]` = 1.5
- `status.conditions[type==PodResizePending].type` = `"Deferred"`
- `status.conditions[type==PodResizeInProgress]` removed
- actual CPU shares = 1536
1. Resize #3: cpu = 1.6
- apiserver validates the request and accepts the operation
- `spec.containers[0].resources.requests[cpu]` = 1.6
- `status.containerStatuses[0].allocatedResources[cpu]` = 1.5
- `actuated[cpu]` = 1.5
- `status.containerStatuses[0].resources.requests[cpu]` = 1.5
- `status.conditions[type==PodResizePending].type` = `"Deferred"`
- actual CPU shares = 1536
1. Kubelet syncs the pod, and sees resize #3 and admits it
- `spec.containers[0].resources.requests[cpu]` = 1.6
- `status.containerStatuses[0].allocatedResources[cpu]` = 1.6
- `actuated[cpu]` = 1.5
- `status.containerStatuses[0].resources.requests[cpu]` = 1.5
- `status.conditions[type==PodResizePending]` removed
- `status.conditions[type==PodResizeInProgress]` added
- actual CPU shares = 1536
1. Container runtime applied cpu=1.6
- `spec.containers[0].resources.requests[cpu]` = 1.6
- `status.containerStatuses[0].allocatedResources[cpu]` = 1.6
- `actuated[cpu]` = 1.6
- `status.containerStatuses[0].resources.requests[cpu]` = 1.5
- `status.conditions[type==PodResizeInProgress]`
- actual CPU shares = 1638
1. Kubelet syncs the pod
- `spec.containers[0].resources.requests[cpu]` = 1.6
- `status.containerStatuses[0].allocatedResources[cpu]` = 1.6
- `actuated[cpu]` = 1.6
- `status.containerStatuses[0].resources.requests[cpu]` = 1.6
- `status.conditions[type==PodResizeInProgress]` removed
- actual CPU shares = 1638
1. Resize #4: cpu = 100
- apiserver validates the request and accepts the operation
- `spec.containers[0].resources.requests[cpu]` = 100
- `status.containerStatuses[0].allocatedResources[cpu]` = 1.6
- `actuated[cpu]` = 1.6
- `status.containerStatuses[0].resources.requests[cpu]` = 1.6
- actual CPU shares = 1638
1. Kubelet syncs the pod, and sees resize #4
- this node does not have 100 CPUs, so kubelet cannot admit it
- `spec.containers[0].resources.requests[cpu]` = 100
- `status.containerStatuses[0].allocatedResources[cpu]` = 1.6
- `actuated[cpu]` = 1.6
- `status.containerStatuses[0].resources.requests[cpu]` = 1.6
- `status.conditions[type==PodResizePending].type` = `"Infeasible"`
- actual CPU shares = 1638
#### Container resource limit update ordering
When in-place resize is requested for multiple Containers in a Pod, Kubelet
updates resource limit for the Pod and its Containers in the following manner:
1. If resource resizing results in net-increase of a resource type (CPU or
Memory), Kubelet first updates Pod-level cgroup limit for the resource
type.
1. All container limit decreases are applied.
1. If all container limit decreases succeeded and resource resizing results in net-decrease of a
resource type, Kubelet then updates the Pod-level cgroup limit.
1. If all previous steps succeeded, container limit increases are applied.
In all the above cases, Kubelet applies Container resource limit decreases
before applying limit increases.
#### Container resource limit update failure handling
If an `UpdateContainerResources` request fails while container limit decreases are being applied,
the remainder of the container limit decreases will be attempted, but container limit increases or
pod limit decreases will not. This ensures that sum of the container limits does not exceed
Pod-level cgroup limit at any point.
If an `UpdateContainerResources` request fails while container limit increases are being applied,
the remaining container limit increases will still be attempted.
If any errors are raised during the resize process:
- An event will be emitted with the error details
- The ResizeStatus will be set to `Error`
- The pod will be requeued for sync, and the resize will be retried on the next pod sync.
#### CRI Changes Flow
Below diagram is an overview of Kubelet using UpdateContainerResources and
ContainerStatus CRI APIs to set new container resource limits, and update the
Pod Status in response to user changing the desired resources in Pod Spec.
```
+-----------+ +-----------+ +-----------+
| | | | | |
| apiserver | | kubelet | | runtime |
| | | | | |
+-----+-----+ +-----+-----+ +-----+-----+
| | |
| watch (pod update) | |
|------------------------------>| |
| [Containers.Resources] | |
| | |
| (admit) |
| | |
| | UpdateContainerResources() |
| |----------------------------->|
| | (set limits)
| |<- - - - - - - - - - - - - - -|
| | |
| | ContainerStatus() |
| |----------------------------->|
| | |
| | [ContainerResources] |
| |<- - - - - - - - - - - - - - -|
| | |
| update (pod status) | |
|<------------------------------| |
| [ContainerStatuses.Resources] | |
| | |
```
* Kubelet invokes UpdateContainerResources() CRI API in ContainerManager
interface to configure new CPU and memory limits for a Container by
specifying those values in ContainerResources parameter to the API. Kubelet
sets ContainerResources parameter specific to the target runtime platform
when calling this CRI API.
* Kubelet calls ContainerStatus() CRI API in ContainerManager interface to get
the CPU and memory limits applied to a Container. It uses the values returned
in ContainerStatus.Resources to update ContainerStatuses[i].Resources.Limits
for that Container in the Pod's Status.
#### Kubelet Restart Analysis
Analysis of Kubelet restarts happening at various points of resize, and how recovery happens.
Impacts of a restart outside of resource configuration are out of scope.
1. Kubelet Admits a new pod
- Resource allocation checkpointed before sending the pod to the pod workers
- Restart before checkpointing: pod goes through admission again as if new
- Restart after checkpointing: pod goes through admission using the allocated resources
1. Kubelet creates a container
- Resources actuated after CreateContainer call succeeds
- Restart before acknowledgement: Kubelet issues a superfluous UpdatePodResources request
- Restart after acknowledgement: No resize needed
1. Container starts, triggering a pod sync event
- Kubelet updates status with actual resources reported by runtime, allocated resources from checkpoint
- Allocated == Acknowledeged, so no resize needed
- No races around restart.
1. Pod is resized in the API, Kubelet observes the update
- Triggers a pod sync
- On restart, Kubelet reads the latest pod from the API and triggers a pod sync, so same effect as
observing the update.
1. Updated pod is synced: Check if pod can be admitted
- No: add `PodResizePending` condition with type `Deferred`, no change to allocated resources
- Restart: redo admission check, still deferred.
- Yes: add `PodResizeInProgress` condition, update allocated checkpoint
- Restart before update: readmit, then update allocated
- Restart after update: allocated != actuated --> proceed with resize
1. Allocated != Actuated
- Trigger an `UpdateContainerResources` CRI call, then update Actuated resources on success
- Restart before CRI call: allocated != actuated, will still trigger the update call
- Restart after CRI call, before actuated update: will redo update call
- Restart after actuated update: allocated == actuated, condition removed
- In all restart cases, `LastTransitionTime` is propagated from the old pod status `PodResizeInProgress`
condition, and remains unchanged.
1. PLEG updates PodStatus cache, triggers pod sync
- Pod status updated with actual resources, `PodResizeInProgress` condition removed
- Desired == Allocated == Actuated, no resize changes needed.
#### Notes
* To avoid races and possible gamification, all components will use Pod's
Status.ContainerStatuses[i].Resources when computing resources used
by Pods.
* If additional resize requests arrive when a Pod is being resized, those
requests are handled after completion of the resize that is in progress. And
resize is driven towards the latest desired state.
* Impact of Pod Overhead: Kubelet adds Pod Overhead to the resize request to
determine if in-place resize is possible.
* At this time, Vertical Pod Autoscaler should not be used with Horizontal Pod
Autoscaler on CPU, memory. This enhancement does not change that limitation.
### Lifecycle Nuances
* Terminated containers can be "resized" in that the resize is permitted by the API, and the Kubelet
will accept the changes. This makes race conditions where the container terminates around the
resize "fail open", and prevents a resize of a terminated container from blocking the resize of a
running container (see [Atomic Resizes](#atomic-resizes)).
* Resizing pods in a graceful shutdown state is permitted, and will be actuated best-effort.
### Atomic Resizes
A single resize request can change multiple values, including any or all of:
* Multiple resource types
* Requests & Limits
* Multiple containers
These resource requests & limits can have interdependencies that Kubernetes may not be aware of. For
example, two containers (in the same pod) coordinating work may need to be scaled in tandem. It probably doesn't makes
sense to scale limits independently of requests, and scaling CPU without memory could just waste
resources. To mitigate these issues and simplify the design, the Kubelet will treat the requests &
limits for all containers in the spec as a single atomic request, and won't accept any of the
changes unless all changes can be accepted. If multiple requests mutate the resources spec before
the Kubelet has accepted any of the changes, it will treat them as a single atomic request.
Note: If a second infeasible resize is made before the Kubelet allocates the first resize, there can
be a race condition where the Kubelet may or may not accept the first resize, depending on whether
it admits the first change before seeing the second. This race condition is accepted as working as
intended.
The atomic resize requirement may be reevaluated in the context of pod-level resources.
### Actuating Resizes
The resources specified by the Kubelet are not guaranteed to be the actual resources configured for
a pod or container. Examples include:
- Linux kernel enforced minimums for CPU shares & quota
- Systemd cgroup driver rounds CPU quota up to the nearest 10ms
- NRI plugins can change resource configuration
Therefore the Kubelet cannot reliably compare desired & actual resources to know whether to trigger
a resize (a level-triggered approach).
To accommodate this, the Kubelet stores the set of "actuated" resources per container.
Actuated resources represent the resource configuration that was passed to the runtime (either
via a CreateContainer or UpdateContainerResources call) and received a successful response. The
actuated resources are checkpointed alongside the allocated resources to persist across
restarts. There is the possibility that a poorly timed restart could lead to a resize request being
repeated, so `UpdateContainerResources` must be idempotent.
When a resize CRI request succeeds, the pod will be marked for resync to read the latest resources. If
the actual configured resources do not match the desired resources, this will be reflected in the
pod status resources, but not otherwise acted upon.
If a resize request does not succeed, the Kubelet will retry the resize on every subsequent pod
sync, until it succeeds or the container is terminated.
### Memory Limit Decreases
Setting the memory limit below current memory usage can cause problems. If the kernel cannot reclaim
sufficient memory, the outcome depends on the cgroups version. With cgroups v1 the change will
simply be rejected by the kernel, whereas with cgroups v2 it will trigger an oom-kill.
If the memory resize restart policy is `NotRequired` (or unspecified), the Kubelet will make a
**best-effort** attempt to prevent oom-kills when decreasing memory limits, but doesn't provide any
guarantees. Before decreasing container memory limits, the Kubelet will read the container memory
usage (via the StatsProvider). If usage is greater than the desired limit, the resize will be
skipped for that container. The pod condition `PodResizeInProgress` will remain, with an `Error`
reason, and a message reporting the current usage & desired limit. This is considered best-effort
since it is still subject to a time-of-check-time-of-use (TOCTOU) race condition where the usage exceeds the limit after the
check is performed. A similar check will also be performed at the pod level before lowering the pod
cgroup memory limit.
_Version skew note:_ Kubernetes v1.33 (and earlier) nodes only check the pod-level memory usage.
### Swap
Currently (v1.35), if swap is enabled & configured, burstable pods are allocated swap based on their
memory requests. Since resizing swap requires more thought and additional design, we will forbid
resizing memory requests of such containers for now. Since the API server is not privy to the node's
swap configuration, this will be surfaced as resizes being marked `Infeasible`.
We try to relax this restriction in the future.
### Sidecars
Sidecars, a.k.a. restartable InitContainers can be resized the same as regular containers. There are
no special considerations here. Non-restartable InitContainers cannot be resized.
### QOS Class
A pod's QOS class is immutable. This is enforced during validation, which requires that after a
resize the computed QOS Class matches the previous QOS class.
[Future enhancements: Mutable QOS Class "Shape"](#mutable-qos-class-shape) proposes a potential
change to partially relax this restriction, but is removed from the scope of this KEP.
[Future enhancements: explicit QOS Class](#design-sketch-explicit-qos-class) proposes an alternative
enhancement on that, to make QOS class explicit and improve semantics around [workload resource
resize](#design-sketch-workload-resource-resize).
### Resource Quota
With InPlacePodVerticalScaling enabled, resource quota needs to consider pending resizes. Similarly
to how this is handled by scheduling, resource quota will use the maximum of:
1. Desired resources, computed from container requests in the pod spec, unless the resize is marked as `Infeasible`
1. Actual resources, computed from the `.status.containerStatuses[i].resources.requests`
1. Allocated resources, reported in `.status.containerStatuses[i].allocatedResources`
To properly handle scale-down, resource quota controller now needs to evaluate
pod updates where `.status...resources` changed.
### Affected Components
Pod v1 core API:
* extend API
* added validation allowing only CPU and memory resource changes
Admission Controllers: LimitRanger, ResourceQuota need to support Pod Updates:
* for ResourceQuota, podEvaluator.Handler implementation is modified to allow
Pod updates, and verify that sum of Pod.Spec.Containers[i].Resources for all
Pods in the Namespace don't exceed quota,
* PodResourceAllocation admission plugin is ordered before ResourceQuota.
* for LimitRanger we check that a resize request does not violate the min and
max limits specified in LimitRange for the Pod's namespace.
Kubelet:
* set Pod's Status.ContainerStatuses[i].Resources for Containers upon placing
a new Pod on the Node,
* update Pod's Status...AllocatedResources and Status...Resources upon resize,
* manage the new `PodResizePending` and `PodResizeInProgress` conditions
* change UpdateContainerResources CRI API to work for both Linux & Windows.
Scheduler:
* compute resource allocations using actual Status...Resources.
Other components:
* check how the change of meaning of resource requests influence other
Kubernetes components.
### Instrumentation
The kubelet will record the following metrics:
#### `kubelet_container_requested_resizes_total`
This metric tracks the total number of resize attempts observed by the Kubelet, counted at the container level.
A single pod update changing multiple containers will be considered separate resize attempts.
Labels:
- `resource` - what resource. Possible values: `cpu`, or `memory`. If more than one of these is changing in the resize request, we increment the counter multiple times, once for each.
- `requirement` - Possible values: `limits`, or `requests`. If more than one of these is changing in the resize request, we increment the counter multiple times, once for each.
- `operation` - whether the resize is an increase or a decrease. Possible values: `increase`, `decrease`, `add`, or `remove`.
This metric is recorded as a counter.
#### `kubelet_pod_resize_duration_seconds`
This metric tracks the duration of [doPodResizeAction](https://github.com/kubernetes/kubernetes/blob/92de70895830ea1a9c2c6554bdab4cbee7ce867d/pkg/kubelet/kuberuntime/kuberuntime_manager.go#L699), which
is responsible for actuating the resize.
This metric is recorded as a histogram.
#### `kubelet_pod_infeasible_resizes_total`
This metric tracks the total number of resizes that were rejected by the kubelet as infeasible.
Labels:
- `reason_detail` - more details about why the resize is pending. Although a more detailed "message" will be provided in the `PodResizePending`
condition in the pod, we limit this label to only the following possible values to keep cardinality low:
- `guaranteed_pod_cpu_manager_static_policy` - In-place resize is not supported for Guaranteed Pods alongside CPU Manager static policy.
- `guaranteed_pod_memory_manager_static_policy` - In-place resize is not supported for Guaranteed Pods alongside Memory Manager static policy.
- `static_pod` - In-place resize is not supported for static pods.
- `swap_limitation` - In-place resize is not supported for containers with swap.
- `insufficient_node_allocatable` - The node doesn't have enough capacity for this resize request.
This list of possible reasons may shrink or grow depending on limitations that are added or removed in the future.
This metric is recorded as a counter.
#### `kubelet_pod_pending_resizes`
This metric tracks the current count of pods that the kubelet marks as pending. This will make it
easier for us to see which of the current limitations users are running into the most.
Labels:
- `reason` - why the resize is pending. Possible values: `infeasible` or `deferred`.
This metric is recorded as a gauge.
#### `kubelet_pod_in_progress_resizes`
This metric tracks the total count of resize requests that the kubelet marks as in progress, meaning that
the resources have been allocated but not yet actuated.
This metric is recorded as a gauge.
#### `kubelet_pod_deferred_resize_accepted_total`
This metric tracks the total number of resize requests that the Kubelet originally marked as deferred but
later accepted. This metric primarily exists because if a deferred resize is accepted through the timed retry (as
opposed to being triggered by an event such as another pod being deleted or sized down), it indicates an issue in the Kubelet's logic for handling deferred resizes that we should fix.
Labels:
- `retry_trigger` - whether the resize was accepted through the timed retry or due to another pod event. Possible values: `periodic_retry`, `pod_resized`, `pod_updated`, `pods_added`, `pods_removed`.
This metric is recorded as a counter.
### Static CPU & Memory Policy
Resizing pods with static CPU & memory policy configured is out-of-scope for this KEP. If a pod is a guaranteed QOS on a node with a static CPU or memory policy configured, then the resize will be marked as infeasible.
This suppport will be added post-GA as a separate enhancement in its own KEP.
### Future Enhancements
1. Improve memory limit decrease oom-kill prevention by leveraging other kernel mechanisms or using
gradual decreaese.
1. Kubelet (or Scheduler) evicts lower priority Pods from Node to make room for
resize. Pre-emption by Kubelet may be simpler and offer lower latencies.
1. Allow ResizePolicy to be set on Pod level, acting as default if (some of)
the Containers do not have it set on their own.
1. Extend ResizePolicy to separately control resource increase and decrease
(e.g. a Container can be given more memory in-place but decreasing memory
requires Container restart).
1. Handle resize of guaranteed pods with static CPU or memory policy.
1. Extend controllers (Job, Deployment, etc) to propagate Template resources
update to running Pods.
1. Allow resizing local ephemeral storage.
1. Handle pod-scoped resources (https://github.com/kubernetes/enhancements/pull/1592)
1. Explore periodic resyncing of resources. That is, periodically issue resize requests to the
runtime even if the allocated resources haven't changed.
1. Allow resizing containers with swap allocated.
#### Mutable QOS Class "Shape"
This change was originally proposed for Beta, but moved out of the scope. It may still be considered
for a future enhancement to relax the constraints on resizes.
A pod's QOS class **cannot be changed** once the pod is started, independent of any resizes.
To clarify the discussion of the proposed QOS Class changes, the following terms are defined:
* "QOS Class" - The QOS class that was computed based on the original resource requests & limits
when the pod was first created.
* "QOS Shape" - The QOS class that _would_ be computed based on the current resource requests &
limits.
On creation, the QOS Class is equal to the QOS Shape. After a resize, the QOS Shape must be greater
than or equal to the original QOS Class:
* Guaranteed pods: must maintain `requests == limits`, and must be set for both CPU & memory
* Burstable pods: _can_ be resized such that `requests == limits`, but their original QOS
class will stay burstable. Must retain at least one CPU or memory request or limit.
* BestEffort pods: can be freely resized, but stay BestEffort.
Even though the QOS Shape is allowed to change, the original QOS class is used for all
decisions based on QOS class:
* `.status.qosClass` always reports the original QOS class
* Pod cgroup hierarchy is static, using the original QOS class
* Non-guaranteed pods remain ineligible for guaranteed CPUs or NUMA pinning
* Preemption uses the original QOS Class
* OOMScoreAdjust is calculated with the original QOS Class
* Memory pressure eviction is unaffected (doesn't consider QOS Class)
The original QOS Class is persisted to the status. On restart, the Kubelet is allowed to read the
QOS class back from the status.
See [future enhancements: explicit QOS Class](#design-sketch-explicit-qos-class) for a possible
change to make QOS class explicit and improve semantics around
[workload resource resize](#design-sketch-workload-resource-resize).
#### Design Sketch: Workload resource resize
The following [workload resources](https://kubernetes.io/docs/concepts/workloads/) are considered
for in-place resize support:
* Deployment
* ReplicaSet
* StatefulSet
* DaemonSet
* Job
* CronJob
Each of these resources will have a new `ResizePolicy` field added to the spec. In the case of
Deployments or Cronjobs, the child (ReplicaSet/Job) will inherit the policy. The resize policy is
set to one of: `InPlace` or `Recreate` (default). If the policy is set to recreate, the behavior is
unchanged, and generally induces a rolling update.
If the policy is set to in-place, the controller will *attempt* to issue an in-place resize to all
the child pods. If the resize is not a legal in-place resize, such as changing from guaranteed to
burstable, the replicas will be recreated.
Open Questions:
* Will resizes be issued through a new `/resize` subresource? If so, what happens if a resize is
made that doesn't go through the subresource?
* Does ResizePolicy need to be per-resource type (similar to the resize restart policy on pods)?
* Can you do a rolling-in-place-resize, or are all child pod resizes issued more or less
simultaneously?
#### Design Sketch: Explicit QOS Class
Workload resource resize presents a problem for QOS handling. For example:
1. ReplicaSet created with a burstable pod shape
2. Initial burstable replicas created
3. Resize to a guaranteed shape
4. Initial replicas are still burstable, but with a guaranteed shape
5. Horizontally scale the RS to add additional replicas
6. New replicas are created with the guaranteed resource shape, and assigned the guaranteed QOS class
7. Resize back to a burstable shape (undoing step 3)
After step 6, there are a mix of burstable & guaranteed replicas. In step 7, the burstable pods can
be resized in-place, but the guaranteed pods will need to be recreated.
To mitigate this, we can introduce an explicit QOSClass field to the pod spec. If set, it must be
less than or equal to the QOS shape. In other words, you can set a guaranteed resource shape but an
explicit QOSClass of burstable, but not the other way around. If set, the status QOSClass is synced
to the explicit QOSClass, and the rest of the behavior is unchanged from the
[QOS Class Proposal](#qos-class).
Going back to the earlier example, if the original ReplicaSet set an explicit Burstable QOSClass,
then the heterogeneity in step 6 is avoided. Alternatively, if there was a real desire to switch to
guaranteed in step 3, then the explicit QOSClass can be changed, triggering a recreation of all
replicas.
#### Design Sktech: Pod-level Resources
Adding resize capabilities to [Pod-level Resources](https://github.com/kubernetes/enhancements/issues/2837)
should largely mirror container-level resize. This includes:
- Add actual resources to `PodStatus.Resources`
- Track allocated pod-level resources
- Factor pod-level resource resize into ResizeStatus logic
- Pod-level resizes are treated as atomic with container level resizes.
Open questions:
- Details around defaulting logic, pending finalization in the pod-level resources KEP
- If the resize policy is `RestartContainer`, are all containers restarted on pod-level resize? Or
does it depend on whether container-level cgroups are changing?
### Test Plan
[x] I/we understand the owners of the involved components may require updates to
existing tests to make this code solid enough prior to committing the changes necessary
to implement this enhancement.
#### Prerequisite testing updates
#### Unit Tests
Unit tests will cover the sanity of code changes that implements the feature,
and the policy controls that are introduced as part of this feature. This is
not exhaustive, but a few specifics are covered below:
##### Allocation Manager
Tests: https://github.com/kubernetes/kubernetes/blob/ad82c3d39f5e9f21e173ffeb8aa57953a0da4601/pkg/kubelet/allocation/allocation_manager_test.go
The allocation manager is responsible for determining whether a resize can be allocated.
Unit tests cover this logic, including:
- Resizes with unsupported features such as static cpu/memory memory or swap are marked infeasible.
- Resizes for which the node does not currently have room for are marked as deferred.
- Deferred resizes are retried according to the desired priority.
##### Kuberuntime Manager
Tests:
- https://github.com/kubernetes/kubernetes/blob/ad82c3d39f5e9f21e173ffeb8aa57953a0da4601/pkg/kubelet/kuberuntime/kuberuntime_manager_test.go#L3048
- https://github.com/kubernetes/kubernetes/blob/ad82c3d39f5e9f21e173ffeb8aa57953a0da4601/pkg/kubelet/kuberuntime/kuberuntime_manager_test.go#L2320
- https://github.com/kubernetes/kubernetes/blob/ad82c3d39f5e9f21e173ffeb8aa57953a0da4601/pkg/kubelet/kuberuntime/kuberuntime_manager_test.go#L3290
- https://github.com/kubernetes/kubernetes/blob/ad82c3d39f5e9f21e173ffeb8aa57953a0da4601/pkg/kubelet/kuberuntime/kuberuntime_manager_test.go#L3668
The kuberuntime manager is responsible for actuating a resize after it has been allocated.
Unit tests cover this logic, including:
- Validation of the resize, i.e. that memory limits cannot be resized below the usage
- The logic for determining whether a pod resize is in progress (and that the corresponding pod condition gets added)
- Computation of what resize actions need to be performed
- The mock container manager has the expected cgroup values post-resize.
##### CRI uunit tests
CRI unit tests are updated to reflect use of ContainerResources object in
UpdateContainerResources and ContainerStatus APIs.
#### Integration tests
Comprehensive E2E tests provide good coverage. The following integration tests are also
added for additional coverage:
- https://github.com/kubernetes/kubernetes/blob/ad82c3d39f5e9f21e173ffeb8aa57953a0da4601/test/integration/pods/pods_test.go#L852
- https://github.com/kubernetes/kubernetes/blob/ad82c3d39f5e9f21e173ffeb8aa57953a0da4601/test/integration/scheduler/queueing/queue.go#L287
#### Pod Resize E2E Tests
##### How the tests perform verification
End-to-End tests resize a Pod via PATCH to Pod's Spec.Containers[i].Resources.
The e2e tests use docker as container runtime.
- Resizing of Requests are verified by querying the values in Pod's
Status.ContainerStatuses[i].AllocatedResources field.
- Resizing of Limits are verified by querying the cgroup limits of the Pod's
containers.
- Pending resizes have the corresponding condition set in the Pod Status.
Completed resizes have their resize status cleared.
##### Success test cases for Guaranteed Pods with one container
Tests: https://github.com/kubernetes/kubernetes/blob/ad82c3d39f5e9f21e173ffeb8aa57953a0da4601/test/e2e/common/node/pod_resize.go#L116-L127
For these tests, all pods had a restartable initContainer attached.
Resize operations performed:
1. Increase, decrease Requests & Limits for CPU only.
1. Increase, decrease Requests & Limits for memory only.
1. Increase, decrease Requests & Limits for CPU and memory in the same direction.
1. Increase, decrease Requests & Limits for CPU and memory in opposite directions.
The following cases are tested against all the above resize operations:
1. No restart policy; no resize of init container.
1. No restart policy + resize of init container.
1. Memory restart policy; no resize of init container.
1. CPU restart policy; no resize of init container.
1. CPU + Memory restart policy; no resize of init container.
1. CPU + Memory restart policy + resize of init container.
##### Success test cases for Guaranteed Pods with multiple containers
Tests: https://github.com/kubernetes/kubernetes/blob/ad82c3d39f5e9f21e173ffeb8aa57953a0da4601/test/e2e/common/node/pod_resize.go#L130
1. 3 containers - increase cpu & mem on c1, c2, decrease cpu & mem on c3 - net increase
1. 3 containers - increase cpu & mem on c1, decrease cpu & mem on c2, c3 - net decrease
1. 3 containers - increase: CPU (c1,c3), memory (c2, c3) ; decrease: CPU (c2)
##### Success test cases for Burstable Pods with one container
Tests: https://github.com/kubernetes/kubernetes/blob/ad82c3d39f5e9f21e173ffeb8aa57953a0da4601/test/e2e/common/node/pod_resize.go#L208-L220
For these tests, there were no initContainers (since that is covered by the Guaranteed Pods cases).
Resize operations performed:
1. Increase, decrease CPU Requests
1. Increase, decrease CPU Limits
1. Increase, decrease memory Requests
1. Increase, decrease memory Limits
1. Increase, decrease CPU & memory Requests and Limits in the same direction
1. Increase, decrease CPU and memory in opposite directions
1. Increase, decrease Requests & Limits in opposite directions
The following cases are tested against all the above resize operations:
1. No restart policy
1. Memory restart policy
1. CPU restart policy
1. CPU + Memory restart policy
##### Other success test cases for Burstable Pods
Tests: https://github.com/kubernetes/kubernetes/blob/ad82c3d39f5e9f21e173ffeb8aa57953a0da4601/test/e2e/common/node/pod_resize.go#L228
1. 6 containers - various operations performed (including adding limits and requests)
1. Resizing with equivalents (e.g. 2m -> 1m)
##### Memory limit decrease
Test: https://github.com/kubernetes/kubernetes/blob/ad82c3d39f5e9f21e173ffeb8aa57953a0da4601/test/e2e/common/node/pod_resize.go#L548
This test covers that memory limits can be decreased, but not below the current usage.
##### Patch error tests
Tests: https://github.com/kubernetes/kubernetes/blob/ad82c3d39f5e9f21e173ffeb8aa57953a0da4601/test/e2e/common/node/pod_resize.go#L307
These tests cover that the following attempts to patch a pod for resize will be rejected by the API server:
1. Best Effort pod - request memory
1. Best Effort pod - request CPU
1. Guaranteed pod - remove cpu & memory limits
1. Burstable pod - remove cpu & memory limits + increase requests
1. Burstable pod - remove memory requests
1. Burstable pod - remove cpu requests
1. Burstable pod - reorder containers
1. Guaranteed pod - rename containers
1. Burstable pod - set requests == limits
1. Burstable pod - resize ephemeral storage
1. Burstable pod - nonrestartable initContainer
##### Scheduler logic tests
Tests: https://github.com/kubernetes/kubernetes/blob/ad82c3d39f5e9f21e173ffeb8aa57953a0da4601/test/e2e/node/pod_resize.go#L494
These tests cover the scheduler logic with respect to in-place pod resize and the defered / infeasible
conditions. The flow of this test is:
1. Create pod1 and pod2 on node such that pod1 has enough CPU to be scheduled, but pod2 does not.
1. Resize pod2 down so that it fits on the node and can be scheduled.
1. Verify that pod2 gets scheduled and comes up and running.
1. Create pod3 that requests more CPU than available, verify that it is pending.
1. Resize pod1 down so that pod3 gets room to be scheduled.
1. Verify that pod3 is scheduled and running.
1. attempt to scale up pod1 to requests more CPU than available, verify the resize is deferred.
1. Delete pod2 + pod3 to make room for pod3.
1. Verify that pod1 resize has completed.
1. Attempt to scale up pod1 to request more cpu than the node has, verify the resize is infeasible.
##### Retry of deferred resizes
Tests: https://github.com/kubernetes/kubernetes/blob/ad82c3d39f5e9f21e173ffeb8aa57953a0da4601/test/e2e/node/pod_resize.go#L690
These tests cover the logic for retrying deferred resizes in the following cases:
1. Deferred resizes succeed after the scale down of another pod. (Deletion case is covered in the previous tests).
1. Deferred resizes are attempted according to the desired priority.
1. Place 4 pods on the node; delete the first one and verify the chain reaction of deferred resizes succeeding. The
resources are carefully chosen such that
- deletion of pod1 should make room for pod2's resize (but not pod3 or pod4).
- pod2's resize should make room for pod3's resize (but not pod4).
- pod3's resize should make room for pod4's resize.
##### Resource Quota tests
Tests: https://github.com/kubernetes/kubernetes/blob/ad82c3d39f5e9f21e173ffeb8aa57953a0da4601/test/e2e/node/pod_resize.go#L47
1. Exceed max CPU
1. Exceed max memory
1. Exceed max CPU and memory
1. Valid increase of CPU
1. Valid increase of memory
1. Valid increase of CPU and memory
##### Limit Ranger tests
Tests: https://github.com/kubernetes/kubernetes/blob/ad82c3d39f5e9f21e173ffeb8aa57953a0da4601/test/e2e/node/pod_resize.go#L218
1. Exceed max CPU
1. Exceed max memory
1. Exceed max CPU and memory
1. Valid increase of CPU
1. Valid increase of memory
1. Valid increase of CPU and memory
1. Go below min CPU
1. Go below min memory
1. Go below min CPU and memory
1. Valid decrease of CPU
1. Valid decrease of memory
1. Valid decrease of CPU and memory
##### Coverage of the READ and REPLACE endpoints
The previous tests are planned to use the PATCH endpoint, but we also need coverage of READ and REPLACE endpoints.
A basic test will be added that uses REPLACE to perform a resize, and the READ endpoint to verify the result.
#### Backward Compatibility and Negative Tests
1. Verify that Node is allowed to update only a Pod's AllocatedResources field.
1. Verify that only Node account is allowed to update AllocatedResources field.
1. Verify that updating Pod Resources in workload template spec retains current
behavior:
- Updating Pod Resources in Job template is not allowed.
- Updating Pod Resources in Deployment template continues to result in Pod
being restarted with updated resources.
1. Verify Pod updates by older version of client-go doesn't result in current
values of AllocatedResources and ResizePolicy fields being dropped.
1. Verify that only CPU and memory resources are mutable by user.
### Graduation Criteria
#### Alpha
- In-Place Pod Resouces Update functionality is implemented for running Pods,
- LimitRanger and ResourceQuota handling are added,
- Resize Policies functionality is implemented,
- Unit tests and E2E tests covering basic functionality are added,
- E2E tests covering multiple containers are added.
- UpdateContainerResources API changes are done and tested with containerd
runtime, backward compatibility is maintained.
- ContainerStatus API changes are done. Tests are ready but not enforced.
#### Beta
- E2E tests covering Resize Policy, LimitRanger, and ResourceQuota are added.
- Negative tests are identified and added.
- A "/resize" subresource is defined and implemented.
- Pod-scoped resources are handled if that KEP is past alpha
- ContainerStatus API change tests are enforced and containerd runtime must comply.
- ContainerStatus API change tests are enforced and Windows runtime should comply.
#### Stable
- VPA integration of feature, `InPlaceOrRecreate` update mode, is moved to beta
- User feedback (ideally from at least two distinct users) is green
- No major bugs reported for three months
- The following tests are promoted to Conformance:
- Coverage of the READ and REPLACE endpoints (https://github.com/kubernetes/kubernetes/pull/134407)
- The multi-container tests for guaranteed pods: https://github.com/kubernetes/kubernetes/blob/ad82c3d39f5e9f21e173ffeb8aa57953a0da4601/test/e2e/common/node/pod_resize.go#L130
- The multi-container test for burstable pods: https://github.com/kubernetes/kubernetes/blob/ad82c3d39f5e9f21e173ffeb8aa57953a0da4601/test/e2e/common/node/pod_resize.go#L231
The following items have been removed from the stable graduation criteria:
- In-place pod resize support for pod level resources. Pod level resources is now beta, so the
lack of support for resize is now a significant missing piece of that functionality; however
we don't believe this is a strong enough reason to block IPPR GA. We can, however, consider
whether this should block GA of pod level resources.
- `UpdatePodSandboxResources` is implemented by containerd & CRI-O. This is going to be re-evaluated
in the context of pod level resources resizing.
- Re-evaluate the following decisions:
- Resize atomicity: Resizes will stay atomic. Allowing partial resizes adds significant complexity
and the use case is unclear.
- Exposing allocated resources in the pod status: We will continue to expose allocated resources in
the pod status.
- QOS class changes: This is a large feature with broad implications, so can be considered in a future
enhancement.
### Upgrade / Downgrade Strategy
Scheduler and API server should be updated before Kubelets in that order.
Kubelet and the runtime versions should use the same CRI version in lock-step.
Upgrade involves draining all pods from a node, installing a CRI runtime with this
version of the API and update to a matching kubelet and making node schedulable again.
Downgrade involves doing the above in reverse.
### Version Skew Strategy
CRI changes were merged in v1.25 in order to enable runtimes to implement support.
- containerd added support for this feature in 1.6.9
Previous versions of clients that are unaware of the new ResizePolicy fields would set them
to nil. API server mutates such updates by copying non-nil values from old Pod to the current
Pod.
Prior to v1.31, with InPlacePodVerticalScaling disabled, the kubelet interprets mutation to Pod
Resources as a Container definition change and will restart the container with the new Resources.
This could lead to Node resource over-subscription. In v1.31, the kubelet no longer considers
resource changes a change in the pod definition and doesn't restart the container. In this case, the
change to the new resource value happens if the container is restart for any other reason, making
the change non-deterministic and not reflected in the API. Both of these cases are undesirable, so
the API server should reject a resize request if the Kubelet does not support it
(InPlacePodVerticalScaling enabled).
To achieve this, the apiserver will check if the `.status.containerStatuses[*].resources` field is
non-nil on any running containers. This field is set by the kubelet on running containers if and
only if IPPVS is enabled, and can therefore be used as a proxy to determine if the Kubelet running
the pod has the feature enabled. The apiserver logic to determine if a resource mutation is allowed
then becomes:
```go
if !InPlacePodVerticalScaling {
return false
}
for _, c := range pod.Status.ContainerStatuses {
if c.State.Running != nil {
return c.Resources != nil
}
}
// No running containers
return true
```
Note that even if the container does not specify any resources requests, the status
Resources is still set to the non-nill empty value `{}`.
If a pod has not yet been scheduled, the resize is allowed, and the new values are used when
scheduling & starting the pod.
If a pod has been scheduled but does not have any running containers, there is no signal indicating
whether the assigned node supports resize, so we default to allowing resize. If the node does not
have resize enabled in this case, then a resized container will be started with the new resource
value. It is possible that the node could end up over-provisioned in this case.
It is also possible for a race condition to occur: resize on a non-running container is allowed, but
the Kubelet simultaneously starts the container. The resulting behavior would depend on the version:
prior to v1.31, the container is restarted with the new values. After v1.31, the container continues
running with the old resource values. Since this race condition only exists during enablement skew,
we choose to accept it as a known-issue.
## Production Readiness Review Questionnaire
### Feature Enablement and Rollback
_This section must be completed when targeting alpha to a release._
* **How can this feature be enabled / disabled in a live cluster?**
- [x] Feature gate (also fill in values in `kep.yaml`)
- Feature gate name: `InPlacePodVerticalScaling`
- Components depending on the feature gate: kubelet, kube-apiserver, kube-scheduler
- Feature gate name: `InPlacePodVerticalScalingAllocatedStatus`
- Components depending on the feature gate: kubelet, kube-apiserver
- Requires `InPlacePodVerticalScaling` be enabled
* **Does enabling the feature change any default behavior?**
- Kubelet sets several pod status fields: `AllocatedResources`, `Resources`
* **Can the feature be disabled once it has been enabled (i.e. can we roll back
the enablement)?** Yes
- `InPlacePodVerticalScaling` can be disabled without issue in the control plane.
- `InPlacePodVerticalScaling` can be disabled on nodes, but if there are any pending resizes
container resource configurations may be left in an unknown state. This can be avoided by
draining the node before disabling in-place resize.
- `InPlacePodVerticalScalingAllocatedStatus` can be disabled and reenabled without consequence.
* **What happens if we reenable the feature if it was previously rolled back?**
- API will once again permit modification of Resources for 'cpu' and 'memory'.
- Actual resources applied will be reflected in in Pod's ContainerStatuses.
* **Are there any tests for feature enablement/disablement?**
Unit tests and E2E tests.
- Unit tests verify that feature does not introduce any regression.
- E2E tests run against a local cluster verify that feature works as expected.
### Rollout, Upgrade and Rollback Planning
_This section must be completed when targeting beta graduation to a release._
* **How can a rollout fail? Can it impact already running workloads?**
- Failure scenarios are already covered by the version skew strategy.
* **What specific metrics should inform a rollback?**
- Scheduler indicators:
- `scheduler_pending_pods`
- `scheduler_pod_scheduling_attempts`
- `scheduler_pod_scheduling_duration_seconds`
- `scheduler_unschedulable_pods`
- Kubelet indicators:
- `kubelet_pod_worker_duration_seconds`
- `kubelet_runtime_operations_errors_total{operation_type=update_container}`
* **Were upgrade and rollback tested? Was the upgrade->downgrade->upgrade path tested?**
Testing plan:
1. Create test pod
2. Upgrade API server
3. Attempt resize of test pod
- Expected outcome: resize is rejected (see version skew section for details)
4. Create upgraded node
5. Create second test pod, scheduled to upgraded node
6. Attempt resize of second test pod
- Expected outcome: resize successful
7. Delete upgraded node
8. Restart API server with feature disabled
- Ensure original test pod is still running
9. Attempt resize of original test pod
- Expected outcome: request rejected by apiserver
10. Restart API server with feature enabled
- Verify original test pod is still running
* **Is the rollout accompanied by any deprecations and/or removals of features, APIs,
fields of API types, flags, etc.?**
No.
### Monitoring Requirements
_This section must be completed when targeting beta graduation to a release._
* **How can an operator determine if the feature is in use by workloads?**
Metric: `apiserver_request_total{resource=pods,subresource=resize}`
* **How can someone using this feature know that it is working for their instance?**
- If the Kubelet supports InPlacePodVerticalScaling, it will always set the `Resources` field in
container status.
- The `ResizeStatus` in the pod status should converge to the empty value, indicating the resize has completed.
- The `Resources` in the container status should converge to the resized resources, or an
approximation of it (see [Actuating Resizes](#actuating-resizes) for more details on
when these resources can diverge).
* **What are the SLIs (Service Level Indicators) an operator can use to determine
the health of the service?**
- [x] Metrics
- Metric name: `apiserver_request_total{resource=pods,subresource=resize}`
- Components exposing the metric: apiserver
- Metric name: `runtime_operations_duration_seconds{operation_type=container_update}`
- Components exposing the metric: kubelet
- Metric name: `runtime_operations_errors_total{operation_type=container_update}`
- Components exposing the metric: kubelet
* **What are the reasonable SLOs (Service Level Objectives) for the above SLIs?**
- Resize requests should succeed (`apiserver_request_total{resource=pods,subresource=resize}` with non-success `code` should be low)
- Resource update operations should complete quickly (`runtime_operations_duration_seconds{operation_type=container_update} < X` for 99% of requests)
- Resource update error rate should be low (`runtime_operations_errors_total{operation_type=container_update}/runtime_operations_total{operation_type=container_update}`)
* **Are there any missing metrics that would be useful to have to improve observability
of this feature?**
- ~~Kubelet admission rejections: https://github.com/kubernetes/kubernetes/issues/125375~~ (DONE)
- Resize operate duration (time from the Kubelet seeing the request to actuating the changes): this would require persisting more state about when the resize was first observed.
### Dependencies
_This section must be completed when targeting beta graduation to a release._
* **Does this feature depend on any specific services running in the cluster?**
Compatible container runtime (see [CRI changes](#cri-changes)).
### Scalability
_For alpha, this section is encouraged: reviewers should consider these questions
and attempt to answer them._
_For beta, this section is required: reviewers must answer these questions._
_For GA, this section is required: approvers should be able to confirm the
previous answers based on experience in the field._
* **Will enabling / using this feature result in any new API calls?** Yes
Describe them, providing:
- API call type (e.g. PATCH pods)
- One new PATCH PodStatus API call in response to Pod resize request.
- No additional overhead unless Pod resize is invoked.
- estimated throughput
- originating component(s) (e.g. Kubelet, Feature-X-controller)
- Kubelet
focusing mostly on:
- components listing and/or watching resources they didn't before
- API calls that may be triggered by changes of some Kubernetes resources
(e.g. update of object X triggers new updates of object Y)
- periodic API calls to reconcile state (e.g. periodic fetching state,
heartbeats, leader election, etc.)
* **Will enabling / using this feature result in introducing new API types?** No
Describe them, providing:
- API type
- Supported number of objects per cluster
- Supported number of objects per namespace (for namespace-scoped objects)
* **Will enabling / using this feature result in any new calls to the cloud
provider?** No
* **Will enabling / using this feature result in increasing size or count of
the existing API objects?** Yes
Describe them, providing:
- API type(s):
- Estimated increase in size: (e.g., new annotation of size 32B)
- Estimated amount of new objects: (e.g., new Object X for every existing Pod)
- type Container has new field ResizePolicy, a list that adds upto 50 bytes.
- type PodStatus has a new field, a list that adds upto 32 bytes.
- type ContainerStatus has new field of type v1.ResourceList that mirrors
Container.Resources.Requests in size.
- type ContainerStatus has new field of type v1.ResourceRequirements that
mirrors Container.Resources in size.
* **Will enabling / using this feature result in increasing time taken by any
operations covered by [existing SLIs/SLOs]?** No
Think about adding additional work or introducing new steps in between
(e.g. need to do X to start a container), etc. Please describe the details.
* **Will enabling / using this feature result in non-negligible increase of
resource usage (CPU, RAM, disk, IO, ...) in any components?** No
Things to keep in mind include: additional in-memory state, additional
non-trivial computations, excessive access to disks (including increased log
volume), significant amount of data sent and/or received over network, etc.
This through this both in small and large cases, again with respect to the
[supported limits].
* **Can enabling / using this feature result in resource exhaustion of some node resources (PIDs, sockets, inodes, etc.)?** No
### Troubleshooting
The Troubleshooting section currently serves the `Playbook` role. We may consider
splitting it into a dedicated `Playbook` document (potentially with some monitoring
details). For now, we leave it here.
_This section must be completed when targeting beta graduation to a release._
* **How does this feature react if the API server and/or etcd is unavailable?**
- If the API is unavailable prior to the resize request being made, the request wil not go through.
- If the API is unavailable before the Kubelet observes the resize, the request will remain pending until the Kubelet sees it.
- If the API is unavailable after the Kubelet observes the resize, then the pod status may not
accurately reflect the running pod state. The Kubelet tracks the resource state internally.
* **What are other known failure modes?**
- Race condition with scheduler can cause pods to be rejected with `OutOfCPU` or
`OutOfMemory`.
- Race condition with pod startup on version-skewed clusters can lead to pods running in an
unknown resource configuration. See [Version Skew Strategy](#version-skew-strategy) for more
details.
- Shrinking memory limit below memory usage can leave the resize in an `InProgress` state
indefinitely. Race conditions around reading usage info could cause container to OOM on resize.
* **What steps should be taken if SLOs are not being met to determine the problem?**
- Investigate Kubelet and/or container runtime logs.
[supported limits]: https://git.k8s.io/community//sig-scalability/configs-and-limits/thresholds.md
[existing SLIs/SLOs]: https://git.k8s.io/community/sig-scalability/slos/slos.md#kubernetes-slisslos
## Implementation History
- 2018-11-06 - initial KEP draft created
- 2019-01-18 - implementation proposal extended
- 2019-03-07 - changes to flow control, updates per review feedback
- 2019-08-29 - updated design proposal
- 2019-10-25 - Initial CRI changes KEP draft created
- 2019-10-25 - update key open items and move KEP to implementable
- 2020-01-06 - API review suggested changes incorporated
- 2020-01-13 - Test plan and graduation criteria added
- 2020-01-14 - CRI changes test plan and graduation criteria added
- 2020-01-21 - Graduation criteria updated per review feedback
- 2020-11-06 - Updated with feedback from reviews
- 2020-12-09 - Add "Deferred"
- 2021-02-05 - Final consensus on allocatedResources[] and resize[]
- 2022-05-01 - KEP 2273-kubelet-container-resources-cri-api-changes merged with this KEP
- 2023-04-08 - Catch up KEP details to what is actually implemented
- 2024-10-09 - v1.32 updates for planned beta
- Remove container-level status `AllocatedResources`
- Add `/resize` subresource specification
- Make `ResizePolicy` mutable
- Introduce best-effort `UpdatePodSandboxResources` CRI call
- Add sidecar resize support
- Describe the [Atomic Resizes](#atomic-resizes) principle
- Add ResourceQuota details
- Heuristic version skew handling in API validation
- 2025-01-24 - v1.33 updates for planned beta
- Replace ResizeStatus with conditions
- Improve memory limit downsize handling
- Rename ResizeRestartPolicy `NotRequired` to `PreferNoRestart`,
and update CRI `UpdateContainerResources` contract
- Add back `AllocatedResources` field to resolve a scheduler corner case
- Introduce Actuated resources for actuation
- 2025-06-03 - v1.34 post-beta updates
- Allow no-restart memory limit decreases
- Add instrumentation section
- Priority of resize requests
- 2025-09-22 - Correct KEP details to match actual implementation
- revert PreferNoRestart resize policy back to NotRequired
- add more details about the resize status
- document kubelet-triggered eviction for critical pods
- update outdated notes regarding static CPU
- correct details about instrumentation
- 2025-10-15 - Update in-place pod resize for GA
- Update test plan
- Remove `UpdatePodSandboxResources` from graduation criteria
- 2025-12-29 - Mark as implemented after GA release
## Drawbacks
There are no drawbacks that we are aware of.
## Alternatives
We considered having scheduler approve the resize. We also considered PodSpec as
the location to checkpoint allocated resources.
### Allocated Resource Limits
If we need allocated limits in the pod status API, the following options have been
considered:
**Option 1: New field "AcceptedResources"**
We can't change the type of the existing field, so instead we
introduce a new field `ContainerStatus.AcceptedResources` of type `ResourceRequirements`, to track both
allocated requests & limits, and remove the old `AllocatedResources` field. For consistency, we also
add `AcceptedResourcesStatus` and remove `AllocatedResourcesStatus`.
Pros:
- Consistent type across PodSpec.Container.Resources (desired), ContainerStatus.AcceptedResources (allocated), and ContainerStatus.Resources (actual)
- If/when we implement in-place resize for DRA resources, Claims are already included in the API.
- No need for local checkpointing, if the Kubelet can read back from the status API.
Cons:
- No path to beta without waiting a release (new fields need to start in alpha)
- Extra code churn to migrate to the new fields
- Inconsistent with PVC API (which has AllocatedResources), and the Node Allocatable resources.
- The Claims field is currently unnecessary, and needs its behavior defined.
Variations:
- Use an alternative type that is a subset of the ResourceRequirements type without Claims, adding back Claims only when needed.
- Field name ContainerStatus.Allocated, as a struct holding both the allocated resources and and the allocated resource status
**Option 2: New field "AllocatedResourceLimits"**
Rather than changing the type with a new field, we could use a flattened API structure and just add
`ContainerStatus.AllocatedResourceLimits` alongside `AllocatedResources` (requests).
Pros:
- Preserves the "Allocated" name
- Less churn to implement
- Does not prematurely import Claims into the problem space
Cons:
- Uglier API: unnested fields adds noise to the documentation and makes it harder for humans to read the status.
- Inconsistent types between Allocated* and Resources
- We will want to mirror the same structure in the PodStatus for pod-level resources, and may eventually want to add AllocatedResourceClaims for DRA resource resize
**Option 3: Pod-level "AllocatedResources", drop container-level API**
If we assume that outside the node, controllers and people only care about pod-level allocated
resources, then we could drop the container-level allocated resources, and just add a
`PodStatus.AllocatedResources` field of type `ResourceRequirements`. The Kubelet still needs to track
container-level allocation, and would use a checkpoint to do so.
Pros:
- Minimalist API, without unnecessary or redundant information
- Preserve the "Allocated" name while still getting the advantages of type consistency
- Similar path to beta as Option 2
Cons:
- Requires long-term checkpointing to track container allocation
- Extra risk in assuming nothing outside the node ever needs to know container-level allocated
resources, such as for hierarchical or container/task level scheduling.
- No observability into container allocation
- No recourse if erroneous values are reported by the runtime