By this time we know how to write a terraform code for simple infrastructure, but in reality, things are never easy, business needs are everchanging leading to complex infrastructure.
In this blog we will try to build our knowledge on features offered by terraform which helps to transform complex code into simple code and remove redundancy.
From now on, in this series, I want to introduce the topic with a use case because I believe that understanding a concept is one thing but knowing how to apply it in the real world makes the actual difference.
Count and Count Index
Use Case
Until now, we have been creating single resources for example an ec2 instance or an IAM user, but in production-grade applications, there are multiple resources, what would be an ideal way to create multiple resources of the same type? for instance multiple ec2 instances!!!
- One approach would be to write 3 identical ec2 resource blocks with different local resource names.
provider "aws" {
region = "us-west-2"
access_key = "your-access-key"
secret_key = "your-secret-key"
}
resource "aws_instance" "instance1" {
ami = "ami-0ca285d4c2cda3300"
instance_type = "t2.micro"
}
resource "aws_instance" "instance2" {
ami = "ami-0ca285d4c2cda3300"
instance_type = "t2.micro"
}
This approach is not scalable if we are needed to create 40 ec2 instances, which will result in 40 ec2 resource blocks, updating such blocks would be prone to errors and difficult.
Terraform provides a count parameter which allows creating many resources using a single resource block, hence easier updation.
The following code allows us to create 5 ec2 instances using the count parameter.
resource "aws_instance" "instance" {
ami = "ami-0ca285d4c2cda3300"
instance_type = "t2.micro"
count = 5
}
Count Index
Use Case
Count parameter solved our problem but it also brings its own set of problems with it. We want to create 5 IAM users, an ideal approach to this problem would be to use the count parameter, however using count would result in 5 iam users with the same name, hence it is difficult to identify resources when the count is used.
resource "aws_iam_user" "users" {
name = "iam_users"
count = 3
path = "/system/"
}
- this result in 3 iam users with the same name iam_users leading to difficulty in the identification of resources
- terraform provides a
count.index
value which behind the scenes, access count parameter (count runs a loop) index value to identify the loop cycle. This helps in generating unique values with count parameters ascount.index
will always be unique for a particular loop cycle ( for example if the loop is running the first time it will be 1 and so on)
resource "aws_iam_user" "users" {
name = "iam_users.${count.index}"
count = 2
path = "/system/"
}
- This results in 3 iam users with the name iam_users.1, iam_user.2.
- count.index with count can be combined together using array data type.
variable "names" {
type = list(any)
default = ["admin", "dev"]
}
resource "aws_iam_user" "users" {
name = var.names[count.index]
count = 2
path = "/system/"
}
- This results in 2 iam users with the name admin, dev.
Behind the scenes with Count and Count Index
- Count internally runs a loop and the count index maps the count of the particular loop.
Conditional Expression
Use Case
We need to launch an ec2 instance for the production if a particular variable exists else for the development.
- Conditional expression
? :
helps us to select a value depending on the condition.
count = var.istest ? 1 : 0
- If is
isTest
variable istrue
, the count will be1
otherwise0
.
resource "aws_instance" "dev" {
ami = "ami-0ca285d4c2cda3300"
instance_type = "t2.micro"
count = var.istest ? 1 : 0
}
resource "aws_instance" "prod" {
ami = "ami-0ca285d4c2cda3300"
instance_type = "t2.large"
count = var.istest ? 0 : 1
}
variable "istest" {
type = bool
default = true
}
- This code block results in the creation of t2.micro instance if
istest = true
else t2.large instance for production ifistest = false
## Locals
Use Case
We need to same name tags for 2 ec2 instances and ebs volume. One approach would be to write tags in 2 resource blocks for ec2 and in one resource block for ebs volume. This will work however, this will lead to writing code which is repetitive and if in the future the name tag needs to be changed it needs to be replaced in 3 different places. Quite inconvenient!!!!
An ideal way would be to define it in one place and reference it in all the places.
- Locals assign a name to an expression or value and allow it to be used in multiple places.
provider "aws" {
region = "us-west-2"
access_key = "your-access-key"
secret_key = "your-secret-key"
}
locals {
common_tags = {
Owner = "DevOps Team"
service = "backend"
}
}
resource "aws_instance" "dev" {
ami = "ami-082b5a644766e0e6f"
instance_type = "t2.micro"
tags = local.common_tags
}
resource "aws_instance" "db-dev" {
ami = "ami-082b5a644766e0e6f"
instance_type = "t2.small"
tags = local.common_tags
}
resource "aws_ebs_volume" "db_ebs" {
availability_zone = "us-west-2a"
size = 8
tags = local.common_tags
}
- This code allows us to use the same tags across all resources without repeating the same code in all the resources.
Bonus Accessing data from maps and list in the variable
variable "types" {
type = list(any)
default = ["t2.micro", "t2.large"]
}
resource "aws_instance" "myFirstEc2Instance" {
ami = "ami-0ca285d4c2cda3300"
instance_type = var.instance[0]
}
- list values start with
index 0
which means instance type t2.micro will be selected.
provider "aws" {
region = "us-west-2"
access_key = "your-access-key"
secret_key = "your-secret-key"
}
resource "aws_instance" "myFirstEc2Instance" {
ami = "ami-0ca285d4c2cda3300"
instance_type = var.instance["delhi"]
}
variable "instance" {
type = map(any)
default = {
"mumbai" = "t2.large"
"delhi" = "t2.micro"
}
}
- To access the map data type value of the key is used to access its value within
[]
From a DevOps perspective
- Count, conditional, and local values are the tools which help to write clean and non-repetitive code, which helps to scale easily when the requirements are changing.
Top comments (0)