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)
-
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
-
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:
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):
What happened
kubectl describe node <name>omits Node events when those events haveinvolvedObject.uidunset/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 likeNodeHasInsufficientMemory,SystemOOMorNodeNotReady, butkubectl describe node <node>only shows events whereinvolvedObject.uidmatches either:Example:
i-0438dcc31e07e20a0.us-east-2.compute.internalkubectl get node ... -o jsonpath='{.metadata.uid}'):66cedd99-0823-46bf-a36d-5db3fa6f28cdinvolvedObject.uid: null(kubelet is not setting it), but matches by kind+name:involvedObject.apiVersion=v1involvedObject.kind=NodeinvolvedObject.name=i-0438...So
kubectl describe nodedoes not show it, whilekubectl get events ... kind+nameandkubectl events --for node/<node> -n defaultdo.What you expected to happen
kubectl describe node <name>should show all events involving that Node (at least by kind+name), even ifinvolvedObject.uidis 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)
Ensure there are Node events that match kind+name but have
involvedObject.uidnull/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:Compare outputs:
Observe that
describe nodeomits the events withinvolvedObject.uid: null.Anything else we need to know?
This appears to be due to kubectl’s node event lookup requiring
involvedObject.uidin the fieldSelector:NodeDescriber.Describe()does two searches and merges:ref.UID = ref.Name(string UID workaround)but it never matches events where
involvedObject.uidis null/unset.Code pointers (at least in commit
8144b746a47f142759a073a46f581de92b1886aa):NodeDescriber.Describe():kubectl/pkg/describe/describe.go
Lines 3435 to 3490 in 8144b74
searchEvents()always passes UID intoGetFieldSelector, so the query requires UID:kubectl/pkg/describe/describe.go
Lines 5398 to 5428 in 8144b74
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
kubectl version):cat /etc/os-release):uname -a: Linux di15 5.15.0-156-generic Add a sane default constructor for the Etcd and EtcdConfig structs #166-Ubuntu SMP Sat Aug 9 00:02:46 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux