Deploying to multiple accounts with Terraform?

I've been looking for a Terraform way to be able to deploy to multiple AWS accounts simultaneously in Terraform and coming up dry. AWS has the concept of doing this with Stacks but I'm not sure if there is a way to do this in TF? If so what would be some solutions?

You can read more about the AWS solution here, https://aws.amazon.com/blogs/mt/supercharge-multi-account-management-with-aws-cloudformation/


Solution 1:

There are two problems that need to be overcome to use the same terraform configurations in multiple AWS accounts. The first, is that you will need to use a different state file for each account. The second, will be making sure the AWS provider uses the correct profile/credentials for the desired account.

Using a different state file for each account

At a high level, terraform works simply by creating a dependency resource graph from the terraform configuration files (*.tf) given; and compares them with a state file. The state file can be either local (terraform.tfstate) or in a remote backend; a common AWS remote backend is s3.

Even if you specify the AWS provider to use separate accounts through variables (more on this below), if both accounts use the same backend, terraform will continuously crash. This will occur, because as it switches from one account to another, the resource AWS IDs won't match, and/or resources will be stated in the state file that don't exist in the account terraform is currently looking at.

A simple way around this is to use workspaces. Workspaces allow you to use different state files without specifying different backend keys. Using workspaces is very straightforward. If you have a simple terraform block such as:

terraform {
    backend "s3" {
       bucket = "aws_bucket"
       key    = "terraform.tfstate"
       region = "us-east-1"
    }
}

You can create new workspaces using terraform workspace new <workspace-name>. So, you can create a workspace for both of your accounts using a sequence such as:

terraform workspace new account-1
terraform workspace new account-2

And you can switch you're current workspace using terraform workspace select:

terraform workspace select account-1

Configuring the AWS provider to be workspace aware

Besides using separate state files. You will also need to specify different AWS credentials (i.e. access keys) for each account needed. AWS credentials are stated using providers. In this case, we are obviously using the AWS provider.. You essentially want to use interpolation within the AWS provider resource block, so when you switch workspaces, terraform will use the correct credentials. Assuming you have your aws credentials file setup with a separate profile per account:

[account-1]
aws_access_key_id=AKIAIOSFODNN7EXAMPLE
aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

[account-2]
aws_access_key_id=AKIAI44QH8DHBEXAMPLE
aws_secret_access_key=je7MtGbClwBF/2Zp9Utk/h3yCo8nvbEXAMPLEKEY

Then you can simply write your aws provider block as:

provider "aws" {
    region  = "us-east-1"
    profile = "${terraform.workspace}"
}

So as long as you name your workspaces as your aws profile names, then when you switch workspaces, terraform will use a different aws credential profile when refreshing state and building a dependency graph.

Alternative methods

This is by far not the only way to accomplish this. You could build out modules; and then have different directories that call the module with different AWS providers and state files specified. There are pros and cons to each method.

The workspace method is how you would do it with a single directory, and you would be using the exact same terraform configuration files as written.

Modules would require separate configuration files that at least call the modules and specify the providers and backends. Modules would make a lot of sense if you where going to do multi accounts, multi region, and multi environments. For instance, if you were going to run in two regions for each account, and two different environments (staging and production). Modules make sense here because it provides flexibility using arguments such as count (perhaps staging will run less number of instances, etc.).

Solution 2:

Basically the trick is to get the state within a one shared bucket and to specify different credentials for the provided when switching accounts. Workspaces work perfectly for me

provider "aws" {
  version = "~> 2.18"
  profile = "second_account"
}

terraform {
  backend "s3" {
    encrypt = true
    bucket  = "shared_bucket"
    region  = "us-east-1"
    key     = "state"
    profile = "first_shared_account"
  }

  required_version = "~> 0.12.7"
}

Notice different profiles specified for both provider and backend config. You can have a bunch of different profiles as long as each one isolated via own workspace

aws configure --profile third_account
terraform workspace new third_account
terraform workspace select third_account