kubectl describe: migrate to component-helpers resource package and add pod-level resources test (#137394)

* kubectl describe: migrate to component-helpers resource package and add pod-level resources test

Refactor describe.go to use k8s.io/component-helpers/resource package for pod resource
calculations. This migration enables proper support for pod-level resources feature.

Changes:
- Import resourcehelper from k8s.io/component-helpers/resource
- Replace PodRequestsAndLimits() with separate PodRequests() and PodLimits() calls

Add TestDescribeNodeWithPodLevelResources to verify describeNodeResource works correctly
when pods have pod-level resources (Spec.Resources) configured.

Signed-off-by: KunWuLuan <kunwuluan@gmail.com>

* address review feedback: verify computed values in test and deprecate PodRequestsAndLimits

---------

Signed-off-by: KunWuLuan <kunwuluan@gmail.com>
Co-authored-by: KunWuLuan <kunwuluan@gmail.com>
This commit is contained in:
Nikhil 2026-03-05 18:18:25 +05:30 committed by GitHub
parent 8873025e93
commit 63080a762b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 105 additions and 6 deletions

View file

@ -72,6 +72,7 @@ import (
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/reference"
utilcsr "k8s.io/client-go/util/certificate/csr"
resourcehelper "k8s.io/component-helpers/resource"
"k8s.io/klog/v2"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/certificate"
@ -80,7 +81,7 @@ import (
"k8s.io/kubectl/pkg/util/fieldpath"
"k8s.io/kubectl/pkg/util/qos"
"k8s.io/kubectl/pkg/util/rbac"
resourcehelper "k8s.io/kubectl/pkg/util/resource"
kubectlresourcehelper "k8s.io/kubectl/pkg/util/resource"
storageutil "k8s.io/kubectl/pkg/util/storage"
)
@ -1997,7 +1998,7 @@ func describeContainerEnvVars(container corev1.Container, resolverFn EnvVarResol
}
w.Write(LEVEL_3, "%s:\t%s (%s:%s)\n", e.Name, valueFrom, e.ValueFrom.FieldRef.APIVersion, e.ValueFrom.FieldRef.FieldPath)
case e.ValueFrom.ResourceFieldRef != nil:
valueFrom, err := resourcehelper.ExtractContainerResourceValue(e.ValueFrom.ResourceFieldRef, &container)
valueFrom, err := kubectlresourcehelper.ExtractContainerResourceValue(e.ValueFrom.ResourceFieldRef, &container)
if err != nil {
valueFrom = ""
}
@ -3993,7 +3994,8 @@ func describeNodeResource(nodeNonTerminatedPodsList *corev1.PodList, node *corev
}
for _, pod := range nodeNonTerminatedPodsList.Items {
req, limit := resourcehelper.PodRequestsAndLimits(&pod)
req := resourcehelper.PodRequests(&pod, resourcehelper.PodResourcesOptions{SkipPodLevelResources: false})
limit := resourcehelper.PodLimits(&pod, resourcehelper.PodResourcesOptions{SkipPodLevelResources: false})
cpuReq, cpuLimit, memoryReq, memoryLimit := req[corev1.ResourceCPU], limit[corev1.ResourceCPU], req[corev1.ResourceMemory], limit[corev1.ResourceMemory]
fractionCpuReq := float64(cpuReq.MilliValue()) / float64(allocatable.Cpu().MilliValue()) * 100
fractionCpuLimit := float64(cpuLimit.MilliValue()) / float64(allocatable.Cpu().MilliValue()) * 100
@ -4038,9 +4040,9 @@ func describeNodeResource(nodeNonTerminatedPodsList *corev1.PodList, node *corev
extResources := make([]string, 0, len(allocatable))
hugePageResources := make([]string, 0, len(allocatable))
for resource := range allocatable {
if resourcehelper.IsHugePageResourceName(resource) {
if kubectlresourcehelper.IsHugePageResourceName(resource) {
hugePageResources = append(hugePageResources, string(resource))
} else if !resourcehelper.IsStandardContainerResourceName(string(resource)) && resource != corev1.ResourcePods {
} else if !kubectlresourcehelper.IsStandardContainerResourceName(string(resource)) && resource != corev1.ResourcePods {
extResources = append(extResources, string(resource))
}
}
@ -4069,7 +4071,8 @@ func describeNodeResource(nodeNonTerminatedPodsList *corev1.PodList, node *corev
func getPodsTotalRequestsAndLimits(podList *corev1.PodList) (reqs map[corev1.ResourceName]resource.Quantity, limits map[corev1.ResourceName]resource.Quantity) {
reqs, limits = map[corev1.ResourceName]resource.Quantity{}, map[corev1.ResourceName]resource.Quantity{}
for _, pod := range podList.Items {
podReqs, podLimits := resourcehelper.PodRequestsAndLimits(&pod)
podReqs := resourcehelper.PodRequests(&pod, resourcehelper.PodResourcesOptions{SkipPodLevelResources: false})
podLimits := resourcehelper.PodLimits(&pod, resourcehelper.PodResourcesOptions{SkipPodLevelResources: false})
for podReqName, podReqValue := range podReqs {
if value, ok := reqs[podReqName]; !ok {
reqs[podReqName] = podReqValue.DeepCopy()

View file

@ -6597,6 +6597,98 @@ func TestDescribeNodeWithSidecar(t *testing.T) {
}
}
}
func TestDescribeNodeWithPodLevelResources(t *testing.T) {
holderIdentity := "holder"
nodeCapacity := getResourceList("8", "24Gi")
nodeAllocatable := getResourceList("4", "12Gi")
fake := fake.NewClientset(
&corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
UID: "uid",
},
Spec: corev1.NodeSpec{
Unschedulable: false,
},
Status: corev1.NodeStatus{
Capacity: nodeCapacity,
Allocatable: nodeAllocatable,
},
},
&coordinationv1.Lease{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: corev1.NamespaceNodeLease,
},
Spec: coordinationv1.LeaseSpec{
HolderIdentity: &holderIdentity,
AcquireTime: &metav1.MicroTime{Time: time.Now().Add(-time.Hour)},
RenewTime: &metav1.MicroTime{Time: time.Now()},
},
},
&corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-with-pod-level-resources",
Namespace: "foo",
},
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
},
Spec: corev1.PodSpec{
// Pod-level resources
Resources: &corev1.ResourceRequirements{
Requests: getResourceList("2", "4Gi"),
Limits: getResourceList("4", "8Gi"),
},
Containers: []corev1.Container{
{
Name: "container-1",
Image: "image:latest",
},
{
Name: "container-2",
Image: "image:latest",
},
},
},
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
},
},
)
c := &describeClient{T: t, Namespace: "foo", Interface: fake}
d := NodeDescriber{c}
out, err := d.Describe("foo", "bar", DescriberSettings{ShowEvents: true})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
// Verify that describeNodeResource works correctly with pod-level resources
// Pod-level resources: requests cpu=2, memory=4Gi; limits cpu=4, memory=8Gi
// Node allocatable: cpu=4, memory=12Gi
// Expected: cpu requests 2 (50%), cpu limits 4 (100%), memory requests 4Gi (33%), memory limits 8Gi (66%)
expectedOut := []string{
"pod-with-pod-level-resources",
// Verify per-pod row shows correct computed values for pod-level resources
"pod-with-pod-level-resources 2 (50%) 4 (100%) 4Gi (33%) 8Gi (66%)",
// Verify the allocated resources totals correctly account for pod-level resources
`Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 2 (50%) 4 (100%)
memory 4Gi (33%) 8Gi (66%)
ephemeral-storage 0 (0%) 0 (0%)`,
}
for _, expected := range expectedOut {
if !strings.Contains(out, expected) {
t.Errorf("expected to find %q in output: %q", expected, out)
}
}
}
func TestDescribeStatefulSet(t *testing.T) {
var partition int32 = 2672
var replicas int32 = 1

View file

@ -32,6 +32,10 @@ import (
// containers of the pod. If pod overhead is non-nil, the pod overhead is added to the
// total container resource requests and to the total container limits which have a
// non-zero quantity.
//
// Deprecated: Use k8s.io/component-helpers/resource.PodRequests and
// k8s.io/component-helpers/resource.PodLimits instead, which also support
// pod-level resources.
func PodRequestsAndLimits(pod *corev1.Pod) (reqs, limits corev1.ResourceList) {
return podRequests(pod), podLimits(pod)
}