deploy: gen-suffixed container names + cameleer.generation label

Append an 8-char generation id (first 8 chars of deployment UUID) to:
- container name: {tenant}-{env}-{app}-{replica}-{gen}
- CAMELEER_AGENT_INSTANCEID (so old+new agents are distinct in the registry)
- Traefik cameleer.instance-id label

And emit a new standalone cameleer.generation label so dashboards
(Prometheus/Grafana) can pin deploy boundaries without regex on
instance-id.

Strategy branching comes next — this commit is foundation only; the
interim destroy-then-start flow still runs regardless of strategy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-23 09:45:44 +02:00
parent 5304c8ee01
commit 652346dcd4
2 changed files with 18 additions and 5 deletions

View File

@@ -89,6 +89,13 @@ public class DeploymentExecutor {
this.applicationConfigRepository = applicationConfigRepository;
}
/** Deployment-scoped id suffix — distinguishes container names and
* CAMELEER_AGENT_INSTANCEID across redeploys so old + new replicas can
* coexist during a blue/green swap. First 8 chars of the deployment UUID. */
static String generationOf(Deployment deployment) {
return deployment.id().toString().substring(0, 8);
}
@Async("deploymentTaskExecutor")
public void executeAsync(Deployment deployment) {
long deployStart = System.currentTimeMillis();
@@ -96,6 +103,7 @@ public class DeploymentExecutor {
App app = appService.getById(deployment.appId());
Environment env = envService.getById(deployment.environmentId());
String jarPath = appService.resolveJarPath(deployment.appVersionId());
String generation = generationOf(deployment);
var globalDefaults = new ConfigMerger.GlobalRuntimeDefaults(
parseMemoryLimitMb(globalMemoryLimit),
@@ -192,11 +200,11 @@ public class DeploymentExecutor {
List<String> newContainerIds = new ArrayList<>();
for (int i = 0; i < config.replicas(); i++) {
String instanceId = env.slug() + "-" + app.slug() + "-" + i;
String instanceId = env.slug() + "-" + app.slug() + "-" + i + "-" + generation;
String containerName = tenantId + "-" + instanceId;
// Per-replica labels (include replica index and instance-id)
Map<String, String> labels = TraefikLabelBuilder.build(app.slug(), env.slug(), tenantId, config, i);
// Per-replica labels (include replica index, generation, and instance-id)
Map<String, String> labels = TraefikLabelBuilder.build(app.slug(), env.slug(), tenantId, config, i, generation);
labels.putAll(prometheusLabels);
// Per-replica env vars (set agent instance ID to match container log identity)

View File

@@ -10,9 +10,13 @@ public final class TraefikLabelBuilder {
private TraefikLabelBuilder() {}
public static Map<String, String> build(String appSlug, String envSlug, String tenantId,
ResolvedContainerConfig config, int replicaIndex) {
ResolvedContainerConfig config, int replicaIndex,
String generation) {
// Traefik router/service keys stay generation-agnostic so load balancing
// spans old + new replicas during a blue/green overlap. instance-id and
// the new generation label carry the per-deploy identity.
String svc = envSlug + "-" + appSlug;
String instanceId = envSlug + "-" + appSlug + "-" + replicaIndex;
String instanceId = envSlug + "-" + appSlug + "-" + replicaIndex + "-" + generation;
Map<String, String> labels = new LinkedHashMap<>();
labels.put("traefik.enable", "true");
@@ -21,6 +25,7 @@ public final class TraefikLabelBuilder {
labels.put("cameleer.app", appSlug);
labels.put("cameleer.environment", envSlug);
labels.put("cameleer.replica", String.valueOf(replicaIndex));
labels.put("cameleer.generation", generation);
labels.put("cameleer.instance-id", instanceId);
labels.put("traefik.http.services." + svc + ".loadbalancer.server.port",