Provision a Vultr Cloud Server with Terraform and Cloud-Init
Introduction
Infrastructure as Code (IaC) is the provisioning and managing of infrastructure defined through code instead of manually. It eliminates the need to manually deploy the infrastructure and manage cloud resources like virtual instances, bare metal servers, load balancers, and other resources whenever you want to develop, test or deploy an application.
Terraform is an open-source infrastructure provisioning tool. It allows you to write, plan, and apply changes to deliver infrastructure as code. It uses declarative language, where you don't write steps to perform a task. Instead, you write the result and Terraform figures out how to achieve that result.
Advantages of Using Terraform
- Fast: The total time for deploying an infrastructure reduces when you Terraform. It automates the provisioning of resources defined in your configuration files.
- Reliable: It eliminates the need to manually provision your infrastructure whenever you want to develop, test or deploy an application. Fewer human errors occur while provisioning infrastructure using Terraform.
- Reusable: You can reuse the infrastructure or individual cloud resources provisioned by Terraform for creating a new infrastructure.
Infrastructure Lifecycle with Terraform
- Write: Write the required infrastructure as code.
- Plan: Preview changes before applying.
- Apply: Deploy the infrastructure.
This article demonstrates how to deploy an instance and related resources using Terraform.
Introduction to Terraform Language
Terraform uses declarative language, where you write the intended action rather than the steps to perform that action. You can segment the elements of your infrastructure into multiple different configuration files. How you organize resources in files or the order of configuration blocks does not matter. Instead, Terraform considers the relation between resources to determine the provisioning order. It combines the collection of .tf
configuration files while performing operations.
Terraform Language Syntax:
<BLOCK TYPE> "<BLOCK LABEL>" "<BLOCK LABEL>" {
# Block body
<IDENTIFIER> = <EXPRESSION> # Argument
}
Syntax Elements:
- Block: Container that accommodates arguments or subblocks. It has a type and can have zero or more labels.
{
and}
are delimiters of a block body. - Identifier: An identifier is essentially a name of an attribute.
- Expressions: A sublanguage used to specify values.
- Argument: A combination of name and value. In Terraform language, you use arguments to assign values to attributes within configuration blocks.
The following are the main components of Terraform.
Provider
Terraform can manage cloud resources available in a provider plugin, allowing it to interact with a cloud service provider. The Vultr provider plugin allows creating, modifying, and destroying infrastructure objects like cloud instances, bare metal servers, load balancers, and so on.
Provider Block Example:
provider "vultr" {
api_key = var.VULTR_API_KEY
}
Resource
Individual resources are the building blocks in a cloud infrastructure. A resource in Terraform describes a single individual infrastructure object, such as an instance, block storage, or DNS record. The primary purpose of Terraform language is to declare resources, and a majority of other language features are present to make the definition of resources more flexible.
Resource Block Example:
resource "vultr_instance" "example_instance" {
plan = "vc2-1c-1gb"
region = "ewr"
os_id = "387"
}
State
Terraform maintains the state of your infrastructure as a terraform.tfstate
file in your workspace. This file contains the present state of your infrastructure, like the defined resources and their configuration. When you update your configuration files and apply the changes, Terraform checks the current state and decides the required steps to achieve your desired result.
Prerequisites
- Deploy a fresh Ubuntu 20.04 Server
- Create non-root sudo user
Enable Vultr API
The Vultr provider plugin uses the Vultr API to let Terraform interact with the cloud infrastructure. Following are the steps to enable Vultr API access and get an API Key.
- Navigate to the Vultr Customer Portal.
- Go to Account > API.
- Click the "Enable API" button.
- Copy the API Key.
- Add the Server IP to access control.
Set Up the Terraform Environment
1. Install Terraform
Add the GPG key to the sources keyring.
$ curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
Add the official Terraform repository.
$ sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com focal main"
Install Terraform.
$ sudo apt install terraform
2. Create a Terraform Workspace
Generally, a workspace contains all the configuration files required for a single project. Set up a new directory to store your Terraform configuration files.
Create a new directory.
$ mkdir ~/terraform_demo
Enter the directory.
$ cd ~/terraform_demo
3. Add Provider Configuration
Terraform uses the Vultr provider plugin for interacting with Vultr API to manage the cloud resources. To install the plugin, you need to add the plugin source and version into required_providers
inside a .tf
configuration file.
Make a separate configuration file to contain the provider plugin configuration.
Using a text editor, create a new provider configuration file.
$ nano provider.tf
Add the following content to the file.
terraform {
required_providers {
vultr = {
source = "vultr/vultr"
version = ">= 2.10.1"
}
}
}
provider "vultr" {
api_key = var.VULTR_API_KEY
}
variable "VULTR_API_KEY" {}
Using a text editor, create a new variable container file.
$ nano terraform.tfvars
Add the following content to the file and replace api_key
with your API key.
VULTR_API_KEY = "api_key"
Install the provider plugin.
$ terraform init
Provision a Vultr Instance
The vultr_instance
resource available in the provider plugin allows Terraform to create, read, update or delete a Vultr instance. Find the available arguments and attributes in the Vultr Provider documentation.
Common Arguments:
- plan: Set subscription plan. See available plans list.
- region: Set deployment region. See available regions list.
- os_id / app_id / snapshot_id: Set initialization preference. See available operating systems, applications list and the steps to retrieve snapshot ID.
- user_data: Set the initialization script, like bash, cloud-init, or Ignition.
- enable_ipv6: Enable IPv6 Networking.
Common Attributes:
- id: Retrieve the instance ID.
- main_ip: Retrieve the main IPv4 Address.
- v6_main_ip: Retrieve the main IPv6 Address.
Please Note: You must set os_id, image_id, snapshot_id, iso_id, or app_id to deploy an instance.
Make a new configuration file with a vultr_instance
resource block inside it to define the provisioning arguments.
Using a text editor, create a new resource configuration file.
$ nano vultr_instance.tf
Add the following content to the file.
resource "vultr_instance" "example_instance" {
plan = "vc2-1c-1gb"
region = "ewr"
os_id = "387"
enable_ipv6 = true
}
The above code block provisions a server with 1 vCPU and 1GB RAM in the New Jersey region with Ubuntu 20.04.
Preview the changes.
$ terraform plan
Apply the changes.
$ terraform apply
Retrieve the details.
$ terraform show
Attach a Block Storage Volume to a Vultr Instance
The vultr_block_storage
resource available in the provider plugin allows Terraform to create, read, update or delete a block storage volume. Find the available arguments and attributes in the Vultr Provider documentation.
Update the configuration file by adding the vultr_block_storage
resource block inside it to define the provisioning arguments.
Using a text editor, edit the resource configuration file.
$ nano vultr_instance.tf
Add the following content to the file.
resource "vultr_block_storage" "example_blockstorage" {
size_gb = 10
region = "ewr"
attached_to_instance = "${vultr_instance.example_instance.id}"
}
The above code block provisions a block storage volume with 10GB storage in the New Jersey region and attaches it to the previously provisioned instance using its ID attribute (vultr_instance.example_instance.id).
Preview the changes.
$ terraform plan
Apply the changes.
$ terraform apply
Retrieve the details.
$ terraform show
Attach a Reserved IP Address to a Vultr Instance
The vultr_reserved_ip
resource available in the provider plugin allows Terraform to create, read, update or delete a reserved IP address. Find the available arguments and attributes in the Vultr Provider documentation.
Update the configuration file by adding the vultr_reserved_ip
resource block inside it to define the provisioning arguments.
Using a text editor, edit the resource configuration file.
$ nano vultr_instance.tf
Add the following content to the file.
resource "vultr_reserved_ip" "example_reserved_ip" {
region = "ewr"
ip_type = "v4"
instance_id = "${vultr_instance.example_instance.id}"
}
The above code block provisions a reserved IPv4 address in the New Jersey region and attaches it to the previously provisioned instance using its ID attribute (vultr_instance.example_instance.id). You can deploy an IPv6 address by setting the ip_type
attribute to v6
.
Preview the changes.
$ terraform plan
Apply the changes.
$ terraform apply
Retrieve the details.
$ terraform show
Initialize a Vultr Instance with Cloud-Init
Automate the initialization of your Vultr instance using cloud-init by providing a cloud-config
file as the user-data
attribute while provisioning the instance.
The cloud-config
file uses YAML language with the #cloud-config
shebang, and the cloud-init process executes it at the first boot of your instance. You may refer to the official cloud-init documentation to know more about its syntax and features.
Using a text editor, create a new resource configuration file.
$ nano cloud-config.yaml
Add the content below to the cloud-init configuration file.
#cloud-config packages: - nginx write_files: - owner: www-data:www-data path: /var/www/html/index.html content: "<h1>Server Provisioned by Terraform & Cloud-Init</h1>" runcmd: - ufw allow http
The demonstrated configuration tells cloud-init to install the Nginx package, overwrite the default index page with a heading (H1) element, and allow incoming HTTP connections.
Using a text editor, create a new resource configuration file.
$ nano vultr_instance_cloudinit.tf
Add the following content to the file.
resource "vultr_instance" "instance_cloudinit" { plan = "vc2-1c-1gb" region = "ewr" os_id = "387" user_data = "${file("cloud-config.yaml")}" }
The above code block provisions a server with 1 vCPU and 1GB RAM in the New Jersey region with Ubuntu 20.04 and the provided cloud-config
file.
Preview changes.
$ terraform plan
Apply changes.
$ terraform apply
Retrieve details.
$ terraform show
Verify the deployment by opening your instance IP in a web browser or using curl. It takes around 10 mins for cloud-init to complete processing after the instance creation. You can track the progress by opening the web console of the newly deployed instance using Vultr Customer Portal.
Change Existing Resource Configuration
You can change certain configurations of resources provisioned by Terraform, such as changing instance subscription plans, block storage size, or reserved IP instance pointers.
This section demonstrates the steps to update the configuration of individual resources using Terraform. You can refer to these examples and adapt them to your use case.
Change Instance Configuration
Update attributes in the vultr_instance
resource block to change the instance subscription plan.
Using a text editor, edit the resource configuration file.
$ nano vultr_instance.tf
Change the plan
attribute in example_instance
resource block from vc2-1c-1gb
to vc2-1c-2gb
. This change upgrades the subscription plan of the instance from 1 vCPU 1GB RAM to 1 vCPU 2GB RAM.
Preview changes.
$ terraform plan
Output:
# vultr_instance.example_instance will be updated in-place
~ resource "vultr_instance" "example_instance" {
id = "5382258f-b99d-4deb-a256-ab9b8bd85661"
~ plan = "vc2-1c-1gb" -> "vc2-1c-2gb"
# (27 unchanged attributes hidden)
}
Apply changes.
$ terraform apply
Output:
vultr_instance.example_instance: Modifying... [id=5382258f-b99d-4deb-a256-ab9b8bd85661]
vultr_instance.example_instance: Still modifying... [id=5382258f-b99d-4deb-a256-ab9b8bd85661, 10s elapsed]
vultr_instance.example_instance: Modifications complete after 11s [id=5382258f-b99d-4deb-a256-ab9b8bd85661]
Retrieve details.
$ terraform show
Change Block Storage Configuration
Update attributes in the vultr_blockstorage
resource block to change block storage size.
Using a text editor, edit the resource configuration file.
$ nano vultr_instance.tf
Change the size_gb
in example_blockstorage
resource block from 10
to 20
. This change increases the size of the block storage volume from 10GB to 20GB.
Preview changes.
$ terraform plan
Output:
# vultr_block_storage.example_blockstorage will be updated in-place
~ resource "vultr_block_storage" "example_blockstorage" {
id = "79897b51-5222-47c9-a884-a732080903bb"
~ size_gb = 10 -> 20
# (7 unchanged attributes hidden)
}
Apply changes.
$ terraform apply
Output:
vultr_block_storage.example_blockstorage: Modifying... [id=79897b51-5222-47c9-a884-a732080903bb]
vultr_block_storage.example_blockstorage: Modifications complete after 1s [id=79897b51-5222-47c9-a884-a732080903bb]
Retrieve details.
$ terraform show
Change Reserved IP Address Configuration
Update attributes in the vultr_reserved_ip
resource block to change the target instance.
Using a text editor, edit the resource configuration file.
$ nano vultr_instance.tf
Change the instance_id
in example_reserved_ip
resource block from ${vultr_instance.example_instance.id}
to ${vultr_instance.instance_cloudinit.id}
. This change sets the target instance from example_instance
to instance_cloudinit
.
Preview changes.
$ terraform plan
Output:
# vultr_reserved_ip.example_reserved_ip will be updated in-place
~ resource "vultr_reserved_ip" "example_reserved_ip" {
id = "54e80ad8-e6e3-428c-83c0-bfdefa71f5ed"
~ instance_id = "5382258f-b99d-4deb-a256-ab9b8bd85661" -> "602273ad-279e-4728-9000-594152126c70"
Apply changes.
$ terraform apply
Output:
vultr_reserved_ip.example_reserved_ip: Modifying... [id=54e80ad8-e6e3-428c-83c0-bfdefa71f5ed]
vultr_reserved_ip.example_reserved_ip: Modifications complete after 2s [id=54e80ad8-e6e3-428c-83c0-bfdefa71f5ed]
Retrieve details.
$ terraform show
Fetch Precise Resource Information
Unlike the terraform show
command, output
blocks allow you to get output containing specific attributes. For example, return only the IP address of an instance.
Update the resource configuration to add an output
block to fetch the IP address of the example_instance
resource.
Using a text editor, edit the resource configuration file.
$ nano vultr_instance.tf
Add the following content to the file.
output "example_instance_ip" {
value = [vultr_instance.example_instance.main_ip, vultr_instance.example_instance.v6_main_ip]
}
Apply changes.
$ terraform apply
Retrieve the instance IP address.
$ terraform output example_instance_ip
Use this example to adapt it to your case regardless of the resource or attribute you may want as an output.
Warning: Applying the demonstrated infrastructure deploys actual cloud resources on your Vultr account. Be sure to delete unused services after testing. Use the
terraform destroy
command to delete all resources provisioned in the demo Terraform workspace.
Conclusion
You used Terraform to create, read, update or delete instances, block storage volumes & additional IP addresses on Vultr. You can deploy infrastructure for your applications using Terraform. These basic operations are a good starting for integrating Terraform into your workflow.