Infrastructure management with Terraform

This guide describes how to manage Infrastructure as Code by using Terraform.

In this guide, we will dive into a typical Terraform project setup, exploring each file and its role in the broader context of infrastructure automation on Virtuozzo Infrastructure. Whether you are new to Terraform or looking to enhance your understanding of best practices in file organization and usage, this guide will provide valuable insights into crafting and managing your Terraform configurations effectively.

Prerequisites

  • An OS supported by Terraform. In this guide, we are using CentOS Stream 9 as the workstation.
  • Install Terraform.
  • A project in a Virtuozzo Infrastructure domain. Ensure that you have the credentials to access this project.
  • Your favorite text editor. In this guide, we are using Vim.
Note: You need to change the variables used in this guide, such as image or flavor names, to match your environment.

Overview

Our deployment will consist of three Apache web servers behind a load balancer and a database server with MariaDB installed. We will learn how to automatically create all the necessary resources required for this deployment by using Terraform:

  • Creating VXLANs (private networks for your VMs)
  • Configuring security groups
  • Creating and configuring VMs:
    • Creating network interfaces
    • Creating boot volumes
    • Attaching additional volumes
    • Installing packages
    • Creating configuration files
    • Running commands automatically on the first boot
  • Creating and configuring a load balancer to leverage 3 web instances
  • Adding a floating IP address to your load balancer

As a result, the directory where you will be working should contain the following items:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
├── 00-variables.tf # Variables that we will define for our deployment
├── 010-ssh-key.tf  # This file will hold your public SSH key to access the environments
├── 020-network.tf  # Network creation/configuration 
├── 030-security_group.tf # Security Groups creation
├── 060-instance_http.tf  # Web server instances definition 
├── 061-instance_db.tf    # DB instance definition 
├── 070-loadbalancer.tf   # Load balancer definition  
├── provider.tf           # OpenStack provider definition 
├── scripts
│   ├── first-boot-server1.yaml # Cloud-config example for web servers 1 to 3
│   ├── first-boot-server2.yaml
│   ├── first-boot-server3.yaml
│   └── mariadb.yml             # Cloud-config example for the Maria DB installation
└── secrets.tfvars              # File with the password and info about your cloud

Step-by-step guide

1. Create the cloud provider file that will define the modules and terraform provider versions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# cat > provider.tf <<\EOT
terraform {
required_version = ">= 0.14.0"
  required_providers {
    openstack = {
      source  = "terraform-provider-openstack/openstack"
      version = "~> 1.53.0"
    }
  }
}
provider "openstack" {
  user_name   = var.os_username
  tenant_name = var.os_tenant_name
  domain_name = var.os_domain_name
  password    = var.os_password
  auth_url    = var.os_auth_url
  region      = var.os_region
}
EOT

2. Create the secrets.tfvars file to store your secrets:

Note: Keep this file locally and safe.
1
2
3
4
5
6
7
8
# cat > secrets.tfvars <<\EOT
os_username   = "virtuozzo_user_name"
os_tenant_name = "project_name"
os_password   = "password"
os_auth_url   = "https://<your_cloud>.com:5000/v3"
os_region     = "RegionOne"
os_domain_name = "virtuozzo"
EOT

3. Create the variables file and change variables to match your current environment. These variables are used globally in each of the definition files, and their file should be placed in the same directory as the the definition file you are working on. There are also other ways to do this, but this approach is much simpler.

 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
cat > 00-variables.tf <<\EOT
variable "os_username" {}
variable "os_tenant_name" {}
variable "os_password" {}
variable "os_auth_url" {}
variable "os_region" {}
variable "os_domain_name" {}
# Params file for variables
#Set here the storage policy name
variable "volume_type" {
  type  = string
  default = "standard" #change this to match the name of your storage policy
}
#### GLANCE
variable "image" {
  type    = string
  default = "CentOS-9" #change this to match the name of the image you'd like to use (must be centos based)
}
#### NEUTRON
variable "external_network" {
  type    = string
  default = "external-network"
}
# UUID of external gateway
variable "external_gateway" {
  type    = string
  default = "26e450e5-e600-4174-a1e1-cc023e850095" # Find your public network on your project. Click on the network and replace the ID
}
variable "dns_ip" {
  type    = list(string)
  default = ["8.8.8.8", "1.1.1.1"] # whatever you like to use here 
}
#### VM HTTP parameters ####
variable "flavor_http" {
  type    = string
  default = "va-4-8" # change this to match the available flavor on your cloud
}
variable "network_http" {
  type = map(string)
  default = {
    subnet_name = "subnet-http"
    cidr        = "192.168.1.0/24"
  }
}
variable "http_instance_names" {
  description = "Map of instance names to their cloud-config filenames"
  type        = map(string)
  default     = {
    "http-instance-1" = "scripts/first-boot-server1.yaml", #make your you create a subdir called scripts we will populate this later
    "http-instance-2" = "scripts/first-boot-server2.yaml",
    "http-instance-3" = "scripts/first-boot-server3.yaml"
  }
}
#### MAIN DISK SIZE FOR HTTP
variable "volume_http" {
  type    = number
  default = 10
}
#### VM DB parameters ####
variable "flavor_db" {
  type    = string
  default = "va-4-8" # change this to match the available flavor on your cloud
}
variable "network_db" {
  type = map(string)
  default = {
    subnet_name = "subnet-db"
    cidr        = "192.168.2.0/24"
  }
}
variable "db_instance_names" {
  type = set(string)
  default = ["db-instance-1"]
}
#### ATTACHED VOLUME PARAMS
variable "volume_db" {
  type    = number
  default = 15
}
EOT

4. Create the SSH key file that will define the key to be added to workloads. You need to specify your public SSH key.

1
2
3
4
5
6
7
# cat > 010-ssh-key.tf <<\EOT
#Define ssh to config in instance
resource "openstack_compute_keypair_v2" "user_key" {
  name       = "user-key"
  public_key = "ssh-rsa AAAAB3NzaC......" # Your public SSH key here
}
EOT

5. Create the network configuration for your VMs that will define a network, a router, and two subnets: one is for the HTTP servers (web servers), the other is for the MariaDB server.

 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
32
33
34
35
36
37
38
# cat > 020-network.tf <<\EOT
#### NETWORK CONFIGURATION ####
# Router creation
resource "openstack_networking_router_v2" "generic" {
  name                = "router"
  external_network_id = var.external_gateway
}
# Network creation
resource "openstack_networking_network_v2" "generic" {
  name = "network-generic"
}
#### HTTP SUBNET ####
# Subnet http configuration
resource "openstack_networking_subnet_v2" "http" {
  name            = var.network_http["subnet_name"]
  network_id      = openstack_networking_network_v2.generic.id
  cidr            = var.network_http["cidr"]
  dns_nameservers = var.dns_ip
}
# Router interface configuration
resource "openstack_networking_router_interface_v2" "http" {
  router_id = openstack_networking_router_v2.generic.id
  subnet_id = openstack_networking_subnet_v2.http.id
}
#### DB NETWORK ####
# Subnet db configuration
resource "openstack_networking_subnet_v2" "db" {
  name            = var.network_db["subnet_name"]
  network_id      = openstack_networking_network_v2.generic.id
  cidr            = var.network_db["cidr"]
  dns_nameservers = var.dns_ip
}
# Router interface configuration
resource "openstack_networking_router_interface_v2" "db" {
  router_id = openstack_networking_router_v2.generic.id
  subnet_id = openstack_networking_subnet_v2.db.id
}
EOT

6. Create the security group file that will define a security group to be created for your VMs, web servers, and database servers:

 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
32
33
34
35
# cat > 030-security_group.tf <<\EOT
# Access group, open input port 80 and ssh port
resource "openstack_compute_secgroup_v2" "http" {
  name        = "http"
  description = "Open input http port"
  rule {
    from_port   = 80
    to_port     = 80
    ip_protocol = "tcp"
    cidr        = "0.0.0.0/0"
  }
}
# Open mariadb port
resource "openstack_compute_secgroup_v2" "db" {
  name        = "db"
  description = "Open input db port"
  rule {
    from_port   = 3306
    to_port     = 3306
    ip_protocol = "tcp"
    cidr        = "0.0.0.0/0"
  }
}
# Open Apache2 port
resource "openstack_compute_secgroup_v2" "ssh" {
  name        = "ssh"
  description = "Open input ssh port"
  rule {
    from_port   = 22
    to_port     = 22
    ip_protocol = "tcp"
    cidr        = "0.0.0.0/0"
  }
}
EOT

7. Create the web server file that will define your web server VMs:

 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
32
33
34
35
36
37
38
39
40
41
42
43
44
# cat > 060-instance_http.tf <<\EOT
# Get the uiid of image
data "openstack_images_image_v2" "http" {
  name        = var.image
  most_recent = true
}
# Create instance
#
resource "openstack_compute_instance_v2" "http" {
  for_each    = var.http_instance_names
  name        = each.key
  image_name  = var.image
  flavor_name = var.flavor_http
  key_pair    = openstack_compute_keypair_v2.user_key.name
  user_data   = file(each.value)
  network {
    port = openstack_networking_port_v2.http[each.key].id
  }
 # Install system in volume
  block_device {
    volume_size           = var.volume_http
    destination_type      = "volume"
    delete_on_termination = true
    boot_index            = 0
    source_type           = "image"
    uuid                  = data.openstack_images_image_v2.http.id
    volume_type           = var.volume_type
 }
}
# Create network port
resource "openstack_networking_port_v2" "http" {
  for_each       = var.http_instance_names
  name           = "port-http-${each.key}"
  network_id     = openstack_networking_network_v2.generic.id
  admin_state_up = true
  security_group_ids = [
    openstack_compute_secgroup_v2.ssh.id,
    openstack_compute_secgroup_v2.http.id,
  ]
  fixed_ip {
    subnet_id = openstack_networking_subnet_v2.http.id
  }
}
EOT

8. Create the database file that will define the database instance:

 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# cat > 061-instance_db.tf <<\EOT
#### INSTANCE DB ####
# Get the uiid of image
data "openstack_images_image_v2" "db" {
  name        = var.image
  most_recent = true
}
# Create instance
#
resource "openstack_compute_instance_v2" "db" {
  for_each    = var.db_instance_names
  name        = each.key
  image_name  = var.image
  flavor_name = var.flavor_db
  key_pair    = openstack_compute_keypair_v2.user_key.name
  user_data   = file("scripts/mariadb.yml")
  network {
    port = openstack_networking_port_v2.db[each.key].id
  }
 # Install system in volume
  block_device {
    volume_size           = var.volume_http
    destination_type      = "volume"
    delete_on_termination = true
    boot_index            = 0
    source_type           = "image"
    uuid                  = data.openstack_images_image_v2.db.id
    volume_type           = var.volume_type
 }
}
# Create network port
resource "openstack_networking_port_v2" "db" {
  for_each       = var.db_instance_names
  name           = "port-db-${each.key}"
  network_id     = openstack_networking_network_v2.generic.id
  admin_state_up = true
  security_group_ids = [
    openstack_compute_secgroup_v2.ssh.id,
    openstack_compute_secgroup_v2.db.id,
  ]
  fixed_ip {
    subnet_id = openstack_networking_subnet_v2.db.id
  }
}
#### VOLUME MANAGEMENT ####
# Create volume
resource "openstack_blockstorage_volume_v3" "db" {
 # name = "volume-db"
 # size = var.volume_db
  for_each = var.db_instance_names
  name     = "volume-db-${each.key}"
  size     = var.volume_db
  volume_type = var.volume_type
}
# Attach volume to instance instance db
resource "openstack_compute_volume_attach_v2" "db" {
  for_each    = var.db_instance_names
  instance_id = openstack_compute_instance_v2.db[each.key].id
  volume_id   = openstack_blockstorage_volume_v3.db[each.key].id
}
EOT

9. Create the load balancer file will define load balancers for your VMs and also a public IP address for the web server load balancer:

 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# cat > 070-loadbalancer.tf <<\EOT
# HTTP LOAD BALANCER CONFIGURATION
#
# Create load balancer
resource "openstack_lb_loadbalancer_v2" "http" {
  name          = "elastic_loadbalancer_http"
  vip_subnet_id = openstack_networking_subnet_v2.http.id
  depends_on    = [openstack_compute_instance_v2.http]
}
# Create listener
resource "openstack_lb_listener_v2" "http" {
  name            = "listener_http"
  protocol        = "TCP"
  protocol_port   = 80
  loadbalancer_id = openstack_lb_loadbalancer_v2.http.id
  depends_on      = [openstack_lb_loadbalancer_v2.http]
}
# Set method for load balancer change between instances
resource "openstack_lb_pool_v2" "http" {
  name        = "pool_http"
  protocol    = "TCP"
  lb_method   = "ROUND_ROBIN"
  listener_id = openstack_lb_listener_v2.http.id
  depends_on  = [openstack_lb_listener_v2.http]
}
# Add multiple instances to pool
resource "openstack_lb_member_v2" "http" {
  for_each      = var.http_instance_names
  address       = openstack_compute_instance_v2.http[each.key].access_ip_v4
  protocol_port = 80
  pool_id       = openstack_lb_pool_v2.http.id
  subnet_id     = openstack_networking_subnet_v2.http.id
  depends_on    = [openstack_lb_pool_v2.http]
}
# Create health monitor to check service instance status
resource "openstack_lb_monitor_v2" "http" {
  name        = "monitor_http"
  pool_id     = openstack_lb_pool_v2.http.id
  type        = "TCP"
  delay       = 2
  timeout     = 2
  max_retries = 2
  depends_on  = [openstack_lb_member_v2.http]
}
# Create floating IP for http load balancer
resource "openstack_networking_floatingip_v2" "http" {
  pool = "Public"
}
# Associate
resource "openstack_networking_floatingip_associate_v2" "http_fip_assoc" {
  floating_ip = openstack_networking_floatingip_v2.http.address
  fixed_ip    = openstack_lb_loadbalancer_v2.http.vip_address
  port_id     = openstack_lb_loadbalancer_v2.http.vip_port_id
}
# DB LOAD BALANCER CONFIGURATION
#
# Create load balancer
resource "openstack_lb_loadbalancer_v2" "db" {
  name          = "elastic_loadbalancer_db"
  vip_subnet_id = openstack_networking_subnet_v2.db.id
  depends_on    = [openstack_compute_instance_v2.db]
}
# Create listener
resource "openstack_lb_listener_v2" "db" {
  name            = "listener_db"
  protocol        = "TCP"
  protocol_port   = 3306
  loadbalancer_id = openstack_lb_loadbalancer_v2.db.id
  depends_on      = [openstack_lb_loadbalancer_v2.db]
}
# Set method for load balancer change between instances
resource "openstack_lb_pool_v2" "db" {
  name        = "pool_db"
  protocol    = "TCP"
  lb_method   = "ROUND_ROBIN"
  listener_id = openstack_lb_listener_v2.db.id
  depends_on  = [openstack_lb_listener_v2.db]
}
# Add multiple instances to pool
resource "openstack_lb_member_v2" "db" {
  for_each      = var.db_instance_names
  address       = openstack_compute_instance_v2.db[each.key].access_ip_v4
  protocol_port = 3306
  pool_id       = openstack_lb_pool_v2.db.id
  subnet_id     = openstack_networking_subnet_v2.db.id
  depends_on    = [openstack_lb_pool_v2.db]
}
# Create health monitor to check service instance status
resource "openstack_lb_monitor_v2" "db" {
  name        = "monitor_db"
  pool_id     = openstack_lb_pool_v2.db.id
  type        = "TCP"
  delay       = 2
  timeout     = 2
  max_retries = 2
  depends_on  = [openstack_lb_member_v2.db]
}
EOT

10. Create the configuration files with cloud-init (cloud-config) that will configure your instances on the first boot:

Important: The directory name should be scripts, as it is referenced in the instance definitions for the database and web server files. All of the YAML definitions should be located inside the scripts directory.

10.1. Create the configuration file for the first web server inside the scripts directory:

 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
32
33
34
35
mkdir scripts
cd scripts 
cat > first-boot-server1.yaml<<\EOT
#cloud-config
packages:
  - httpd
  - policycoreutils-python-utils
  - mariadb # we need this to test access to the mariadb server later
runcmd:
  # Update all packages first
  #- [ dnf, update, -y ] #uncomment this line if you have time. It takes time to install packages
  # Enable and start Apache
  - [ systemctl, enable, httpd ]
  - [ systemctl, start, httpd ]
  # Configure firewall to allow HTTP and HTTPS traffic
  - [ firewall-cmd, --permanent, --add-service=http ]
  - [ firewall-cmd, --reload ]
  # Configure SELinux to allow Apache to serve content
  - [ semanage, fcontext, -a, -t, httpd_sys_content_t, "/var/www/html(/.*)?" ]
  - [ restorecon, -Rv, /var/www/html ]
  # Restart Apache to apply all changes
  - [ systemctl, restart, httpd ]
# This will write a basic test page to the web root
write_files:
  - path: /var/www/html/index.html
    content: |
      <html>
        <head><title>Test Page</title></head>
        <body>
          <h1>Hello, HTTP World! in server1</h1>
        </body>
      </html>
    owner: root:root
    permissions: '0644'
EOT

10.2. Create the configuration file for the second web server:

 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
32
33
# cat > first-boot-server2.yaml<<\EOT
#cloud-config
packages:
  - httpd
  - policycoreutils-python-utils
  - mariadb # we need this to test access to the mariadb server later
runcmd:
  # Update all packages first
 #- [ dnf, update, -y ] #uncomment this line if you have time. It takes time to install packages
  # Enable and start Apache
  - [ systemctl, enable, httpd ]
  - [ systemctl, start, httpd ]
  # Configure firewall to allow HTTP and HTTPS traffic
  - [ firewall-cmd, --permanent, --add-service=http ]
  - [ firewall-cmd, --reload ]
  # Configure SELinux to allow Apache to serve content
  - [ semanage, fcontext, -a, -t, httpd_sys_content_t, "/var/www/html(/.*)?" ]
  - [ restorecon, -Rv, /var/www/html ]
  # Restart Apache to apply all changes
  - [ systemctl, restart, httpd ]
# This will write a basic test page to the web root
write_files:
  - path: /var/www/html/index.html
    content: |
      <html>
        <head><title>Test Page</title></head>
        <body>
          <h1>Hello, HTTP World! in server2</h1>
        </body>
      </html>
    owner: root:root
    permissions: '0644'
EOT

10.3. Create the configuration file for the third web server:

 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
32
33
# cat > first-boot-server3.yaml<<\EOT
#cloud-config
packages:
  - httpd
  - policycoreutils-python-utils
  - mariadb # we need this to test access to the mariadb server later
runcmd:
  # Update all packages first
  #- [ dnf, update, -y ] #uncomment this line if you have time. It takes time to install packages
  # Enable and start Apache
  - [ systemctl, enable, httpd ]
  - [ systemctl, start, httpd ]
  # Configure firewall to allow HTTP and HTTPS traffic
  - [ firewall-cmd, --permanent, --add-service=http ]
  - [ firewall-cmd, --reload ]
  # Configure SELinux to allow Apache to serve content
  - [ semanage, fcontext, -a, -t, httpd_sys_content_t, "/var/www/html(/.*)?" ]
  - [ restorecon, -Rv, /var/www/html ]
  # Restart Apache to apply all changes
  - [ systemctl, restart, httpd ]
# This will write a basic test page to the web root
write_files:
  - path: /var/www/html/index.html
    content: |
      <html>
        <head><title>Test Page</title></head>
        <body>
          <h1>Hello, HTTP World! in server3</h1>
        </body>
      </html>
    owner: root:root
    permissions: '0644'
EOT

10.4. Create the configuration file for the database:

 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
32
33
# cat > mariadb.yml <<\EOT
#cloud-config
packages:
  - mariadb-server
  - mariadb
  - firewalld
  - policycoreutils-python-utils  # Ensure semanage is available
runcmd:
  # Update the system
  #- [ dnf, update, -y ] #uncomment this line if you have time. It takes time to install packages
  # Enable and start MariaDB and firewalld
  - [ systemctl, enable, --now, mariadb ]
  - [ systemctl, enable, --now, firewalld ]
  # Configure MariaDB to listen on all interfaces
  - echo "[mysqld]" >> /etc/my.cnf.d/mariadb-server.cnf
  - echo "bind-address=0.0.0.0" >> /etc/my.cnf.d/mariadb-server.cnf
  # Restart MariaDB to apply configuration changes
  - [ systemctl, restart, mariadb ]
  # Secure the installation
  - [ mysql, -e, "SET PASSWORD FOR 'root'@'localhost' = PASSWORD('yourStrongPasswordHere'); FLUSH PRIVILEGES;" ]
  - [ mysql, -e, "DELETE FROM mysql.user WHERE User = ''; FLUSH PRIVILEGES;" ]
  - [ mysql, -e, "DELETE FROM mysql.user WHERE User = 'root' AND Host NOT IN ('localhost', '127.0.0.1', '::1'); FLUSH PRIVILEGES;" ]
  - [ mysql, -e, "DROP DATABASE IF EXISTS test; FLUSH PRIVILEGES;" ]
  - [ mysql, -e, "CREATE USER 'remote_user'@'%' IDENTIFIED BY 'anotherStrongPassword';" ]
  - [ mysql, -e, "GRANT ALL PRIVILEGES ON *.* TO 'remote_user'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES;" ]
  # Open firewall for remote connections
  - [ firewall-cmd, --permanent, --add-port=3306/tcp ]
  - [ firewall-cmd, --reload ]
  # Adjust SELinux to allow MariaDB to accept remote connections
  - [ semanage, port, -a, -t, mysqld_port_t, -p, tcp, 3306 ]
  - [ setsebool, -P, mysql_connect_any, 1 ]
final_message: "MariaDB server setup is complete and running on: $public_ipv4"
EOT

Your directory should have the following contents:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
├── 00-variables.tf # Variables that we will define for our deployment
├── 010-ssh-key.tf  # This file will hold your public SSH key to access the environments
├── 020-network.tf  # Network creation/configuration 
├── 030-security_group.tf # Security Groups creation
├── 060-instance_http.tf  # Web server instances definition 
├── 061-instance_db.tf    # DB instance definition 
├── 070-loadbalancer.tf   # Load balancer definition  
├── provider.tf           # OpenStack provider definition 
├── scripts
│   ├── first-boot-server1.yaml # Cloud-config example for web servers 1 to 3
│   ├── first-boot-server2.yaml
│   ├── first-boot-server3.yaml
│   └── mariadb.yml             # Cloud-config example for the Maria DB installation
└── secrets.tfvars              # File with the password and info about your cloud

11. The configuration scripts created for the web servers will install HTTPS (Apache on CentOS). Update the system, enable and start the Apache service, configure the firewall, and add the index.html file with a different message to each web server. When testing the load balancer, this message will show us that we are in the round-robin mode.

12. Install, configure, and enable MariaDB on the database server.

Now, you are ready to run Terraform commands to deploy the defined Infrastructure as Code in to your Virtuozzo Infrastructure project.

1. Initialize Terraform:

1
# terraform init

The command output should be similar to the following:

terraform init

2. Once Terraform is successfully initialized, run the terraform plan command:

1
# terraform plan -var-file="secrets.tfvars"

3. If the previous command was successful, proceed with the deployment:

1
# terraform apply -var-file="secrets.tfvars"

The command will create the following resources:

terraform vms
terraform volumes
terraform networks
terraform routers
terraform sgs
terraform keys
terraform floating ips
terraform lbs

Now, when you access your web servers at the load balancer public IP address, you will see the following:

terraform web

Finally, add a floating IP address to one of your web servers and try to access MariaDB on the database server (the password is anotherStrongPassword):

1
# mariadb -u remote_user  -e "SHOW DATABASES;" -h 192.168.2.26 -p

terraform mariadb

To delete all of the created workloads, run the terraform destroy command:

1
# terraform destroy -var-file="secrets.tfvars"

To more details, refer to the Terraform documentation.

Enjoy!