Provision a Vultr Cloud Server with Terraform and Cloud-Init

Author: Mayank Debnath

Last Updated: Mon, May 9, 2022
Best Practices Quickstart Guides System Admin

Introduction

Infrastructure as Code (IaC) is the provisioning and managing of infrastructure defined through code instead of doing so 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 every time 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.

It focuses on creating and managing cloud resources rather than configuration management on existing resources, for example, installing or managing software inside servers. It acts as a provisioner and focuses on the higher abstraction level of setting up the resources.

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 every time 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

  1. Write: Write the required infrastructure as code.
  2. Plan: Preview changes before applying.
  3. 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. The way you organize resources in files or the order of configuration blocks does not matter. Instead, Terraform considers the relation between resources to determine the order of provisioning. 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 which allows 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, DNS record, and so on. The main purpose of Terraform language is declaring 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 present state and decides the required steps to achieve your desired result.

Prerequisites

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.

  1. Navigate to the Vultr Customer Portal.
  2. Go to Account > API.
  3. Click the "Enable API" button.
  4. Copy the API Key.
  5. 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:

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 changes.

$ terraform plan

Apply changes.

$ terraform apply

Retrieve 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 changes.

$ terraform plan

Apply changes.

$ terraform apply

Retrieve 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 changes.

$ terraform plan

Apply changes.

$ terraform apply

Retrieve 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 plan, block storage size, reserved IP instance pointer, and so on.

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.

More Information

Want to contribute?

You could earn up to $600 by adding new articles.