Skip to content

kubectl describe node omits events when involvedObject.uid is null (kubelet-emitted node events are missing) #1838

@marinoborges

Description

@marinoborges

What happened

kubectl describe node <name> omits Node events when those events have involvedObject.uid unset/null (seen with kubelet-emitted Node status events on EKS).

In our cluster, kubectl get events -A --field-selector involvedObject.kind=Node,involvedObject.name=<node> shows Node status events emitted by kubelet with reasons like NodeHasInsufficientMemory, SystemOOM or NodeNotReady, but kubectl describe node <node> only shows events where involvedObject.uid matches either:

  • the Node object UID (UUID), or
  • the node name (string UID workaround used by some producers such as kernel-monitor)

Example:

  • Node: i-0438dcc31e07e20a0.us-east-2.compute.internal
  • Node UID (kubectl get node ... -o jsonpath='{.metadata.uid}'): 66cedd99-0823-46bf-a36d-5db3fa6f28cd
  • “Missing” kubelet event has involvedObject.uid: null (kubelet is not setting it), but matches by kind+name:
    • involvedObject.apiVersion=v1
    • involvedObject.kind=Node
    • involvedObject.name=i-0438...

So kubectl describe node does not show it, while kubectl get events ... kind+name and kubectl events --for node/<node> -n default do.

What you expected to happen

kubectl describe node <name> should show all events involving that Node (at least by kind+name), even if involvedObject.uid is unset/null, because those are still valid/important Node events and are visible via other kubectl event commands.

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

  1. Ensure there are Node events that match kind+name but have involvedObject.uid null/unset (kubelet-emitted Node status events observed on EKS), e.g. simulating a node issue such as MemoryPressure triggering kubelet to emit events for the node:

    kubectl get events -A --field-selector involvedObject.kind=Node,involvedObject.name=<node> -o wide
    kubectl get event -n <ns> <event-name> -o yaml | yq '.involvedObject'
    # shows uid: null
  2. Compare outputs:

    kubectl get events -A --field-selector involvedObject.kind=Node,involvedObject.name=<node>
    kubectl describe node <node>

    Observe that describe node omits the events with involvedObject.uid: null.

Anything else we need to know?

This appears to be due to kubectl’s node event lookup requiring involvedObject.uid in the fieldSelector:

  • NodeDescriber.Describe() does two searches and merges:

    1. using the Node’s actual UID
    2. using ref.UID = ref.Name (string UID workaround)

    but it never matches events where involvedObject.uid is null/unset.

Code pointers (at least in commit 8144b746a47f142759a073a46f581de92b1886aa):

  • NodeDescriber.Describe():

    func (d *NodeDescriber) Describe(namespace, name string, describerSettings DescriberSettings) (string, error) {
    node, err := d.CoreV1().Nodes().Get(context.TODO(), name, metav1.GetOptions{})
    if err != nil {
    return "", err
    }
    fieldSelector := fields.AndSelectors(
    fields.OneTermEqualSelector("spec.nodeName", node.Name),
    fields.OneTermNotEqualSelector("status.phase", string(corev1.PodSucceeded)),
    fields.OneTermNotEqualSelector("status.phase", string(corev1.PodFailed)),
    )
    // in a policy aware setting, users may have access to a node, but not all pods
    // in that case, we note that the user does not have access to the pods
    canViewPods := true
    initialOpts := metav1.ListOptions{
    FieldSelector: fieldSelector.String(),
    Limit: describerSettings.ChunkSize,
    }
    nodeNonTerminatedPodsList, err := getPodsInChunks(d.CoreV1().Pods(node.Namespace), initialOpts)
    if err != nil {
    if !apierrors.IsForbidden(err) {
    return "", err
    }
    canViewPods = false
    }
    var events *corev1.EventList
    if describerSettings.ShowEvents {
    if ref, err := reference.GetReference(scheme.Scheme, node); err != nil {
    klog.Errorf("Unable to construct reference to '%#v': %v", node, err)
    } else {
    // TODO: We haven't decided the namespace for Node object yet.
    // there are two UIDs for host events:
    // controller use node.uid
    // kubelet use node.name
    // TODO: Uniform use of UID
    events, _ = searchEvents(d.CoreV1(), ref, describerSettings.ChunkSize)
    ref.UID = types.UID(ref.Name)
    eventsInvName, _ := searchEvents(d.CoreV1(), ref, describerSettings.ChunkSize)
    // Merge the results of two queries
    events.Items = append(events.Items, eventsInvName.Items...)
    }
    }
    // Fetch ResourceSlices exclusive to this node using indexed field selector (O(1) query)
    var resourceSlices []resourcev1.ResourceSlice
    if sliceList, err := d.ResourceV1().ResourceSlices().List(context.TODO(), metav1.ListOptions{
    FieldSelector: fields.Set{resourcev1.ResourceSliceSelectorNodeName: node.Name}.AsSelector().String(),
    }); err == nil {
    resourceSlices = sliceList.Items
    }
    return describeNode(node, nodeNonTerminatedPodsList, events, canViewPods, &LeaseDescriber{d}, resourceSlices)
    }

  • searchEvents() always passes UID into GetFieldSelector, so the query requires UID:

    func searchEvents(client corev1client.EventsGetter, objOrRef runtime.Object, limit int64) (*corev1.EventList, error) {
    ref, err := reference.GetReference(scheme.Scheme, objOrRef)
    if err != nil {
    return nil, err
    }
    stringRefKind := string(ref.Kind)
    var refKind *string
    if len(stringRefKind) > 0 {
    refKind = &stringRefKind
    }
    stringRefUID := string(ref.UID)
    var refUID *string
    if len(stringRefUID) > 0 {
    refUID = &stringRefUID
    }
    e := client.Events(ref.Namespace)
    fieldSelector := e.GetFieldSelector(&ref.Name, &ref.Namespace, refKind, refUID)
    initialOpts := metav1.ListOptions{FieldSelector: fieldSelector.String(), Limit: limit}
    eventList := &corev1.EventList{}
    err = runtimeresource.FollowContinue(&initialOpts,
    func(options metav1.ListOptions) (runtime.Object, error) {
    newEvents, err := e.List(context.TODO(), options)
    if err != nil {
    return nil, runtimeresource.EnhanceListError(err, options, "events")
    }
    eventList.Items = append(eventList.Items, newEvents.Items...)
    return newEvents, nil
    })
    return eventList, err
    }

Potential fix idea: for Nodes, add a fallback/extra query that matches on kind+name without UID, then merge/deduplicate results (e.g., by event metadata.uid) and keep a reasonable limit to avoid huge outputs.

Environment

  • Kubernetes client and server versions (use kubectl version):
    • Client Version: v1.35.3
    • Kustomize Version: v5.7.1
    • Server Version: v1.35.2-eks-f69f56f
  • Cloud provider or hardware configuration: AWS EKS
  • OS (e.g: cat /etc/os-release):

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/bugCategorizes issue or PR as related to a bug.needs-triageIndicates an issue or PR lacks a `triage/foo` label and requires one.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions