mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-09-05 21:18:57 +00:00
feat: derive k8s.node from OTLP resource attributes (#9002)
The k8s.pod runs_on k8s.node rule can only fire from kube_pod_info: it is the only table declaring both endpoints, and co-declared edges require both on the same row. A deployment sending only OTLP has no kube-state-metrics tables, so its node layer is invisible and that rule has no source at all, even though the resource attributes carry k8s.node.name. Declare k8s.node in otlp_trace_entities and in the synthesized resource descriptor, and project k8s.node.name in the descriptor writer so the column the declaration needs exists. Identity is the name rather than k8s.node.uid: kube-state-metrics carries no node UID, so the name is the only identity both sources can agree on. Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user