#7299·kubeedge

[keadm ctl] keadm ctl get pod with -o json / -o yaml bypasses edge node filtering and outputs pods from all cluster nodes

Author: neo0007777Created Sep 13, 2026Updated Sep 13, 2026

What happened:

In keadm ctl get pod (keadm/cmd/keadm/app/cmd/ctl/get/pod.go), edge node filtering (pod.Spec.NodeName == nodeName) is only performed inside the table output format block:

go
	if *o.PrintFlags.OutputFormat == "" || *o.PrintFlags.OutputFormat == "wide" {
		podListFilter := &api.PodList{
			Items: make([]api.Pod, 0, len(podList.Items)),
		}
		for _, pod := range podList.Items {
			if pod.Spec.NodeName == nodeName {
				var apiPod api.Pod
				if err := k8s_v1_api.Convert_v1_Pod_To_core_Pod(&pod, &apiPod, nil); err != nil {
					fmt.Printf("pod revert to apiPod with err:%v\n", err)
					continue
				}
				podListFilter.Items = append(podListFilter.Items, apiPod)
			}
		}
		table, err := ConvertDataToTable(podListFilter)
		if err != nil {
			return err
		}
		return o.PrintToTable(table, o.AllNamespaces, os.Stdout)
	}

	runtimeObjects := make([]runtime.Object, 0, len(podList.Items))
	for _, pod := range podList.Items {
		runtimeObjects = append(runtimeObjects, &pod)
	}
	return o.PrintToJSONYaml(runtimeObjects)

When a user specifies -o json or -o yaml, the table formatting block is skipped. Execution falls through to lines 148–152, which iterate over podList.Items (the unfiltered cluster/namespace pod list returned by podRequest.GetPods(ctx)).

This leads to the following issues:

  1. Node Filtering Bypass in JSON/YAML Output: keadm ctl get pod (table format) outputs only the pods running on the current edge node, but keadm ctl get pod -o json or keadm ctl get pod -o yaml outputs pods from all nodes across the queried namespace (or the entire cluster if -A is used).
  2. Incorrect Empty-State / "No resources found" Check: The check if len(podList.Items) == 0 (line 115) runs on the raw podList before filtering by nodeName. If the namespace contains pods scheduled on other nodes, but zero pods on the local edge node:
    • len(podList.Items) == 0 is false.
    • In table format, podListFilter.Items is empty, causing keadm ctl get pod to print an empty table header with 0 rows, instead of reporting "No resources found in <namespace> namespace.".
    • In JSON/YAML format, it outputs the pods from the other nodes.
  3. Inconsistent Behavior Between Querying Specific Pods vs Listing: When pod arguments are provided (len(args) > 0, lines 81–102), podList is filtered by nodeName. But when listing all pods (len(args) == 0), podList is unfiltered, causing inconsistent behavior between commands like keadm ctl get pod <name> -o json and keadm ctl get pod -o json.

In contrast, keadm ctl get device (keadm/cmd/keadm/app/cmd/ctl/get/device.go:112–143) and keadm ctl describe pod (keadm/cmd/keadm/app/cmd/ctl/describe/pod.go:132–157) correctly filter into a filtered slice first, check emptiness against the filtered slice, and format both table and JSON/YAML outputs from the filtered slice.

What you expected to happen:

  • keadm ctl get pod -o json and keadm ctl get pod -o yaml should only return pods running on the current edge node (pod.Spec.NodeName == nodeName), consistent with the table format and with keadm ctl get device.
  • When an edge node has no pods in the namespace, keadm ctl get pod should print "No resources found in <namespace> namespace." instead of an empty table header.

How to reproduce it (as minimally and precisely as possible):

  1. On a cluster with an edge node edge-node-1 and another node edge-node-2 (or cloud master):
  2. Deploy a pod pod-other scheduled on edge-node-2, and a pod pod-local scheduled on edge-node-1 in the default namespace.
  3. On edge-node-1, run default table view:
    bash
    keadm ctl get pod -n default
    Result: Displays only pod-local.
  4. Now run with JSON output:
    bash
    keadm ctl get pod -n default -o json
    Result: Both pod-local and pod-other are printed in the JSON output, ignoring the edge node filter.
  5. If edge-node-1 has no pods but edge-node-2 does:
    bash
    keadm ctl get pod -n default
    Result: Displays empty table headers with 0 rows instead of "No resources found in default namespace.".

Anything else we need to know?:

Comparison with keadm/cmd/keadm/app/cmd/ctl/get/device.go:

go
		deviceListFilter = &v1beta1.DeviceList{
			Items: make([]v1beta1.Device, 0, len(deviceList.Items)),
		}

		for _, device := range deviceList.Items {
			if device.Spec.NodeName == nodeName {
				deviceListFilter.Items = append(deviceListFilter.Items, device)
			}
		}
	}

	if len(deviceListFilter.Items) == 0 {
		if len(args) > 0 {
			return nil
		}
		if o.AllNamespaces {
			klog.Info("No resources found in all namespaces.")
		} else {
			klog.Infof("No resources found in %s namespace.", o.Namespace)
		}
		return nil
	}

	if *o.PrintFlags.OutputFormat == "" || *o.PrintFlags.OutputFormat == "wide" {
		return o.PrintToTable(deviceListFilter, o.AllNamespaces, os.Stdout)
	}
	runtimeObjects := make([]runtime.Object, 0, len(deviceListFilter.Items))
	for _, device := range deviceListFilter.Items {
		runtimeObjects = append(runtimeObjects, &device)
	}
	return o.PrintToJSONYaml(runtimeObjects)

Proposed fix for keadm/cmd/keadm/app/cmd/ctl/get/pod.go:

diff
--- a/keadm/cmd/keadm/app/cmd/ctl/get/pod.go
+++ b/keadm/cmd/keadm/app/cmd/ctl/get/pod.go
@@ -77,10 +77,10 @@ func (o *PodGetOptions) getPods(args []string) error {
 		return err
 	}
 
-	var podList *v1.PodList
+	var podListFilter *v1.PodList
 	if len(args) > 0 {
-		podList = &v1.PodList{
+		podListFilter = &v1.PodList{
 			Items: make([]v1.Pod, 0, len(args)),
 		}
 		var podRequest *client.PodRequest
@@ -95,9 +95,9 @@ func (o *PodGetOptions) getPods(args []string) error {
 			}
 
 			if pod.Spec.NodeName == nodeName {
-				podList.Items = append(podList.Items, *pod)
+				podListFilter.Items = append(podListFilter.Items, *pod)
 			} else {
-				fmt.Printf("can't to query pod: \"%s\" for node: \"%s\"\n", pod.Name, pod.Spec.NodeName)
+				fmt.Printf("can't query pod: \"%s\" for node: \"%s\"\n", pod.Name, pod.Spec.NodeName)
 			}
 		}
 	} else {
@@ -106,47 +106,56 @@ func (o *PodGetOptions) getPods(args []string) error {
 			AllNamespaces: o.AllNamespaces,
 			LabelSelector: o.LabelSelector,
 		}
-		podList, err = podRequest.GetPods(ctx)
+		podList, err := podRequest.GetPods(ctx)
 		if err != nil {
 			return err
 		}
+
+		podListFilter = &v1.PodList{
+			Items: make([]v1.Pod, 0, len(podList.Items)),
+		}
+		for _, pod := range podList.Items {
+			if pod.Spec.NodeName == nodeName {
+				podListFilter.Items = append(podListFilter.Items, pod)
+			}
+		}
 	}
 
-	if len(podList.Items) == 0 {
+	if len(podListFilter.Items) == 0 {
 		if len(args) > 0 {
 			return nil
 		}
 		if o.AllNamespaces {
-			fmt.Println("No resources found in all namespace.")
+			fmt.Println("No resources found in all namespaces.")
 		} else {
 			fmt.Printf("No resources found in %s namespace.\n", o.Namespace)
 		}
 		return nil
 	}
 
 	if *o.PrintFlags.OutputFormat == "" || *o.PrintFlags.OutputFormat == "wide" {
-		podListFilter := &api.PodList{
-			Items: make([]api.Pod, 0, len(podList.Items)),
+		corePodList := &api.PodList{
+			Items: make([]api.Pod, 0, len(podListFilter.Items)),
 		}
-		for _, pod := range podList.Items {
-			if pod.Spec.NodeName == nodeName {
-				var apiPod api.Pod
-				if err := k8s_v1_api.Convert_v1_Pod_To_core_Pod(&pod, &apiPod, nil); err != nil {
-					fmt.Printf("pod revert to apiPod with err:%v\n", err)
-					continue
-				}
-				podListFilter.Items = append(podListFilter.Items, apiPod)
+		for i := range podListFilter.Items {
+			var apiPod api.Pod
+			if err := k8s_v1_api.Convert_v1_Pod_To_core_Pod(&podListFilter.Items[i], &apiPod, nil); err != nil {
+				fmt.Printf("pod revert to apiPod with err:%v\n", err)
+				continue
 			}
+			corePodList.Items = append(corePodList.Items, apiPod)
 		}
-		table, err := ConvertDataToTable(podListFilter)
+		table, err := ConvertDataToTable(corePodList)
 		if err != nil {
 			return err
 		}
 		return o.PrintToTable(table, o.AllNamespaces, os.Stdout)
 	}
 
-	runtimeObjects := make([]runtime.Object, 0, len(podList.Items))
-	for _, pod := range podList.Items {
-		runtimeObjects = append(runtimeObjects, &pod)
+	runtimeObjects := make([]runtime.Object, 0, len(podListFilter.Items))
+	for i := range podListFilter.Items {
+		runtimeObjects = append(runtimeObjects, &podListFilter.Items[i])
 	}
 	return o.PrintToJSONYaml(runtimeObjects)
 }

I would be happy to submit a pull request fixing this issue if the maintainers agree with the proposed solution!

Environment:

  • Kubernetes version (use kubectl version): v1.30.0
  • KubeEdge version(e.g. cloudcore --version and edgecore --version): v1.20.0-alpha.0 (commit 62940139aacb211a98c3b55cf7229848812a0755)