Skip to content

Instantly share code, notes, and snippets.

@markus-hentsch
Created August 26, 2025 11:28
Show Gist options
  • Select an option

  • Save markus-hentsch/e729c16aad7291b5187d0a3e08f5e411 to your computer and use it in GitHub Desktop.

Select an option

Save markus-hentsch/e729c16aad7291b5187d0a3e08f5e411 to your computer and use it in GitHub Desktop.
Sample Terraform recipe for OpenStack to create VMs and a load balancer with floating IP
# ######### VARIABLES ######### #
# name of the image in Glance to use for the
# backend VMS that will be load-balanced
variable "image_name" {
type = string
default = "cirros"
}
# name of the flavor to use for the load-balanced backend VMs
variable "flavor_name" {
type = string
default = "S"
}
# name of the shared external network with floating ip pool
variable "provider_network_name" {
type = string
default = "provider-network"
}
# CIDR of the tenant network to be created where
# backend VMs and load balancer will live
variable "tenant_network_cidr" {
type = string
default = "192.168.142.0/24"
}
# names of the backend VMs
# NOTE: will create a dedicated VM for each name specified
variable "server_names" {
type = set(string)
default = ["tf-test-instance-1", "tf-test-instance-2"]
}
# load balancing method to be set for the load balancer
variable "loadbalance_method" {
type = string
default = "ROUND_ROBIN"
}
# ######### TERRAFORM PLUGINS ######### #
terraform {
required_version = ">= 0.14.0"
required_providers {
openstack = {
source = "terraform-provider-openstack/openstack"
version = "~> 1.53.0"
}
}
}
provider "openstack" {
user_name = "terraform"
tenant_name = "terraform-project"
password = "tfpwd?§"
auth_url = "https://keystone.openstack.local:5000/v3"
region = "MyRegion"
endpoint_type = "internal"
}
# ######### SCENARIO ######### #
# acquire provider network ID from name
data "openstack_networking_network_v2" "provider_net" {
name = var.provider_network_name
}
# BEGIN:networking
resource "openstack_networking_network_v2" "tenant_net" {
name = "tf-tenant-network"
}
resource "openstack_networking_subnet_v2" "tenant_subnet" {
name = "tf-tenant-subnet"
network_id = openstack_networking_network_v2.tenant_net.id
cidr = var.tenant_network_cidr
}
resource "openstack_networking_router_v2" "tentant_router" {
name = "tf-tenant-router"
external_network_id = data.openstack_networking_network_v2.provider_net.id
depends_on = [data.openstack_networking_network_v2.provider_net]
}
resource "openstack_networking_router_interface_v2" "internal_iface" {
router_id = openstack_networking_router_v2.tentant_router.id
subnet_id = openstack_networking_subnet_v2.tenant_subnet.id
}
resource "openstack_networking_secgroup_v2" "ssh" {
name = "tf-ssh"
description = "Open input ssh port"
}
resource "openstack_networking_secgroup_rule_v2" "ssh_rule" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 22
port_range_max = 22
remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.ssh.id
}
resource "openstack_networking_secgroup_v2" "icmp" {
name = "tf-icmp"
description = "Allow ping"
}
resource "openstack_networking_secgroup_rule_v2" "icmp_rule" {
direction = "ingress"
ethertype = "IPv4"
protocol = "icmp"
security_group_id = openstack_networking_secgroup_v2.icmp.id
}
# END:networking
# BEGIN:servers
resource "openstack_compute_instance_v2" "servers" {
for_each = var.server_names
name = each.key
image_name = var.image_name
flavor_name = var.flavor_name
security_groups = [openstack_networking_secgroup_v2.ssh.name, openstack_networking_secgroup_v2.icmp.name]
network {
uuid = openstack_networking_network_v2.tenant_net.id
}
}
# END:server
# BEGIN:load-balancer
resource "openstack_lb_loadbalancer_v2" "lb1" {
name = "tf-lb-main"
vip_subnet_id = openstack_networking_subnet_v2.tenant_subnet.id
depends_on = [openstack_compute_instance_v2.servers]
}
resource "openstack_lb_listener_v2" "lb1" {
name = "tf-listener-main"
protocol = "TCP"
protocol_port = 22
loadbalancer_id = openstack_lb_loadbalancer_v2.lb1.id
depends_on = [openstack_lb_loadbalancer_v2.lb1]
}
resource "openstack_lb_pool_v2" "lb1" {
name = "tf-lb-pool-main"
protocol = "TCP"
lb_method = var.loadbalance_method
listener_id = openstack_lb_listener_v2.lb1.id
depends_on = [openstack_lb_listener_v2.lb1]
}
resource "openstack_lb_member_v2" "lb1members" {
for_each = var.server_names
address = openstack_compute_instance_v2.servers[each.key].access_ip_v4
protocol_port = 22
pool_id = openstack_lb_pool_v2.lb1.id
subnet_id = openstack_networking_subnet_v2.tenant_subnet.id
depends_on = [openstack_lb_pool_v2.lb1]
}
resource "openstack_lb_monitor_v2" "lb1" {
name = "tf-lb-mon-main"
pool_id = openstack_lb_pool_v2.lb1.id
type = "TCP"
delay = 5
timeout = 5
max_retries = 3
depends_on = [openstack_lb_member_v2.lb1members]
}
# END:load-balancer
# BEGIN:floating-ip
resource "openstack_networking_floatingip_v2" "lb_floatip" {
pool = var.provider_network_name
description = "Load Balancer Floating IP created by Terraform"
depends_on = [openstack_lb_loadbalancer_v2.lb1]
}
resource "openstack_networking_floatingip_associate_v2" "fip_1" {
floating_ip = openstack_networking_floatingip_v2.lb_floatip.address
port_id = openstack_lb_loadbalancer_v2.lb1.vip_port_id
depends_on = [openstack_networking_floatingip_v2.lb_floatip]
}
# END:floating-ip
# ######### OUTPUTS ######### #
output "instance_public_ip" {
description = "Floating IP of the load balancer"
value = openstack_networking_floatingip_v2.lb_floatip.address
}
# NOTE: make sure to adjust all variables at the top of the *.tf script
# AND the connection settings in the `provider "openstack"` section!
# (preparation)
openstack user create terraform --password "tfpwd?§"
openstack project create terraform-project
openstack role add --user terraform --project terraform-project member
openstack role add --user terraform --project terraform-project load-balancer_member
# (terraform usage)
TF_VERSION="1.13.0"
apt install -y wget unzip
wget https://releases.hashicorp.com/terraform/$TF_VERSION/terraform_$TF_VERSION_linux_amd64.zip
unzip terraform_$TF_VERSION_linux_amd64.zip
./terraform init
./terraform plan
./terraform apply
# (after successful creation, use the resulting floating IP address to verify the load balancing)
ssh -o "UserKnownHostsFile=/dev/null" -o "StrictHostKeyChecking=no" cirros@$FLOATING_IP_ADDR hostname
# (this will print the backend VM's hostname and with round-robin it will alternate every call)
# (cleanup)
./terraform destroy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment