Skip to content

Earthbuilder – Use Terraform with Openstack

Terraform is infrastructure as code. Meaning you describe the desired state of a sum of infrastructure components in a manifest. This is a short tutorial on creating a VM on top of Openstack using Terraform.

In this blog article, we’re creating a virtual machine on an Openstack environment with an internal IP and a floating (external) IP. We build the code as you read along, if you want to quickstart the process, you can find my Terraform examples in my Github repository. This article assumes, that you’ve installed the terraform CLI on your local system. Installation instructions for supported operating systems, can be found here.

Getting started


To use Terraform with an Openstack environment, you need to specify the official Openstack provider for Terraform in your manifests. Find the official documentation on this provider here. The provider is a lot more powerful than what we use in this tutorial, so having a look will pay off. We start with a simple tf file as a framework:

terraform {
required_version = ">= 0.14.0"
  required_providers {
    openstack = {
      source  = "terraform-provider-openstack/openstack"
      version = "~> 1.47.0"
    }
  }
}

# Configure the OpenStack Provider
provider "openstack" {
  user_name         = "<OS_USERNAME>"
  tenant_name       = "<OS_PROJECT_NAME>"
  tenant_id         = "<OS_PROJECT_ID>"
  password          = "<OS_PASSWORD>"
  auth_url          = "<OS_AUTH_URL>"
  region            = "<OS_REGION_NAME>"
  user_domain_name  = "<OS_USER_DOMAIN_NAME>"
  project_domain_id = "<OS_PROJECT_DOMAIN_ID>"
}

The first section describes which provider (Openstack in this case) we want to use and in which version. The second section is more or less where the magic happens and you configure your connection parameters and credentials for Openstack. For a complete list of possible parameters, check the official documentation. In my case, this set of parameters did the job. All parameters have their OS_* environment variable equivalent. If you’re familiar with the openstack CLI, you probably know them. You can get all the needed parameter values from your Openstack deployment or by using the openstack CLI itself.

user_name(Optional) The Username to login with. If omitted, the OS_USERNAME environment variable is used.
tenant_name(Optional) The Name of the Tenant (Identity v2) or Project (Identity v3) to login with. If omitted, the OS_TENANT_NAME or OS_PROJECT_NAME environment variable are used.
tenant_id(Optional) The ID of the Tenant (Identity v2) or Project (Identity v3) to login with. If omitted, the OS_TENANT_ID or OS_PROJECT_ID environment variables are used.
password(Optional) The Password to login with. If omitted, the OS_PASSWORD environment variable is used.
auth_url(Optional; required if cloud is not specified) The Identity authentication URL. If omitted, the OS_AUTH_URL environment variable is used.
region(Optional) The region of the OpenStack cloud to use. If omitted, the OS_REGION_NAME environment variable is used. If OS_REGION_NAME is not set, then no region will be used. It should be possible to omit the region in single-region OpenStack environments, but this behavior may vary depending on the OpenStack environment being used.
user_domain_name(Optional) The domain name where the user is located. If omitted, the OS_USER_DOMAIN_NAME environment variable is checked.
project_domain_id(Optional) The domain ID where the project is located If omitted, the OS_PROJECT_DOMAIN_ID environment variable is checked.

Creating the virtual network


We now create a floating (external) IP. Later, we will associate this created IP with our instance. In my case, the network pool to get the IP from was named floating_network.

resource "openstack_networking_floatingip_v2" "floatip_1" {
  pool = "floating_network"
}

Creating a volume


The next step to add is defining a volume for the VM. In this example, I create a volume with a size of 20GB. Be aware of the parameter image_id, this references the VM Image that’s stored in Openstack to create my VM from. You can get a list of images and their IDs using the openstack image list CLI command.

resource "openstack_blockstorage_volume_v3" "volume_1" {
  name        = "volume1-test-server"
  description = "first test volume"
  size        = 20                      # volume size in GB
  image_id    = "<IMAGE_ID>"            # OS image id
}

Creating an instance


Now it’s time to create the actual VM instance. Here all comes together. Add the following lines to your main.tf.

resource "openstack_compute_instance_v2" "instance_1" {
  name            = "test-server"           # Server name
  flavor_id       = "<FLAVOR_ID>"           # OS flavor ID
  key_pair        = "<SSH_KEY_TO_DEPLOY>"   # SSH key provided in OS
  security_groups = ["default"]

  network {
    name = "default_network"
  }

  block_device {
    uuid                  = "${openstack_blockstorage_volume_v3.volume_1.id}"
    source_type           = "volume"
    destination_type      = "volume"
    boot_index            = 0
    delete_on_termination = true
  }
}

resource "openstack_compute_floatingip_associate_v2" "floatip_1" {
  floating_ip = "${openstack_networking_floatingip_v2.floatip_1.address}"
  instance_id = "${openstack_compute_instance_v2.instance_1.id}"
}

Despite from the obvious parameter, I want to mention two of them especially. First, the flavor_id parameter. This is the flavor of your VM, meaning a preset of sizing parameters (CPU, Memory). You can viel existing flavors using the openstack flavor list CLI command. Second, the key_pair parameter. This references the name of a SSH public key, you can store within your Openstack account. This key will then automatically added to the VM. Passwordless login right from the start.

As you can see, we reference also the volume we’ve created above. As well as associating the floating IP with our instance. In this case, this will provide two network connections to the VM. One internal from the default_network and one external from the floating_network.

Building time


With the code ready, it’s time to get it started. We need to initialize the Terraform project using this command:

terraform init
Initializing the backend...

Initializing provider plugins...
- Finding terraform-provider-openstack/openstack versions matching "~> 1.47.0"...
- Installing terraform-provider-openstack/openstack v1.47.0...
- Installed terraform-provider-openstack/openstack v1.47.0 (self-signed, key ID 4F80527A391BEFD2)

Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/cli/plugins/signing.html

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

The next phase in Terraform is the planning phase. This checks the actual status on your provider against the described status in the manifest. It will tell you, how many components Terraform will add, change or destroy.

terraform plan
Terraform will perform the following actions:
# openstack_blockstorage_volume_v3.volume_1 will be created
+ resource "openstack_blockstorage_volume_v3" "volume_1" {
  + attachment = (known after apply)
  + availability_zone = (known after apply)
  + description = "first test volume"
  + id = (known after apply)
  + image_id = "<IMAGE_ID>"
  + metadata = (known after apply)
  + name = "volume1-test-server"
  + region = (known after apply)
  + size = 20
  + volume_type = (known after apply)
}

# openstack_compute_floatingip_associate_v2.floatip_1 will be created
+ resource "openstack_compute_floatingip_associate_v2" "floatip_1" {
  + floating_ip = (known after apply)
  + id = (known after apply)
  + instance_id = (known after apply)
  + region = (known after apply)
}

# openstack_compute_instance_v2.instance_1 will be created
+ resource "openstack_compute_instance_v2" "instance_1" {
  + access_ip_v4 = (known after apply)
  + access_ip_v6 = (known after apply)
  + all_metadata = (known after apply)
  + all_tags = (known after apply)
  + availability_zone = (known after apply)
  + flavor_id = "<FLAVOR_ID>"
  + flavor_name = (known after apply)
  + force_delete = false
  + id = (known after apply)
  + image_id = (known after apply)
  + image_name = (known after apply)
  + key_pair = "<SSH_KEY_to_DEPLOY>"
  + name = "test-server"
  + power_state = "active"
  + region = (known after apply)
  + security_groups = [
      + "default",
    ]
  + stop_before_destroy = false
  + block_device {
    + boot_index = 0
    + delete_on_termination = true
    + destination_type = "volume"
    + source_type = "volume"
    + uuid = (known after apply)
  }
  + network {
    + access_network = false
    + fixed_ip_v4 = (known after apply)
    + fixed_ip_v6 = (known after apply)
    + floating_ip = (known after apply)
    + mac = (known after apply)
    + name = "default_network"
    + port = (known after apply)
    + uuid = (known after apply)
  }
}

# openstack_networking_floatingip_v2.floatip_1 will be created
+ resource "openstack_networking_floatingip_v2" "floatip_1" {
  + address = (known after apply)
  + all_tags = (known after apply)
  + dns_domain = (known after apply)
  + dns_name = (known after apply)
  + fixed_ip = (known after apply)
  + id = (known after apply)
  + pool = "floating_network"
  + port_id = (known after apply)
  + region = (known after apply)
  + subnet_id = (known after apply)
  + tenant_id = (known after apply)
}

Plan: 4 to add, 0 to change, 0 to destroy.

Looks good doesn’t it? So let’s deploy it then:

terraform apply -auto-approve
...
openstack_networking_floatingip_v2.floatip_1: Creating...
openstack_blockstorage_volume_v3.volume_1: Creating...
openstack_blockstorage_volume_v3.volume_1: Still creating... [10s elapsed]
openstack_networking_floatingip_v2.floatip_1: Still creating... [10s elapsed]
openstack_blockstorage_volume_v3.volume_1: Creation complete after 15s [id=<SOME_ID>]
openstack_compute_instance_v2.instance_1: Creating...
openstack_networking_floatingip_v2.floatip_1: Creation complete after 16s [id=<SOME_ID>]
openstack_compute_instance_v2.instance_1: Still creating... [10s elapsed]
openstack_compute_instance_v2.instance_1: Creation complete after 13s [id=<SOME_ID>]
openstack_compute_floatingip_associate_v2.floatip_1: Creating...
openstack_compute_floatingip_associate_v2.floatip_1: Creation complete after 2s [id=<EXTERNAL_IP>/<SOME_ID>/]

You can now access your newly build Openstack VM!

Philip

Leave a Reply

Your email address will not be published. Required fields are marked *