Infrastructure as Code with Terraform and AWS
When you provision Cloud based resources there are 4 main options:
This article will focus on getting started with Terraform.
$ brew install terraform
When working with others on the same remote resources, you need to be aligned on the Terraform version you use.
$ brew install terraform@0.13.1
$ sudo mv ./terraform /usr/local/bin
# curl -sSL https://releases.hashicorp.com/terraform/0.13.1/terraform_0.13.1_darwin_amd64.zip > /tmp/terraform.zip \
&& unzip /tmp/terraform.zip -d /usr/local/bin
$ mkdir -p /opt/terraform/plugins
$ curl -sSL https://releases.hashicorp.com/terraform-provider-aws/3.53.0/terraform-provider-aws_3.53.0_linux_amd64.zip > /tmp/google-plugin.zip && unzip /tmp/google-plugin.zip -d /opt/terraform/plugins
terraform -plugin-dir=/opt/terraform/plugins
Basically we would like to have 3 environments:
.
├── Makefile
├── README.md
├── main.tf
├── sns.tf
├── varfiles
│ ├── dev.tfvars
│ ├── staging.tfvars
│ └── prod.tfvars
└── variables.tf
It will hold the variable definitions including default values.
variable "company" {
default = "learnpulse"
description = "Company name"
}
variable "region" {
default = "eu-west-1"
}
variable "resource_owner" {
default = "me@my-company.com"
}
variable "env" {
description = "Environment name (dev/stg/prod)"
}
It will contains only the override of variables per environment.
dev.tfvars
env="dev"
prod.tfvars
env="prod"
terraform {
# Define when the state will be stored:
backend "s3" {
bucket = "terraform.learnpulse"
region = "eu-west-1"
}
# You can put some requirement on the Terraform version
required_version = ">= v0.12.18"
}
# Configure the default AWS Provider
provider "aws" {
region = var.region
# Common tags to be assigned to all resources
default_tags {
tags = {
"${var.company}:environment-type" = var.env
"${var.company}:technical-owner" = var.resource_owner
"${var.company}:managed-by" = "terraform"
}
}
}
# Alterative provider for ACM (ACM can only be created on us-east-1 if used by Cloudfront)
provider "aws" {
alias = "virginia"
region = "us-east-1"
# Common tags to be assigned to all resources
default_tags {
tags = {
"${var.company}:environment-type" = var.env
"${var.company}:technical-owner" = var.resource_owner
"${var.company}:managed-by" = "terraform"
}
}
}
data "aws_caller_identity" "current" {}
Notice that we assigned default tags on all resources that support tagging following AWS best practices.
This is an example of a SNS resource creation:
# SNS
resource "aws_sns_topic" "email_ses_notifications" {
name = "${var.env}-ses-notifications"
}
Terraform is stateful. It will store the list of the created resources it manages.
If you have multiple environments, you should avoid sharing the state between the environment otherwise your state will be mixed up.
There are 2 options on Terraform, you can use either the workspace option or specify where (called the backend) is the state folder for each environment.
$ terraform workspace new dev
$ terraform workspace select dev
$ terraform plan -var-file=varfiles/dev.tfvars
This option is recommended as it is compatible with a CI implementation.
Basically, our main.tf
file will contains the S3 based backend, meaning the location of the state files: they will be stored remotely on a S3 bucket (that needs to be created first manually).
terraform {
backend "s3" {
bucket = "terraform.learnpulse"
region = "eu-west-1"
}
}
terraform init
. This will be done in our makefile.ENV ?= dev
KEY := terraform/state/$(ENV)
tf-init:
rm -rf .terraform
terraform init -backend-config="key=$(KEY)"
tf-plan:
terraform plan -var-file=varfiles/$(ENV).tfvars -out .terraform/plan.out
tf-output:
terraform output
tf-plan-destroy:
terraform plan -refresh=true -var-file=varfiles/$(ENV).tfvars -out .terraform/plan_destroy.out -destroy
tf-apply:
terraform apply .terraform/plan.out
tf-destroy:
terraform apply .terraform/plan_destroy.out
Terraform requires the AWS credentials, refer to the documentation.
By default, it will used what is used for your aws
cli, you can check who you are using:
$ aws sts get-caller-identity --output json
Without anu environment variable, aws
will used what is defined in the ~/.aws/profile
file.
profile
[default]
output = json
region = eu-west-1
aws_access_key_id = <key id>
aws_secret_access_key = <secret>
(if you are using several profiles, you can select the one your want to use with an environment variable: export AWS_PROFILE=user1
, refer to the documentation.)
$ cd terraform
$ export ENV=dev
$ make tf-init tf-plan
$ make tf-apply
$ make tf-plan-destroy
$ make tf-destroy
$ cd terraform
$ export ENV=staging
$ make tf-init tf-plan
$ make tf-apply
$ make tf-plan-destroy
$ make tf-destroy
$ cd terraform
$ export ENV=prod
$ make tf-init tf-plan
$ make tf-apply
$ make tf-plan-destroy
$ make tf-destroy
For instance, if you need to reference the arn
of a resource created by terraform, just use <terraform_resource_type>.<name>.<output>
, in the example below aws_sns_topic.email_ses_notifications.arn
will be replaced by terraform at the execution time:
resource "aws_ses_event_destination" "email_campaigns_sns" {
name = "${var.env}-campaign-email-notifications-to-sns"
configuration_set_name = aws_ses_configuration_set.email_campaigns.name
enabled = true
matching_types = ["bounce", "complaint", "reject"]
sns_destination {
topic_arn = aws_sns_topic.email_ses_notifications.arn
}
}
👉 Don't forget to follow me on Twitter to be notified when new posts are available!
Follow @jcbaey