diff --git a/src/frontend/src/instance/entity_graph.rs b/src/frontend/src/instance/entity_graph.rs index eadc6313d0..fe11ecd455 100644 --- a/src/frontend/src/instance/entity_graph.rs +++ b/src/frontend/src/instance/entity_graph.rs @@ -905,6 +905,7 @@ mod tests { "resource_attributes.service.instance.id", "resource_attributes.k8s.pod.uid", "resource_attributes.k8s.pod.name", + "resource_attributes.k8s.node.name", ], &[(TABLE_DATA_MODEL, TABLE_DATA_MODEL_TRACE_V1)], ); @@ -913,20 +914,27 @@ mod tests { .iter() .map(|d| d.entity_type.as_str()) .collect(); - assert_eq!(types, vec!["k8s.pod", "service", "service.instance"]); + assert_eq!( + types, + vec!["k8s.node", "k8s.pod", "service", "service.instance"] + ); assert_eq!( declarations[0].id_columns, + vec!["resource_attributes.k8s.node.name"] + ); + assert_eq!( + declarations[1].id_columns, vec!["resource_attributes.k8s.pod.uid"] ); assert_eq!( - declarations[0].descriptive_columns, + declarations[1].descriptive_columns, vec!["resource_attributes.k8s.pod.name"] ); assert_eq!( - declarations[2].id_columns, + declarations[3].id_columns, vec!["service_name", "resource_attributes.service.instance.id"] ); - assert_eq!(declarations[1].id_qualifier, None); + assert_eq!(declarations[2].id_qualifier, None); let namespaced = table_info( &[ @@ -1113,6 +1121,7 @@ mod tests { "k8s.pod.name", "k8s.container.name", "k8s.namespace.name", + "k8s.node.name", ], OTEL_STAMPS, ); @@ -1127,6 +1136,7 @@ mod tests { "container", "host", "k8s.container", + "k8s.node", "k8s.pod", "service", "service.instance" @@ -1145,13 +1155,14 @@ mod tests { ); assert_eq!(declarations[1].id_columns, vec!["host.id"]); assert_eq!(declarations[1].descriptive_columns, vec!["host.name"]); - assert_eq!(declarations[4].id_columns, vec!["job"]); + assert_eq!(declarations[3].id_columns, vec!["k8s.node.name"]); + assert_eq!(declarations[5].id_columns, vec!["job"]); assert_eq!( - declarations[4].descriptive_columns, + declarations[5].descriptive_columns, vec!["service.name", "service.namespace"] ); - assert_eq!(declarations[5].id_columns, vec!["job", "instance"]); - assert!(declarations[5].descriptive_columns.is_empty()); + assert_eq!(declarations[6].id_columns, vec!["job", "instance"]); + assert!(declarations[6].descriptive_columns.is_empty()); // Nothing here can produce a k8s.container, so the generic one must // stand or the container disappears instead of changing type. diff --git a/src/operator/src/statement/semantic_graph/conventions.yaml b/src/operator/src/statement/semantic_graph/conventions.yaml index 6a2da4de40..85dfe932ab 100644 --- a/src/operator/src/statement/semantic_graph/conventions.yaml +++ b/src/operator/src/statement/semantic_graph/conventions.yaml @@ -75,6 +75,11 @@ otlp_trace_entities: descriptive: - resource_attributes.k8s.pod.name - resource_attributes.k8s.namespace.name + # Named, not k8s.node.uid: kube-state-metrics carries no node UID, so the + # name is the only identity both sources can agree on. Same-named nodes in + # different clusters therefore merge. + - entity: k8s.node + id: [resource_attributes.k8s.node.name] # Implicit declarations for Prometheus entity-descriptor metrics, keyed by # table name. Pod and service identities are the UIDs (names stay @@ -184,3 +189,6 @@ otel_info_metrics: descriptive: - k8s.pod.name - k8s.namespace.name + # Named for the same reason as on the trace side above. + - entity: k8s.node + id: [k8s.node.name] diff --git a/src/servers/src/otlp/metrics/resource_info.rs b/src/servers/src/otlp/metrics/resource_info.rs index 2100ce7313..c3295eb205 100644 --- a/src/servers/src/otlp/metrics/resource_info.rs +++ b/src/servers/src/otlp/metrics/resource_info.rs @@ -37,7 +37,7 @@ use crate::otlp::metrics::{ }; use crate::otlp::trace::{ KEY_CONTAINER_ID, KEY_CONTAINER_NAME, KEY_HOST_ID, KEY_HOST_NAME, KEY_K8S_CONTAINER_NAME, - KEY_K8S_NAMESPACE_NAME, KEY_K8S_POD_NAME, KEY_K8S_POD_UID, KEY_SERVICE_NAME, + KEY_K8S_NAMESPACE_NAME, KEY_K8S_NODE_NAME, KEY_K8S_POD_NAME, KEY_K8S_POD_UID, KEY_SERVICE_NAME, KEY_SERVICE_NAMESPACE, }; use crate::row_writer::{self, MultiTableData}; @@ -64,12 +64,13 @@ fn is_projected_attr(key: &str) -> bool { | KEY_K8S_POD_NAME | KEY_K8S_CONTAINER_NAME | KEY_K8S_NAMESPACE_NAME + | KEY_K8S_NODE_NAME ) } /// Upper bound of [`is_projected_attr`] plus the derived `job`/`instance`, /// used to size the per-row buffers. -const MAX_PROJECTED_TAGS: usize = 12; +const MAX_PROJECTED_TAGS: usize = 13; /// Projected attributes (sorted `(name, value)` pairs) -> graph window -> /// the newest data-point time seen in that window, which is what the row for @@ -264,6 +265,7 @@ mod tests { kv("service.namespace", "shop"), kv("service.instance.id", "inst-1"), kv("host.id", "h-1"), + kv("k8s.node.name", "node-a"), kv("os.type", "linux"), ]; data.observe(&attrs, &gauge_at(&[100, 50]), &OtlpMetricCtx::default()); @@ -273,6 +275,7 @@ mod tests { assert!(tags.contains(&("job".to_string(), "shop/api".to_string()))); assert!(tags.contains(&("instance".to_string(), "inst-1".to_string()))); assert!(tags.contains(&("service.name".to_string(), "api".to_string()))); + assert!(tags.contains(&("k8s.node.name".to_string(), "node-a".to_string()))); assert!( tags.iter() .all(|(k, _)| k != "os.type" && k != "service.instance.id") diff --git a/src/servers/src/otlp/trace.rs b/src/servers/src/otlp/trace.rs index d2e9b1131c..aca52c23e7 100644 --- a/src/servers/src/otlp/trace.rs +++ b/src/servers/src/otlp/trace.rs @@ -56,6 +56,7 @@ pub const KEY_K8S_POD_UID: &str = "k8s.pod.uid"; pub const KEY_K8S_POD_NAME: &str = "k8s.pod.name"; pub const KEY_K8S_CONTAINER_NAME: &str = "k8s.container.name"; pub const KEY_K8S_NAMESPACE_NAME: &str = "k8s.namespace.name"; +pub const KEY_K8S_NODE_NAME: &str = "k8s.node.name"; pub const KEY_SPAN_KIND: &str = "span.kind"; // jaeger const keys, not sure if they are general