DEV Community

Grego
Grego

Posted on

Kubernetes 1.35: In-Place Pod Resize is GA — Scale Vertically Without Restarting

Kubernetes 1.35: In-Place Pod Resize is GA — Scale Vertically Without Restarting

Update CPU and memory of running Pods without recreating them


The Problem: “Simple” Changes That Cause Disruptions

A production service shows CPU throttling. P95 latency is rising. The solution is obvious: increase the CPU limit from 500m to 700m.

In earlier versions of Kubernetes, this “simple change” triggered a cascade of unwanted events:

# Change this...
resources:
  limits:
    cpu: "500m"

# ...to this
resources:
  limits:
    cpu: "700m"

Enter fullscreen mode Exit fullscreen mode

Consequences in K8s ≤1.34:

  • Pod completely recreated
  • Active connections terminated
  • In-memory cache lost
  • Local state deleted
  • Jobs in progress interrupted

No code changed. No behavior changed. Just a number adjustment. But the system treated that adjustment as a full deployment.

Kubernetes 1.35 changes this fundamentally.


The Solution: In-Place Resource Update (GA)

Starting with Kubernetes 1.35,in-place Pod resource updates are GA(Generally Available). This means CPU and memory can be modified in running Pods, and the node applies the new values without the automatic recreation cycle.

Key features:

  • CPU changes applied hot (without restart)
  • Memory changes configurable (with or without container restart)
  • Pod is never recreated — only cgroups are adjusted
  • State, connections, and cache preserved
  • Observable via Pod status and conditions

Architecture: How Resize Works

The process involves several Kubernetes components working in coordination:

The New Mental Model: Desired vs Actual vs Allocated

Kubernetes 1.35 introduces an important distinction in how resources are reported:

Field Description
spec.containers[].resources What the operator*desires*
status.containerStatuses[].allocatedResources What the node*reserved*
status.containerStatuses[].resources What the container*is using*
status.conditions State of the resize (Pending, InProgress)
status.observedGeneration Confirmation that kubelet processed the change

This visibility allows you to know exactly what state the resize is in at any moment.


Configuration: resizePolicy

The resize behavior is configured per resource type usingresizePolicyin the container spec:

Pod Spec with Resize Policy

apiVersion: v1
kind: Pod
metadata:
  name: app-con-resize
spec:
  containers:
    - name: app
      image: mi-app:latest
      ports:
        - containerPort: 8080
      resizePolicy:
        - resourceName: cpu
          restartPolicy: NotRequired      # CPU hot
        - resourceName: memory
          restartPolicy: RestartContainer # Memory with restart
      resources:
        requests:
          cpu: "300m"
          memory: "256Mi"
        limits:
          cpu: "300m"
          memory: "256Mi"

Enter fullscreen mode Exit fullscreen mode

Recommendation for production:

  • CPU:NotRequired— CPU changes are safe to apply hot
  • Memory:RestartContainer— More predictable than waiting for the app to free memory

Demo: Verifying that Resize Works

To demonstrate that resize really works without restart, you can create a simple server that exposes its PID and current cgroup limits.

Demo Server (Go)

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "strings"
)

funcread(path string) string {
    b, err := os.ReadFile(path)
    if err != nil {
        return fmt.Sprintf("unavailable (%v)", err)
    }
    return strings.TrimSpace(string(b))
}

funchandler(w http.ResponseWriter, r *http.Request) {
    pid := os.Getpid()

    // cgroup v2 paths
    cpuMax := read("/sys/fs/cgroup/cpu.max")
    memMax := read("/sys/fs/cgroup/memory.max")

    io.WriteString(w, fmt.Sprintf("pid=%d\n", pid))
    io.WriteString(w, fmt.Sprintf("cpu.max=%s\n", cpuMax))
    io.WriteString(w, fmt.Sprintf("memory.max=%s\n", memMax))
}

funcmain() {
    http.HandleFunc("/", handler)
    fmt.Println("listening on :8080")
    http.ListenAndServe(":8080", nil)
}

Enter fullscreen mode Exit fullscreen mode

Dockerfile

FROM golang:1.23-alpine AS build
WORKDIR /src
COPY . .
RUN go build -o app .

FROM alpine:3.20
WORKDIR /app
COPY --from=build /src/app /app/app
EXPOSE 8080
ENTRYPOINT ["/app/app"]

Enter fullscreen mode Exit fullscreen mode

Deploy and Verify

# Create the Pod
kubectl apply -f pod-resize-demo.yaml

# Port-forward to access
kubectl port-forward pod/app-con-resize 8080:8080 &

# Verify initial state
curl localhost:8080
# pid=7
# cpu.max=30000 100000
# memory.max=268435456

Enter fullscreen mode Exit fullscreen mode

Running the Resize

In Kubernetes 1.35, resize is executed against the Pod’sresizesubresource:

Increase CPU (Without Restart)

kubectl patch pod app-con-resize --subresource resize --type merge -p ' { "spec": { "containers": [ { "name": "app", "resources": { "requests": { "cpu": "700m" }, "limits": { "cpu": "700m" } } } ] } }'

Enter fullscreen mode Exit fullscreen mode

Verify It Worked

# The endpoint should show:
# - Same PID (no restart)
# - New cpu.max value
curl localhost:8080
# pid=7 <- SAME PID
# cpu.max=70000 100000 <- NEW LIMIT
# memory.max=268435456

# Confirm there was no restart
kubectl get pod app-con-resize -o jsonpath='{.status.containerStatuses[0].restartCount}'
# 0

Enter fullscreen mode Exit fullscreen mode

If the PID remains the same and cpu.max changed, the in-place resize worked correctly.


Increase Memory (With Container Restart)

With theRestartContainerpolicy for memory:

kubectl patch pod app-con-resize --subresource resize --type merge -p ' { "spec": { "containers": [ { "name": "app", "resources": { "requests": { "memory": "512Mi" }, "limits": { "memory": "512Mi" } } } ] } }'

Enter fullscreen mode Exit fullscreen mode

In this case:

  • restartCountwill increment
  • The PID will change
  • But the Pod will NOT be recreated— volumes and networking are preserved

Observability During Resize

Kubernetes 1.35 provides visibility of the resize state via conditions:

kubectl describe pod app-con-resize

Enter fullscreen mode Exit fullscreen mode

Relevant conditions:

  • PodResizePending— The resize was requested but not applied yet
  • PodResizeInProgress— The kubelet is applying the change
# See the detailed state
kubectl get pod app-con-resize -o jsonpath='{.status.conditions}' | jq
```

This eliminates uncertainty. There's no longer any guessing whether the change was applied — the system reports it explicitly. --- ## Limitations to Consider The feature is powerful but has clear limits: | Limitation | Description | |------------|-------------| | **QoS Class** | Cannot change post-creation (Guaranteed/Burstable/BestEffort) | | **Init containers** | Do not support resize | | **Ephemeral containers** | Do not support resize | | **Sidecars** | Yes, support resize | | **Windows Pods** | Not supported | | **Memory decrease** | Best-effort without restart (the app must free memory) | | **Node constraints** | Some CPU/Memory managers may block changes | These limitations are part of what makes the feature safe. A feature that promises everything becomes dangerous. A feature that declares its limits is operable. --- ## Scheduler Protection A valid concern: what happens if the resize is pending but the scheduler assumes it's already applied?

Kubernetes prevents this by being conservative during incomplete resizes. When scheduling, it considers the **maximum** between:
- What is requested (desired)
- What is allocated (allocated)
- What is applied (actual)

This prevents overcommit based on changes that haven't yet completed. --- ## Operational Impact The most significant change is not technical — it's cultural.

**Before K8s 1.35:**
- Teams avoided resize until it was urgent
- Engineers over-provisioned to avoid touching resources afterward
- "Right-sizing" was a project, not a habit
- On-call delayed simple fixes out of fear of disruption

**With K8s 1.35:**
- CPU corrections without restart cost
- Faster iteration over resource configuration
- Response to throttling without maintenance window
- Resize becomes a normal operation, not an event

---

## Command Summary



```bash
# Apply CPU resize
kubectl patch pod POD_NAME --subresource resize --type merge -p ' { "spec": { "containers": [{ "name": "CONTAINER_NAME", "resources": { "requests": { "cpu": "NEW_VALUE" }, "limits": { "cpu": "NEW_VALUE" } } }] } }'

# Check resize status
kubectl describe pod POD_NAME | grep -A5 Conditions

# View current vs desired resources
kubectl get pod POD_NAME -o jsonpath='{.status.containerStatuses[0].resources}'

# Confirm no restart occurred
kubectl get pod POD_NAME -o jsonpath='{.status.containerStatuses[0].restartCount}'

```

`

***

## **Conclusion**

Kubernetes 1.35 solves a problem that should never have existed: the need to restart a process just because a resource limit was adjusted.

With in-place resize GA:

* **CPU**can be adjusted without any restart
* **Memory**can be configured for container restart (not Pod)
* **Full observability**of resize state
* **Protection**against overcommit during pending changes

Vertical scaling finally behaves like an adjustment, not a deployment.

***

## **Resources**

* [KEP-1287: In-Place Pod Vertical Scaling](https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/1287-in-place-update-pod-resources)
* [Kubernetes 1.35 Release Notes](https://kubernetes.io/blog/)
* [Pod Resource Management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/)

***

*Published on yoDEV.dev — The Latin American developer community*
Enter fullscreen mode Exit fullscreen mode

Top comments (0)