Terraform Multi-Region Deployment using Modules
April 25, 2019 • 4 min read
I’ve been working on a new authentication platform and one of the requirements was for it to be multi-region to both reduce latency to customers and also be ready for data laws in other countries. I’ve known the theory behind multi-region, but have never had the opportunity to actually deploy an application and infrastructure across multiple regions.
This article is focused on just the infrastructure side and how to use Terraform Modules to easily deploy into multiple regions.
Terraform Modules are:
… self-contained packages of Terraform configurations that are managed as a group. Modules are used to create reusable components in Terraform as well as for basic code organization.
What we’re going to do is use a single module to deploy resources into multiple regions. Since a module is reusable, we can ensure our infrastructure is the same in each region.
Example Scenario
For demonstration purposes, we are going to deploy one IAM policy and a single AWS SQS queue in the us-east-1 and us-west-1 regions. I chose an IAM policy because IAM is global and will sit outside the module. And the AWS SQS queue is one of the easiest services to setup via Terraform — just 3 lines. Here’s what our directory structure will look like:
- modules
- multi-region
main.tf
_provider.tf
iam.tf
main.tf
In the _provider.tf
, we are going to create two providers — one for us-east-1 and the other for eu-west-1. Note the eu-west-1
alias which we will reference later.
# _provider.tf
provider "aws" {
region = "us-east-1"
}
provider "aws" {
alias = "eu-west-1"
region = "eu-west-1"
}
The iam.tf
below is a quick sample role creation for a Lambda function to write to CloudWatch logs — the basic role for a Lambda function.
# iam.tf
resource "aws_iam_role" "lambda" {
name = "my-lambda-role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement":
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow"
}
}
EOF
}
resource "aws_iam_role_policy_attachment" "lambda" {
role = "${aws_iam_role.lambda.name}"
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
Using the module
The main.tf
is where the fun starts. This is where are going to use the multi-region module.
# main.tf
module "us-east-1" {
source = "./modules/multi-region"
name = "my-queue-name"
providers = {
aws = "aws"
}
}
module "eu-west-1" {
source = "./modules/multi-region"
name = "my-queue-name"
providers = {
aws = "aws.eu-west-1"
}
}
Here we are injecting the provider into the module based on the alias name we defined in the _provider.tf file. So when the Terraform applies the resources in the module, they will be created in the correct region.
This allows the module to be fully unaware of the region which makes it reusable across any region.
# modules/multi-region/main.tf
variable "name" {}
resource "aws_sqs_queue" "queue" {
name = "${var.name}"
}
output "sqs_queue_arn" {
value = "${aws_sqs_queue.queue.arn}"
}
Here’s a quick overview of what we did above.
- We defined a required variable for our module called “name” which will be used as the SQS queue name.
- We defined the SQS queue resource using the variable “name”
- We then output the created SQS ARN so we can use that in the IAM policy which we will update below to include in the iam.tf file.
Now let’s update the iam.tf file to use the outputs of the module.
# iam.tf - updated with SQS permissions
resource "aws_iam_role" "lambda" {
name = "my-lambda-role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement":
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow"
}
}
EOF
}
resource "aws_iam_role_policy_attachment" "lambda" {
role = "${aws_iam_role.lambda.name}"
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "aws_iam_role_policy" "sqs" {
name = "sqs-policy"
role = "${aws_iam_role.lambda.id}"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sqs:SendMessageBatch",
"sqs:SendMessage",
"sqs:ReceiveMessage",
"sqs:DeleteMessage",
"sqs:GetQueueAttributes",
"sqs:ChangeMessageVisibility"
],
"Resource": [
"${module.us-east-1.sqs_queue_arn}",
"${module.eu-west-1.sqs_queue_arn}"
]
}
]
}
EOF
}
Conclusion
This was just a quick intro in a basic but practical use case for using a multi-region module. I’ve been working on a new project and have structured all the Terraform this way and it seems to work really well. Even if the application is not going to launch multi-region, this gives you the flexibility and planning to do so in the future.