Every resource that is managed by Terraform has a lifecycle, this lifecycle contains three stages; Apply (Create), Update, and Destroy. The Apply stage is where the resource is actually created by Terraform, Update is when a change is made to a preexisting resource - this might be incremental or a full recreate - and finally Destroy where the resource is removed from the environment. Sometimes however we may want to control these stages of the resources lifecycle a little more for this Terraform gives us the lifecycle
meta-argument.
The below diagram is a simple articulation of the three stages of the lifecycle at work!
As I have said above we can control - to some extent - the lifecycle further than the three above stages for our resources. Terraform gives us the following options that we can use in the lifecycle
meta-argument:
create_before_destroy
— when an in-place update has to occur Terraform will create the new instance prior to destroying the oldprevent_destroy
— do not allow the destroy flow to actually destruct the resourceignore_changes
— ignore any changes on specified fields or an entire objectreplace_triggered_by
precondition
— check some thing before performing the action on the resourcepostcondition
— validate some thing after performing an action on the resourceWe will go through most of these in a little more detail, although for the *condition
you can check out this post.
The create_before_destroy
option is extremely useful in cases where the new instance of the resource must be there before destroy the old one. For example perhaps a public IP needs to be recreated but you don’t want the service to be inaccessible so you would ensure that the new address is created prior to the old one being destroyed.
Using the Scratch provider we can mock out an example of this:
resource "scratch_string" "this" {
in = "create_before_destroy"
lifecycle {
create_before_destroy = true
}
}
The above will now ensure that in the event this resource is required to be replaced in- place that it will create the new instance first.
prevent_destroy
is another bool
option which we can switch on, we would use this to ensure that Terraform never destroys the particular resource. On destroy the resource would be removed from state but still exist in the real world. This is useful in scenarios where perhaps not all your resources are managed by Terraform, or you do not want anyone to accidentally delete a particular resource.
Let’s dive into an example to better understand this concept.
resource "azurerm_resource_group" "this" {
name = "rg-prod"
location = "australiasoutheast"
lifecycle {
prevent_destroy = true
}
}
In the above we are creating a resource group, and we have informed Terraform we want to prevent its destruction through the lifecycle
meta-argument. In this scenario let’s assume that we are only managing a portion of the resources within the resource group (RG) via Terraform and other via some other mechanism. If we were to not have prevent_destroy
when we eventually did a destruction those resources created out of Terraform would also be destroyed. By having prevent_destroy
we are now required to be more assertive when we want to destroy the RG, we would either have to remove it manually or commit a change removing the lifecycle
attribute.
I find that prevent_destroy
is a favourite to security folks as it helps to add an extra level of assurance around destructive operations, especially on resource types that have such a large blast area like a resource group.
Now we come to one of the more commonly used and in my opinion the most dangerous, ignore_changes
. A reason why you might want to use ignore_changes
is if some outside force / process is going to be modifying your resources, an example of this might be mutation of tags or tag values via Azure policy or potentially the number of instances of a resource due to a scaling event. Both of those examples are what I would consider good reasons to utilise ignore_changes
. Lets look at a very basic example:
resource "scratch_block" "this" {
in {
string = "Meow"
number = 42
bool = true
}
lifecycle {
ignore_changes = [
in
]
}
}
In the above scratch_block
we are ignoring any changes to the in
block, and we cannot ignore a specific property on that block as the block is actually represented as a set
which does not have an index or referenceable value. It is important to note that the values provided here must be static, you cannot pass in a variable or a splat. The only exception is the use of the special all
keyword in place of a list which will then ignore all attributes on the resource.
ignore_changes
becomes dangerous when you start ignoring entire resources as then changes you make to the code won’t alter the resource this means you’re only managing two stages of the resource Apply and Destroy all alteration would then have to be managed by an external system.
Many organisations uses tags for managing or attributing cost with cloud resources so ignore changing to particular tags or the tags property can be very valuable as it allows an external system to manage the tags on resources for you without Terraform overwriting the changes. When using ignore_changes
my advice to be as specific about the property you’re wanting to ignore as you possibly can be!
replace_triggered_by
is a very new addition to language, only coming out with Terraform v1.2, it is also a very powerful argument. This will replace a particular resource based on another resource. Below is an example:
resource "scratch_bool" "this" {
in = false
}
resource "scratch_string" "this" {
in = "create_before_destroy"
lifecycle {
replace_triggered_by = [
scratch_bool.this
]
}
}
In the above example we have two resources scratch_bool.this
and scratch_string.this
we are tying a replace to the scratch_boo.this
. What this means is that if we were to update scratch_bool.this.in
to be true
the entire scratch_string.this
resource would be replaced!
This allows us to create really tight dependancies on resources that may not be otherwise related in our Terraform code. You should however be very careful using this as if the referenced resource changes then your resource will be replaced.
So that closes out the options we can implement to augment the standard lifecycle state within Terraform. Altering the lifecycle allows for some very powerful control over our resources but it does come with risk, when you’re making further changes to resources you have to ensure that you’ve considered what could happen based on any lifecycle modifications you’ve made.
I am very interested to see what additional things HashiCorp come out with in the lifecycle realm!
You can follow Brendan @BrendanLiamT on Twitter.