From 6f50187453a246c28568325d276d98e9bbcee370 Mon Sep 17 00:00:00 2001 From: Alex English Date: Sat, 10 Oct 2026 11:26:39 -0700 Subject: [PATCH 1/2] Disable ECS AZ rebalancing so binpack placement applies --- terraform/modules/container/main.tf | 11 ++++++++--- terraform/modules/container/variables.tf | 2 +- terraform/projects/home-unite-us/environment-prod.tf | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/terraform/modules/container/main.tf b/terraform/modules/container/main.tf index 213b9b0..5d11bac 100644 --- a/terraform/modules/container/main.tf +++ b/terraform/modules/container/main.tf @@ -142,9 +142,9 @@ locals { # second copy of the task's memory reservation. Non-prod may drop to zero, which lets a # deploy proceed even when the host has no memory to spare. deployment_min = var.deployment_minimum_healthy_percent != null ? var.deployment_minimum_healthy_percent : (var.environment == "prod" ? 100 : 0) - # max stays 200 everywhere: ECS Availability Zone Rebalancing rejects maximumPercent <= 100, - # so a non-prod 0/100 is refused at UpdateService. 0/200 still lets a deploy fall back to - # stop-then-start when the host has no spare memory, rather than deadlocking on placement. + # max stays 200 everywhere: prod needs it to start the new task before stopping the old, + # and non-prod's 0/200 still lets a deploy fall back to stop-then-start when the host has + # no spare memory, rather than deadlocking on placement. deployment_max = var.deployment_maximum_percent != null ? var.deployment_maximum_percent : 200 # Memory is the scarce resource on the single host. Binpacking on it keeps tasks on as few @@ -593,6 +593,11 @@ resource "aws_ecs_service" "fargate" { deployment_minimum_healthy_percent = local.deployment_min deployment_maximum_percent = local.deployment_max + # Off because the cluster is one host in one Availability Zone, so there is nothing to + # rebalance across. While it was on, ECS accepted only placement strategies that spread + # by Availability Zone and rejected the binpack below at UpdateService (#255). + availability_zone_rebalancing = "DISABLED" + dynamic "ordered_placement_strategy" { for_each = local.placement_strategies content { diff --git a/terraform/modules/container/variables.tf b/terraform/modules/container/variables.tf index 417a89e..ab5fc99 100644 --- a/terraform/modules/container/variables.tf +++ b/terraform/modules/container/variables.tf @@ -115,7 +115,7 @@ variable "deployment_minimum_healthy_percent" { } variable "deployment_maximum_percent" { - description = "Ceiling on running tasks during a deploy. Defaults to 200. Values <= 100 are rejected by ECS when Availability Zone Rebalancing is enabled, which it is on this cluster." + description = "Ceiling on running tasks during a deploy. Defaults to 200, which lets a deploy start the new task before stopping the old one." type = number default = null } diff --git a/terraform/projects/home-unite-us/environment-prod.tf b/terraform/projects/home-unite-us/environment-prod.tf index 8ed5896..ca682fd 100644 --- a/terraform/projects/home-unite-us/environment-prod.tf +++ b/terraform/projects/home-unite-us/environment-prod.tf @@ -46,7 +46,7 @@ module "prod_service" { // run on -- the RDS instance is not involved despite what the abandoned Terragrunt // state suggests. The task definition declares no volume and no mount, so this file // is created empty on every task start and destroyed with the task. Application - // data therefore does not survive a deploy, a host drain or an AZ rebalance; the + // data therefore does not survive a deploy or a host drain; the // log group shows 743 task generations. Reproduced here deliberately so the cutover // changes nothing, and recorded so the next reader does not mistake it for working // persistence. The absolute form is used because a relative URL would depend on the From 673c9b85c75c73f992562818be39ee83248ea060 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 10 Oct 2026 18:26:56 +0000 Subject: [PATCH 2/2] terraform-docs: automated updates to Terraform modules README.md [skip ci] --- terraform/modules/container/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/modules/container/README.md b/terraform/modules/container/README.md index 885cc95..450e6d0 100644 --- a/terraform/modules/container/README.md +++ b/terraform/modules/container/README.md @@ -163,7 +163,7 @@ No modules. | [container\_memory](#input\_container\_memory) | memory allocation in MB. 1024 is one full gig of memory | `number` | `1024` | no | | [container\_memory\_reservation](#input\_container\_memory\_reservation) | Soft memory limit in MiB. ECS subtracts this from a container instance when placing the task; the container may burst above it up to `container_memory`, which stays the hard cap. EC2 launch type only. | `number` | `256` | no | | [container\_port](#input\_container\_port) | what port this container opens up to the outside | `number` | n/a | yes | -| [deployment\_maximum\_percent](#input\_deployment\_maximum\_percent) | Ceiling on running tasks during a deploy. Defaults to 200. Values <= 100 are rejected by ECS when Availability Zone Rebalancing is enabled, which it is on this cluster. | `number` | `null` | no | +| [deployment\_maximum\_percent](#input\_deployment\_maximum\_percent) | Ceiling on running tasks during a deploy. Defaults to 200, which lets a deploy start the new task before stopping the old one. | `number` | `null` | no | | [deployment\_minimum\_healthy\_percent](#input\_deployment\_minimum\_healthy\_percent) | Percent of the desired count that must stay running during a deploy. Leave null to derive from `environment`: 100 for prod, 0 everywhere else. | `number` | `null` | no | | [environment](#input\_environment) | n/a | `string` | n/a | yes | | [health\_check\_path](#input\_health\_check\_path) | path for load balancer health checks. This path should return HTTP 200 if the app is up. This path does not need to follow the prefix of `path`, it can be any path | `string` | `"/"` | no |