Terraform
Manage resource lifecycle
Lifecycle arguments help control the flow of your Terraform operations by creating custom rules for resource creation and destruction. Instead of Terraform managing operations in the built-in dependency graph, lifecycle arguments help minimize potential downtime based on your resource needs as well as protect specific resources from changing or impacting infrastructure.
Prerequisites
This tutorial assumes you are familiar with the standard Terraform workflow. If you are unfamiliar with Terraform, complete the Get Started tutorials first.
For this tutorial, you will need:
- The Terraform CLI, version 0.14 or later.
- AWS Credentials configured for use with Terraform.
- The
awscli
configured.
Create infrastructure
Start by cloning the example repository. This configuration builds an EC2 instance and a security group rule to allow port 8080
access to the instance.
$ git clone https://github.com/hashicorp-education/learn-terraform-lifecycle-management
Change into the repository directory.
$ cd learn-terraform-lifecycle-management
Confirm your AWS CLI region.
$ aws configure get region
us-east-2
Open the terraform.tfvars
file and edit the region to match your AWS CLI configuration.
region = "us-east-2"
Open the main.tf
file and review your configuration. Your two main resources are an EC2 instance and a security group that allows TCP access on port 8080.
##...
resource "aws_instance" "example" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.sg_web.id]
user_data = <<-EOF
#!/bin/bash
apt-get update
apt-get install -y apache2
sed -i -e 's/80/8080/' /etc/apache2/ports.conf
echo "Hello World" > /var/www/html/index.html
systemctl restart apache2
EOF
tags = {
Name = "terraform-learn-state-ec2"
drift_example = "v1"
}
}
resource "aws_security_group" "sg_web" {
name = "sg_web"
ingress {
from_port = "8080"
to_port = "8080"
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
// connectivity to ubuntu mirrors is required to run `apt-get update` and `apt-get install apache2`
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
Initialize your configuration.
$ terraform init
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Installing hashicorp/aws v3.26.0...
- Installed hashicorp/aws v3.26.0 (signed by HashiCorp)
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
Apply your configuration. Enter yes
when prompted to accept your changes.
$ terraform apply
## …
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
instance_id = "i-099bb19ca402a6761"
public_ip = "3.138.139.170"
When your apply operation completes, run terraform state list
to review the resources managed by Terraform in your state file.
$ terraform state list
data.aws_ami.ubuntu
aws_instance.example
aws_security_group.sg_web
Prevent resource deletion
To prevent destroy operations for specific resources, you can add the prevent_destroy
attribute to your resource definition. This lifecycle option prevents Terraform from accidentally removing critical resources.
Add prevent_destroy
to your EC2 instance.
resource "aws_instance" "example" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.sg_web.id]
user_data = <<-EOF
#!/bin/bash
apt-get update
apt-get install -y apache2
sed -i -e 's/80/8080/' /etc/apache2/ports.conf
echo "Hello World" > /var/www/html/index.html
systemctl restart apache2
EOF
tags = {
Name = "terraform-learn-state-ec2"
drift_example = "v1"
}
+ lifecycle {
+ prevent_destroy = true
+ }
}
Run terraform destroy
to observe the behavior.
$ terraform destroy
aws_security_group.sg_web: Refreshing state... [id=sg-0c9b526ba6f89d910]
aws_instance.example: Refreshing state... [id=i-099bb19ca402a6761]
╷
│ Error: Instance cannot be destroyed
│
│ on main.tf line 31:
│ 31: resource "aws_instance" "example" {
│
│ Resource aws_instance.example has lifecycle.prevent_destroy set, but the
│ plan calls for this resource to be destroyed. To avoid this error and
│ continue with the plan, either disable lifecycle.prevent_destroy or reduce
│ the scope of the plan using the -target flag.
The prevent_destroy
attribute is useful in situations where a change to an attribute would force a replacement and create downtime.
Create resources before they are destroyed
For changes that may cause downtime but must happen, use the create_before_destroy
attribute to create your new resource before destroying the old resource.
Update your security group rule to allow port 80
access instead of 8080
.
resource "aws_security_group" "sg_web" {
name = "sg_web"
ingress {
- from_port = "8080"
+ from_port = "80"
- to_port = "8080"
+ to_port = "80"
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
## ...
}
Update your EC2 instance to reflect this change by adding the create_before_destroy
attribute and updating the VM so it runs on port 80
.
resource "aws_instance" "example" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.sg_web.id]
user_data = <<-EOF
#!/bin/bash
apt-get update
apt-get install -y apache2
- sed -i -e 's/80/8080/' /etc/apache2/ports.conf
echo "Hello World" > /var/www/html/index.html
systemctl restart apache2
EOF
tags = {
Name = "terraform-learn-state-ec2"
Drift_example = "v1"
}
lifecycle {
- prevent_destroy = true
+ create_before_destroy = true
}
}
Run terraform apply
and observe the changes that force a replacement. Without the create_before_destroy
tag, Terraform would destroy the instance before recreating it, which may lead to downtime. Enter yes
when prompted to accept your changes.
$ terraform apply
aws_security_group.sg_web: Refreshing state... [id=sg-0c9b526ba6f89d910]
aws_instance.example: Refreshing state... [id=i-099bb19ca402a6761]
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
~ update in-place
+/- create replacement and then destroy
Terraform will perform the following actions:
# aws_instance.example must be replaced
+/- resource "aws_instance" "example" {
##...
# aws_security_group.sg_web will be updated in-place
~ resource "aws_security_group" "sg_web" {
id = "sg-0c9b526ba6f89d910"
##...
Plan: 1 to add, 1 to change, 1 to destroy.
Changes to Outputs:
~ instance_id = "i-099bb19ca402a6761" -> (known after apply)
~ public_ip = "3.138.139.170" -> (known after apply)
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_security_group.sg_web: Modifying... [id=sg-0c9b526ba6f89d910]
aws_security_group.sg_web: Still modifying... [id=sg-0c9b526ba6f89d910, 10s elapsed]
aws_security_group.sg_web: Still modifying... [id=sg-0c9b526ba6f89d910, 20s elapsed]
aws_security_group.sg_web: Modifications complete after 22s [id=sg-0c9b526ba6f89d910]
aws_instance.example: Creating…
aws_instance.example: Creation complete after 1m14s [id=i-0b2fd8a0df19c215d]
aws_instance.example (940b3833): Destroying... [id=i-099bb19ca402a6761]
aws_instance.example: Destruction complete after 41s
Apply complete! Resources: 1 added, 1 changed, 1 destroyed.
Outputs:
instance_id = "i-0b2fd8a0df19c215d"
public_ip = "18.116.49.153"
Ignore changes
For changes outside the Terraform workflow that should not impact Terraform operations, use the ignore_changes
argument.
Update the drift_example
tag in the AWS CLI.
$ aws ec2 create-tags --resources $(terraform output -raw instance_id) --tags Key=drift_example,Value=v2
Add the ignore_changes
attribute to your lifecycle
block in the EC2 instance.
resource "aws_instance" "example" {
##...
lifecycle {
create_before_destroy = true
+ ignore_changes = [tags]
}
}
Run terraform apply
. This apply will refresh your state file with v2
instead of overwriting your tag with v1
as written in your configuration.
$ terraform apply
aws_security_group.sg_web: Refreshing state... [id=sg-0c9b526ba6f89d910]
aws_instance.example: Refreshing state... [id=i-0b2fd8a0df19c215d]
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
instance_id = "i-0b2fd8a0df19c215d"
public_ip = "18.116.49.153"
Examine your instance in the state file to confirm that your drift_example
tag is v2
.
$ terraform state show aws_instance.example
##...
# aws_instance.example:
resource "aws_instance" "example" {
tags = {
"Name" = "terraform-learn-state-ec2"
"drift_example" = "v2"
}
##...
Clean up your resources
When you are finished with this tutorial, destroy the resources you created. Enter yes
when prompted to confirm your changes.
$ terraform destroy
aws_security_group.sg_web: Refreshing state... [id=sg-0c9b526ba6f89d910]
aws_instance.example: Refreshing state... [id=i-0b2fd8a0df19c215d]
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
- destroy
##...
Plan: 0 to add, 0 to change, 2 to destroy.
Changes to Outputs:
- instance_id = "i-0b2fd8a0df19c215d" -> null
- public_ip = "18.116.49.153" -> null
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
aws_instance.example: Destruction complete after 1m11s
aws_security_group.sg_web: Destroying... [id=sg-0c9b526ba6f89d910]
aws_security_group.sg_web: Still destroying... [id=sg-0c9b526ba6f89d910, 10s elapsed]
aws_security_group.sg_web: Destruction complete after 11s
Destroy complete! Resources: 2 destroyed.
Next steps
In this tutorial, you learned the different lifecycle management options you can use to prevent resource deletion. You also used lifecycle management to avoid downtime when Terraform recreates your infrastructure and to ignore changes to certain resource attributes.
For more information about Terraform lifecycle management and state drift, review the resources below:
- Drift Management tutorial
- Learn Terraform Import tutorial
- Lifecycle management documentation