Terraform and Exoscale

Terraform and Exoscale

Terraform is useful for managing infrastructures because it automates the deployment and management of infrastructure resources, ensures consistency across environments, allows for collaboration and scalability, and provides version control for infrastructure code. It simplifies infrastructure management, making it an essential tool for organizations.

In this article, we will code a terraform project to deploy:

  • a virtual machine (Ubuntu 22.04)

  • a private network

  • a security group with security rules

Let's get started

As a prerequisite, you will need to install terraform client -> Terraform install doc.

First thing is to create an IAM key on our Exoscale organization. This key will be used in terraform for the exoscale authentication.

To do so, go to your exoscale portal and create a Key under "IAM" > "API KEYS".
For the tutorial, we created an "unrestricted key", but you can limit the rights to match only needed resources.

Note somewhere the secret, we will need it later.

We will now create the main file "main.tf" that contains the exoscale provider and the input variables for authentication.

# Define the required providers for this configuration
terraform {
  required_providers {
    # Use the "exoscale/exoscale" provider
    exoscale = {
      source  = "exoscale/exoscale"
      # Optional version constraint for the provider
      #versversion = "0.44.0"
    }
  }
}

# Define the required variables for this configuration
variable "exoscale_api_key" {
  # The type of the variable is a string
  type = string
}
variable "exoscale_api_secret" {
  # The type of the variable is a string
  type = string
}

# Configure the "exoscale" provider
provider "exoscale" {
  # Use the value of the "exoscale_api_key" variable for the "key" parameter
  key    = var.exoscale_api_key
  # Use the value of the "exoscale_api_secret" variable for the "secret" parameter
  secret = var.exoscale_api_secret
}

This code will ask you to enter the key and secret when using terraform plan or terraform apply.

Next we will create a separate file for the virtual machine and the private network. We will call it compute.tf.

# Define some local variables for convenience
locals {
  my_zone     = "ch-gva-2"
  my_template = "Linux Ubuntu 22.04 LTS 64-bit"
}

# Retrieve information about the desired template
data "exoscale_template" "my_template" {
  zone = local.my_zone
  name = local.my_template
}

# Create a new Exoscale compute instance
resource "exoscale_compute_instance" "my_instance" {
  zone        = local.my_zone
  name        = "my-server-name"

  # Use the ID of the retrieved template
  template_id = data.exoscale_template.my_template.id
  type        = "cpu.extra-large"
  disk_size   = 100

  # Define the network interface for the instance
  network_interface {
    network_id = exoscale_private_network.private_network.id
    ip_address = "192.168.1.21"
  }
}

# Create a new Exoscale private network
resource "exoscale_private_network" "private_network" {
  zone = "ch-gva-2"
  name = "my-private-network"
  netmask  = "255.255.255.0"
  start_ip = "192.168.1.20"
  end_ip   = "192.168.1.253"
}

You can use the exo CLI to list available templates with exo compute instance-template list and available instance types with exo compute instance-type list. The code below is a straightforward example that creates a compute instance of type 'cpu-extra-large' with 100 GB of disk in the 'geneva 2' zone. It also creates a private network in the same zone with a defined range of IP addresses. The compute instance is associated with the private network and assigned the IP address 192.168.1.21.

At this point, we have a virtual machine with a private IP address on a private subnet. Additionally, every compute instance on Exoscale is assigned a public IP address by default, which is convenient. However, access to the virtual machine is denied by default, so we will create some security groups to allow access from specified IP addresses or ports.

We create a terraform file called security-group.ft.

What we want to open:

  • Access from any IP on port 80 and 443

  • Access from any IP to ping (ICMP)

  • Access from only your public ip for SSH (22)

# Create a new Exoscale security group
resource "exoscale_security_group" "my_security_group" {
  name = "my-secu-group"
}

# Define a new security group rule for SSH access
resource "exoscale_security_group_rule" "ssh_security_group_rule" {
  # Associate the rule with the "exoscale_security_group" resource created above
  security_group_id = exoscale_security_group.my_security_group.id
  type              = "INGRESS"
  protocol          = "TCP"
  cidr              = "YOU_PUBLIC_IP/32"
  start_port        = 22
  end_port          = 22
}

# Define a new security group rule for HTTP access
resource "exoscale_security_group_rule" "http_security_group_rule" {
  # Associate the rule with the "exoscale_security_group" resource created above
  security_group_id = exoscale_security_group.my_security_group.id
  type              = "INGRESS"
  protocol          = "TCP"
  cidr              = "0.0.0.0/0"
  start_port        = 80
  end_port          = 80
}

# Define a new security group rule for HTTPS access
resource "exoscale_security_group_rule" "https_security_group_rule" {
  # Associate the rule with the "exoscale_security_group" resource created above
  security_group_id = exoscale_security_group.my_security_group.id
  type              = "INGRESS"
  protocol          = "TCP"
  cidr              = "0.0.0.0/0"
  start_port        = 443
  end_port          = 443
}

# Define a new security group rule for ICMP access
resource "exoscale_security_group_rule" "icmp_security_group_rule" {
  # Associate the rule with the "exoscale_security_group" resource created above
  security_group_id = exoscale_security_group.my_security_group.id
  type              = "INGRESS"
  protocol          = "ICMP"
  icmp_code         = 0
  icmp_type         = 8
  cidr              = "0.0.0.0/0"
}

In the code above, we create a resource group and add 4 rules in it. One for ssh, one for HTTP, one for HTTPS, and one for ICMP. Note that 0.0.0.0/0 stands for any source IP.

Now we need to bind this security group with our compute instance.

To do so we need to edit our compute.tf file and add the reference to the secruity group defined above.

...
resource "exoscale_compute_instance" "my_instance" {
  zone        = local.my_zone
  name        = "my-server-name"

  template_id = data.exoscale_template.my_template.id
  type        = "cpu.extra-large"
  disk_size   = 100

  network_interface {
    network_id = exoscale_private_network.private_network.id
    ip_address = "192.168.1.21"
  }
  ##### BELOW RED TO THE SECURITY GROUP ####
  security_group_ids = [
    exoscale_security_group.my_security_group.id,
  ]
}
...

n the previous step, we created some security groups to allow access to the virtual machine. In this step, we add the 'security_group_ids' parameter to the 'exoscale_compute_instance' resource to associate the virtual machine with the created security group.

Now that we have defined all the required resources in our Terraform configuration, we can use the 'terraform plan' command to review the proposed changes and 'terraform apply' to apply the configuration and create the resources.

terraform plan
terraform apply

Et Voilà!

Pretty straightforward forward isn't it?