Featured image of post Notes on Terraform

Notes on Terraform

Best practices from personal experience

Introduction

Terraform is a fantastic tool for automating resource creation in clouds like AWS, Azure, GCP, Hetzner, and many others. It’s really cool. With tfenv, you can easily and conveniently update Terraform locally and choose the desired version. You can create a file in the project’s root to specify the required version, allowing tfenv to automatically use the correct Terraform version for your project.
There are several practical recommendations I’ve developed from reading the official documentation, which is excellent, as well as the modules and code written by AWS.

Recommendations

Study the official recommendations. Below are my own notes.

1. Do not duplicate resource type in the name

Incorrect

1
2
3
4
5
6
7
resource "aws_vpc" "production_vpc" {
  cidr_block = "172.16.0.0/16"

  tags = {
    Name = "Production VPC"
  }
}

Correct

1
2
3
4
5
6
7
resource "aws_vpc" "production" {
  cidr_block = "172.16.0.0/16"

  tags = {
    Name = "Production"
  }
}

Explanation

  1. In the code, you’ll still reference the resource as ${aws_vpc.production.id}—it’s already clear that this is a VPC, so there’s no need to waste characters on what’s already written.
  2. The same goes for the resource name; you’ll always see the VPC in the list on the VPC page in the AWS Console, and there won’t be anything else. Again, why duplicate?

2. Use this as a name, but only in modules

Suppose you have a module that creates a virtual machine and minimal attachments. Within the module, the virtual machine is unique.

1
2
3
4
5
resource "aws_instance" "this" {
  ami           = data.aws_ami.this.id
  instance_type = var.instance_type
...
}

Why come up with names for resources if it’s the only one within the module? There’s no need. It’s also important to understand that this is the object name within the Terraform code. It’s convenient when any resource type is simply this, but you should only do this within a single module that creates a logical unit. A virtual machine without its Security Group, which opens the ports, doesn’t make sense; thus, logically, it’s one piece.

3. Create standard files for modules

FileContent
vars.tfAll variables
outputs.tfOutput parameters
versions.tfSection with provider requirements
main.tfFile with the root content

4. Do not declare providers in modules

Specify version constraints, but leave initialization to the discretion of the user of your module.

5. Tags variable

Add a variable that will apply tags to all resources in the module.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
variable "tags" {
  type        = map(string)
  description = "Extra tags."
  default     = {}
}

locals {
  default_tags = {
    Custom    = "Some default tags"
    Terraform = "Path to module folder in your Git repo - helps to find code in the repo fast"
  } 
  tags = merge(
    local.default_tags,
    var.tags
  )
}

resource "aws_vpc" "production" {
  cidr_block = "172.16.0.0/16"

  tags = merge(
    local.tags,
    {
      Name = "Production"
    }
  )
}

In the local.tags variable, we first collect the final dictionary and then use it where needed within the module.

6. Resource naming with name_prefix

Always, without exception, use a name prefix for creating resources. By changing just this variable, the code should create exactly the same infrastructure in the same account without any conflicts—this is a good module that can be reused.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
variable "name_prefix" {
  type        = string
  description = "Name prefix for all resources (required)."
  validation {
    condition = can(regex("^[a-z][a-z0-9-]+[a-z]$", var.name_prefix))
  }
}

resource "tls_private_key" "this" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

resource "aws_key_pair" "this" {
  key_name   = var.name_prefix
  public_key = tls_private_key.this.public_key_openssh
}

And there’s no point in adding anything to the name if there’s only one resource. Suppose this code is part of the same module for creating a virtual machine; it will be used like this.

1
2
3
4
5
6
module "ec2_backend" {
  source = "./ec2"

  name_prefix = "production-backend"
  instance_type = "t3a.small"
}

This way, it will be clear from the prefix to which virtual machine the EC2 Key Pair belongs in the list. If there were two, then the name in the code would be "${var.name_prefix}-some-suffix".

7. No hyphens in resource names

Forget about using hyphens in resource names. It breaks the code analyzer in the IDE, and it causes issues in various situations.

Incorrect

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
module "ec2-backend" {
  source = "./ec2"

  name_prefix = "production-backend"
  instance_type = "t3a.small"
}

output "backend-ip" {
  value module.ec2-backend.private-ipv4
}

Correct

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
module "ec2_backend" {
  source = "./ec2"

  name_prefix = "production-backend"
  instance_type = "t3a.small"
}

output "backend_ip" {
  value module.ec2_backend.private_ipv4
}

I would like to emphasize that for the name of the resource in the cloud, I recommend using hyphens as much as possible—it looks nicer—but in Terraform, never.

Licensed under Apache License, Version 2.0
Last updated on Nov 19, 2024 10:58 +0200
All rights reserved
Built with Hugo
Theme Stack designed by Jimmy