Kubernetes v1.37 [beta](disabled by default)This tutorial demonstrates how to configure the kubelet's resource managers (topology, CPU, and memory) to support pod-level resource specifications. You can define hybrid allocation models where some containers receive exclusive, NUMA-aligned infrastructure resources while others share the remaining resources from a pod-level shared pool.
To learn more about the concepts behind this feature, read the Pod-level resource managers concept page.
pod Topology Manager scope to achieve
single-NUMA alignment with mixed exclusive and shared containers.container Topology Manager scope with mixed
container allocations.You need to have a Kubernetes cluster, and the kubectl command-line tool must be configured to communicate with your cluster. It is recommended to run this tutorial on a cluster with at least two nodes that are not acting as control plane hosts. If you do not already have a cluster, you can create one by using minikube or you can use one of these Kubernetes playgrounds:
Your Kubernetes server must be at or later than version v1.36.To check the version, enter kubectl version.
To complete this tutorial, you need:
sudo) on the worker node(s) to modify
kubelet configuration
and restart the kubelet service.kubectl access with permission to create namespaces and pods.Ensure the following feature gates are enabled for your control plane and for the worker nodes:
PodLevelResourcesPodLevelResourceManagersCreate a namespace so that the resources created in this tutorial are isolated from the rest of your cluster:
kubectl create namespace plrm-tutorial
When the Topology Manager scope is set to pod, the kubelet performs a single
NUMA alignment for the entire Pod based on .spec.resources. The resulting
resource budget is then partitioned: containers requesting Guaranteed
resources receive exclusive slices, while containers that do not receive an
exclusive allocation share the remaining budget in a pod-level shared pool.
To enable this behavior, configure the kubelet on the target worker node(s)
where you want to run these workloads with the required policies. You can update
your kubelet configuration
for those nodes as follows:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cpuManagerPolicy: "static"
memoryManagerPolicy: "Static"
topologyManagerScope: "pod"
topologyManagerPolicy: "single-numa-node"
topologyManagerPolicy, the valid values are
single-numa-node, restricted, or best-effort. You cannot specify
any other value when using Pod-level resource management.Restart the kubelet to apply the configuration. For example, on Linux with
systemd: systemctl restart kubelet.service.
Consider the following example Pod manifest. The Pod requests a total budget of
4 CPUs at the pod level (.spec.resources). Inside the Pod:
main-app container requests an exclusive allocation of 2 entire CPU
cores (requests = limits = 2 CPU).metrics-sidecar and logging-sidecar do not specify
container-level requests, those two sidecar containers share the CPU cores
that remain from the pod-level shared pool: 2 CPU cores.apiVersion: v1
kind: Pod
metadata:
name: pod-scope-mixed
annotations:
kubernetes.io/description: "A pod demonstrating pod-level scope where one container gets exclusive resources and others share the remaining pod resources in a shared pool."
spec:
# At Pod level, the Pod has CPU request equal to limits and memory request
# also equal to memory limits. The main-app container meets the requirements
# for the Guaranteed QoS class at container level, and the sidecar containers
# don't specify any resource request. Under pod scope, this means that the
# kubelet could statically assign 4 CPUs to the overall Pod, of which 2 are
# assigned exclusively to the main-app container, and the remaining 2 are
# shared by the sidecars in the pod's shared pool.
resources:
requests:
cpu: "4"
memory: "4Gi"
limits:
cpu: "4"
memory: "4Gi"
initContainers:
- name: metrics-sidecar
# Note: This is a placeholder image for demonstration purposes, not an
#actual metrics helper.
image: registry.k8s.io/pause:3.9
restartPolicy: Always
- name: logging-sidecar
# Note: This is a placeholder image for demonstration purposes, not an
# actual logging agent.
image: registry.k8s.io/pause:3.9
restartPolicy: Always
containers:
- name: main-app
# Note: This is a placeholder image for demonstration purposes.
image: registry.k8s.io/pause:3.9
resources:
requests:
cpu: "2"
memory: "2Gi"
limits:
cpu: "2"
memory: "2Gi"
Apply the manifest to your cluster:
kubectl apply -f https://k8s.io/examples/pods/resource/pod-level-resource-managers-pod-scope-mixed.yaml --namespace=plrm-tutorial
Check that the Pod is running successfully:
kubectl get pod pod-scope-mixed --namespace=plrm-tutorial
Understand what happened behind the scenes:
flowchart TD
subgraph Pod["Pod-Level Budget: 4 CPUs, 4Gi Memory"]
direction TB
C1["main-app<br/>(Exclusive: 2 CPUs, 2Gi Memory)"]
subgraph Pool["Pod Shared Pool: 2 CPUs, 2Gi Memory"]
C2["metrics-sidecar"]
C3["logging-sidecar"]
end
endspec.resources) and assigned the entire Pod to a single NUMA node.main-app. CPU CFS quota throttling is disabled for main-app,
giving it unthrottled access to those exclusive cores.metrics-sidecar and logging-sidecar specify no container-level
resources (resources: {}), so they run within this pod-isolated shared
pool with CFS quota enforcement enabled. While shared between these two
sidecars, the pool is isolated from external workloads on the node,
providing the sidecars with dedicated NUMA locality and protection from
node-level resource contention.When using the pod scope, kubelet admission control rejects Pod
specifications that would result in an empty Pod shared pool when there are
containers that require one.
If the sum of exclusive resource requests from Guaranteed containers equals
the total pod-level budget, and at least one other container requires the shared
pool, the kubelet rejects the Pod.
Consider the following manifest. The Pod requests a total budget of 4 CPUs.
container-a requests an exclusive 1 CPU and container-b requests an
exclusive 3 CPUs (totaling 4 CPUs). container-c does not request exclusive
resources and requires a shared pool, but 0 CPUs remain:
Apply the manifest:
kubectl apply -f https://k8s.io/examples/pods/resource/pod-level-resource-managers-empty-shared-pool.yaml --namespace=plrm-tutorial
Inspect the Pod events to observe the admission error:
kubectl describe pod empty-shared-pool --namespace=plrm-tutorial
Notice the event message explaining that the kubelet rejected the Pod
because the pod-level shared pool would be empty for containers requiring
shared resources:
Status: Failed
Reason: TopologyAffinityError
Message: Pod was rejected: Pod Scope pod with pod-level resources failed admission under pod-scope topology manager
flowchart TD
subgraph Pod["Pod-Level Budget: 4 CPUs, 4Gi Memory"]
direction TB
C1["container-a<br/>(Exclusive: 1 CPU, 1Gi Memory)"]
C2["container-b<br/>(Exclusive: 3 CPUs, 3Gi Memory)"]
subgraph Pool["Pod Shared Pool: 0 CPUs, 0Gi Memory"]
C3["container-c<br/>(Requires shared pool)"]
end
end
Pool --> Rejection["Admission Error: Pod Rejected!"]
style Rejection fill:#ffcccc,stroke:#ff0000,stroke-width:2pxYou can also configure the Topology Manager scope to container. In this mode,
the kubelet evaluates each container individually for exclusive allocation,
while the overall Pod budget in .spec.resources still enforces QoS and cgroup
limit boundaries.
Update your
kubelet configuration on
the target worker node(s) for the container scope:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cpuManagerPolicy: "static"
memoryManagerPolicy: "Static"
topologyManagerScope: "container"
topologyManagerPolicy: "single-numa-node"
Restart the kubelet to apply the configuration. For example, on Linux with
systemd: systemctl restart kubelet.service.
Consider the following example Pod manifest. The Pod has a total budget of 4 CPUs:
infrastructure-sidecar requests an exclusive 2 CPU slice (requests =
limits = 2 CPU).worker-1 and worker-2 do not specify container-level requests and run in
the general, node-wide shared pool.apiVersion: v1
kind: Pod
metadata:
name: container-scope-mixed
annotations:
kubernetes.io/description: "A pod demonstrating container-level scope where one container gets exclusive resources and others run in the node's shared pool."
spec:
# At Pod level, the Pod has CPU request equal to limits and memory request
# also equal to memory limits. The infrastructure-sidecar container meets the
# requirements for the Guaranteed QoS class at container level, and the worker
# containers don't specify any resource request. Under container scope, the
# kubelet evaluates containers individually for exclusive allocation. This
# means the infrastructure-sidecar gets an exclusive 2 CPU slice, while the
# worker containers run in the node's general shared pool, all while bounded
# by the overall pod limits.
resources:
requests:
cpu: "4"
memory: "4Gi"
limits:
cpu: "4"
memory: "4Gi"
initContainers:
- name: infrastructure-sidecar
# Note: This is a placeholder image for demonstration purposes, not an
# actual infrastructure helper.
image: registry.k8s.io/pause:3.9
restartPolicy: Always
resources:
requests:
cpu: "2"
memory: "2Gi"
limits:
cpu: "2"
memory: "2Gi"
containers:
- name: worker-1
# Note: This is a placeholder image for demonstration purposes.
image: registry.k8s.io/pause:3.9
- name: worker-2
# Note: This is a placeholder image for demonstration purposes.
image: registry.k8s.io/pause:3.9
Apply the manifest:
kubectl apply -f https://k8s.io/examples/pods/resource/pod-level-resource-managers-container-scope-mixed.yaml --namespace=plrm-tutorial
Check that the Pod is running:
kubectl get pod container-scope-mixed --namespace=plrm-tutorial
Understand what happened behind the scenes:
flowchart TD
subgraph Pod["Pod-Level Budget: 4 CPUs, 4Gi Memory"]
direction TB
C1["infrastructure-sidecar<br/>(Exclusive NUMA Slice: 2 CPUs, 2Gi Memory)"]
subgraph NodePool["Node Shared Pool: Pod-level limit"]
C3["worker-2"]
C2["worker-1"]
end
endcontainer scope, the kubelet
evaluates containers individually. infrastructure-sidecar receives an
exclusive, NUMA-aligned 2 CPU slice directly from the node's allocatable
pool.worker-1 and worker-2 specify no container-level
resource requests (resources: {}), so under container scope they run
in the node's general shared pool (rather than a pod-isolated pool).spec.resources.limits).Delete the namespace and all sample Pods created during this tutorial:
kubectl delete namespace plrm-tutorial