feat: add Prometheus docker_sd_configs labels to agent containers
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m26s
CI / docker (push) Successful in 1m12s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 43s

Labels prometheus.scrape, prometheus.path, and prometheus.port are now
set on every deployed container based on the resolved runtime type,
enabling automatic Prometheus service discovery via docker_sd_configs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-12 18:00:32 +02:00
parent c1fbe1a63a
commit caaa1ab0cc
4 changed files with 89 additions and 1 deletions

View File

@@ -107,6 +107,7 @@ java -jar cameleer3-server-app/target/cameleer3-server-app-1.0-SNAPSHOT.jar
- `DockerNetworkManager` — ensures bridge networks (cameleer-traefik, cameleer-env-{slug}), connects containers - `DockerNetworkManager` — ensures bridge networks (cameleer-traefik, cameleer-env-{slug}), connects containers
- `DockerEventMonitor` — persistent Docker event stream listener (die, oom, start, stop), updates deployment status - `DockerEventMonitor` — persistent Docker event stream listener (die, oom, start, stop), updates deployment status
- `TraefikLabelBuilder` — generates Traefik Docker labels for path-based or subdomain routing - `TraefikLabelBuilder` — generates Traefik Docker labels for path-based or subdomain routing
- `PrometheusLabelBuilder` — generates Prometheus Docker labels (`prometheus.scrape/path/port`) per runtime type for `docker_sd_configs` auto-discovery
- `DisabledRuntimeOrchestrator` — no-op when runtime not enabled - `DisabledRuntimeOrchestrator` — no-op when runtime not enabled
**storage/** — PostgreSQL repositories (JdbcTemplate) **storage/** — PostgreSQL repositories (JdbcTemplate)
@@ -235,6 +236,7 @@ When deployed via the cameleer-saas platform, this server orchestrates customer
- **ConfigMerger** (`core/runtime/ConfigMerger.java`) — pure function: resolve(globalDefaults, envConfig, appConfig) -> ResolvedContainerConfig. Three-layer merge: global (application.yml) -> environment (defaultContainerConfig JSONB) -> app (containerConfig JSONB). Includes `runtimeType` (default `"auto"`) and `customArgs` (default `""`). - **ConfigMerger** (`core/runtime/ConfigMerger.java`) — pure function: resolve(globalDefaults, envConfig, appConfig) -> ResolvedContainerConfig. Three-layer merge: global (application.yml) -> environment (defaultContainerConfig JSONB) -> app (containerConfig JSONB). Includes `runtimeType` (default `"auto"`) and `customArgs` (default `""`).
- **TraefikLabelBuilder** (`app/runtime/TraefikLabelBuilder.java`) — generates Traefik Docker labels for path-based (`/{envSlug}/{appSlug}/`) or subdomain-based (`{appSlug}-{envSlug}.{domain}`) routing. Supports strip-prefix and SSL offloading toggles. - **TraefikLabelBuilder** (`app/runtime/TraefikLabelBuilder.java`) — generates Traefik Docker labels for path-based (`/{envSlug}/{appSlug}/`) or subdomain-based (`{appSlug}-{envSlug}.{domain}`) routing. Supports strip-prefix and SSL offloading toggles.
- **PrometheusLabelBuilder** (`app/runtime/PrometheusLabelBuilder.java`) — generates Prometheus `docker_sd_configs` labels per resolved runtime type: Spring Boot `/actuator/prometheus:8081`, Quarkus/native `/q/metrics:9000`, plain Java `/metrics:9464`. Labels merged into container metadata alongside Traefik labels at deploy time.
- **DockerNetworkManager** (`app/runtime/DockerNetworkManager.java`) — manages two Docker network tiers: - **DockerNetworkManager** (`app/runtime/DockerNetworkManager.java`) — manages two Docker network tiers:
- `cameleer-traefik` — shared network; Traefik, server, and all app containers attach here. Server joined via docker-compose with `cameleer3-server` DNS alias. - `cameleer-traefik` — shared network; Traefik, server, and all app containers attach here. Server joined via docker-compose with `cameleer3-server` DNS alias.
- `cameleer-env-{slug}` — per-environment isolated network; containers in the same environment discover each other via Docker DNS. In SaaS mode, env networks are tenant-scoped: `cameleer-env-{tenantId}-{envSlug}` (overloaded `envNetworkName(tenantId, envSlug)` method) to prevent cross-tenant collisions when multiple tenants have identically-named environments. - `cameleer-env-{slug}` — per-environment isolated network; containers in the same environment discover each other via Docker DNS. In SaaS mode, env networks are tenant-scoped: `cameleer-env-{tenantId}-{envSlug}` (overloaded `envNetworkName(tenantId, envSlug)` method) to prevent cross-tenant collisions when multiple tenants have identically-named environments.
@@ -298,7 +300,7 @@ In SaaS mode, each tenant's server and its deployed apps are isolated at the Doc
<!-- gitnexus:start --> <!-- gitnexus:start -->
# GitNexus — Code Intelligence # GitNexus — Code Intelligence
This project is indexed by GitNexus as **cameleer3-server** (5967 symbols, 15136 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. This project is indexed by GitNexus as **cameleer3-server** (5968 symbols, 15141 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.

View File

@@ -162,6 +162,7 @@ public class DeploymentExecutor {
Map<String, String> baseEnvVars = buildEnvVars(app, env, config); Map<String, String> baseEnvVars = buildEnvVars(app, env, config);
Map<String, String> labels = TraefikLabelBuilder.build(app.slug(), env.slug(), tenantId, config); Map<String, String> labels = TraefikLabelBuilder.build(app.slug(), env.slug(), tenantId, config);
labels.putAll(PrometheusLabelBuilder.build(resolvedRuntimeType));
List<Map<String, Object>> replicaStates = new ArrayList<>(); List<Map<String, Object>> replicaStates = new ArrayList<>();
List<String> newContainerIds = new ArrayList<>(); List<String> newContainerIds = new ArrayList<>();

View File

@@ -0,0 +1,35 @@
package com.cameleer3.server.app.runtime;
import java.util.LinkedHashMap;
import java.util.Map;
public final class PrometheusLabelBuilder {
private PrometheusLabelBuilder() {}
public static Map<String, String> build(String resolvedRuntimeType) {
Map<String, String> labels = new LinkedHashMap<>();
labels.put("prometheus.scrape", "true");
switch (resolvedRuntimeType) {
case "spring-boot" -> {
labels.put("prometheus.path", "/actuator/prometheus");
labels.put("prometheus.port", "8081");
}
case "quarkus", "native" -> {
labels.put("prometheus.path", "/q/metrics");
labels.put("prometheus.port", "9000");
}
case "plain-java" -> {
labels.put("prometheus.path", "/metrics");
labels.put("prometheus.port", "9464");
}
default -> {
labels.put("prometheus.path", "/actuator/prometheus");
labels.put("prometheus.port", "8081");
}
}
return labels;
}
}

View File

@@ -0,0 +1,50 @@
package com.cameleer3.server.app.runtime;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
class PrometheusLabelBuilderTest {
@Test
void springBootLabels() {
Map<String, String> labels = PrometheusLabelBuilder.build("spring-boot");
assertEquals("true", labels.get("prometheus.scrape"));
assertEquals("/actuator/prometheus", labels.get("prometheus.path"));
assertEquals("8081", labels.get("prometheus.port"));
}
@Test
void quarkusLabels() {
Map<String, String> labels = PrometheusLabelBuilder.build("quarkus");
assertEquals("true", labels.get("prometheus.scrape"));
assertEquals("/q/metrics", labels.get("prometheus.path"));
assertEquals("9000", labels.get("prometheus.port"));
}
@Test
void nativeLabels() {
Map<String, String> labels = PrometheusLabelBuilder.build("native");
assertEquals("true", labels.get("prometheus.scrape"));
assertEquals("/q/metrics", labels.get("prometheus.path"));
assertEquals("9000", labels.get("prometheus.port"));
}
@Test
void plainJavaLabels() {
Map<String, String> labels = PrometheusLabelBuilder.build("plain-java");
assertEquals("true", labels.get("prometheus.scrape"));
assertEquals("/metrics", labels.get("prometheus.path"));
assertEquals("9464", labels.get("prometheus.port"));
}
@Test
void unknownDefaultsToSpringBoot() {
Map<String, String> labels = PrometheusLabelBuilder.build("unknown");
assertEquals("true", labels.get("prometheus.scrape"));
assertEquals("/actuator/prometheus", labels.get("prometheus.path"));
assertEquals("8081", labels.get("prometheus.port"));
}
}