Day 69 of #90DaysOfDevOps
When you define a resource block in Terraform, by default, this specifies one resource that will be created. To manage several of the same resources, you can use either count or for_each, which removes the need to write a separate block of code for each one. Using these options reduces overhead and makes your code neater.
Count is what is known as a ‘meta-argument’ defined by the Terraform language. Meta-arguments help achieve certain requirements within the resource block.
Count
The count meta-argument accepts a whole number and creates the number of instances of the resource specified.
When each instance is created, it has its own distinct infrastructure object associated with it, so each can be managed separately. When the configuration is applied, each object can be created, destroyed, or updated as appropriate.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.16"
}
}
required_version = ">= 1.2.0"
}
provider "aws" {
region = "us-east-1"
}
resource "aws_instance" "server_for_count" {
count = 4
ami = "ami-0005e0cfe09cc9050"
instance_type = "t2.micro"
tags = {
Name = "Server ${count.index}"
}
}
Create a file with .tf extension and paste the above code. Next, write command terraform init
and then terraform apply
Check the console, 4 instances will be created with the 0 to 3 (total 4) name.
If your instances are almost identical, count
is appropriate. If some of their arguments need distinct values that can't be directly derived from an integer, it's safer to use for_each
.
for_each
Like the count argument, the for_each meta-argument creates multiple instances of a module or resource block.
However, instead of specifying the number of resources, the for_each meta-argument accepts a map or a set of strings.
This is useful when multiple resources are required that have different values.
Example with a set of strings:
locals {
ami_ids = toset([
"ami-0005e0cfe09cc9050",
"ami-0c7217cdde317cfec",
])
}
resource "aws_instance" "server" {
for_each = local.ami_ids
ami = each.key
instance_type = "t2.micro"
tags = {
Name = "Server ${each.key}"
}
}
Write command terraform apply
Check console for output
Example with multiple key value iteration:
locals {
ami_ids = {
"linux" : "ami-0005e0cfe09cc9050",
"ubuntu" : "ami-0c7217cdde317cfec",
}
}
resource "aws_instance" "server" {
for_each = local.ami_ids
ami = each.value
instance_type = "t2.micro"
tags = {
Name = "Server ${each.key}"
}
}
Write command terraform apply
Check console for output.
Task 2
Write about meta-arguments and its use in Terraform.
Meta-arguments in Terraform are special arguments that provide additional functionality and control over resource configuration. They can be used with resource blocks and modules to control their behavior or influence the infrastructure provisioning process.
Meta-arguments are not specific to any particular resource type, but rather provide a way to configure behavior across all resources in a Terraform configuration. They are used to define how resources are managed and interacted with in a broader context.
There are 5 meta-arguments available in terraform:
depends_on
count
for_each
provider
lifecycle
depends_on:
Use the depends_on
meta-argument to handle hidden resource or module dependencies that Terraform cannot automatically infer. You only need to explicitly specify a dependency when a resource or module relies on another resource's behavior but does not access any of that resource's data in its arguments.
You can use the depends_on
meta-argument in module
blocks and in all resource
blocks, regardless of resource type. It requires a list of references to other resources or child modules in the same calling module. This list cannot include arbitrary expressions because the depends_on
value must be known before Terraform knows resource relationships and thus before it can safely evaluate expressions.
example:
resource "aws_iam_role" "example" {
name = "example"
# assume_role_policy is omitted for brevity in this example. Refer to the
# documentation for aws_iam_role for a complete example.
assume_role_policy = "..."
}
resource "aws_iam_instance_profile" "example" {
# Because this expression refers to the role, Terraform can infer
# automatically that the role must be created first.
role = aws_iam_role.example.name
}
resource "aws_iam_role_policy" "example" {
name = "example"
role = aws_iam_role.example.name
policy = jsonencode({
"Statement" = [{
# This policy allows software running on the EC2 instance to
# access the S3 API.
"Action" = "s3:*",
"Effect" = "Allow",
}],
})
}
resource "aws_instance" "example" {
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
# Terraform can infer from this that the instance profile must
# be created before the EC2 instance.
iam_instance_profile = aws_iam_instance_profile.example
# However, if software running in this EC2 instance needs access
# to the S3 API in order to boot properly, there is also a "hidden"
# dependency on the aws_iam_role_policy that Terraform cannot
# automatically infer, so it must be declared explicitly:
depends_on = [
aws_iam_role_policy.example
]
}
provider:
The provider
meta-argument specifies which provider configuration to use for a resource, overriding Terraform's default behavior of selecting one based on the resource type name. Its value should be an unquoted <PROVIDER>.<ALIAS>
reference.
By default, Terraform interprets the initial word in the resource type name (separated by underscores) as the local name of a provider, and uses that provider's default configuration. For example, the resource type google_compute_instance
is associated automatically with the default configuration for the provider named google
.
By using the provider
meta-argument, you can select an alternate provider configuration for a resource:
# default configuration
provider "google" {
region = "us-central1"
}
# alternate configuration, whose alias is "europe"
provider "google" {
alias = "europe"
region = "europe-west1"
}
resource "google_compute_instance" "example" {
# This "provider" meta-argument selects the google provider
# configuration whose alias is "europe", rather than the
# default configuration.
provider = google.europe
# ...
}
lifecycle:
lifecycle
is a nested block that can appear within a resource block. The lifecycle
block and its contents are meta-arguments, available for all resource
blocks regardless of type.
The arguments available within a lifecycle
block are create_before_destroy
, prevent_destroy
, ignore_changes
, and replace_triggered_by
.
resource "azurerm_resource_group" "example" {
# ...
lifecycle {
create_before_destroy = true
}
}