Skip to content

Commit fe162bc

Browse files
committed
19446 Kubernetes CPU and Memory resource services: Support Pod-level limits and requests
CMK-31901 Change-Id: I65b145e91ee0d942b964128140dbb83dacfdf7f1
1 parent e82cdd1 commit fe162bc

15 files changed

Lines changed: 720 additions & 82 deletions

File tree

.werks/19446.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[//]: # (werk v3)
2+
# Kubernetes CPU and Memory resource services: Support Pod-level limits and requests
3+
4+
key | value
5+
---------- | ---
6+
date | 2026-03-31T03:46:02.522347+00:00
7+
version | 2.5.0b5
8+
class | feature
9+
edition | community
10+
component | checks
11+
level | 1
12+
compatible | no
13+
14+
The _CPU resources_ and _Memory resources_ services on Kubernetes hosts now take
15+
Pod-level resource limits and requests into account. Previously, only
16+
container-level values were summed.
17+
18+
Request and limit are handled independently: if a Pod specifies a pod-level
19+
request, that value is used instead of summing the container requests for that
20+
Pod. Likewise for limits. A Pod can have a pod-level request but container-level
21+
limits, or vice versa.
22+
23+
The service output now indicates pod-level values separately, e.g.
24+
"2/3 containers with requests, 1 pod with pod-level requests".
25+
26+
If your cluster uses Pod-level resource limits or requests, the values reported
27+
by these services may change after upgrading. If you have configured thresholds
28+
for _CPU resources_ or _Memory resources_, review them to ensure they still
29+
reflect your expectations.

packages/cmk-plugins/cmk/plugins/kube/agent_handlers/common.py

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -124,28 +124,65 @@ def kube_object_namespace_name(kube_object: KubeNamespacedObj) -> NamespaceName:
124124

125125

126126
def aggregate_resources(
127-
resource_type: Literal["memory", "cpu"], containers: Collection[api.ContainerSpec]
127+
resource_type: Literal["memory", "cpu"], pods: Sequence[api.Pod]
128128
) -> section.Resources:
129-
specified_requests = [
130-
request
131-
for c in containers
132-
if (request := getattr(c.resources.requests, resource_type)) is not None
133-
]
134-
specified_limits = [
135-
limit
136-
for c in containers
137-
if (limit := getattr(c.resources.limits, resource_type)) is not None
138-
]
139-
140-
count_total = len(containers)
129+
# requests
130+
total_request = 0.0 # sum of all requests (pod-level or container-level)
131+
count_unspecified_requests = 0 # containers without a request set
132+
count_total_requests = 0 # containers from pods using container-level requests
133+
count_pods_pod_level_request = 0 # pods with a non-zero pod-level request
134+
135+
# limits
136+
total_limit = 0.0 # sum of all limits (pod-level or container-level)
137+
count_unspecified_limits = 0 # containers without a limit set
138+
count_zeroed_limits = 0 # containers with limit==0 (unlimited)
139+
count_total_limits = 0 # containers from pods using container-level limits
140+
count_pods_pod_level_limit = 0 # pods with a non-zero pod-level limit
141+
142+
for pod in pods:
143+
pod_request = getattr(pod.spec.resources.requests, resource_type)
144+
pod_limit = getattr(pod.spec.resources.limits, resource_type)
145+
containers = pod.spec.containers
146+
147+
if pod_request is not None and pod_request: # also handles: 0 -> unlimited
148+
total_request += pod_request
149+
count_pods_pod_level_request += 1
150+
else:
151+
specified_requests = [
152+
request
153+
for c in containers
154+
if (request := getattr(c.resources.requests, resource_type)) is not None
155+
]
156+
total_request += sum(specified_requests)
157+
count_unspecified_requests += len(containers) - len(specified_requests)
158+
count_total_requests += len(containers)
159+
160+
if pod_limit is not None and pod_limit: # also handles: 0 -> unlimited
161+
total_limit += pod_limit
162+
count_pods_pod_level_limit += 1
163+
else:
164+
specified_limits = [
165+
limit
166+
for c in containers
167+
if (limit := getattr(c.resources.limits, resource_type)) is not None
168+
]
169+
total_limit += sum(specified_limits)
170+
count_unspecified_limits += len(containers) - len(specified_limits)
171+
count_zeroed_limits += sum(1 for x in specified_limits if x == 0)
172+
count_total_limits += len(containers)
141173

142174
return section.Resources(
143-
request=sum(specified_requests),
144-
limit=sum(specified_limits),
145-
count_unspecified_requests=count_total - len(specified_requests),
146-
count_unspecified_limits=count_total - len(specified_limits),
147-
count_zeroed_limits=sum(1 for x in specified_limits if x == 0),
148-
count_total=count_total,
175+
request=total_request,
176+
limit=total_limit,
177+
# requests
178+
count_unspecified_requests=count_unspecified_requests,
179+
count_total_requests=count_total_requests,
180+
count_pods_pod_level_request=count_pods_pod_level_request,
181+
# limits
182+
count_unspecified_limits=count_unspecified_limits,
183+
count_zeroed_limits=count_zeroed_limits,
184+
count_total_limits=count_total_limits,
185+
count_pods_pod_level_limit=count_pods_pod_level_limit,
149186
)
150187

151188

@@ -161,11 +198,11 @@ def thin_containers(pods: Collection[api.Pod]) -> section.ThinContainers:
161198

162199

163200
def collect_memory_resources_from_api_pods(pods: Sequence[api.Pod]) -> section.Resources:
164-
return aggregate_resources("memory", [c for pod in pods for c in pod.spec.containers])
201+
return aggregate_resources("memory", pods)
165202

166203

167204
def collect_cpu_resources_from_api_pods(pods: Sequence[api.Pod]) -> section.Resources:
168-
return aggregate_resources("cpu", [c for pod in pods for c in pod.spec.containers])
205+
return aggregate_resources("cpu", pods)
169206

170207

171208
def pod_resources_from_api_pods(pods: Sequence[api.Pod]) -> section.PodResources:

packages/cmk-plugins/cmk/plugins/kube/checkman/kube_cpu

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ description:
77
This check shows the CPU usage of a Kubernetes object, its requests utilization and its limits
88
utilization.
99

10-
Requests utilization is the CPU usage divided by the sum of all configured container CPU
11-
requests. Limits utilization is computed in the same manner. The utilization percentage values are
12-
omitted if any of the associated request/limit value are not configured. Moreover, limits
10+
Requests utilization is the CPU usage divided by the configured CPU requests of the Kubernetes
11+
object. For pods without pod-level requests, the sum of container requests is used. For pods with
12+
pod-level requests, the pod-level value takes precedence so long as the pod-level value is
13+
non-zero. Limits utilization is computed in the same manner. The utilization percentage values
14+
are omitted if any of the associated request/limit values are not configured. Moreover, limits
1315
utilization is omitted if any container has a limit of zero. The reason for omission is displayed
1416
in the check's details.
1517

packages/cmk-plugins/cmk/plugins/kube/checkman/kube_memory

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ description:
77
This check shows the memory usage of a Kubernetes object, its requests utilization and its limits
88
utilization.
99

10-
Requests utilization is the memory usage divided by the sum of all configured container memory
11-
requests. Limit utilization is computed in the same manner. The utilization percentage values are
12-
omitted if any of the associated request/limit value are not configured. Moreover, limits
13-
utilization is omitted if any container has a limit of zero. The reason for omission is displayed
14-
in the check's details.
10+
Requests utilization is the memory usage divided by the configured memory requests of the
11+
Kubernetes object. For pods without pod-level requests, the sum of container requests is used.
12+
For pods with pod-level requests, the pod-level value takes precedence so long as the pod-level
13+
value is non-zero. Limits utilization is computed in the same manner. The utilization percentage
14+
values are omitted if any of the associated request/limit values are not configured. Moreover,
15+
limits utilization is omitted if any container has a limit of zero. The reason for omission is
16+
displayed in the check's details.
1517

1618
The primary metric driving Kubernetes' memory-based restart decisions is
1719
container_memory_working_set_bytes. The plug-in reports this metric under the label "Usage".
@@ -33,7 +35,7 @@ description:
3335
The check requires that the special agent kube is configured.
3436

3537
For the Checkmk Openshift monitoring, the reported metric values will differ from the values
36-
reported on the Openshift dashboard as as the included values describe a different quantity.
38+
reported on the Openshift dashboard as the included values describe a different quantity.
3739

3840
discovery:
3941
One service is created per Kubernetes object.

packages/cmk-plugins/cmk/plugins/kube/from_json/pod/pod_spec.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from typing import Literal, NotRequired, TypedDict
77

88
from ...schemata import api
9+
from ..resources import JSONResourceRequirements
910
from .container_spec import containers_spec, JSONContainerSpec
1011
from .volume import JSONPodVolume, parse_pod_volumes
1112

@@ -20,6 +21,30 @@ class JSONPodSpec(TypedDict):
2021
priorityClassName: NotRequired[str]
2122
activeDeadlineSeconds: NotRequired[int]
2223
volumes: NotRequired[Sequence[JSONPodVolume]]
24+
resources: NotRequired[JSONResourceRequirements]
25+
26+
27+
def pod_resources(pod_spec: JSONPodSpec) -> api.ResourceRequirements:
28+
parsed_limits = api.ResourceRequirement()
29+
parsed_requests = api.ResourceRequirement()
30+
if resources := pod_spec.get("resources"):
31+
if limits := resources.get("limits"):
32+
parsed_limits = api.ResourceRequirement(
33+
memory=api.parse_resource_value(limits["memory"]) if "memory" in limits else None,
34+
cpu=api.parse_cpu_cores(limits["cpu"]) if "cpu" in limits else None,
35+
)
36+
if requests := resources.get("requests"):
37+
parsed_requests = api.ResourceRequirement(
38+
memory=api.parse_resource_value(requests["memory"])
39+
if "memory" in requests
40+
else None,
41+
cpu=api.parse_cpu_cores(requests["cpu"]) if "cpu" in requests else None,
42+
)
43+
44+
return api.ResourceRequirements(
45+
limits=parsed_limits,
46+
requests=parsed_requests,
47+
)
2348

2449

2550
def pod_spec(spec: JSONPodSpec) -> api.PodSpec:
@@ -33,4 +58,5 @@ def pod_spec(spec: JSONPodSpec) -> api.PodSpec:
3358
priority_class_name=spec.get("priorityClassName"),
3459
active_deadline_seconds=spec.get("activeDeadlineSeconds"),
3560
volumes=parse_pod_volumes(volumes) if (volumes := spec.get("volumes")) else None,
61+
resources=pod_resources(spec),
3662
)

packages/cmk-plugins/cmk/plugins/kube/kube_resources.py

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,25 +37,42 @@ def parse_performance_usage(string_table: StringTable) -> PerformanceUsage:
3737

3838

3939
def count_overview(resources: Resources, requirement: RequirementType) -> str:
40-
ignored = (
41-
resources.count_unspecified_requests
42-
if requirement == "request"
43-
else resources.count_unspecified_limits + resources.count_zeroed_limits
44-
)
45-
return (
46-
f"{resources.count_total - ignored}/{resources.count_total} containers with {requirement}s"
47-
)
40+
if requirement == "request":
41+
specified = resources.count_total_requests - resources.count_unspecified_requests
42+
total = resources.count_total_requests
43+
pod_level = resources.count_pods_pod_level_request
44+
else:
45+
specified = (
46+
resources.count_total_limits
47+
- resources.count_unspecified_limits
48+
- resources.count_zeroed_limits
49+
)
50+
total = resources.count_total_limits
51+
pod_level = resources.count_pods_pod_level_limit
52+
53+
container_part = f"{specified}/{total} containers with {requirement}s"
54+
if pod_level == 0:
55+
return container_part
56+
57+
pod_part = f"{pod_level} pod{'s' if pod_level > 1 else ''} with pod-level {requirement}s"
58+
if total == 0:
59+
return pod_part
60+
61+
return f"{container_part}, {pod_part}"
4862

4963

5064
def parse_resources(string_table: StringTable) -> Resources:
5165
"""Parses limit and request values into Resources
5266
>>> parse_resources([['{"request": 209715200.0,'
5367
... '"limit": 104857600.0,'
5468
... '"count_unspecified_requests": 0,'
69+
... '"count_total_requests": 1,'
70+
... '"count_pods_pod_level_request": 0,'
5571
... '"count_unspecified_limits": 0,'
5672
... '"count_zeroed_limits": 1,'
57-
... '"count_total": 1}']])
58-
Resources(request=209715200.0, limit=104857600.0, count_unspecified_requests=0, count_unspecified_limits=0, count_zeroed_limits=1, count_total=1)
73+
... '"count_total_limits": 1,'
74+
... '"count_pods_pod_level_limit": 0}']])
75+
Resources(request=209715200.0, limit=104857600.0, count_unspecified_requests=0, count_total_requests=1, count_pods_pod_level_request=0, count_unspecified_limits=0, count_zeroed_limits=1, count_total_limits=1, count_pods_pod_level_limit=0)
5976
"""
6077
return Resources.model_validate_json(string_table[0][0])
6178

packages/cmk-plugins/cmk/plugins/kube/schemata/api.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,7 @@ class PodSpec(BaseModel):
778778
priority_class_name: str | None = None
779779
active_deadline_seconds: int | None = None
780780
volumes: Sequence[Volume] | None = None
781+
resources: ResourceRequirements
781782

782783

783784
@enum.unique

packages/cmk-plugins/cmk/plugins/kube/schemata/section.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,17 @@ class Resources(Section):
200200

201201
request: float
202202
limit: float
203+
204+
# requests
203205
count_unspecified_requests: int
206+
count_total_requests: int
207+
count_pods_pod_level_request: int
208+
209+
# limits
204210
count_unspecified_limits: int
205211
count_zeroed_limits: int
206-
count_total: int
212+
count_total_limits: int
213+
count_pods_pod_level_limit: int
207214

208215

209216
class AllocatableResource(Section):

packages/cmk-plugins/tests/cmk/plugins/kube/agent_based/test_kube_cpu.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,13 @@ class ResourcesFactory(ModelFactory):
4646
RESOURCES_SECTION = Resources(
4747
request=0.18,
4848
limit=0.36,
49-
count_total=2,
5049
count_zeroed_limits=0,
5150
count_unspecified_limits=0,
5251
count_unspecified_requests=0,
52+
count_total_requests=2,
53+
count_total_limits=2,
54+
count_pods_pod_level_request=0,
55+
count_pods_pod_level_limit=0,
5356
)
5457

5558

0 commit comments

Comments
 (0)