Create Nova VM Instances With Floating IP using Terraform

Floating IPs within OpenStack are important to ensure that there is proper external connectivity for virtual machine instances that are set to run within private networks. The Floating IPs are the key channels of communication that enable the virtual machine to connect with the world outside, including not only the crucial internet connect but also access to other external services that can be necessary.

In this how-to guide, you will discover all the steps required to provision a Nova compute instance with a floating IP address using Terraform by leveraging the very handy nova_floating module to do so.

Prerequisites

Ensure the following before continuing:

Step 1: Install Terraform

If you don’t have terraform installed, run one of the following commands that match your working environment:

# Ubuntu/Debian
wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform

# CentOS/RHEL
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo yum -y install terraform

# Fedora
sudo dnf install -y dnf-plugins-core
sudo dnf config-manager addrepo --from-repofile=https://rpm.releases.hashicorp.com/fedora/hashicorp.repo
sudo dnf -y install terraform

# Amazon Linux
sudo yum install -y yum-utils shadow-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo
sudo yum -y install terraform

# macOS Homebrew
brew tap hashicorp/tap
brew install hashicorp/tap/terraform

Step 2: Configure Terraform Provider

To authenticate Terraform with OpenStack, define the provider as follows in your main.tf file:

# Define required providers
terraform {
  required_version = ">= 0.14.0"
  required_providers {
    openstack = {
      source = "terraform-provider-openstack/openstack"
      version = "~> 2.1.0"
    }
  }
}

# Configure the OpenStack Provider
provider "openstack" {
  user_name   = "admin"
  tenant_name = "admin"
  password    = "pwd"
  auth_url    = "http://myauthurl:5000/v3"
  region      = "RegionOne"
}

For a local statefile, you can configure it as follows:

terraform {
  backend "local" {
    path = "${path.module}/terraform.tfstate"
  }
}

Step 3: The Instance Module Overview

The instance module contains the following config files:

  • data.tf – retrieves network and image info
  • main.tf – creates the server and attaches a floating IP
  • variables.tf – defines inputs
  • outputs.tf – exposes the floating IP and instance ID
data.info

This file fetches network information to be used in configuring the VM:

# Fetch network details
data "openstack_networking_network_v2" "network" {
  name = var.floating_ip_pool
}
main.tf

Provisions the VM and floating IP:

# Define instance creation resource
resource "openstack_compute_instance_v2" "instance" {
  for_each = { for idx, instance in var.instances : idx => instance }
  
  name            = each.value.name
  image_id        = each.value.image_id
  flavor_id       = each.value.flavor_id
  key_pair        = each.value.key_pair
  security_groups = each.value.security_groups
  
  network {
    uuid          = each.value.network_id
    fixed_ip_v4   = each.value.fixed_ip != "" ? each.value.fixed_ip : null
  }
  # Conditionally include userdata if provided
  user_data = each.value.userdata_file != null ? file(each.value.userdata_file) : null
  metadata = {
    role = each.value.metadata_role
  }
}

# Flatten the volume definitions and create a unique map for volumes
locals {
  volumes_list = flatten([
    for instance_idx, instance in var.instances : [
      for volume_idx, volume in lookup(instance, "volumes", []) : {
        instance_idx = instance_idx
        volume_idx   = volume_idx
        volume       = volume
        volume_key   = "${instance_idx}-${volume_idx}"
      }
    ]
  ])
}

# Define volumes
resource "openstack_blockstorage_volume_v3" "volume" {
  for_each = { for vol in local.volumes_list : vol.volume_key => vol }

  name              = "volume-${each.value.instance_idx}-${each.value.volume_idx}-${openstack_compute_instance_v2.instance[each.value.instance_idx].name}"
  size              = each.value.volume.volume_size
}

# Attach volume(s) to instance
resource "openstack_compute_volume_attach_v2" "volume_attachment" {
  for_each = { for vol in local.volumes_list : vol.volume_key => vol }

  instance_id = openstack_compute_instance_v2.instance[each.value.instance_idx].id
  volume_id   = openstack_blockstorage_volume_v3.volume[each.key].id
}

# Null resource to wait for instances creation
resource "null_resource" "wait_for_instances" {
  for_each = { for idx, instance in var.instances : idx => instance if instance.assign_floating_ip }

  provisioner "local-exec" {
    command = "echo Instance ${each.key} created"
  }

  depends_on = [openstack_compute_instance_v2.instance]
}
# Resource to create floating IPs
resource "openstack_networking_floatingip_v2" "fip" {
  for_each = { for idx, instance in var.instances : idx => instance if instance.assign_floating_ip }
  pool     = var.floating_ip_pool
}

# Resource to associate floating IP
resource "openstack_compute_floatingip_associate_v2" "fip_assoc" {
  for_each = { for idx, instance in var.instances : idx => instance if instance.assign_floating_ip }
  floating_ip = openstack_networking_floatingip_v2.fip[each.key].address

  instance_id = openstack_compute_instance_v2.instance[each.key].id
  fixed_ip    = openstack_compute_instance_v2.instance[each.key].network[0].fixed_ip_v4
}
variables.tf

The variable file defines the input variables for the module:

variable "instances" {
  description = "List of instance configurations or a single instance configuration."
  type = list(object({
    name                = string
    image_id            = string
    flavor_id           = string
    key_pair            = string
    network_id          = string
    security_groups     = list(string)
    fixed_ip            = optional(string)
    assign_floating_ip  = bool
    metadata_role       = string
    userdata_file       = optional(string, null)
    volumes = list(object({
        volume_size       = number
        }))
    }))
}

variable "floating_ip_pool" {
  description = "The name of the floating IP pool"
  type        = string
  default     = "public"
}
outputs.tf

Outputs volume attachment information such as instance names and the floating IPs:

# Output volume IDs and names
output "volume_ids_and_names" {
  value = { for volume in openstack_blockstorage_volume_v3.volume :
    volume.id => volume.name
  }
}

# Output volume attachment information with instance names
output "volume_attachments_with_names" {
  value = [
    for attachment in openstack_compute_volume_attach_v2.volume_attachment :
    {
      instance_name = lookup({ for inst in openstack_compute_instance_v2.instance : inst.id => inst.name }, attachment.instance_id, "unknown")
      volume_id     = attachment.volume_id
    }
  ]
}

output "instance_names_and_ips" {
  value = { for idx, instance in openstack_compute_instance_v2.instance : idx => {
      name        = instance.name
      fixed_ip    = instance.network[0].fixed_ip_v4
      access_ip_v4 = instance.access_ip_v4
      floating_ip = try(openstack_networking_floatingip_v2.fip[idx].address, null)
    }
  }
}

Step 4: Module Usage

Configure your main.tf file as follows:

module "instance" {
  source = "git::https://github.com/cloudspinx/terraform-openstack.git//modules/instance?ref=main"
  
  floating_ip_pool = "public"
  instances = [
         {
            name               = "instancename"
            image_id           = "imageid"
            flavor_id          = module.flavors.flavor_ids["medium"]
            key_pair           = module.keypair.keypair_name
            network_id         = module.network.network_id
            fixed_ip           = "172.34.50.11"
            assign_floating_ip = true
            security_groups    = [module.security_group.security_group_id]
            userdata_file      = null
            metadata_role      = "web-server"
            volumes            = []
        }
        ]
}

If you prefer to use DHCP instead of assigning a static ip, set fixed_ip to null.

Apply the configuration

Deploy your cloud infrastructure using the commands below:

terraform init
terraform plan
terraform apply

Join our Linux and open source community. Subscribe to our newsletter for tips, tricks, and collaboration opportunities!

Recent Post

Unlock the Right Solutions with Confidence

At CloudSpinx, we don’t just offer services - we deliver clarity, direction, and results. Whether you're navigating cloud adoption, scaling infrastructure, or solving DevOps challenges, our seasoned experts help you make smart, strategic decisions with total confidence. Let us turn complexity into opportunity and bring your vision to life.

Leave a Comment

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

Related Post

OpenStack Security Groups provide an extremely powerful and very effective way of controlling and managing both the incoming and outgoing […]

OpenStack private networks enable instances to be reached safely without being exposed to the public internet. Private networks are vital […]

The flavors in OpenStack specify the compute, memory, and storage resources available to virtual machines (instances). The configurations that specify […]

Let's Connect

Unleash the full potential of your business with CloudSpinx. Our expert solutions specialists are standing by to answer your questions and tailor a plan that perfectly aligns with your unique needs.
You will get a response from our solutions specialist within 12 hours
We understand emergencies can be stressful. For immediate assistance, chat with us now

Contact CloudSpinx today!

Download CloudSpinx Profile

Discover the full spectrum of our expertise and services by downloading our detailed Company Profile. Simply enter your first name, last name, and email address.