Terraform 101
Introduction
The objective is to learn terraform and configure terraform with AWS provider.
Terraform
The terraform { }
block includes the version of terraform, providers/dependencies settings, and the backend configuration.
required_providers
The required_providers {}
contains list of provider settings. Each provider includes:
- source: source will include a optional HOSTNAME, a NAMESPACE and a TYPE. So the complete definition for source is registry.terraform.io/hashicorp/aws.
- version: the version number in string literal. The version can be a composite of many conditions, seperated by comma:
- “=”: allow only the version specified
- “!=”: exclude the version
- “>, >=, <, <=”: specify the comparison against a specific version
- “
>”: allow all patch version greater than the specified version, so “> 1.2.1” allows all “1.2.2” and “1.2.4”, but not “1.2.0”
- local name: local name is the unique identifier (allowed one per module) that refers to its provider configuration. Conventionally, terraform suggests local name should be the prefix name of its resource types. For example, here, prvoder hashicorp/aws uses a local name “aws” that has resource types such as aws_instance, aws_sns_topic etc.
- note: the official docs is wrong for the naming convention. Underscores are not allowed based on this issue.”aws_profile” is not allow, but “aws-profile” is ok.
backend configuration
The backend "name" {}
controls the save location (.tfstate file) of Terraform managed resource state. By default, Terraform uses backend "local" {}
which saves the file in the same directory the command is executed.
For a cloud environment, because the .tfstate file shouldn’t be checked out to the version control, terraform needs a remote storage for its state. And to ease the process, for example, to configure a backend using S3, do
1 | terraform { |
provider
The provider "aws" {}
block defines neccessary metadata for target providers. For example, for the hashicorp/aws provider, it needs to configure target region, credential profiles, access key etc.
Alias
provider block can use a alias value to define multiple provider profile for the provider. For example
1 | provider "aws" { |
will deploy two ec2 instance in two regions using seperate aws provider.
Resource
The resource {}
block represents a single unit of resource depolyment. I would consider this as an interface/abstraction that terraform API provided to manage one resource.
For example, resource "aws_s3_bucket" "bucket1" {}
is a abstraction representing a S3 bucket. “bucket1” is a local name in Terraform that other resources can reference using aws.aws_s3_bucket.bucket1
.
Module
The purpose of module block is to create reusability of complex resources. Based on the official docs and this reddit post, one directory (not including its subdirectory) is considered as one module. Just like in a programming language, you would refactor a complex process into a seperate function and only expose neccessary parameters. In Terraform, you can put the cohesive resources into a seperate directory. For example, with the following file structure,
1 | root |
I have all resource definition related to lambda and ec2 instances in sperate folders. root/main.tf can access all resources/variables from root/variables.tf and root/output.tf, but not root/lambda/main.tf and root/instances/main.tf. In order to bring those two modules into root module’s scope, you need to use a source definition with relative pathing and supplied with required variables from root/lambda/variables.tf and root/instances/variables.tf.
1 | module "module_name" { |
Notes
This section will be some notes
Backend configs
Because Terraform does not allow variables within the backend block, e.g. the following will result an error
1 | backend "s3" { |
The partial configuration suggests to use terraform apply cli with -backend-config= option. For instance, if running Terraform with Github Actions, I can configure backend with cli
1 | terraform apply -backend-config="region=$region" \ |
Lambda with SNS trigger
To configure a SNS trigger for AWS Lambda:
- Define both resources for SNS topic and lambda function
- the SNS need to define a topic subscription to the lambda function
- and the lambda function needs to define invoke permission to allow SNS resource to invoke the function.
For example:
1 | # Create sns topic and lambda |
One thing also to mention that the “aws_sns_topic_subscription” resource uses default filter_policy that applies to MessageAttribute, not MessageBody. The change the value, set
1 | resource "aws_sns_topic_subscription" "sns_lambda_subscription" { |