diff --git a/cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/DeploymentExecutor.java b/cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/DeploymentExecutor.java index a6adfbc9..a39fbad7 100644 --- a/cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/DeploymentExecutor.java +++ b/cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/DeploymentExecutor.java @@ -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 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 labels = TraefikLabelBuilder.build(app.slug(), env.slug(), tenantId, config, i); + // Per-replica labels (include replica index, generation, and instance-id) + Map 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) diff --git a/cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/TraefikLabelBuilder.java b/cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/TraefikLabelBuilder.java index df223813..6b1ae2c0 100644 --- a/cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/TraefikLabelBuilder.java +++ b/cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/TraefikLabelBuilder.java @@ -10,9 +10,13 @@ public final class TraefikLabelBuilder { private TraefikLabelBuilder() {} public static Map 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 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",