Contents

Managing Infrastructure With Terraform

Terraform is another amazing tool from the great people over at HashiCorp (The people who brought us Vagrant). It allows you to manage your infrastructure as code on multiple providers.

The Terraform website describes it best:

Terraform enables you to safely and predictably create, change, and improve infrastructure. It is an open source tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, edited, reviewed, and versioned.

Providers

Terraform has the concept of providers. A provider can be seen as a module in Terraform that is responsible for understanding the API and provide resources of a target platform.

Providers are generally some sort of IaaS platform such as AWS, GCP, and even Microsoft Azure.

The documentation contains a full list of providers.

Using Providers

To understand how you use a provider it will be easiest to refer to an example:

1
2
3
4
5
6
7
data "http" "example" {
  url = "https://checkpoint-api.hashicorp.com/v1/check/terraform"

  request_headers {
    "Accept" = "application/json"
  }
}

The above configuration tells Terraform to use the http provider, loaded as a data source and tagged in the component web.

Terraform uses the HashiCorp Configuration Lanuage. For more information about the syntax please visit the repo on GitHub.

Your First Server as Code

I will be using Microsoft Azure for the following examples. To authenticate Terraform on Azure there are 2 options available. They are:

I recommend using the CLI option if you are following along with this article on your localhost.

The documentation for the Azure Resource Manager provider can be found at: https://www.terraform.io/docs/providers/azurerm/authenticating_via_azure_cli.html

The following configuration can all be appended to a single .tf file.

The Resource Group

The first step is to define the resource group:

1
2
3
4
resource "azurerm_resource_group" "first_server" {
  name     = "iacrg"
  location = "northeurope"
}

Networking

One of the required parameters when creating a VM with the ARM provider is the network interface IDs. The networking interface in turn requires a subnet configuration and virtual network. The following creates all 3:

 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
# Virtual Network
resource "azurerm_virtual_network" "first_server" {
  name                = "iacvnet"
  address_space       = ["10.0.0.0/16"]
  location            = "northeurope"
  resource_group_name = "${azurerm_resource_group.first_server.name}"
}

# Subnet
resource "azurerm_subnet" "first_server" {
  name                 = "iacsub"
  resource_group_name  = "${azurerm_resource_group.first_server.name}"
  virtual_network_name = "${azurerm_virtual_network.first_server.name}"
  address_prefix       = "10.0.2.0/24"
}

# Network Interface
resource "azurerm_network_interface" "first_server" {
  name                = "iacni"
  location            = "northeurope"
  resource_group_name = "${azurerm_resource_group.first_server.name}"

  ip_configuration {
    name                          = "testconfiguration1"
    subnet_id                     = "${azurerm_subnet.first_server.id}"
    private_ip_address_allocation = "dynamic"
  }
}

The above will create a virtual network named iacvnet with an address space of 10.0.0.0/16, a subnet configuration with an address prefix of 10.0.2.0/24, and finally a virtual network interface using the subnet configuration. The vNIC also sets the IP address allocation to dynamic which means our VM will receive a random available IP every time it boots up.

The VM

Now that all the required resources have been creates we can create the VM:

 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
30
31
resource "azurerm_virtual_machine" "first_server" {
  name                  = "iacvm"
  location              = "northeurope"
  resource_group_name   = "${azurerm_resource_group.first_server.name}"
  network_interface_ids = ["${azurerm_network_interface.first_server.id}"]
  vm_size               = "Standard_A0"

  storage_image_reference {
    publisher = "Canonical"
    offer     = "UbuntuServer"
    sku       = "16.04-LTS"
    version   = "latest"
  }

  storage_os_disk {
    name              = "myosdisk1"
    caching           = "ReadWrite"
    create_option     = "FromImage"
    managed_disk_type = "Standard_LRS"
  }

  os_profile {
    computer_name  = "infrastructure-as-code"
    admin_username = "testadmin"
    admin_password = "Password1234!"
  }

  os_profile_linux_config {
    disable_password_authentication = false
  }
}

Running The Script

Before you can apply the above configuration you first need to initialise the Terraform project. To do this simply run the following command:

1
terraform init

This create a .terraform directory which will hold all the required plugins and state for this project. It will also install the required plugins.

To apply the configuration (create the VM) run the following command:

1
terraform apply

Once Terraform completes the operation you should see something like this in the Azure portal:

/images/terraform-resources.png

To remove the resources it is as easy as deleting the content in the .tf file and running terraform apply again.

ARM Templates

Azure has a tendency to grow quite quick. So quick that some 3rd party tools such as Terraform struggle to keep up. This means that Terraform might not provide nifty configuration options for all the available Azure resources.

Luckily Terraform provides the ability to run an ARM template. However, you lose the ability to manage the resources created via the ARM template as Terraform will not be able to link it to local state. For more information please see the documentation for azure_template_deployment.

Conclusion

Although this post only scratches the surface of what Terraform can do I hope you can see how easy it is to manage your infrastructure using this tool.

The only problem I have encountered thus far is mapping existing resources. It is definitely easier starting from scratch than trying to create the config files for all the resources we already have set up in Azure.

Terraform does provide an import command, but this requires very specific parameters to successfully map a complex existing infrastructure.