We are close to the end of my Certified Terraform Associate course, but we have one major concept left to go through: Terraform state. That is the topic of this lesson.
If you come from a background of setting up resources using infrastructure-as-code tools such as AWS CloudFormation or Azure Bicep you might be confused about the concept of state in Terraform - and rightly so.
In this lesson I will go through Terraform state, what it is, how we work with it, and where we store it. This lesson covers all of section 7 of the Certified Terraform Associate exam curriculum. This part of the curriculum is outlined below:
Part | Content |
---|---|
7 | Implement and maintain state |
(a) | Describe default local backend |
(b) | Describe state locking |
(c) | Handle backend and cloud integration authentication methods |
(d) | Differentiate remote state back end options |
(e) | Manage resource drift and Terraform state |
(f) | Describe backend block and cloud integration in configuration |
(g) | Understand secret management in state files |
Terraform state
We have already seen that we have a real world, and we have our Terraform configuration.
How do Terraform keep track of what exists in the real world? This is where the concept of state comes in. State represents what Terraform knows about the resources it has created. In an ideal situation the Terraform state should map one-to-one with the resources in the real world. This is not always the case, because the real world can change independently from the Terraform configuration. This is known as resource drift. We will hear more about resource drift later in this lesson.
The state is stored in a JSON file. In general we should never directly open and modify the state file. We will see an example of a state file later in this lesson, but it is not necessary for the certification exam to be familiar with how the state file looks in detail.
In the official documentation there are three1 reasons listed for why Terraform has a state file, I will briefly go summarize them here:
- Terraform must be able to map your configuration to the real world in some way. It was early on decided that the state file was the most pragmatic approach. Alternatives included using provider-specific ways to tag a resource in some way to be able to identify it, but this approach differs for each provider and it does not scale well.
- Terraform must store metadata that specifies things such as the relationship (dependencies) between different resources in, or outside of, your configuration. For instance, when you delete a resource from a configuration Terraform might require information about dependencies outside of your configuration, and this information is kept in the state file.
- Terraform stores many attributes for each resource in your configuration. When you run
terraform plan
Terraform must reach out to your providers to ask for the latest configuration for each resource, to determine if there are any changes to perform. This works well for small configurations, but less so for larger configurations. The state file can be used as a cache to improve performance when working with larger infrastructures. Ifterraform plan
takes unreasonably long time you can add-refresh=false
to the command, this will instead use the values in the state file as the source of truth.
Backends
A backend is a place where your state file is stored. If you do not explicitly specify a backend
block in your configuration then Terraform will use the local backend.
The local backend consists of a local file named terraform.tfstate
. This backend is not suited for team environments where there is more than a single developer.
What other options than the local backend are there? In fact there are a number of options. A few of the popular options are: Azure storage account (azurerm
), Google Cloud Storage (gcs
), and AWS S3 (s3
).
A backend
block in HCL is a sub-block to the terraform
block in your configuration. An example of what this looks like:
terraform {
backend "azurerm" {
resource_group_name = "rg-terraform-backend"
storage_account_name = "terraform"
container_name = "tfstate"
key = "terraform.tfstate"
}
}
In this example we see an example of using an Azure backend (azurerm
). This backend stores the state file as a blob2 in a storage account. To configure this backend we must provide:
- the name of an Azure resource group (
resource_group_name
) - the name of an Azure storage account (
storage_account_name
) - the name of a container in this storage account (
container_name
) - the name of the blob that is the state file (
key
)
There are additional configuration options available, most notably options related to authenticating to Azure to be able to use the backend. More about authentication to a backend in a later section.
Each backend type will require different configuration options. Let's look at another example for the S3 backend in AWS:
terraform {
backend "s3" {
bucket = "my-state-bucket"
key = "tfstate/terraform.tfstate"
region = "eu-west-1"
}
}
S3 is the blob storage service in AWS. For this backend we must configure:
- the name of an S3 bucket (
bucket
) - the name of the blob that is the state file (
key
) - the AWS region (
region
)
As with the Azure backend this backend has additional optional configuration options. See the documentation for the backend you want to use.
Once you have included a backend
block and you have included the proper authentication details (read more about authentication later in this lesson) your state file will be stored at the configured location. Anyone else using the same Terraform configuration will use the same state file.
State locking
Some backends support state locking.
State locking is when the state file is set in a locked mode when you run Terraform, prohibiting others from running Terraform and performing write operations to this same state file at the same time. This is important when working together with other team members with the same Terraform configuration.
You should check the documentation for the backend you are using to know if it supports state locking or not.
If your selected backend does not support state locking you might end up in a bad situation where several developers are writing to the same state file at once.
Authenticate to a backend
If you use some other backend than the local backend then chances are that you must authenticate to the backend to be able to use it. There are usually a few options for how to authenticate to different backends, so here you must also read the documentation for your selected backend.
Let us look at the azurerm
backend as an example. For this backend you can authenticate in the following ways:
- Using a Managed Service Identity
- Using a Service Principal with OpenID Connect
- Using a SAS token
- Using a storage account access key
- Using Azure AD authentication
- Using a Service Principal with a certificate
- Using a Service Principal with a client secret
As you can see, there are many alternatives. What is best for you depends on your situation. The important take-away from this section is: how you authenticate to your backend depends on what backend it is, and most often there are many alternatives for how to authenticate yourself.
Resource drift
If you make an update to a resource outside of your Terraform configuration then Terraform will report this as resource drift, i.e. your state no longer corresponds to the real resource. This is generally something you want to avoid at all costs, but sometimes it does happen.
What to do when you end up in that situation? This could be a tricky situation, but one recommended approach is to run terraform plan -refresh-only
and terraform apply -refresh-only
. These will update the state file to match the real-world, without applying any changes to the resources.
In the certification exam I did not encounter any questions regarding this topic, but be aware that there could be questions regarding this. If so, keep the -refresh-only
flag in mind.
Secrets management in state files
What happens if you generate a password using the random
provider, where is this password stored? Let us run a small example using the following Terraform configuration:
terraform {
required_providers {
random = {
source = "hashicorp/random"
}
}
}
resource "random_password" "db_password" {
length = 16
upper = true
lower = true
special = true
numeric = true
}
In this configuration I have included the random
provider, which can be used to generate random numbers, strings, names, and passwords. I include a single resource
block of the random_password
type. This resource will result in a password with 16 characters.
I run through terraform init
, terraform plan
, and terraform apply
. After these commands Terraform has created a state file for me. Let's take a look at the full state file terraform.tfstate
:
{
"version": 4,
"terraform_version": "1.3.6",
"serial": 1,
"lineage": "a14afc24-a787-3cee-a687-d65d226ba259",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "random_password",
"name": "db_password",
"provider": "provider[\"registry.terraform.io/hashicorp/random\"]",
"instances": [
{
"schema_version": 3,
"attributes": {
"bcrypt_hash": "$2a$10$5vF3NaAMaoX4GTbzVfe82ureygwmZiBrAiGAQIH3uSOZ2.p44Xqay",
"id": "none",
"keepers": null,
"length": 16,
"lower": true,
"min_lower": 0,
"min_numeric": 0,
"min_special": 0,
"min_upper": 0,
"number": true,
"numeric": true,
"override_special": null,
"result": "7L8AAn!@I\u003e53(+6K",
"special": true,
"upper": true
},
"sensitive_attributes": []
}
]
}
],
"check_results": null
}
In .resources[0].instances[0].attributes.result
we can see the generated secret in plain text. This means that, in general, your state file should be considered a secret and it should be handled as such.
Summary
In this lesson we learned everything we need to know about Terraform state for the certification exam.
In summary we looked at:
- What the Terraform state is and what it is used for.
- What a backend is and how we define one using a
backend
block in HCL. - We learned that if we do not specify an explicit backend then Terraform will use the local backend, which is a local file named
terraform.tfstate
. - We learned about state locking and the benefits of this when working together with a team with the same Terraform configuration.
- We learned that most backends, except for the local backend, in general requires authentication. We saw how different backends accept different authentication methods.
- We briefly covered the concept of resource drift and what we can do when we have resource drift. We saw the
-refresh-only
flag forterraform plan
andterraform apply
. - We briefly covered secrets management in state files and we learned that the state file should be handled as a secret.
-
In fact there are four reasons, but the fourth is more a benefit of using state when working in a larger team, but is not strictly a reason why Terraform requires a state file. See https://developer.hashicorp.com/terraform/language/state/purpose for the full list of reasons and details description. ↩
-
"A binary large object (BLOB or blob) is a collection of binary data stored as a single entity." - Description fetched from https://en.wikipedia.org/wiki/Binary_large_object at 2023-02-05. ↩
Top comments (0)