Multiple availability zones with terraform on AWS
Asked Answered
R

4

17

The VPC I'm working on has 3 logical tiers: Web, App and DB. For each tier there is one subnet in each availability zone. Total of 6 subnets in the region I'm using.

I'm trying to create EC2 instances using a module and the count parameter but I don't know how to tell terraform to use the two subnets of the App tier. An additional constraint I have is to use static IP addresses (or a way to have a deterministic private name)

I'm playing around with the resource

resource "aws_instance" "app_server" {
  ...
  count = "${var.app_servers_count}"

  # Not all at the same time, though!
  availability_zone = ...
  subnet_id = ...
  private_ip = ...
}

Things I've tried/thought so far:

  • Use data "aws_subnet" "all_app_subnets" {...}, filter by name, get all the subnets that match and use them as a list. But aws_subnet cannot return a list;
  • Use data "aws_availability_zones" {...} to find all the zones. But I still have the problem of assigning the correct subnet;
  • Use data "aws_subnet_ids" {...} which looks like the best option. But apparently it doesn't have a filter option to match the networks namel
  • Pass the subnets IDs as list of strings to the module. But I don't want to hard code the IDs, it's not automation;
  • Hard code the subnets as data "aws_subnet" "app_subnet_1" {...}, data "aws_subnet" "app_subnet_2" {...} but then I have to use separate sets of variables for each subnet which I don't like;
  • Get information for each subnet like in the point above but then create a map to access it as a list. But it's not possibile to use interpolation in variables definition;
  • Not using modules and hard-code each instance for each environment. Mmmm... really?

I really ran out of ideas. It seems that nobody has to deploy instances in specific subnetworks and keep a good degree of abstration. I see only examples where subnetworks are not specified or where people just use default values for everything. Is this really something so unusual?

Thanks in advance to everyone.

Roehm answered 4/9, 2017 at 16:50 Comment(2)
Has anyone figured out how to sort subnets by available IP count? This sort of thing is simple in tools like Ansible with ec2_vpc_subnet_facts available_ip_address_count. I've tried using aws_subnet_ids and aws_subnet data providers similar to your answer below, but it seems there are no attributes on the returned subnets with available IP address count at all!Blynn
You would need to get the available IP count first and then sort. But just given how limited the interpolation syntax is (have a look at the documentation for sort) I guess there's no way of doing that with a simple and clean solution.Roehm
R
4

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)

  ...
}
Roehm answered 5/9, 2017 at 9:42 Comment(1)
Isn't count variable being overwritten?Brokenhearted
E
14

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.

Empiric answered 14/8, 2019 at 16:26 Comment(0)
A
8

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)}"
Adaurd answered 11/10, 2018 at 14:24 Comment(0)
R
4

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)

  ...
}
Roehm answered 5/9, 2017 at 9:42 Comment(1)
Isn't count variable being overwritten?Brokenhearted
R
3

I get Terraform to loop through the subnets in an availability zone by using the aws_subnet_ids data source and filtering by a tag representing the tier (in my case public/private).

This then looks something like this:

variable "vpc" {}
variable "ami" {}
variable "subnet_tier" {}
variable "instance_count" {}

data "aws_vpc" "selected" {
  tags {
    Name = "${var.vpc}"
  }
}

data "aws_subnet_ids" "selected" {
  vpc_id = "${data.aws_vpc.selected.id}"

  tags {
    Tier = "${var.subnet_tier}"
  }
}

resource "aws_instance" "instance" {
  count         = "${var.instance_count}"
  ami           = "${var.ami}"
  subnet_id     = "${data.aws_subnet_ids.selected.ids[count.index]}"
  instance_type = "${var.instance_type}"
}

This returns a consistent sort order but not necessarily starting with AZ A in your account. I suspect that the AWS API returns the subnets in AZ order but ordered by their own internal id as the AZs are shuffled by account (presumably to stop AZ A being flooded as humans are predictably bad at putting everything in the first place they can use).

You would have to tie yourself in some horrible knots if for some odd reason you particularly care about instances being placed in AZ A first but this minimal example should at least get instances being round-robined through the AZs you have subnets in by relying on Terraform's looping back through arrays when exceeding the array length.

Rehm answered 5/9, 2017 at 9:37 Comment(3)
Thanks for the answer. No, luckily I don't care about the order. Anyway I found a similar solution addressing also the static IP issue.Roehm
Also wanted to add that because we are bad at choosing the first option that AWS actually randomly associates an AZ letter in the background. So this means that my account's us-east1a is not going to be the same as your us-east-1a zone. If you have multiple accounts you can contact AWS to sync up your AZs across your accounts.Toluidine
@SteohenLester yep, I mentioned that in the answer.Rehm

© 2022 - 2024 — McMap. All rights reserved.