Tutorial

How To Troubleshoot Terraform

Updated on November 23, 2021
How To Troubleshoot Terraform

The author selected the Free and Open Source Fund to receive a donation as part of the Write for DOnations program.

Introduction

Things do not always go according to plan: deployments can fail, existing resources may break unexpectedly, and you and your team could be forced to fix the issue as soon as possible. Understanding the methods to approach debugging your Terraform project is crucial when you need to make a swift response.

Similarly to developing with other programming languages and frameworks, setting log levels in Terraform to gain insight into its internal workflows with the necessary verbosity is a feature that can help you when troubleshooting. By logging the internal actions, you can uncover implicitly hidden errors, such as variables defaulting to an unsuitable data type. Also common with frameworks is the ability to import and use third-party modules (or libraries) to reduce code duplication between projects.

In this tutorial, you’ll verify that variables always have sensible values and you’ll specify exactly which versions of providers and modules you need to prevent conflicts. You’ll also enable various levels of debug mode verbosity, which can help you diagnose an underlying issue in Terraform itself.

Prerequisites

  • A DigitalOcean Personal Access Token, which you can create via the DigitalOcean Control Panel. You can find instructions in the DigitalOcean product documents, How to Create a Personal Access Token.
  • Terraform installed on your local machine and a project set up with the DigitalOcean provider. Complete Step 1 and Step 2 of the How To Use Terraform with DigitalOcean tutorial, and be sure to name the project folder terraform-troubleshooting, instead of loadbalance. During Step 2, do not include the pvt_key variable and the SSH key resource.

Note: This tutorial has specifically been tested with Terraform 1.0.2.

Setting Version Constraints

Although the ability to make use of third-party modules and providers can minimize code duplication and effort when writing your Terraform code, it is also possible for developers of third-party modules to release new versions that can potentially bring breaking changes to your specific code. To prevent this, Terraform allows you to specify version boundaries to ensure that only the versions you want are installed and used. Specifying versions means that you will require the versions you’ve tested in your code, but it also leaves the possibility for a future update.

Version constraints are specified as strings and passed in to the version parameter when you define module or provider requirements. As part of the Prerequisites, you’ve already requested the digitalocean provider in provider.tf. Run the following command to review what version requirements it specifies:

  1. cat provider.tf

You’ll find the provider code as follows:

terraform-troubleshooting/provider.tf
terraform {
  required_providers {
    digitalocean = {
      source = "digitalocean/digitalocean"
      version = "~> 2.0"
    }
  }
}
...

In this case, you have requested the digitalocean provider, and set the version to 2.x, meaning that any version starting with 2 will be matched. You could also specify an explicit version, such as 2.10.1.

Version constraints can be more complex than just specifying one version, as is the case above. They can contain one or more groups of conditions, separated by a comma (,). The groups each define an acceptable range of versions and may include operators, such as:

  • >, <, >=, <=: for comparisons, such as >=1.0, which would require the version to be equal to or greater than 1.0.
  • !=: for excluding a specific version—!= 1.0 would deny version 1.0 from being used and requested.
  • ~>: for matching the specified version up to the right-most version part, which is allowed to increment (~>1.5.10 will match 1.5.10 and 1.5.11, but won’t match 1.5.9).

Here are two examples of version constraints with multiple groups:

  • >=1.0, <2.0: allows all versions from the 1.0 series onward, up to 2.0.
  • >1.0, != 1.5: allows versions greater than, but not equal to 1.0, with the exception of 1.5, which it also excludes.

For a potential available version to be selected, it must pass every specified constraint and remain compatible with other modules and providers, as well as the version of Terraform that you’re using. If Terraform deems no combination acceptable, it won’t be able to perform any tasks because the dependencies remain unresolved. When Terraform identifies acceptable versions satisfying the constraints, it uses the latest one available.

In this section, you’ve learned about locking the range of module and resource versions that you can install in your project by specifying version constraints. This is useful when you want stability by using only tested and approved versions of third-party code. In the next section, you’ll configure Terraform to show more verbose logs, which are necessary for bug reports and further debugging in case of crashes.

Enabling Debug Mode

There could be a bug or malformed input within your workflow, which may result in your resources not provisioning as intended. In such rare cases, it’s important to know how to access detailed logs describing what Terraform is doing. They may aid in pinpointing the cause of the error, tell you if it’s user-made, or prompt you to report the issue to Terraform developers if it’s an internal bug.

Terraform exposes the TF_LOG environment variable for setting the level of logging verbosity. There are five levels:

  • TRACE: the most elaborate verbosity, as it shows every step taken by Terraform and produces enormous outputs with internal logs.
  • DEBUG: describes what happens internally in a more concise way compared to TRACE.
  • ERROR: shows errors that prevent Terraform from continuing.
  • WARN: logs warnings, which may indicate misconfiguration or mistakes, but are not critical to execution.
  • INFO: shows general, high-level messages about the execution process.

To specify a desired log level, you’ll have to set the environment variable to the appropriate value:

  1. export TF_LOG=log_level

If TF_LOG is defined, but the value is not one of the five listed verbosity levels, Terraform will default to TRACE.

You’ll now define a Droplet resource and try deploying it with different log levels. You’ll store the Droplet definition in a file named droplets.tf, so create and open it for editing:

  1. nano droplets.tf

Add the following lines:

terraform-troubleshooting/droplets.tf
resource "digitalocean_droplet" "test-droplet" {
  image  = "ubuntu-18-04-x64"
  name   = "test-droplet"
  region = "fra1"
  size   = "s-1vcpu-1gb"
}

This Droplet will run Ubuntu 18.04 with one CPU core and 1GB RAM in the fra1 region; you’ll call it test-droplet. That is all you need to define, so save and close the file.

Before deploying the Droplet, set the log level to DEBUG by running:

  1. export TF_LOG=DEBUG

Then, plan the Droplet provisioning:

  1. terraform plan -var "do_token=${DO_PAT}"

The output will be very long, and you can inspect it more closely to find that each line starts with the level of verbosity (importance) in double brackets. You’ll see that most of the lines start with [DEBUG].

[WARN] and [INFO] are also present; that’s because TF_LOG sets the lowest log level. This means that you’d have to set TF_LOG to TRACE to show TRACE and all other log levels at the same time.

If an internal error occurred, Terraform will show the stack trace and exit, stopping execution. From there, you’ll be able to locate where in the source code the error occurred, and if it’s a bug, report it to Terraform developers. Otherwise, if it’s an error in your code, Terraform will point it out to you, so you can fix it in your project.

Here is how the log output would be when the DigitalOcean backend can’t verify your API token. It throws a user error because of incorrect input:

Output
... digitalocean_droplet.test-droplet: Creating... 2021/01/20 06:54:35 [ERROR] eval: *terraform.EvalApplyPost, err: Error creating droplet: POST https://api.digitalocean.com/v2/droplets: 401 Unable to authenticate you 2021/01/20 06:54:35 [ERROR] eval: *terraform.EvalSequence, err: Error creating droplet: POST https://api.digitalocean.com/v2/droplets: 401 Unable to authenticate you Error: Error creating droplet: POST https://api.digitalocean.com/v2/droplets: 401 Unable to authenticate you on droplets.tf line 1, in resource "digitalocean_droplet" "test-droplet": 1: resource "digitalocean_droplet" "test-droplet" { ...

To disable debug mode and reset the logging verbosity to its default level, clear the TF_LOG environment variable by running:

  1. unset TF_LOG

You’ve now learned to enable more verbose logging modes. They are very useful for diagnosing crashes and unexpected Terraform behavior. In the next section, you’ll review verifying variables and preventing edge cases.

Validating Variables

In this section, you’ll ensure that variables always have sensible and appropriate values according to their type and validation parameters.

In HCL (HashiCorp Configuration Language), when defining a variable you do not necessarily need to specify anything except its name. You would declare an example variable called test_ip like this:

variable "test_ip" { }

You can then use this value through the code, passing its value in when you run Terraform.

While that will work, this definition has two shortcomings: first, you can not pass a value in at runtime; and second, it can be of any type (bool, string, and so on), which may not be suitable for its purpose. To remedy this, you should always specify its default value and type:

variable "test_ip" {
  type    = string
  default = "8.8.8.8"
}

By setting a default value, you ensure that the code referencing the variable remains operational in the event that a more specific value was not provided. When you specify a type, Terraform can validate the new value the variable should be set to, and show an error if it’s non-conforming to the type. An instance of this behavior would be trying to fit a string into a number.

You can provide a validation routine for variables that can give an error message if the validation fails. Examples of validation would be checking the length of the new value if it’s a string, or looking for at least one match with a RegEx expression in the case of structured data.

To add input validation to your variable, define a validation block:

variable "test_ip" {
  type    = string
  default = "8.8.8.8"

  validation {
    condition     = can(regex("\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", var.test_ip))
    error_message = "The provided value is not a valid IP address."
  }
}

Under validation, you can specify two parameters within the curly braces:

  • A condition that accepts a bool it will calculate, which will signify if the validation passes.
  • An error_message that specifies the error message in case the validation does not pass.

In this example, you compute the condition by searching for a regex match in the variable value. You pass that to the can function. The can function returns true if the function that’s passed in as a parameter ran without errors, so it’s useful for checking when a function completed successfully or returned results.

The regex function we’re using here accepts a Regular Expression (RegEx), applies it to a given string, and returns the matched substrings. The RegEx matches four pairs of three digit numbers, separated by dots between them. You can learn more about RegEx by visiting the Introduction to Regular Expressions tutorial.

You now know how to specify a default value for a variable, how to set its type, and how to enable input validation using RegEx expressions.

Conclusion

In this tutorial, you’ve troubleshooted Terraform by enabling debug mode and setting the log verbosity to appropriate levels. You’ve also learned about some of the advanced features of variables, such as declaring validation procedures and setting good defaults. Leaving out default values is a common pitfall that may cause strange issues further along in your project’s development.

For the stability of your project, locking third-party module and provider versions is recommended, since it leaves you with a stable system and an upgrade path when it becomes necessary.

Verification of input values for variables is not confined to matching with regex. For more built-in functions that you can make use of, visit the official docs.

This tutorial is part of the How To Manage Infrastructure with Terraform series. The series covers a number of Terraform topics, from installing Terraform for the first time to managing complex projects.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products


Tutorial Series: How To Manage Infrastructure with Terraform

Terraform is a popular open source Infrastructure as Code (IAC) tool that automates provisioning of your infrastructure in the cloud and manages the full lifecycle of all deployed resources, which are defined in source code. Its resource-managing behavior is predictable and reproducible, so you can plan the actions in advance and reuse your code configurations for similar infrastructure.

In this series, you will build out examples of Terraform projects to gain an understanding of the IAC approach and how it’s applied in practice to facilitate creating and deploying reusable and scalable infrastructure architectures.

About the authors
Default avatar
Savic

author



Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Featured on Community

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more