diff --git a/staging/src/k8s.io/kubectl/pkg/describe/describe.go b/staging/src/k8s.io/kubectl/pkg/describe/describe.go index 19817821c5f..03f2bdc075f 100644 --- a/staging/src/k8s.io/kubectl/pkg/describe/describe.go +++ b/staging/src/k8s.io/kubectl/pkg/describe/describe.go @@ -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() diff --git a/staging/src/k8s.io/kubectl/pkg/describe/describe_test.go b/staging/src/k8s.io/kubectl/pkg/describe/describe_test.go index e0e0c28e400..543f3de6d11 100644 --- a/staging/src/k8s.io/kubectl/pkg/describe/describe_test.go +++ b/staging/src/k8s.io/kubectl/pkg/describe/describe_test.go @@ -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 diff --git a/staging/src/k8s.io/kubectl/pkg/util/resource/resource.go b/staging/src/k8s.io/kubectl/pkg/util/resource/resource.go index 3a34da9e8a1..f769eb0f4e2 100644 --- a/staging/src/k8s.io/kubectl/pkg/util/resource/resource.go +++ b/staging/src/k8s.io/kubectl/pkg/util/resource/resource.go @@ -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) }