Multiple availability zones with terraform on AWS

It is possible to evenly distribute instances across multiple zones using modulo.

variable "zone" {
  description = "for single zone deployment"
  default = "europe-west4-b"
}

variable "zones" {
  description = "for multi zone deployment"
  default = ["europe-west4-b", "europe-west4-c"]
}

resource "google_compute_instance" "default" {
  count = "${var.role.count}"
  ...
  zone = "${var.zone != "" ? var.zone: var.zones[ count.index % length(var.zones) ]}"
  ...
}

This distribution mechanism allow to distribute nodes evenly across zones.
E.g. zones = [A,B] - instance-1 will be in A, instance-2 will in B, instance-3 will be in A again.
By adding zone C to zones will shift instance-3 to C.


At the end I figured out how to do it, using data "aws_subnet_ids" {...} and more importantly understanding that terraform creates lists out of resources when using count:

variable "target_vpc" {}
variable "app_server_count" {}
variable "app_server_ip_start" {}

# Discover VPC
data "aws_vpc" "target_vpc" {
  filter = {
    name = "tag:Name"
    values = [var.target_vpc]
  }
}

# Discover subnet IDs. This requires the subnetworks to be tagged with Tier = "AppTier"
data "aws_subnet_ids" "app_tier_ids" {
  vpc_id = data.aws_vpc.target_vpc.id
  tags {
    Tier = "AppTier"
  }
}

# Discover subnets and create a list, one for each found ID
data "aws_subnet" "app_tier" {
  count = length(data.aws_subnet_ids.app_tier_ids.ids)
  id = data.aws_subnet_ids.app_tier_ids.ids[count.index]
}

resource "aws_instance" "app_server" {
  ...

  # Create N instances
  count = var.app_server_count

  # Use the "count.index" subnet
  subnet_id = data.aws_subnet_ids.app_tier_ids.ids[count.index]

  # Create an IP address using the CIDR of the subnet
  private_ip = cidrhost(element(data.aws_subnet.app_tier.*.cidr_block, count.index), var.app_server_ip_start + count.index)

  ...
}

The count index in the resource will throw an error if you have more instances than subnets. Use the element interpolation from Terraform

element(list, index) - Returns a single element from a list at the given index. If the index is greater than the number of elements, this function will wrap using a standard mod algorithm. This function only works on flat lists.

subnet_id = "${element(data.aws_subnet_ids.app_tier_ids.ids, count.index)}"