Use Terraform to Provision Linode Environments
Updated by Linode Contributed by Damaso Sanoja
Infrastructure as code (IaC) is a development and operations methodology that allows server deployments and software configuration to be represented as code. This methodology reduces the chance for human error, makes complex systems more manageable, eases collaboration on systems engineering projects, and offers a number of other benefits.
Terraform is an IaC tool that focuses on creating, modifying, and destroying servers, instead of managing the software on those servers. Terraform offers plugins to interface with different hosting providers, and an official Linode plugin is available. This guide will show how to get started with Terraform and Linode.
Linodes created with Terraform can be further configured with container systems like Docker, or with configuration management software like Salt, Puppet, Ansible, or Chef.
CautionThe configurations and commands used in this guide will result in multiple Linodes being added to your account. Be sure to monitor your account closely in the Linode Manager to avoid unwanted charges.
Before You Begin
This guide will show how to install and use the Terraform client software from a Linux system. Terraform can be installed on other operating systems, and the instructions for those platforms will be analogous to the commands presented in this guide.
Note
When following this guide, your Linux user may need sudo privileges in order to install supplementary software packages.You will need a personal access token for Linode’s v4 API to use with Terraform. Follow the Getting Started with the Linode API to get a token.
Note
Any Personal Access Tokens generated from the previous Linode Manager are API v3 tokens and will not work with Terraform’s Linode provider.
Install Terraform
The installation steps in this section are for Linux operating systems. To install Terraform on a different operating system, like macOS, see Terraform’s downloads page. Once installed, skip to Building with the Terraform Provider.
NoteThe Terraform Provider for Linode requires Terraform version 0.12.0+. The examples in this guide were written to be compatible with Terraform version 0.11 and will be updated in the near future.
Make a Terraform project directory in your home directory and then navigate to it:
mkdir ~/terraform cd ~/terraform
Download the following files from Terraform’s website. Example
wget
commands are listed using the latest version available at time of publishing (0.12.5). You should inspect the links on the download page to see if a newer version is available and update thewget
commands to use those URLs instead:The 64-bit Linux
.zip
archivewget https://releases.hashicorp.com/terraform/0.12.5/terraform_0.12.5_linux_amd64.zip
The SHA256 checksums file
wget https://releases.hashicorp.com/terraform/0.12.5/terraform_0.12.5_SHA256SUMS
The checksum signature file
wget https://releases.hashicorp.com/terraform/0.12.5/terraform_0.12.5_SHA256SUMS.sig
Verify the Download
Import the HashiCorp Security GPG key (listed on the HashiCorp Security page under Secure Communications):
gpg --recv-keys 51852D87348FFC4C
The output should show that the key was imported:
gpg: /home/user/.gnupg/trustdb.gpg: trustdb created gpg: key 51852D87348FFC4C: public key "HashiCorp Security
" imported gpg: no ultimately trusted keys found gpg: Total number processed: 1 gpg: imported: 1 Note
If you receive errors that indicate thedirmngr
software is missing or inaccessible, installdirmngr
using your package manager and run the GPG command again.Verify the checksum file’s GPG signature:
gpg --verify terraform*.sig terraform*SHA256SUMS
The output should contain the
Good signature from "HashiCorp Security <security@hashicorp.com>"
confirmation message:gpg: Signature made Wed 15 Aug 2018 10:07:05 PM UTC gpg: using RSA key 51852D87348FFC4C gpg: Good signature from "HashiCorp Security <security@hashicorp.com>" [unknown] gpg: WARNING: This key is not certified with a trusted signature! gpg: There is no indication that the signature belongs to the owner. Primary key fingerprint: 91A6 E7F8 5D05 C656 30BE F189 5185 2D87 348F FC4C
Verify that the fingerprint output matches the fingerprint listed in the Secure Communications section of the HashiCorp Security page.
Verify the
.zip
archive’s checksum:sha256sum -c terraform*SHA256SUMS 2>&1 | grep OK
The output should show the file’s name as given in the
terraform*SHA256SUMS
file:terraform_0.12.5_linux_amd64.zip: OK
Configure the Terraform Environment
Unzip
terraform_*_linux_amd64.zip
to your~/terraform
directory:unzip terraform_*_linux_amd64.zip
Note
If you receive an error that indicatesunzip
is missing from your system, install theunzip
package and try again.Edit your
~./profile
to include the~/terraform
directory in your PATH. Then, reload the Bash profile:echo 'export PATH="$PATH:$HOME/terraform"' >> ~/.profile source ~/.profile
Verify Terraform can run by simply calling it with no options or arguments:
terraform
Usage: terraform [-version] [-help]
[args] The available commands for execution are listed below. The most common, useful commands are shown first, followed by less common or more advanced commands. If you're just getting started with Terraform, stick with the common commands. For the other commands, please read the help and docs before usage. Common commands: apply Builds or changes infrastructure console Interactive console for Terraform interpolations destroy Destroy Terraform-managed infrastructure env Workspace management fmt Rewrites config files to canonical format get Download and install modules for the configuration graph Create a visual graph of Terraform resources import Import existing infrastructure into Terraform init Initialize a Terraform working directory output Read an output from a state file plan Generate and show an execution plan providers Prints a tree of the providers used in the configuration push Upload this Terraform module to Atlas to run refresh Update local state file against real resources show Inspect Terraform state or plan taint Manually mark a resource for recreation untaint Manually unmark a resource as tainted validate Validates the Terraform files version Prints the Terraform version workspace Workspace management All other commands: debug Debug output management (experimental) force-unlock Manually unlock the terraform state state Advanced state management
Building with the Linode Provider
Terraform uses a declarative approach in which configuration files specify the desired end-state of the infrastructure, so the examples in this guide will simply list the Linodes that we want to create. Terraform can understand two types of configuration files: JSON, and HashiCorp Configuration Language (HCL). This guide uses the HCL format, and HCL files end in the .tf
extension.
Create the file
linode-terraform-web.tf
in your~/terraform
directory with the snippet below. Fill in your Linode API token, public SSH key, and desired root password where indicated.- ~/terraform/linode-terraform-web.tf
-
1 2 3 4 5 6 7 8 9 10 11 12 13
provider "linode" { token = "YOUR_LINODE_API_TOKEN" } resource "linode_instance" "terraform-web" { image = "linode/ubuntu18.04" label = "Terraform-Web-Example" group = "Terraform" region = "us-east" type = "g6-standard-1" authorized_keys = [ "YOUR_PUBLIC_SSH_KEY" ] root_pass = "YOUR_ROOT_PASSWORD" }
This snippet creates a Linode 2GB labelled
Terraform-Web-Example
in aTerraform
Linodes group. While the server’s software won’t be configured in this guide, we can imagine for now that the Linode acts as a webserver.Note
See Terraform’s documentation for more information on configuration syntax.Initialize the Terraform configuration:
terraform init
Terraform will confirm successful initialization:
Initializing provider plugins... - Checking for available provider plugins on https://releases.hashicorp.com... - Downloading plugin for provider "linode" (1.0.0)... The following providers do not have any version constraints in configuration, so the latest version was installed. To prevent automatic upgrades to new major versions that may contain breaking changes, it is recommended to add version = "..." constraints to the corresponding provider blocks in configuration, with the constraint strings suggested below. * provider.linode: version = "~> 1.0" Terraform has been successfully initialized!
Note
If an error occurs, run the command again in debug mode:
TF_LOG=debug terraform init
Run Terraform’s plan command:
terraform plan
Refreshing Terraform state in-memory prior to plan... The refreshed state will be used to calculate this plan, but will not be persisted to local or remote state storage. ------------------------------------------------------------------------ An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: + linode_instance.terraform-web id: <computed> alerts.#: <computed> authorized_keys.#: "1" authorized_keys.0: "ssh-rsa ..." backups.#: <computed> backups_enabled: <computed> boot_config_label: <computed> group: "Terraform" image: "linode/ubuntu18.04" ip_address: <computed> ipv4.#: <computed> ipv6: <computed> label: "web" private_ip_address: <computed> region: "us-east" root_pass: <sensitive> specs.#: <computed> status: <computed> swap_size: <computed> type: "g6-standard-1" watchdog_enabled: "true" Plan: 1 to add, 0 to change, 0 to destroy. ------------------------------------------------------------------------ Note: You didn't specify an "-out" parameter to save this plan, so Terraform can't guarantee that exactly these actions will be performed if "terraform apply" is subsequently run.
terraform plan
won’t take any action or make any changes on your Linode account. Instead, an analysis is done to determine which actions (i.e. Linode instance creations, deletions, or modifications) are required to achieve the state described in your configuration.Note
Debug mode can be applied to the plan command if you need to perform troubleshooting:
TF_LOG=debug terraform plan
If there are no errors, start the deployment:
terraform apply
You’ll be asked to confirm the action. Enter
yes
and press Enter:Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes linode_instance.terraform-web: Creating... alerts.#: "" => "<computed>" authorized_keys.#: "" => "1" authorized_keys.0: "" => "ssh-rsa ..." backups.#: "" => "<computed>" backups_enabled: "" => "<computed>" boot_config_label: "" => "<computed>" group: "" => "Terraform" image: "" => "linode/ubuntu18.04" ip_address: "" => "<computed>" ipv4.#: "" => "<computed>" ipv6: "" => "<computed>" label: "" => "web" private_ip_address: "" => "<computed>" region: "" => "us-east" root_pass: "<sensitive>" => "<sensitive>" specs.#: "" => "<computed>" status: "" => "<computed>" swap_size: "" => "<computed>" type: "" => "g6-standard-1" watchdog_enabled: "" => "true" linode_instance.terraform-web: Still creating... (10s elapsed) linode_instance.terraform-web: Still creating... (20s elapsed) linode_instance.terraform-web: Still creating... (30s elapsed) linode_instance.terraform-web: Still creating... (40s elapsed) linode_instance.terraform-web: Still creating... (50s elapsed) linode_instance.terraform-web: Creation complete after 52s (ID: 10975739) Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Visit the Linode Manager. You should see that the
Terraform-Web-Example
Linode has been added to your account.
Provision Additional Servers
In the previous step, you used Terraform to provision a Linode that could act as a webserver. To illustrate how to add another Linode via Terraform, let’s say you now also need a separate database server. To do this, you can create another Terraform configuration file for the second Linode.
NoteWhen deploying multiple Linodes with Terraform, remember that you need to assign a unique name for each Linode.
In production environments, your SSH key and root password should be unique for each resource. Having said that, the example Linodes in this guide will share keys and root passwords.
Create another file called
linode-terraform-db.tf
. Substitute in your SSH key and root password where indicated. Do not deletelinode-terraform-web.tf
.- ~/terraform/linode-terraform-db.tf
-
1 2 3 4 5 6 7 8 9 10
resource "linode_instance" "terraform-db" { image = "linode/centos7" label = "Terraform-Db-Example" group = "Terraform" region = "us-south" type = "g6-standard-1" swap_size = 1024 authorized_keys = [ "YOUR_PUBLIC_SSH_KEY" ] root_pass = "YOUR_ROOT_PASSWORD" }
You may notice that the Terraform provider is not specified in this file as it was in
linode-terraform-web.tf
. Terraform loads into memory and concatenates all files present in the working directory which have a.tf
extension. This means you don’t need to define the provider again in new.tf
files.Note
In this configuration a new parameter,swap_size
, is used to override the default value of 512MB. You can check all available options for the Linode Terraform provider in the plugin’s GitHub repository readme.md.Review the Terraform plan:
terraform plan
Terraform knows that your Terraform-Web-Example Linode still exists, so the plan only shows that the Terraform-Db-Example Linode would be created:
An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: + linode_instance.terraform-db # [...] Plan: 1 to add, 0 to change, 0 to destroy. # [...]
Apply the configuration:
terraform apply
Check the Linode Manager to ensure that the
Terraform-Db-Example
Linode was added to your account.
Destroy Servers
Terraform includes a destroy command to remove servers managed by Terraform. Prior to running the destroy command, you can run the plan command with the -destroy
option to see which servers would be removed:
terraform plan -destroy
# [...]
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
- linode_instance.terraform-db
- linode_instance.terraform-web
Plan: 0 to add, 0 to change, 2 to destroy.
# [...]
Run the destroy command to remove the servers from the last section. Confirm the deletion with
yes
when prompted:terraform destroy
Verify that the Linodes were removed in the Linode Manager.
Remove the configuration files:
rm *.tf
Provision Multiple Servers Using Variables
Up to this point, the procedure for adding a new node to your infrastructure was to create a new file and run terraform apply
. There are some downsides to this approach:
You need to repeatedly copy certain values across each file, like your SSH key.
If you want to change certain parameters across multiple servers, like the Linodes’
group
attribute, then you need to change each file.
To solve these issues, Terraform allows you to declare variables and insert those variables’ values into your configurations:
Create a new file to define your variable names and optional default variable values. This file can have any name; for this example, use
variables.tf
:- ~/terraform/variables.tf
-
1 2 3 4 5 6
variable "token" {} variable "authorized_keys" {} variable "root_pass" {} variable "region" { default = "us-southeast" }
Create the file
terraform.tfvars
to store your variables’ values. Substitute in your API token, SSH key, and root password where indicated. You cannot change this file’s name after creating it.- ~/terraform/terraform.tfvars
-
1 2 3
token = "YOUR_LINODE_API_TOKEN" authorized_keys = "YOUR_PUBLIC_SSH_KEY" root_pass ="YOUR_ROOT_PASSWORD"
Create a new configuration file called
linode-terraform-template.tf
:- ~/terraform/linode-terraform-template.tf
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
# Linode Provider definition provider "linode" { token = var.token } # Example Web Server resource "linode_instance" "terraform-web" { image = "linode/centos7" label = "Terraform-Web-Example" group = "Terraform" region = var.region type = "g6-standard-1" swap_size = 1024 authorized_keys = [var.authorized_keys] root_pass = var.root_pass } # Example Database Server resource "linode_instance" "terraform-db" { image = "linode/ubuntu18.04" label = "Terraform-Db-Example" group = "Terraform" region = var.region type = "g6-standard-1" swap_size = 1024 authorized_keys = [var.authorized_keys] root_pass = var.root_pass }
Check your new deployment for errors:
terraform plan
Apply the configuration:
terraform apply
The end result should be the same as before.
Modify Live Deployments
Terraform allows you to change a server’s name, size, or other attributes without needing to destroy and rebuild it. Terraform handles this through changes to the configuration files.
CautionChanging the size of your Linode will force your server to be powered off and migrated to a different host in the same data center. The associated disk migration will take approximately 1 minute for every 3-5 gigabytes of data. See our Resizing a Linode guide for more information.
Modify
linode-terraform-template.tf
and update thetype
value tog6-standard-4
for theterraform-db
resource.- ~/terraform/linode-terraform-template.tf
-
1 2 3 4 5 6 7
# [...] resource "linode_instance" "terraform-db" { # [...] type = "g6-standard-4" # [...] }
Review the plan:
terraform plan
# [...] An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: ~ linode_instance.terraform-db type: "g6-standard-1" => "g6-standard-4" Plan: 0 to add, 1 to change, 0 to destroy. # [...]
Apply your changes:
terraform apply
Verify the changes in the Linode Manager.
Terraform Modules
Terraform uses a concept called modules to group common server requirements and configurations. Think of modules as similar to functions in programming languages.
As an example, let’s say that you run a web agency and need to deploy identical pairs of webservers and database servers for different clients. To facilitate this, you can create a reusable Terraform module which describes the webserver and database server pairing.
The module’s description allows for variable substitution of relevant attributes (passwords, keys, etc), just as in the configuration from the previous section. Once the module is configured, new servers can be instantiated for each of your clients by combining the module code with a new set of variable values.
Basic Module Structure
The module structure is flexible, so you can use as many Terraform files as needed to describe your infrastructure. This example contains just one configuration file describing the reusable code.
Create a
modules/app-deployment/
directory to hold the module configuration:cd ~/terraform mkdir -p modules/app-deployment
Create a
main.tf
configuration file insidemodules/app-deployment/
:- ~/terraform/modules/app-deployment/main.tf
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
# Web Server resource "linode_instance" "terraform-web" { image = "linode/ubuntu18.04" label = var.webserver_label group = "Terraform" region = var.region type = "g6-standard-1" swap_size = 1024 authorized_keys = var.authorized_keys root_pass = var.root_pass } # Database Server resource "linode_instance" "terraform-db" { image = "linode/centos7" label = var.dbserver_label group = "Terraform" region = var.region type = var.db_type swap_size = 1024 authorized_keys = var.authorized_keys root_pass = var.root_pass }
The configuration above reproduces the previous examples using variables. The next file contains variable definitions. Assign a default value for each variable. That value will be used if you don’t override it when you call the module.
Substitute in your SSH key and root password where indicated:
- ~/terraform/modules/app-deployment/variables.tf
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
variable "webserver_label" { description = "The name for the Web Server" default = "default-web" } variable "dbserver_label" { description = "The name for the Database Server" default = "default-db" } variable "db_type" { description = "The size (plan) for your Database Linode" default = "g6-standard-1" } variable "region" { description = "The default Linode region to deploy the infrastructure" default = "us-east" } variable "authorized_keys" { description = "The Public id_rsa.pub key used for secure SSH connections" default = ["default-ssh-public-key"] } variable "root_pass" { description = "The default root password for the Linode server" default = "default-root-password" }
Working with Modules
Create a deployment for an imaginary client:
Create a
client1
directory:cd ~/terraform mkdir client1
Create a
main.tf
configuration file insideclient1/
that uses your module. The module is referenced by providing the path to the module’s configuration. Substitute in your API token, SSH key, and root password where indicated:- ~/terraform/client1/main.tf
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
# Client 1 Infrastructure provider "linode" { token = "YOUR_LINODE_API_TOKEN" } module "app-deployment" { source = "../modules/app-deployment" # Variables Specific to this Deployment region = "us-east" authorized_keys = [ "YOUR_PUBLIC_SSH_KEY" ] root_pass ="YOUR_ROOT_PASSWORD" # Variables Specific to Servers webserver_label = "client1-web" dbserver_label = "client1-db" db_type = "g6-standard-8" }
The file structure for your module and for
client1
should now look as follows. This structure is not mandated by Terraform, but it is useful as as simple example:client1 └── main.tf modules └── app-deployment ├── main.tf └── variables.tf
Initialize the Terraform configuration for the client, review the plan, and apply it:
cd ~/terraform/client1/ terraform init terraform plan terraform apply
Join our Community
Find answers, ask questions, and help others.
This guide is published under a CC BY-ND 4.0 license.