Terraform - Deploy to OpenStack

The OpenStack provider allows you to create Terraform configurations to deploy infrastructure on OpenStack. Among the resources we can manage are:

  • instances
  • Credentials
  • Images
  • networks
  • block storage
  • NFS storage
  • load balancers

Provider configuration
To use it you have to configure its access parameters . We will do it in a file providers.tf

The file providers.tf

provider "openstack" {
  user_name   = var.openstack_user_name
  tenant_name = var.openstack_tenant_name
  password    = var.openstack_password
  auth_url    = var.openstack_auth_url
Variables defined in the file are used variables.tf
variable "openstack_user_name" {
    description = "The username for the Tenant."
    default  = "your-openstack-user"
}
variable "openstack_tenant_name" {
    description = "The name of the Tenant."
    default  = "your-openstack-project"
}
variable "openstack_password" {
    description = "The password for the Tenant."
    default  = "your-openstack-password"
}

variable "openstack_auth_url" {
    description = "The endpoint url to connect to OpenStack."
    default  = "http://openstack.di.ual.es:5000/v3"
}
variable "openstack_keypair" {
    description = "The keypair to be used."
    default  = "your-openstack-keypair-name"
}
Using environment variables
To avoid introducing sensitive data into configuration files and from being exposed to the version control system, it is good practice to set sensitive values ​​in environment variables.
The Terraform convention is that we define in the shell the variables preceded by TF_VAR_. For example, we define an environment variable TF_VAR_PASSWORDthat will be accessed by Terraform as PASSWORD.
Table 1. Environment variable naming
environment variable    Variable Terraform
TF_VAR_PASSWORD PASSWORD
We will follow these steps:
1. Set variables in the shell
$ export TF_VAR_PASSWORD=xxxx
2. Load the variable in Terraform
Archive variables.tf
...
variable "PASSWORD" {} #1
...

  1. The environment variable TF_VAR_PASSWORDis recognized in Terraform as PASSWORD

3. Using variable in Terraform

Archive providers.tf

provider "openstack" {
  user_name   = var.openstack_user_name
  tenant_name = var.openstack_tenant_name
  password    = var.PASSWORD #1
  auth_url    = var.openstack_auth_url
}

  1. Variable Usage

Initialize the provider
To initialize run terraform init.

terraform init
Initializing the backend...
Initializing provider plugins...
- Finding latest version of terraform-provider-openstack/openstack...
- Installing terraform-provider-openstack/openstack v1.33.0...
- Installed terraform-provider-openstack/openstack v1.33.0 (self-signed, key ID 4F80527A391BEFD2)
...
* terraform-provider-openstack/openstack: version = "~> 1.33.0"

Terraform has been successfully initialized!

...

This will create a folder .terraform with the OpenStack plugin installed and available for use in the project.

Configuration update
Initialization may report an error like this indicating the need for a configuration update.
Error: Failed to install providers
Could not find required providers, but found possible alternatives:
  hashicorp/openstack -> terraform-provider-openstack/openstack
If these suggestions look correct, upgrade your configuration with the
following command:
    terraform 0.13upgrade .
In this case, Terraform will indicate the way to solve it. In this case it suggests to solve it with
$ terraform 0.13upgrade . #1
  1. Don't forget the end point

This will create a file versions.tf with the module update in the current directory.
Once updated, run again

$ terraform init
Deploy an instance
Creating an instance is done with openstack_compute_instance_v2.
Next, we'll create an instance named tf_vm. The name used in resource, not the name assigned in name, is the one that refers to the resourcecreated object. This allows to treat the created resource (eg to assign it a floating IP address, to connect a volume to it, …​).
The following example illustrates creating a virtual machine, a floating IP address ( openstack_networking_floatingip_v2), and assigning the floating IP to the created virtual machine ( openstack_compute_floatingip_associate_v2).
#Crear node tf_vm
resource "openstack_compute_instance_v2" "tf_vm" { #1
  name              = "tf_vm"
  image_name        = "Ubuntu 16.04 LTS"
  availability_zone = "nova"
  flavor_name       = "medium"
  key_pair          = var.openstack_keypair
  security_groups   = ["default"]
  network {
    name = "mtorres-net" #2
  }
}
resource "openstack_networking_floatingip_v2" "tf_vm_ip" { #3
  pool = "ext-net"
}
resource "openstack_compute_floatingip_associate_v2" "tf_vm_ip" { #4
  floating_ip = openstack_networking_floatingip_v2.tf_vm_ip.address #5
  instance_id = openstack_compute_instance_v2.tf_vm.id #6
}
output tf_vm_Floating_IP {
  value      = openstack_networking_floatingip_v2.tf_vm_ip.address #7
  depends_on = [openstack_networking_floatingip_v2.tf_vm_ip] #8
}

  1. Creating an instance resource (virtual machine) in OpenStack. The created resource object is assigned to the variable tf_vm.
  2. Network to which the created instance will connect
  3. Creating a floating IP address resource. The created resource object is assigned to the variable tf_vm_ip.
  4. Association of the floating IP to the instance
  5. Access to the address of the floating IP resource created
  6. Access to idthe created instance
  7. Access to the address of the floating IP resource created
  8. Wait for the floating IP resource to be created

Modify the deployment
As an illustration, this example shows how to apply changes to a previously deployed configuration. In this case it is:

resource "openstack_compute_instance_v2" "tf_vm" {
  name              = "tf_vm"
  image_name        = "Ubuntu 16.04 LTS"
  availability_zone = "nova"
  flavor_name       = "large" #1
  key_pair          = var.openstack_keypair
  security_groups   = ["default"]
  network {
    name = "mtorres-net"
  }
}

...

resource "openstack_blockstorage_volume_v3" "tf_vol" { #2
  name        = "tf_vol"
  description = "first test volume"
  size        = 1 #3
}
resource "openstack_compute_volume_attach_v2" "va_1" { #4
  instance_id = "${openstack_compute_instance_v2.tf_vm.id}" #5
  volume_id   = "${openstack_blockstorage_volume_v3.tf_vol.id}" #6
}

  1. Image flavor modification
  2. Creating a volume resource
  3. Volume Size Specification
  4. Connecting the volume to the instance
  5. Access to idthe instance
  6. Access to idthe created volume

When executing with terraform apply, Terraform will inform us of the detected changes and the new configuration. The new settings will be applied if we confirm the operation.
Run an initialization script
A very interesting feature in the deployment of an instance is the possibility of running an initialization script during its creation. This allows the creation of instances with packages installed and configured.
Terraform allows this operation in OpenStack by passing a script in the parameter user_datawhen creating the instance.
Modifying the value of user_datawill create a new server if is used terraform apply.
Below is a script install_mysql.sh that performs various operations:

  • Update the package repository.
  • Install a MySQL server with password my_password.
  • Download a file with an SQL script to initialize a sample database.
  • Run the SQL file to initialize the database.
  • Modify the MySQL configuration file ( mysqld.cnf) to allow connections from anywhere.

the scriptinstall_mysql.sh

#!/bin/bash
sudo debconf-set-selections <<< 'mysql-server mysql-server/root_password password my_password'
sudo debconf-set-selections <<< 'mysql-server mysql-server/root_password_again password my_password'
sudo apt-get update
sudo apt-get -y install mysql-server
wget https://gist.githubusercontent.com/ualmtorres/55325478004104fbe828683ea5131e40/raw/0c8edc5750cac0a6a5796544860c8cd94d5c94ac/sginit.sql -O /home/ubuntu/sginit.sql
mysql -h "localhost" -u "root" "-pmy_password" < "/home/ubuntu/sginit.sql"

sudo sed -i 's/127.0.0.1/0.0.0.0/g' /etc/mysql/mysql.conf.d/mysqld.cnf
sudo service mysql restart
To create the instance with Terraform, simply create the resource by passing user_datathe name and path of the initialization script to the property. In this case, the initialization script is assumed to be in the same directory as the Terraform script.
#Crear node mysql
resource "openstack_compute_instance_v2" "mysql" {
  name              = "mysql"
  image_name        = "Ubuntu 16.04 LTS"
  availability_zone = "nova"
  flavor_name       = "medium"
  key_pair          = "mtorres_ual"
  security_groups   = ["default"]
  network {
    name = "desarrollo-net"
  }

  user_data = file("install_mysql.sh") #1
}

  1. Pass the instance initialization script

Template files
A very interesting feature of Terraform is the possibility to define scripts with dynamic content. These are files that interpolate the value of variables generated during the deployment process.
The procedure is the next:

  • Generate output variables
  • Create template files with extension .tplthat get the values ​​of these variables with the following syntax ${nombre-variable}.
  • Interpolate using the function templatefilewhere necessary the template files with the following syntax data.template_file.objeto-template-file.rendered.

To illustrate its use:

  • We will interpolate the variables in the template file
  • We will create a template that gets the IP address of a MySQL server created in the deployment (stored in a variable output). This variable will be used to define an environment variable in the defined instance and to change Apache environment variables.
  • We will create an instance initialized with the template file. The instance will be a web server initialized with a simple PHP application. The application will use the environment variable initialized by the script. The environment variable contains the IP address of the MySQL server that the application accesses to display its data.

Interpolation process of the variables in the file main.tf

data "template_file" "install_appserver" {
  template = file("install_appserver.tpl") #1
  vars = {
    mysql_ip = openstack_compute_instance_v2.mysql.network.0.fixed_ip_v4 #2
  }
  depends_on = [openstack_compute_instance_v2.mysql] #3
}

  • template file
  • Variable Initialization
  • Wait for the instance to be created to obtain its IP.

template file install_appserver.tpl

#!/bin/bash
echo "export MYSQL_SERVER=${mysql_ip}" >> /home/ubuntu/.profile #1

sudo apt-get update
sudo apt-get install -y apache2 php php-mysql libapache2-mod-php php-mcrypt
sudo chgrp -R www-data /var/www
sudo chmod -R 775 /var/www
sudo chmod -R g+s /var/www
sudo useradd -G www-data ubuntu
sudo chown -R ubuntu /var/www/

sudo rm /var/www/html/index.html
wget https://gist.githubusercontent.com/ualmtorres/1c833f9b471fa7351e2725731596f45e/raw/a66b26d90b5f75c3a37cfe12a2370b57d2768132/sginit.php -O /var/www/html/index.php

echo "export MYSQL_SERVER=${mysql_ip}" >> /etc/apache2/envvars #2
sudo service apache2 restart

  • Initializing an environment variable with the value of the variable mysql_ip.
  • Initializing an Apache environment variable with the value of the variable mysql_ip.

Creating the resource with the interpolated initialization script

#Crear node appserver
resource "openstack_compute_instance_v2" "appserver" {
  name              = "appserver"
  image_name        = "Ubuntu 16.04 LTS"
  availability_zone = "nova"
  flavor_name       = "medium"
  key_pair          = "mtorres_ual"
  security_groups   = ["default"]
  network {
    name = "desarrollo-net"
  }

  user_data = data.template_file.install_appserver.rendered #1

  depends_on = [openstack_compute_instance_v2.mysql]

}

  1. Template File Interpolation

Full example
In this section we will create a more complex scenario that combines creation of network resources and provisioned instances during their creation.
It is about creating the following:
Named network desarrollo-net. It will contain a named subnet desarrollo-subnetwith addresses 10.2.0.0./24 and these DNS servers: 150.214.156.2 8.8.8.8.
Named router desarrollo-routerthat connects the outside network ext-netwith the desarrollo-netpreviously created network.
A MySQL server initialized with the scriptinstall_mysql.sh
A web server with PHP initialized with the scriptinstall_appserver.tpl
The following figure illustrates the infrastructure diagram.

After finishing the deployment we will have the network configuration done, a MySQL server with an initialized database and a web server with a PHP product catalog application deployed. Terraform will inform us with the output variables.
Apply complete! Resources: 10 added, 0 changed, 0 destroyed.
Outputs:
Appserver_Floating_IP = 192.168.68.112
MySQL_Floating_IP = 192.168.68.135
If we access the IP address of the web server we will see the catalog application showing the products stored in the database.


No comments

Powered by Blogger.