Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions terraform/modules/container/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ Containers on the bridge network cannot reach the host's instance credentials on
because the hosts set an instance metadata hop limit of 1 (hackforla/incubator#251).

Switching a service changes `target_type`, which is part of `local.tg_suffix`, so it gets
a new target group stood up beside the old one. Comments elsewhere in this module about
ENI slots apply to `awsvpc` services only.
a new target group stood up beside the old one.

## How the target group name is built

Expand Down
19 changes: 9 additions & 10 deletions terraform/modules/container/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@
* because the hosts set an instance metadata hop limit of 1 (hackforla/incubator#251).
*
* Switching a service changes `target_type`, which is part of `local.tg_suffix`, so it gets
* a new target group stood up beside the old one. Comments elsewhere in this module about
* ENI slots apply to `awsvpc` services only.
* a new target group stood up beside the old one.
*
* ## How the target group name is built
*
Expand Down Expand Up @@ -139,20 +138,20 @@ locals {

is_fargate = var.launch_type == "fargate"

# prod must keep a task serving throughout a deploy, so it always needs a spare ENI
# slot. Non-prod may drop to zero, which lets a deploy proceed even when the cluster
# has no free slot left.
# prod must keep a task serving throughout a deploy, so the host always needs room for a
# 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 no spare ENI slot is free, rather than deadlocking on placement.
# 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

# ENI slots (10 per m5.large) are the scarce resource and ECS cannot binpack on ENIs,
# so spread -- binpacking memory would fill one host's slots while the other sat idle.
# Memory is the scarce resource on the single host. Binpacking on it keeps tasks on as few
# hosts as possible, so when the group briefly runs two -- while an instance is replaced --
# tasks consolidate onto one instead of spreading across both.
placement_strategies = local.is_fargate ? [] : [
{ type = "spread", field = "attribute:ecs.availability-zone" },
{ type = "spread", field = "instanceId" },
{ type = "binpack", field = "memory" },
]

is_bridge = var.network_mode == "bridge"
Expand Down
9 changes: 7 additions & 2 deletions terraform/platform/compute.tf
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ resource "aws_ecs_cluster_capacity_providers" "this" {
resource "aws_launch_template" "ecs" {
name = "ecs-incubator-prod"
image_id = "ami-036428f37186903ce"
instance_type = "m5.large"
instance_type = "t3a.medium"
key_name = "ecs-incubator-prod"

# The VPC default group, not a purpose-built one. The group that looked like the container
Expand All @@ -55,6 +55,9 @@ resource "aws_launch_template" "ecs" {
# Enables awsvpcTrunking, installs the SSM agent, sets ECS_CLUSTER. Kept as literal base64
# because the script has a trailing space a heredoc would drop, changing what instances boot
# with. Unchanged since version 8; version 9 (#251) changed only metadata_options.
#
# The awsvpcTrunking line is now a no-op: t3a.medium does not support trunking, and every
# task uses bridge networking (#257), so nothing needs an ENI.
user_data = "IyEvYmluL2Jhc2gKYXdzIGVjcyBwdXQtYWNjb3VudC1zZXR0aW5nIC0tbmFtZSBhd3N2cGNUcnVua2luZyAtLXZhbHVlIGVuYWJsZWQgLS1yZWdpb24gdXMtd2VzdC0yIApjZCAvdG1wCnN1ZG8gZG5mIGluc3RhbGwgLXkgaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL2VjMi1kb3dubG9hZHMtd2luZG93cy9TU01BZ2VudC9sYXRlc3QvbGludXhfYW1kNjQvYW1hem9uLXNzbS1hZ2VudC5ycG0Kc3VkbyBzeXN0ZW1jdGwgZW5hYmxlIGFtYXpvbi1zc20tYWdlbnQKc3VkbyBzeXN0ZW1jdGwgc3RhcnQgYW1hem9uLXNzbS1hZ2VudAplY2hvICJFQ1NfQ0xVU1RFUj1pbmN1YmF0b3ItcHJvZCIgPj4gL2V0Yy9lY3MvZWNzLmNvbmZpZwo="

# Hop limit 1 keeps containers on Docker's bridge network from reaching the host's
Expand All @@ -71,10 +74,12 @@ resource "aws_launch_template" "ecs" {
}

resource "aws_autoscaling_group" "ecs" {
# One host runs everything. max_size keeps a second slot so an instance can be replaced --
# a new AMI, say -- by launching its successor before draining it, without an outage.
name = "ecs-incubator-prod"
min_size = 1
max_size = 2
desired_capacity = 2
desired_capacity = 1
default_cooldown = 300
health_check_type = "EC2"
health_check_grace_period = 300
Expand Down
3 changes: 2 additions & 1 deletion terraform/platform/iam.tf
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ resource "aws_iam_instance_profile" "ecs_instance" {
}

# Customer-managed and itself unmanaged, so adopted alongside the role. Lets the instance
# user_data turn on awsvpcTrunking, which raises the ENI limit per instance.
# user_data turn on awsvpcTrunking, which raises the ENI limit per instance. Nothing has needed
# that since every task moved to bridge networking (#257), but the user_data still makes the call.
resource "aws_iam_policy" "ecs_put_account_settings" {
name = "ecs-put-account-settings"

Expand Down