Introduction
Welcome to our comprehensive guide to Terraform provisioners. Whether you are an experienced DevOps professional or an enthusiastic beginner, understanding the nuances of Terraform can transform your approach to infrastructure automation.
In this guide, we’re focusing on a crucial component: Terraform provisioners. Here’s a quick glance at what we’ll cover:
- A deep dive into what Terraform provisioners are and how they function.
- The debate around why provisioners are considered a last resort.
- The nitty-gritty of effectively using Terraform provisioners, from handling logs in the CLI output to managing failure behavior.
- A comprehensive exploration of various types of provisioners available in Terraform, including the
local-exec
,file
,remote-exec
, and more specialized provisions, such aschef
,habitat
,puppet
, andsalt-masterless
. - A practical walkthrough of installing an Nginx web server using provisioners.
By the end of this guide, you’ll understand the power and limitations of Terraform provisioners and how to incorporate them into your Infrastructure as Code (IaC) practices.
Let’s start with a fundamental question: What exactly are Terraform provisioners?
Table of Contents
Understanding Terraform provisioners
Terraform provisioners are specific built-in components that allow you to execute scripts on a local or remote machine as part of the resource creation or destruction process. They enable additional configuration and setup tasks that can’t be accomplished with Terraform’s declarative syntax alone. Examples of provisioners include local-exec
, remote-exec
, file
, and several others, each serving a unique purpose.
Terraform Provisioners are used as a “last resort” when executing specific commands or scripts on a local or remote machine during resource creation or destruction. They can be helpful for certain system setup tasks where there’s no existing Terraform resource or existing configuration management tool.
In this section, we’ll break down the following aspects of Terraform provisioners:
- Their role in the Terraform ecosystem
- How they interact with resources
- Typical use cases
Role in the Terraform Ecosystem
At its core, Terraform is designed to declare resources, not to handle the configuration of those resources. However, sometimes we may need to perform specific actions on the resources. This is where Terraform provisioners come into play. They act as bridges, filling gaps that Terraform or the resources can’t cover.
Interaction with Resources
Provisioners are added within the resource block. For instance, the following local-exec
provisioner would execute a simple echo command on the local machine when a file resource is created:
resource "local_file" "example" {
content = "Hello, Terraform Provisioners!"
filename = "example.txt"
provisioner "local-exec" {
command = "echo 'File resource created!'"
}
}
This provisioner will trigger when Terraform creates the example.txt
file.
Typical Use Cases
Despite being considered a last resort, provisioners can be incredibly useful in the following scenarios:
- Bootstrapping a system for configuration management tools such as Chef, Puppet, or Ansible. For example, a
remote-exec
provisioner could install Chef on a newly created EC2 instance. - Performing system-specific setup tasks where there’s no corresponding Terraform resource. An example of this might be a
file
provisioner used to upload specific configuration files to a server.
As we progress through this guide, we’ll delve deeper into these use cases, showcasing how Terraform provisioners can enhance and streamline your IaC practices.
Why are Provisioners Considered a Last Resort?
Although Terraform provisioners can be beneficial, they are often considered a last resort in the Terraform ecosystem. The main reasons for this perception are:
- Idempotency and Convergence: Provisioners execute scripts that may not guarantee idempotency – the ability to apply the same operation multiple times without changing the result beyond the initial application. Most resources in Terraform are idempotent and converge towards a specific state, but provisioners could break this behavior, leading to inconsistencies.
- Error Recovery: If a provisioner fails, Terraform will mark the resource as tainted and plan to recreate it on the next “apply” operation. This behavior can lead to cascading failures and make recovering from errors hard.
- Not Suitable for Modifying Resources: Provisioners cannot modify resources after creation; they only run during resource creation or destruction.
Let’s illustrate these points with a simple example. Consider the following local-exec
provisioner:
resource "local_file" "example" {
content = "Hello, Terraform Provisioners!"
filename = "example.txt"
provisioner "local-exec" {
command = "echo 'File resource created!'"
}
}
Here, if the local-exec
provisioner fails to execute the echo
command for any reason, Terraform will mark the local_file
resource as tainted. The next time you run terraform apply
, it will attempt to destroy and recreate the resource, potentially leading to cascading failures.
Moreover, if we wanted to modify the example.txt
file content, the provisioner would not be rerun.
Despite these concerns, there are scenarios where the use of provisioners is justified, particularly when alternative approaches are impractical. In the following sections, we’ll explore how to use Terraform Provisioners effectively, highlighting their utility in real-world scenarios.
How to Effectively Use Terraform Provisioners
Even though provisioners are considered a last resort, they can still be beneficial when used judiciously and effectively. Here’s how to make the most out of Terraform provisioners:
- Understand the self object: The
self
object represents the resource to which the provisioner is attached. For instance, in alocal-exec
provisioner, you can use theself
object to reference attributes of the resource:
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
provisioner "local-exec" {
command = "echo The instance ID is ${self.id}"
}
}
In this example, ${self.id}
will output the ID of the AWS instance.
- Suppressing Provisioner Logs in CLI Output: If you don’t want to show provisioner logs in your CLI output, you can set the
TF_LOG
environment variable to""
. - Creation-Time and Destroy-Time Provisioners: Remember that provisioners can run either during resource creation (
when = "create"
) or destruction (when = "destroy"
). For example, to run a script only when an AWS instance is destroyed:
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
provisioner "local-exec" {
when = "destroy"
command = "echo 'Instance ${self.id} is being destroyed'"
}
}
- Handling Multiple Provisioners: You can attach multiple provisioners to a single resource, and they will execute in the order they are declared.
- Handling Failure Behavior: If a provisioner fails, the resource will be marked as tainted by default. However, you can adjust this behavior with the
on_failure
attribute. Ifon_failure
is set tocontinue
, Terraform will ignore provisioner failures.
These tips should help you make effective use of Terraform provisioners. In the next section, we’ll delve into different types of provisioners and explore how each can be leveraged to enhance your IaC practices.
The self Object
One crucial aspect of Terraform provisioners is understanding the self
object. The self
object represents the resource to which the provisioner is attached and allows us to reference the resource’s attributes within the provisioner block.
Below, we illustrate the self
object’s use in a few scenarios:
- Reference Resource Attributes: The
self
object can be used to reference resource attributes. For example, consider the followingaws_instance
resource with an attachedlocal-exec
provisioned:
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
provisioner "local-exec" {
command = "echo The instance ID is ${self.id}"
}
}
Here, ${self.id}
will output the ID of the AWS instance.
- Pass Resource Attributes to Scripts: The
self
object can also be useful for passing resource attributes as arguments to scripts. For example, suppose we have a script that takes an instance’s public IP address as an argument:
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
provisioner "local-exec" {
command = "./script.sh ${self.public_ip}"
}
}
Here, ${self.public_ip}
will pass the instance’s public IP to the script.
Understanding and effectively using the self
object can significantly enhance your capability to manage and configure resources using Terraform provisioners.
Suppressing Provisioner Logs in CLI Output
While the logs provided by Terraform provisioners can be valuable for debugging, they might also clutter the output or expose sensitive information in certain cases. Luckily, you can suppress these logs from your CLI output.
Here’s how you can accomplish this:
- Set the TF_LOG environment variable: The easiest way to suppress provisioner logs in the CLI output is to set the
TF_LOG
environment variable to an empty value. Here’s an example in a bash environment:
export TF_LOG=""
terraform apply
With TF_LOG
set to an empty value, running terraform apply
will execute without displaying the provisioner logs in the output.
- Using a separate log file: If you want to keep the logs for later debugging but don’t want them in your CLI output, you can direct the logs to a separate file using the
TF_LOG_PATH
environment variable. Here’s an example:
export TF_LOG="TRACE"
export TF_LOG_PATH="terraform.log"
terraform apply
This will execute terraform apply
without displaying the provisioner logs in the CLI output, but all logs will be saved in the terraform.log
file.
Remember that while it can be useful to suppress provisioner logs in some cases, they often provide valuable insights, especially when troubleshooting. So, consider your use case carefully before deciding to suppress them.
Creation-Time Provisioners
In Terraform, provisioners can run at two phases in a resource’s lifecycle: creation-time and destroy-time. Creation-time provisioners are executed when a resource is first created.
Here’s how to work with creation-time provisioners:
- Default behavior: By default, all provisioners are creation-time provisioners. They execute right after the resource is created. Here’s an example:
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
provisioner "local-exec" {
command = "echo Instance ${self.id} has been created"
}
}
In this example, the local-exec
provisioner will execute after the AWS instance is created.
- Explicitly Setting Creation-Time: Although it’s the default behavior, you can explicitly set a provisioner to run at creation-time by using the
when = "create"
directive. This can be helpful for clarity, especially when your Terraform code also includes destroy-time provisioners.
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
provisioner "local-exec" {
when = "create"
command = "echo Instance ${self.id} has been created"
}
}
Creation-time provisioners are particularly useful for configuring a newly created resource or triggering certain actions right after a resource is created.
Destroy-Time Provisioners
Unlike creation-time provisioners, which execute when a resource is first created, destroy-time provisioners are designed to run just before a resource is destroyed. They’re useful for performing cleanup tasks or triggering other actions before a resource’s deletion.
Here’s how to work with destroy-time provisioners:
Defining a Destroy-Time Provisioner: To set a provisioner to run at destroy-time, you’ll use the when = "destroy"
directive. Here’s an example:
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
provisioner "local-exec" {
when = "destroy"
command = "echo Instance ${self.id} is being destroyed"
}
}
In this example, the local-exec
provisioner will execute right before the AWS instance is destroyed.
- Caveats of Destroy-Time Provisioners: If a creation-time provisioner fails, the associated resource gets tainted but not deleted. However, if a destroy-time provisioner fails, the resource is still destroyed.
Also note that if a resource is tainted and being recreated, the destroy-time provisioner won’t run because Terraform avoids destroying a resource that hasn’t been successfully created.
Destroy-time provisioners provide an excellent way to manage tasks that need to be performed before a resource’s deletion. However, use them judiciously to avoid unintended consequences.
Multiple Provisioners
In Terraform, you aren’t limited to using just one provisioner per resource. You can define multiple provisioners for a single resource, and they’ll be executed in the order they are defined.
Here’s how to work with multiple provisioners:
- Defining Multiple Provisioners: List the provisioners one after the other in your resource definition. Here’s an example with an
aws_instance
resource:
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
provisioner "local-exec" {
command = "echo Creating instance ${self.id}"
}
provisioner "remote-exec" {
inline = ["sudo apt-get update", "sudo apt-get install nginx"]
}
provisioner "local-exec" {
when = "destroy"
command = "echo Destroying instance ${self.id}"
}
}
Here, the first local-exec
provisioner will run upon creation of the AWS instance, the remote-exec
provisioner will run commands on the instance, and the second local-exec
provisioner will run when the instance is destroyed.
- Order of Execution: Remember, the order of execution matters. Provisioners are executed in the order they are listed in the resource block. In the example above, the
remote-exec
provisioner will not run until the firstlocal-exec
provisioner has completed.
Using multiple provisioners provides flexibility in configuring and managing resources. However, keep in mind that the failure of any provisioner will taint the resource, so ensure your provisioners are robust and resilient.
Failure Behavior
Understanding how Terraform reacts to provisioner failure is vital. As the execution of provisioners carries the potential for things to go wrong, it’s important to know how to control the resulting behavior.
Here are some important points about provisioner failure behavior:
- Tainting of Resources: By default, if a provisioner fails during the creation of a resource, the resource gets ‘tainted’. A tainted resource will be destroyed and recreated in the next
terraform apply
. Here’s an example:
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
provisioner "local-exec" {
command = "false" // This will fail
}
}
In this example, because the local-exec
provisioner command will fail, the aws_instance
resource will be tainted upon creation.
- Failure Behavior Directives: Terraform allows you to control the failure behavior by setting the
on_failure
directive to either"continue"
,"fail"
(default), or"destroy"
.
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
provisioner "local-exec" {
command = "false" // This will fail
on_failure = "continue"
}
}
In this example, even though the local-exec
provisioner fails, the aws_instance
resource will not be tainted thanks to the on_failure = "continue"
directive.
Understanding provisioner failure behavior and knowing how to control it helps ensure your Terraform code behaves as expected in the face of potential provisioner failures.
Various Types of Terraform Provisioners
Terraform offers a variety of provisioners to suit your infrastructure needs. Understanding these different types is vital to manage your resources effectively.
Here are some of the key provisioners available:
- local-exec Provisioner: Executes a command on the machine where Terraform is run.
- remote-exec Provisioner: Executes a command on the remote resource.
- file Provisioner: Copies files or directories from the local machine to the remote resource.
- Chef Provisioner: Executes Chef recipes on the remote resource.
- Puppet Provisioner: Executes Puppet configurations on the remote resource.
- Habitat Provisioner: Installs and configures the Habitat supervisor on the remote resource.
- Salt-masterless Provisioner: Executes SaltStack states on the remote resource without requiring a Salt master.
These provisioners are powerful tools that can help you configure and manage your resources. However, they should be used sparingly, as they can lead to procedural complexity and non-declarative behaviors.
For a deeper understanding and practical application of these provisioners, the subsequent sections of this blog post will cover each type in detail.
The local-exec Provisioner
The local-exec
provisioner in Terraform invokes a local executable, typically a shell script, on the same machine where Terraform is running. This can be useful for various tasks, such as invoking CLI commands, running scripts, or invoking other tools.
Here’s how you can use it:
- Basic Usage: To use the
local-exec
provisioner, define it in a resource block and provide a command to execute. Here’s an example:
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
provisioner "local-exec" {
command = "echo Created instance ${self.id}"
}
}
In this example, the local-exec
provisioner executes the echo
command after creating an AWS instance.
- Accessing Resource Attributes: Note that in the command, we can reference the resource’s attributes. For instance,
${self.id}
refers to the ID of the AWS instance being created.
Remember, the local-exec
provisioner executes on the machine running Terraform, not on the resource being created. Hence, it should be used for tasks related to local setup or configuration, and not for configuring the remote resource.
The Connection Block (Prerequisites)
Before diving into remote provisioners, it’s crucial to understand the connection
block in Terraform. This block is used to specify settings needed for remote connections, which are essential for the remote-exec
and file
provisioners.
Here are the key details:
- Purpose of the Connection Block: The
connection
block provides Terraform with the necessary information to connect to a resource remotely. It tells Terraform how to communicate with the resource.
- Supported Connection Types: Terraform currently supports
ssh
andwinrm
types of connections.
- Defining a Connection Block: The
connection
block is defined within a resource or provisioner block. Here’s an example with anaws_instance
resource:
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
connection {
type = "ssh"
user = "admin"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
provisioner "file" {
source = "script.sh"
destination = "/home/admin/script.sh"
}
}
In this example, an aws_instance
is created, and a file (script.sh
) is copied from the local system to the instance using the file
provisioner. The connection
block details how Terraform should connect to the instance for this purpose.
Please note that you need a proper SSH setup with the necessary keys and permissions for the connection
block to work correctly.
The File Provisioner
The file
provisioner in Terraform is used to manage files and directories on a machine created through Terraform. This provisioner can copy files from your local system to a remote resource.
Key aspects of the file
provisioner include:
- Copying Files: This provisioner can copy a file from the local system to the remote resource. Here’s an example:
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
connection {
type = "ssh"
user = "admin"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
provisioner "file" {
source = "localfile.txt"
destination = "/home/admin/remotefile.txt"
}
}
In this example, the file
provisioner copies the localfile.txt
from your local system to the AWS instance at the specified destination path.
- Copying Directories: The
file
provisioner can also copy entire directories. Thesource
anddestination
parameters function in the same way:
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
connection {
type = "ssh"
user = "admin"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
provisioner "file" {
source = "config/"
destination = "/home/admin/config/"
}
}
This example copies the entire config/
directory to the remote AWS instance.
Remember, the file
provisioner requires a connection
block to define how Terraform should connect to the remote resource.
The remote-exec Provisioner
The remote-exec
provisioner in Terraform allows you to execute commands on a remote resource after it’s created. This can be useful to perform initial setup tasks on a new resource.
Here’s how you can use it:
- Basic Usage: The
remote-exec
provisioner requires aconnection
block to specify how to connect to the remote resource. Here’s an example:
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
connection {
type = "ssh"
user = "admin"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
provisioner "remote-exec" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx"
]
}
}
In this example, the remote-exec
provisioner executes a series of commands on the AWS instance after it’s created. These commands update the system and install Nginx on the instance.
- Accessing Resource Attributes: Similar to the
local-exec
provisioner, you can reference the resource’s attributes in the command. For example,${self.public_ip}
refers to the public IP of the AWS instance being created. - Inline and Script: The
remote-exec
provisioner supports executing an array of commands inline or running a script located on the local machine or available via a URL.
The remote-exec
provisioner can be a powerful tool for initial resource setup, but it should be used sparingly to avoid procedural complexity.
Chef Provisioner
Terraform’s chef
provisioner allows you to integrate your infrastructure’s creation with the powerful configuration management tool, Chef. This provisioner bootstraps Chef Client on a newly created or existing machine, allowing you to manage its configuration through Chef Server.
Here’s what you need to know:
- Chef Server: Chef Server is a hub for configuration data. The Chef Infra Server stores cookbooks, the policies applied to nodes, and metadata describing each registered node in your infrastructure.
- Chef Client: Chef Client is an agent that runs locally on every node managed by Chef.
- Bootstrapping with Terraform: Terraform’s
chef
provisioner allows you to bootstrap the Chef Client on a new node, enabling it to communicate with the Chef Server.
Here’s a basic example:
resource "aws_instance" "web" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
provisioner "chef" {
server_url = "https://api.chef.io/organizations/org_name"
run_list = ["cookbook::recipe"]
node_name = "webserver"
recreate_client = true
user_name = "admin"
user_key = file("~/.chef/admin.pem")
validation_client_name = "chef-validator"
validation_key_path = "~/.chef/chef-validator.pem"
environment = "_default"
ssl_verify_mode = ":verify_peer"
}
}
In this example, an aws_instance
is created, and the chef
provisioner bootstraps Chef Client on this instance. It connects to the specified Chef Server (server_url
) and applies the specified run list to the node.
It’s essential to manage your Chef Server, clients, and keys securely to maintain the integrity of your infrastructure.
Remember, the chef
provisioner is deprecated as of Terraform 0.13.0 and above. You are encouraged to use other tools like the remote-exec
provisioner or configuration management software such as Ansible for more complex setups.
Habitat Provisioner
The habitat
provisioner in Terraform is used for installing and managing services in a resource via Habitat. Habitat is an open-source project by Chef that provides automation capabilities for defining, packaging, and delivering applications to almost any environment with any operating system, platform, or cloud.
Here’s how you can use it:
- Installation: The
habitat
provisioner installs Habitat in a remote resource, either from a specific release URL or the latest stable version. - Service Management: After installation, the provisioner can start, load, or unload services, control service groups, and bind services to each other.
Here’s an example:
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
provisioner "habitat" {
version = "0.85.0"
peers = ["1.2.3.4", "5.6.7.8"]
use_sudo = true
service {
name = "core/nginx"
topology = "standalone"
user_toml = <<-USER_TOML
[server]
listen = "0.0.0.0"
port = 80
USER_TOML
}
}
}
In this example, the habitat
provisioner installs a specific version of Habitat on an AWS instance, specifies peers for the supervisor, and starts an Nginx service with specific configurations.
Keep in mind that the habitat
provisioner requires a connection block to specify how to connect to the remote resource.
Puppet Provisioner
The puppet
provisioner in Terraform is an excellent tool for those who use Puppet for configuration management. Puppet is an open-source software configuration management and deployment tool available in free and commercial versions.
Here are some key aspects:
- Puppet Master: Puppet follows a master-agent architecture. The Puppet master server controls the configuration information for Puppet agents.
- Puppet Agent: Puppet agents are the servers managed by the Puppet master server.
- Puppet Integration with Terraform: Terraform’s
puppet
provisioner allows you to bootstrap the Puppet agent on a new node, enabling it to communicate with the Puppet master.
Here’s an example:
resource "aws_instance" "web" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
provisioner "puppet" {
server = "puppet.example.com"
extension_requests = {
pp_role = "webserver"
pp_image = "ami-0c94855ba95c574c8"
}
}
}
In this example, an aws_instance
is created, and the puppet
provisioner bootstraps a Puppet agent on this instance. It connects to the specified Puppet master (server
) and sends extension requests.
Please note that as of Terraform 0.13.0 and later, the puppet
provisioner is deprecated. It is recommended to use other tools like the remote-exec
provisioner or configuration management software such as Ansible for more complex setups.
Salt-masterless Provisioner
The salt-masterless
provisioner in Terraform allows you to configure your infrastructure using Salt in a masterless setup. Salt is a Python-based open-source configuration management software and remote execution engine.
Here’s how it works:
- Local Salt States: This provisioner allows you to upload your local Salt States to the remote resource. Salt States represent the configuration of each component of your infrastructure.
- Applying Configuration: Once uploaded, the provisioner will apply the states with
salt-call
, effectively configuring your resource.
Here’s an example:
resource "aws_instance" "web" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
provisioner "salt-masterless" {
local_state_tree = "${path.module}/salt-states"
remote_state_tree = "/srv/salt"
}
}
In this example, the salt-masterless
provisioner is used to upload local Salt States from ${path.module}/salt-states
directory to the /srv/salt
directory on an AWS instance. After the states are uploaded, salt-call
applies them to configure the resource.
Please note that the salt-masterless
provisioner requires a connection block to specify how to connect to the remote resource.
Practical Application: Installing Nginx Web Server Using Provisioners
In this section, we will demonstrate a practical application of Terraform provisioners by installing an Nginx web server on an AWS EC2 instance using the remote-exec
provisioner.
Here’s how we do it:
- Creating AWS Instance: We first create an AWS instance using the
aws_instance
resource. - Using remote-exec Provisioner: The
remote-exec
provisioner is then used to execute commands on the remote instance to install Nginx.
Here’s an example:
resource "aws_instance" "nginx" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
provisioner "remote-exec" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx",
"sudo systemctl start nginx"
]
}
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
}
In this example, we first create an AWS EC2 instance. We then use the remote-exec
provisioner to run a sequence of commands. These commands update the package lists, install Nginx, and start the Nginx service. The connection
block specifies the connection details to the remote instance.
Please note that you need to replace "ami-0c94855ba95c574c8"
with the appropriate Amazon Machine Image (AMI) ID for your region and the path to your SSH private key file in the private_key
field of the connection
block.
FAQ
What are the provisioners in Terraform?
Provisioners in Terraform are components that enable users to perform specific actions on local or remote machines during the resource creation or destruction stage. There are several types of provisioners, such as ‘local-exec’ (executes a local command), ‘remote-exec’ (runs a command on a remote resource), and ‘file’ (copies files to a resource). Other provisioners interact with configuration management tools like Chef, Puppet, and Salt. While provisioners can offer additional control and flexibility, they’re typically considered a ‘last resort’ as they introduce an imperative element into the otherwise declarative Terraform system.
What are the alternatives to Terraform provisioners?
Alternatives to Terraform provisioners often involve using more native or declarative approaches. Depending on the specific task, solutions can include using cloud-init scripts, Docker, Kubernetes configurations, or serverless technologies like AWS Lambda. For example, a cloud-init script could be used to install software on an instance. Configuration management tools like Ansible, Chef, Puppet, or Salt can also be utilized directly to configure servers. These alternatives are preferred because they maintain the declarative nature of Terraform, avoid potential issues related to the sequencing and lifecycle of resources, and are often easier to maintain and troubleshoot.
What are providers in Terraform?
Providers in Terraform serve as a bridge between Terraform and the targeted service (like a cloud or SaaS platform), interpreting the API interactions needed to create, read, update, and delete resources. Each provider offers a set of resource types corresponding to specific services or features of the target platform. For example, the AWS provider offers resources like aws_instance for EC2 instances, while the Google Cloud provider has resources like google_compute_instance. Providers also expose data sources that fetch data from the service. Terraform users can manage a vast array of services consistently and declaratively using providers.
Can Terraform provisioner be added to any resource block?
Terraform provisioners can technically be added to any resource block, although their usage is generally considered a last resort. Provisioners are used to execute scripts on a local or remote machine as part of resource creation or destruction. Despite being flexible, they introduce an imperative component to Terraform’s otherwise declarative nature. This could lead to complications, especially in failure scenarios, due to difficulty managing side effects and statefulness. Therefore, alternatives such as cloud-init scripts, containerization, configuration management tools, or directly utilizing cloud-specific tools and services are often preferred.
Conclusion: The Power and Limitations of Terraform Provisioners
As we’ve seen throughout this blog post, Terraform provisioners are powerful tools that allow us to perform specific actions on our resources during the creation or destruction stages. This functionality can be a boon in many scenarios, enabling us to automate many aspects of infrastructure management.
However, it’s essential to be aware of the following:
- Power of Provisioners: Provisioners can perform various actions, from running scripts on local and remote instances to managing specific configurations using management tools such as Chef, Puppet, or Salt. They provide an added layer of flexibility and control over your infrastructure.
- Last Resort: Despite their power, Terraform’s official documentation often refers to provisioners as a ‘last resort.’ This is because provisioners introduce an imperative element into what is otherwise a declarative system, complicating the understanding of your infrastructure’s desired state.
- No Error Recovery: If a provisioner fails, Terraform will mark the resource as ‘tainted’ but won’t be able to recover or retry automatically.
- Limited Scope: Provisioners can only run on resources that provide a physical server. They won’t work for database resources, VPCs, or other non-server resources.
By understanding these limitations and being judicious in our use of provisioners, we can leverage their strengths without compromising the integrity and reliability of our infrastructure.
References
To learn more about Terraform provisioners and related topics, you may find the following resources helpful:
- Terraform Provisioners: This is the official documentation page for Terraform provisioners. It provides in-depth explanations, additional examples, and insights into many supported provisioners.
- Terraform AWS Provider: This documentation page contains detailed information about creating and managing AWS resources using Terraform.
- Terraform Style Guide: This guide offers best practices for writing clear, maintainable, and idiomatic Terraform code.
- Infrastructure as Code (IaC): Martin Fowler’s detailed article on Infrastructure as Code, the philosophy behind tools like Terraform.
- Introduction to the Remote-Exec Provisioner: This tutorial provides a practical, step-by-step guide to using the
remote-exec
provisioner.
Please remember that Terraform provisioners are a last resort in most scenarios, and there may be other methods for managing configurations and performing operations on your resources.