This is an automated email from the ASF dual-hosted git repository.
cryptoe pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new b89f5d7e9ac feat: For k8s-overlord-extensions users, emit the pod
template as a dimension for peon metrics (#20001)
b89f5d7e9ac is described below
commit b89f5d7e9acc89729a0f3c6c8647141f37c67c3a
Author: Lucas Capistrant <[email protected]>
AuthorDate: Fri Aug 14 09:57:02 2026 -0500
feat: For k8s-overlord-extensions users, emit the pod template as a
dimension for peon metrics (#20001)
* For k8s-overlord-extensions users, emit the pod template as a dimension
for peon metrics
* more test
---
docs/development/extensions-core/k8s-jobs.md | 8 ++
.../k8s/overlord/KubernetesPeonMetricsModule.java | 81 ++++++++++++++++++
.../k8s/overlord/common/DruidK8sConstants.java | 2 +
.../taskadapter/PodTemplateTaskAdapter.java | 10 ++-
.../org.apache.druid.initialization.DruidModule | 1 +
.../overlord/KubernetesPeonMetricsModuleTest.java | 96 ++++++++++++++++++++++
.../src/test/resources/expectedNoopJobLongIds.yaml | 4 +
.../test/resources/expectedNoopJobNoTaskJson.yaml | 4 +
.../resources/expectedNoopJobTlsEnabledBase.yaml | 4 +
9 files changed, 209 insertions(+), 1 deletion(-)
diff --git a/docs/development/extensions-core/k8s-jobs.md
b/docs/development/extensions-core/k8s-jobs.md
index 8ba0e215922..242d17a9924 100644
--- a/docs/development/extensions-core/k8s-jobs.md
+++ b/docs/development/extensions-core/k8s-jobs.md
@@ -844,6 +844,14 @@ Set the `podTemplateSelectionKey` key in a task's context
to pick a configured p
This is gated by the runtime property
`druid.indexer.runner.allowTaskPodTemplateSelection`, which defaults to
`false`. If the key doesn't match any configured template, the task fails to
launch.
+##### Pod template metrics dimension
+
+Every metric emitted by a task pod carries a `podTemplate` dimension naming
the pod template the pod runs under, alongside the existing `taskType`,
`dataSource`, `taskId`, and `groupId` dimensions. Use it to group metrics by
task type and pod template, for example to see which task types run on which
templates.
+
+The dimension reflects the template actually applied to the pod, whichever
selection strategy or context override chose it. Druid passes the name to the
pod through the `DRUID_POD_TEMPLATE` environment variable, sourced from the
pod's own `task.jobTemplate` annotation; you don't need to declare it in your
pod templates.
+
+The dimension requires `druid-kubernetes-overlord-extensions` in the task
pod's `druid.extensions.loadList`. Task pods that have no pod template, such as
those launched by another task adapter, omit the dimension.
+
#### Running Task Pods in Another Namespace
It is possible to run task pods in a different namespace from the rest of your
Druid cluster.
diff --git
a/extensions-core/kubernetes-overlord-extensions/src/main/java/org/apache/druid/k8s/overlord/KubernetesPeonMetricsModule.java
b/extensions-core/kubernetes-overlord-extensions/src/main/java/org/apache/druid/k8s/overlord/KubernetesPeonMetricsModule.java
new file mode 100644
index 00000000000..f22954297ed
--- /dev/null
+++
b/extensions-core/kubernetes-overlord-extensions/src/main/java/org/apache/druid/k8s/overlord/KubernetesPeonMetricsModule.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.k8s.overlord;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.inject.Binder;
+import com.google.inject.multibindings.MapBinder;
+import org.apache.druid.discovery.NodeRole;
+import org.apache.druid.guice.annotations.LoadScope;
+import org.apache.druid.initialization.DruidModule;
+import org.apache.druid.java.util.common.logger.Logger;
+import org.apache.druid.k8s.overlord.common.DruidK8sConstants;
+import org.apache.druid.server.emitter.ExtraServiceDimensions;
+
+import javax.annotation.Nullable;
+
+/**
+ * Tags every metric a task pod emits with the pod template it runs under, so
metrics can be grouped
+ * by pod template alongside the task dimensions the peon already reports.
+ * <p>
+ * The template name arrives in {@link DruidK8sConstants#POD_TEMPLATE_ENV},
which
+ * {@link org.apache.druid.k8s.overlord.taskadapter.PodTemplateTaskAdapter}
populates from the pod's
+ * own {@link DruidK8sConstants#TASK_JOB_TEMPLATE} annotation. Peons launched
without a pod template,
+ * such as those from another task adapter, leave the variable unset and emit
no extra dimension.
+ */
+@LoadScope(roles = NodeRole.PEON_JSON_NAME)
+public class KubernetesPeonMetricsModule implements DruidModule
+{
+ private static final Logger log = new
Logger(KubernetesPeonMetricsModule.class);
+
+ @Override
+ public void configure(Binder binder)
+ {
+ final MapBinder<String, String> extraServiceDimensions =
MapBinder.newMapBinder(
+ binder,
+ String.class,
+ String.class,
+ ExtraServiceDimensions.class
+ );
+
+ final String podTemplate = getPodTemplateName();
+ if (podTemplate == null || podTemplate.isEmpty()) {
+ log.debug(
+ "Env variable [%s] is not set, so metrics will not carry a [%s]
dimension.",
+ DruidK8sConstants.POD_TEMPLATE_ENV,
+ DruidK8sConstants.POD_TEMPLATE_DIMENSION
+ );
+ return;
+ }
+
+ log.info("Emitting metrics with dimension [%s] set to [%s].",
DruidK8sConstants.POD_TEMPLATE_DIMENSION, podTemplate);
+
extraServiceDimensions.addBinding(DruidK8sConstants.POD_TEMPLATE_DIMENSION).toInstance(podTemplate);
+ }
+
+ /**
+ * Overridden by tests, which cannot set an environment variable on the
running process.
+ */
+ @VisibleForTesting
+ @Nullable
+ String getPodTemplateName()
+ {
+ return System.getenv(DruidK8sConstants.POD_TEMPLATE_ENV);
+ }
+}
diff --git
a/extensions-core/kubernetes-overlord-extensions/src/main/java/org/apache/druid/k8s/overlord/common/DruidK8sConstants.java
b/extensions-core/kubernetes-overlord-extensions/src/main/java/org/apache/druid/k8s/overlord/common/DruidK8sConstants.java
index f0eba8c63e6..bd43dfbb57d 100644
---
a/extensions-core/kubernetes-overlord-extensions/src/main/java/org/apache/druid/k8s/overlord/common/DruidK8sConstants.java
+++
b/extensions-core/kubernetes-overlord-extensions/src/main/java/org/apache/druid/k8s/overlord/common/DruidK8sConstants.java
@@ -35,6 +35,8 @@ public class DruidK8sConstants
public static final String DEFAULT_JAVA_HEAP_SIZE = "1G";
public static final String TLS_ENABLED = "tls.enabled";
public static final String TASK_JSON_ENV = "TASK_JSON";
+ public static final String POD_TEMPLATE_ENV = "DRUID_POD_TEMPLATE";
+ public static final String POD_TEMPLATE_DIMENSION = "podTemplate";
public static final String TASK_DIR_ENV = "TASK_DIR";
public static final String TASK_ID_ENV = "TASK_ID";
public static final String LOAD_BROADCAST_DATASOURCE_MODE_ENV =
"LOAD_BROADCAST_DATASOURCE_MODE";
diff --git
a/extensions-core/kubernetes-overlord-extensions/src/main/java/org/apache/druid/k8s/overlord/taskadapter/PodTemplateTaskAdapter.java
b/extensions-core/kubernetes-overlord-extensions/src/main/java/org/apache/druid/k8s/overlord/taskadapter/PodTemplateTaskAdapter.java
index b7976e1dab3..bcabfb08438 100644
---
a/extensions-core/kubernetes-overlord-extensions/src/main/java/org/apache/druid/k8s/overlord/taskadapter/PodTemplateTaskAdapter.java
+++
b/extensions-core/kubernetes-overlord-extensions/src/main/java/org/apache/druid/k8s/overlord/taskadapter/PodTemplateTaskAdapter.java
@@ -240,7 +240,15 @@ public class PodTemplateTaskAdapter implements TaskAdapter
new EnvVarBuilder()
.withName(DruidK8sConstants.LOAD_BROADCAST_SEGMENTS_ENV)
.withValue(Boolean.toString(task.getBroadcastDatasourceLoadingSpec().getMode().needsBroadcastSegments()))
- .build()
+ .build(),
+ // Lets the peon tag its metrics with the template it runs under.
Sourced from the annotation
+ // rather than passed by value so it always reflects the template
actually applied to the pod.
+ new EnvVarBuilder()
+ .withName(DruidK8sConstants.POD_TEMPLATE_ENV)
+ .withValueFrom(new EnvVarSourceBuilder().withFieldRef(new
ObjectFieldSelector(
+ null,
+ StringUtils.format("metadata.annotations['%s']",
DruidK8sConstants.TASK_JOB_TEMPLATE)
+ )).build()).build()
);
if (!shouldUseDeepStorageForTaskPayload(task)) {
envVars.add(new EnvVarBuilder()
diff --git
a/extensions-core/kubernetes-overlord-extensions/src/main/resources/META-INF/services/org.apache.druid.initialization.DruidModule
b/extensions-core/kubernetes-overlord-extensions/src/main/resources/META-INF/services/org.apache.druid.initialization.DruidModule
index 927a4349a2e..550cdff5390 100644
---
a/extensions-core/kubernetes-overlord-extensions/src/main/resources/META-INF/services/org.apache.druid.initialization.DruidModule
+++
b/extensions-core/kubernetes-overlord-extensions/src/main/resources/META-INF/services/org.apache.druid.initialization.DruidModule
@@ -14,3 +14,4 @@
# limitations under the License.
org.apache.druid.k8s.overlord.KubernetesOverlordModule
+org.apache.druid.k8s.overlord.KubernetesPeonMetricsModule
diff --git
a/extensions-core/kubernetes-overlord-extensions/src/test/java/org/apache/druid/k8s/overlord/KubernetesPeonMetricsModuleTest.java
b/extensions-core/kubernetes-overlord-extensions/src/test/java/org/apache/druid/k8s/overlord/KubernetesPeonMetricsModuleTest.java
new file mode 100644
index 00000000000..1c8b0891032
--- /dev/null
+++
b/extensions-core/kubernetes-overlord-extensions/src/test/java/org/apache/druid/k8s/overlord/KubernetesPeonMetricsModuleTest.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.k8s.overlord;
+
+import com.google.inject.Guice;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+import org.apache.druid.initialization.DruidModule;
+import org.apache.druid.k8s.overlord.common.DruidK8sConstants;
+import org.apache.druid.server.emitter.ExtraServiceDimensions;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import javax.annotation.Nullable;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+public class KubernetesPeonMetricsModuleTest
+{
+ private static final Key<Map<String, String>> EXTRA_DIMENSIONS_KEY =
+ Key.get(new TypeLiteral<>() {}, ExtraServiceDimensions.class);
+
+ @Test
+ public void test_moduleIsRegisteredAsDruidModule()
+ {
+ final List<String> registered = ServiceLoader.load(DruidModule.class)
+ .stream()
+ .map(provider ->
provider.type().getName())
+ .toList();
+
+ Assertions.assertTrue(
+ registered.contains(KubernetesPeonMetricsModule.class.getName()),
+ "KubernetesPeonMetricsModule must be listed in
META-INF/services/org.apache.druid.initialization.DruidModule"
+ );
+ }
+
+ @Test
+ public void test_podTemplateEnvSet_addsDimension()
+ {
+ Assertions.assertEquals(
+ Map.of(DruidK8sConstants.POD_TEMPLATE_DIMENSION, "podSpec1"),
+ extraDimensions("podSpec1")
+ );
+ }
+
+ @Test
+ public void test_podTemplateEnvUnset_addsNoDimension()
+ {
+ Assertions.assertEquals(Map.of(), extraDimensions(null));
+ }
+
+ @Test
+ public void test_podTemplateEnvEmpty_addsNoDimension()
+ {
+ Assertions.assertEquals(Map.of(), extraDimensions(""));
+ }
+
+ /**
+ * Resolves what the module contributes to {@link ExtraServiceDimensions}.
{@code EmitterModule}
+ * owns getting these onto emitted events, and {@code EmitterModuleTest}
covers that.
+ */
+ private static Map<String, String> extraDimensions(@Nullable String
podTemplateName)
+ {
+ return
Guice.createInjector(podTemplateModule(podTemplateName)).getInstance(EXTRA_DIMENSIONS_KEY);
+ }
+
+ private static KubernetesPeonMetricsModule podTemplateModule(@Nullable
String podTemplateName)
+ {
+ return new KubernetesPeonMetricsModule()
+ {
+ @Override
+ String getPodTemplateName()
+ {
+ return podTemplateName;
+ }
+ };
+ }
+}
diff --git
a/extensions-core/kubernetes-overlord-extensions/src/test/resources/expectedNoopJobLongIds.yaml
b/extensions-core/kubernetes-overlord-extensions/src/test/resources/expectedNoopJobLongIds.yaml
index eec68115b1d..e8ceb2100ff 100644
---
a/extensions-core/kubernetes-overlord-extensions/src/test/resources/expectedNoopJobLongIds.yaml
+++
b/extensions-core/kubernetes-overlord-extensions/src/test/resources/expectedNoopJobLongIds.yaml
@@ -52,6 +52,10 @@ spec:
value: "NONE"
- name: "LOAD_BROADCAST_SEGMENTS"
value: "false"
+ - name: "DRUID_POD_TEMPLATE"
+ valueFrom:
+ fieldRef:
+ fieldPath: "metadata.annotations['task.jobTemplate']"
- name: "TASK_JSON"
valueFrom:
fieldRef:
diff --git
a/extensions-core/kubernetes-overlord-extensions/src/test/resources/expectedNoopJobNoTaskJson.yaml
b/extensions-core/kubernetes-overlord-extensions/src/test/resources/expectedNoopJobNoTaskJson.yaml
index 499eff1df05..e910d1e0fe9 100644
---
a/extensions-core/kubernetes-overlord-extensions/src/test/resources/expectedNoopJobNoTaskJson.yaml
+++
b/extensions-core/kubernetes-overlord-extensions/src/test/resources/expectedNoopJobNoTaskJson.yaml
@@ -51,5 +51,9 @@ spec:
value: "NONE"
- name: "LOAD_BROADCAST_SEGMENTS"
value: "false"
+ - name: "DRUID_POD_TEMPLATE"
+ valueFrom:
+ fieldRef:
+ fieldPath: "metadata.annotations['task.jobTemplate']"
image: one
name: primary
diff --git
a/extensions-core/kubernetes-overlord-extensions/src/test/resources/expectedNoopJobTlsEnabledBase.yaml
b/extensions-core/kubernetes-overlord-extensions/src/test/resources/expectedNoopJobTlsEnabledBase.yaml
index ef7641ec987..68ba7fa49ef 100644
---
a/extensions-core/kubernetes-overlord-extensions/src/test/resources/expectedNoopJobTlsEnabledBase.yaml
+++
b/extensions-core/kubernetes-overlord-extensions/src/test/resources/expectedNoopJobTlsEnabledBase.yaml
@@ -52,6 +52,10 @@ spec:
value: "NONE"
- name: "LOAD_BROADCAST_SEGMENTS"
value: "false"
+ - name: "DRUID_POD_TEMPLATE"
+ valueFrom:
+ fieldRef:
+ fieldPath: "metadata.annotations['task.jobTemplate']"
- name: "TASK_JSON"
valueFrom:
fieldRef:
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]