Resize API
Attune uses the Kubernetes 1.32+ in-place pod resize API to adjust container resources without restarting pods. This page explains how the resize API works and how the operator uses it.
The /resize subresource¶
Kubernetes 1.32 added the /resize subresource on pods as part of the
InPlacePodVerticalScaling alpha feature gate. Kubernetes 1.33 graduated
the feature to beta (enabled by default). The subresource accepts a
modified PodSpec with new container resource requests/limits.
PATCH /api/v1/namespaces/{ns}/pods/{name}/resize
The kubelet applies the new resources to the running container's cgroup limits without restarting it. CPU changes take effect immediately; memory limit increases take effect immediately but decreases only apply when the container's working set drops below the new limit.
How Attune uses it¶
The operator's resize engine (internal/resize/engine.go) performs
resizes via the typed Kubernetes client:
clientset.CoreV1().Pods(namespace).UpdateResize(ctx, name, updatedPod, opts)
Pre-checks before resize¶
Before calling UpdateResize, the controller runs several safety checks:
- Pod already at target: Skips if the running pod's actual resources match the recommendation (compares against the live pod, not the Deployment template).
- Node capacity: Verifies that total pod requests after resize don't exceed the node's allocatable resources.
- LimitRange/ResourceQuota: Checks that the target doesn't violate namespace constraints.
- QoS preservation: Ensures the resize won't change the pod's QoS class (e.g., from Guaranteed to Burstable).
- Resize policy warning: If the container has
resizePolicyset toRestartContainer, the operator logs a warning but proceeds with the resize (the kubelet will restart the container).
Post-resize tracking¶
After a successful resize, the operator:
- Writes tracking annotations to the pod (see table below).
- If
autoRevert: true, monitors the pod for safety violations (OOMKill, CPU throttle, restart spikes, NotReady). - Records the operation in
status.resizeHistory. - Emits a Kubernetes Event (
Normal/Resized).
Pod tracking annotations¶
After a resize, the operator writes these annotations to the pod for safety observation and revert tracking:
| Annotation | Description |
|---|---|
attune.io/resized-at |
RFC 3339 timestamp of the resize |
attune.io/resized-containers |
Comma-separated list of resized container names |
attune.io/resized-workload |
Name of the parent workload |
attune.io/original-cpu-request.<container> |
CPU request before the resize (per container) |
attune.io/original-memory-request.<container> |
Memory request before the resize (per container) |
attune.io/original-restart-count.<container> |
Container restart count at resize time (per container) |
attune.io/original-cpu-limit.<container> |
CPU limit before the resize (when limit is non-zero) |
attune.io/original-memory-limit.<container> |
Memory limit before the resize (when limit is non-zero) |
attune.io/policy |
Name of the AttunePolicy managing this pod (used for safety observation provenance checks and finalizer cleanup) |
attune.io/startup-boost-at |
RFC 3339 timestamp when a startup CPU boost was applied |
These annotations are removed once the safety observation period completes
(regardless of whether the resize is kept or reverted). When a policy is
deleted, the attune.io/cleanup finalizer removes all tracking
annotations from managed pods before allowing garbage collection. Pods
keep their current (resized) resource values; only annotations are cleaned.
When multiple containers in the same pod are resized in the same cycle,
each container gets its own set of per-container annotations (e.g.,
attune.io/original-cpu-request.app, attune.io/original-cpu-request.sidecar).
The attune.io/resized-containers annotation lists all resized containers
as a comma-separated value.
Resize lifecycle¶
The diagram below shows the complete decision tree for a single pod during a resize cycle. The operator evaluates every pod in the workload through this flow on each reconciliation.
stateDiagram-v2
[*] --> Discovered: Pod found in workload
Discovered --> Eligible: IsEligibleForResize = true
Discovered --> Ineligible: IsEligibleForResize = false
state Ineligible {
direction LR
[*] --> NotRunning: Phase != Running
[*] --> Deleting: DeletionTimestamp set
[*] --> InProgress: PodResizeInProgress
[*] --> Pending: PodResizePending (non-Infeasible)
}
Eligible --> Infeasible: IsResizeInfeasible = true
Eligible --> PreChecks: IsResizeInfeasible = false
state PreChecks {
direction TB
[*] --> AtTarget: Already at recommended values
[*] --> NodeCap: Would exceed node allocatable
[*] --> QuotaViolation: Would violate LimitRange/Quota
[*] --> QoSChange: Would change QoS class
[*] --> PassedChecks: All checks pass
}
Infeasible --> EvictOrSkip
state EvictOrSkip {
direction LR
[*] --> InfeasibleSkipped: resizeMethod = InPlaceOnly
[*] --> EvictionCheck: resizeMethod = InPlaceOrRecreate
}
note right of InfeasibleSkipped: Skipped to avoid wasted\nUpdateResize API calls\nthat would fail every cycle
EvictionCheck --> LastReplicaGuard
state LastReplicaGuard {
direction LR
[*] --> EvictionBlocked: Only 1 running replica
[*] --> EvictPod: 2+ running replicas
}
EvictPod --> Evicted: Eviction API accepts
EvictPod --> EvictionDenied: PDB blocks eviction
PassedChecks --> ResizePod: UpdateResize API call
ResizePod --> ResizeSuccess: API returns success
ResizePod --> ResizeFailed: API returns error
ResizeFailed --> EvictOrSkipFailed
state EvictOrSkipFailed {
direction LR
[*] --> Failed: resizeMethod = InPlaceOnly
[*] --> EvictionFallback: resizeMethod = InPlaceOrRecreate
}
EvictionFallback --> LastReplicaGuard
ResizeSuccess --> SafetyObservation: autoRevert enabled
ResizeSuccess --> Done: autoRevert disabled
state SafetyObservation {
direction TB
[*] --> Monitoring: Watching for OOMKill, throttle, restarts
Monitoring --> Safe: No violations after observation period
Monitoring --> Unsafe: Violation detected
}
Safe --> Done: Annotations removed
Unsafe --> RevertPod: Restore original resources
RevertPod --> Done: Annotations removed
Decision points explained¶
| Decision | Function | Location |
|---|---|---|
| IsEligibleForResize | resize.IsEligibleForResize() |
internal/resize/engine.go |
| IsResizeInfeasible | resize.IsResizeInfeasible() |
internal/resize/engine.go |
| Pre-checks | shouldSkipResize() |
internal/controller/resize.go |
| Resize method | policy.Spec.UpdateStrategy.ResizeMethod |
CRD field |
| Last replica guard | tryEvictionFallback() |
internal/controller/safety.go |
| ResizePod | resizer.ResizePod() |
internal/resize/engine.go |
| Safety observation | checkPendingSafetyObservations() |
internal/controller/safety.go |
| Revert | safety.Monitor.RevertPod() |
internal/safety/monitor.go |
Key behaviors¶
-
Infeasible pods are eligible.
IsEligibleForResizereturnstruefor pods the kubelet has markedPodResizePending=Infeasible. These pods cannot be resized in-place on their current node, but they are included in the resize cycle so the eviction fallback can handle them whenresizeMethodisInPlaceOrRecreate. WithInPlaceOnly, they are skipped with a log message ("Pod resize is Infeasible and resizeMethod is InPlaceOnly, skipping") to avoid wasting an UpdateResize API call that would fail on every reconcile cycle. -
Eviction respects PDBs. The operator uses the Kubernetes Eviction API (
EvictV1), which enforces PodDisruptionBudgets. If the PDB would be violated, the eviction is denied and the pod stays as-is. -
Last replica protection. The operator never evicts the last running replica of a workload, even if the PDB would allow it. This prevents complete service outage during resize.
-
Eviction fallback restarts the pod from the current template. When a pod is evicted, the workload controller (Deployment, StatefulSet) creates a replacement pod from the current PodTemplate. Attune does not patch workload templates as part of eviction fallback, so the replacement pod may come back with the original resources until a later in-place resize succeeds. Evicted pods are recorded separately from successful in-place resizes and do not enter the safety observation path.
-
Fail-open schedule. If the configured timezone is invalid,
isWithinResizeWindowreturnstrue(allows resize) rather than silently blocking all resizes. Invalid timezones should be caught by webhook validation at admission time.
Limits and caveats¶
- Memory limit decreases: Kubernetes forbids decreasing a container's
memory limit in-place unless the container's
resizePolicyfor memory is set toRestartContainer. If your pod usesNotRequired(the default) or has no resize policy, the operator will clamp the memory limit to the current value and only decrease the memory request. To allow memory limit decreases without a restart, upgrade to Kubernetes 1.34+ where this restriction was relaxed. - Memory request decreases: The kernel only reclaims memory when the working set drops below the new limit. If the application holds onto allocated memory, the decrease has no practical effect until the process releases it.
- Init containers: Not resizable in-place. The operator only resizes regular containers.
- Restart policy: Containers with
resizePolicy: RestartContainerwill be restarted by the kubelet when their resources change. - Prometheus address limit: The operator caches at most 64 unique Prometheus collector instances. If more than 64 distinct addresses are configured across all policies, additional addresses are rejected with an error status.
- Minimum cooldown: The operator enforces a minimum cooldown of 1 minute
regardless of the
cooldownfield value. This prevents accidental DoS via rapid resize loops.