Debugging OpenTofu with Remote Execution
OpenTofu is an open-source fork of Terraform, a popular infrastructure-as-code (IaC) tool. It was created in response to HashiCorp's decision to change the license for Terraform from the Mozilla Public License (MPL) to the Business Source License (BSL) in August 2023. This change restricted the use of Terraform in production environments, leading to concerns among the open-source community. OpenTofu is a drop-in replacement for Terraform v1.6.x and is fully backward compatible with all prior versions. It employs a declarative syntax similar to the Hashicorp Configuration Language (HCL) and supports the same workflow as Terraform, including the write -> plan -> apply process.
OpenTofu is committed to being truly open source, community-driven, impartial, layered and modular, and backward compatible. The project aims to provide a reliable and accessible IaC tool for the tech community, ensuring that the essential building blocks of the modern internet remain truly open source. We are committed to using OpenTofu as our IaC tool of choice, leveraging its open-source nature and community-driven development to ensure the long-term sustainability and reliability of our infrastructure deployments.
Remote execution is a feature of OpenTofu that allows you to execute commands on a remote machine instead of your local machine. This can be useful when you need to debug issues that are specific to the remote environment, such as permissions or network connectivity.
How Remote Execution Works in OpenTofu
To use remote execution, you will need to have a remote machine that is running the OpenTofu binary and has access to the necessary resources. You can use any machine that meets these requirements, such as a virtual machine or a remote server.
Once you have a remote machine set up, you can use the OpenTofu CLI to execute commands on it. The following command will execute the OpenTofu plan command on the remote machine:
opentofu plan -out=tfplan -remote=ssh://user@remote_machine_ip
This command will connect to the remote machine using SSH and execute the OpenTofu plan command. The -out
flag specifies the name of the plan file that will be generated, and the -remote
flag specifies the remote machine to connect to.
You can also use the -var
flag to pass variables to the remote machine. For example, the following command will pass the aws_access_key
and aws_secret_key
variables to the remote machine:
opentofu plan -out=tfplan -remote=ssh://user@remote_machine_ip -var 'aws_access_key=< access_key >' -var 'aws_secret_key=< secret_key >'
Once the plan command has completed, you can use the OpenTofu apply command to apply the changes to the remote environment. The following command will apply the changes specified in the tfplan
file:
opentofu apply tfplan -remote=ssh://user@remote_machine_ip
If you encounter any errors during the deployment process, you can use the OpenTofu output command to view the output of the remote machine.
The following command will display the output of the aws_instance
resource:
opentofu output aws_instance_public_ip -remote=ssh://user@remote_machine_ip
You can also use the OpenTofu state command to view the current state of the remote environment.
The following command will display the current state of the aws_instance
resource:
opentofu state show aws_instance.example -remote=ssh://user@remote_machine_ip
If you need to make changes to the remote environment, you can use the OpenTofu console command to open an interactive console session on the remote machine.
The following command will open a console session on the remote machine:
opentofu console -remote=ssh://user@remote_machine_ip
Once you are in the console session, you can use the OpenTofu CLI to make changes to the remote environment. For example, the following command will create a new AWS instance:
aws_instance.example = aws_instance.new(
ami = "ami-0c94855ba95c574c8",
instance_type = "t2.micro",
subnet_id = "subnet-0123456789abcdef0",
)
When you are finished making changes, you can use the OpenTofu apply command to apply the changes to the remote environment.
Here is an example of a OpenTofu configuration file that uses remote execution to deploy an AWS instance:
provider "aws" {
region = "us-west-2"
}
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
subnet_id = "subnet-0123456789abcdef0"
provisioner "remote-exec" {
inline = [
"sudo yum update -y",
"sudo amazon-linux-extras install -y nginx1",
"sudo service nginx start",
]
}
}
output "aws_instance_public_ip" {
value = aws_instance.example.public_ip
}
}
This configuration file uses the remote-exec
provisioner to execute commands on the remote AWS instance after it has been created. The inline
argument specifies a list of commands to execute, which in this case updates the package manager, installs Nginx, and starts the Nginx service.
To deploy this configuration using remote execution, you can use the following command:
opentofu apply -out=tfplan -remote=ssh://user@remote_machine_ip
This command will connect to the remote machine using SSH and execute the OpenTofu apply command. The -out
flag specifies the name of the plan file that will be generated, and the -remote
flag specifies the remote machine to connect to.
Once the deployment has completed, you can use the OpenTofu output command to view the public IP address of the AWS instance:
opentofu output aws_instance_public_ip -remote=ssh://user@remote_machine_ip
This will display the public IP address of the AWS instance, which you can use to access the Nginx service
Here is an example of a OpenTofu configuration file that uses the null_resource resource to execute commands on the remote machine during the deployment process:
provider "aws" {
region = "us-west-2"
}
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c574c8"
instance_type = "t2.micro"
subnet_id = "subnet-0123456789abcdef0"
}
resource "null_resource" "example" {
provisioner "remote-exec" {
inline = [
"sudo yum update -y",
"sudo amazon-linux-extras install -y nginx1",
"sudo service nginx start",
]
connection {
type = "ssh"
user = "ec2-user"
host = aws_instance.example.public_ip
private_key = file("~/.ssh/id_rsa")
}
}
}
output "aws_instance_public_ip" {
value = aws_instance.example.public_ip
}
This configuration file uses the null_resource
resource to execute commands on the remote AWS instance during the deployment process. The provisioner
block specifies the commands to execute, and the connection block specifies the connection
details for the remote machine.
Here are some best practices for using remote execution in OpenTofu:
-
Use the
remote-exec
provisioner for one-time setup tasks, such as installing packages or configuring services. -
Use the
null_resource
resource for tasks that need to be executed multiple times during the deployment process, such as running scripts or updating configuration files. -
Use the
connection
block to specify the connection details for the remote machine, such as the user name, host name, and private key. -
Use the
inline
argument to specify a list of commands to execute on the remote machine. -
Use the
interpreter
argument to specify the command interpreter to use on the remote machine, such as/bin/bash
or/bin/sh
. -
Use the
working_dir
argument to specify the working directory for the commands on the remote machine. -
Use the
environment
argument to specify environment variables for the commands on the remote machine. -
Use the
timeouts
argument to specify the maximum amount of time to wait for the commands to complete on the remote machine.
By following these best practices, you can ensure that your OpenTofu configurations are robust and reliable, and that you can effectively debug issues that may arise during the deployment process.
Conclusion
OpenTofu remote execution is a powerful feature that allows users to execute scripts or shell commands on remote machines as part of resource creation or deletion. However, it should be used with caution and only as a last resort. The main reason for this is that provisioners add considerable complexity and uncertainty to OpenTofu usage, making it difficult to model the actions of provisioners as part of a plan. Successful use of provisioners requires coordinating more details than OpenTofu usage usually requires, such as direct network access to servers, issuing OpenTofu credentials to log in, and ensuring that all necessary external software is installed.
Moreover, OpenTofu provisioners can be slow and may cause performance issues, especially when dealing with large and highly connected configuration graphs or resources with very large numbers of instances. This can lead to long plan times, making it troublesome for large-scale deployments.
Another complexity of OpenTofu remote execution is that it lacks idempotence, meaning that it will only run "file", "remote-exec", or "local-exec" on resources once. If the commands in a "remote-exec" are changed or a file from the provisioner "file" is updated, OpenTofu will not re-run the provisioner automatically. This can be a drawback compared to other tools like Ansible, which can achieve the same tasks as OpenTofu but with idempotence.
In conclusion, while OpenTofu remote execution can be a useful feature, it should be used judiciously and with careful consideration of its complexities. It is recommended to use provisioners only when there is no other option and to explore alternative solutions that can provide more efficient and idempotent infrastructure provisioning.