Terraform on Flexible Engine

Introduction

This articles present how to build you Flexible Engine cloud infrastructure with code (IaC) using Terraform.

Infrastructure as Code

Infrastructure as Code (IaC) is a powerful paradigm for envisioning, configuring, and deploying an IT environment. In the IaC model, your target infrastructure is not constructed manually but is described in a file using a specialized language. An automation tool then reads the file and builds the system to the user’s specifications. The efficiency and versatility of IaC have made it a popular complement to DevOps and the cloud computing revolution.

Flexible Engine and Terraform

Hashicorp Terraform is the most popular opensource infrastructure as code software tool that enables you to safely and predictably create, change, and improve infrastructure.

Flexible Engine is certified by Hashicorp and the Terraform Common Vendor List. It means that Terraform can be used to create, manage and update infrastructure resources such as physical machines, virtual machines, network switches, containers, etc. Almost any type of infrastructure can be represented as a Terraform resource on Flexible Engine.

Terraform providers

As Flexible Engine is an OpenStack based solution , you can use the two following providers to deploy your infrastructure with Terraform :

We recommend to use the Flexible Engine provider if you wish to deploy advanced resources as a Relational Database, Managed Kubernetes cluster, Big Data services and many more that are not part of Openstack. But for interoperability with other Openstack environment you can also use the second provider.

You can also access our Github dedicated to the Flexible Engine provider here :

https://github.com/terraform-providers/terraform-provider-flexibleengine

About Terraform

Terraform State (Terraform docs)

“Terraform must store state about your managed infrastructure and configuration. This state is used by Terraform to map real world resources to your configuration, keep track of metadata, and to improve performance for large infrastructures.

This state is stored by default in a local file named “terraform.tfstate”, but it can also be stored remotely, which works better in a team environment.

Terraform uses this local state to create plans and make changes to your infrastructure. Prior to any operation, Terraform does a refresh to update the state with the real infrastructure.

The primary purpose of Terraform state is to store bindings between objects in a remote system and resource instances declared in your configuration. When Terraform creates a remote object in response to a change of configuration, it will record the identity of that remote object against a particular resource instance, and then potentially update or delete that object in response to future configuration changes…”

Terraform Cycle

In Terraform the life cycle of your resources consists of the following steps : init, plan, apply and destroy.

  1. Terraform init initializes the working directory which contains all the configuration files
  2. Terraform plan is used to create an execution plan to achieve the desired state of the infrastructure. Changes to the configuration files are made in order to reach the desired state.
  3. Terraform apply then performs the changes to the infrastructure as defined in the plan, and the infrastructure reaches the desired state.
  4. Terraform destroy is used to delete all old infrastructure resources, which are marked as corrupt after the apply phase.

Install Terraform

The primary distribution packages for Terraform are .zip archives containing single executable files that you can extract anywhere on your system. However, for easier integration with configuration management tools and other systematic system configuration strategies, Terraform also offer package repositories for Debian, Ubuntu, Red Hat Enterprise Linux, CentOS, or Fedora systems, which allow you to install Terraform using the APT or YUM command.

Build Infrastructure

Prerequisites

To follow this tutorial you will need:

Write Configuration

The set of files used to describe infrastructure in Terraform is known as a Terraform configuration. You will write your first configuration to define a single FE ECS (VM) instance.

Each Terraform configuration must be in its own working directory. Create a directory for your configuration.

$ mkdir learn-FE-terraform

Change into the directory

$ cd learn-FE-terraform

Provider

When using the Flexible Engine Cloud Provider with Terraform 0.13 and later, the recommended approach is to declare Provider versions in the root module Terraform configuration, using a required_providers block as per the following example. For previous versions, please continue to pin the version within the provider block.

To start we need to create a provider.tf file to defined which Terraform provider to use and how to authenticate on FE.

$ touch provider.tf

Then edit this file with your preferred text editor :

  1. Add FlexibleEngineCloud/flexibleengine to your required_providers.# provider.tf
    terraform {
    required_version = “>= 0.13”

    required_providers {
      flexibleengine = {
        source = “FlexibleEngineCloud/flexibleengine”
        version = “>= 1.20.0”
       }
     }
    }
  2. Run terraform init -upgrade to download/upgrade the provider.

Authentication

Before deploying your first resources you need to add credentials to your provider. You can authenticate with two methods

  • AK/SK Authenticate
# provider.tf

# Configure the FlexibleEngine Provider with AK/SK
provider "flexibleengine" {
access_key = "access key"
secret_key = "secret key"
domain_name = "domain name"
region     = "eu-west-0"
}
  • Username/Password Authenticate
# provider.tf

# Configure the FlexibleEngine Provider with Username/Password
provider "flexibleengine" {
user_name   = "user name"
password   = "password"
domain_name = "domain name"
region     = "eu-west-0"
}

Create resources

First we need to go through the Console to gather the following ID’s :

  • network ID
  • security_groups ID

Navigate to the FE Network Console in the web UI, select an existing VPC in your region, select a subnet and copy the ID to your clipboard add it to the following configuration of your ECS. Then do the same for the security group ID (vpc_security_group_ids).

# main.tf

# Create an Elastic Cloud Server resource
resource "flexibleengine_compute_instance_v2" "myinstance" {
name           = "myinstance"
image_name     = "OBS Ubuntu 20.04"
flavor_id       = "s3.large.2"
key_pair       = "my_KeyPair"
security_groups = ["a85055fa-857a-4cfd-b317-c4091577aa73"]

network {
  uuid = "37c7ac09-e9e4-4527-b3e4-64f04841f9d3"
 }
}

Initialize directory

When you create a new configuration — or check out an existing configuration from version control — you need to initialize the directory with terraform init.

Initializing a configuration directory downloads and installs the providers defined in the configuration, which in this case is the flexibleengine provider.

Initialize the directory.

$ terraform init

Initializing the backend...

Initializing provider plugins...
- Finding flexibleenginecloud/flexibleengine versions matching ">= 1.20.0"...
- Installing flexibleenginecloud/flexibleengine v1.27.0...
- Installed flexibleenginecloud/flexibleengine v1.27.0 (self-signed, key ID 0D0EDE6AC300F5EE)

Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/cli/plugins/signing.html

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Terraform downloads the flexibleengine provider and installs it in a hidden subdirectory of your current working directory, named .terraform. The terraform init command prints out which version of the provider was installed. Terraform also creates a lock file named .terraform.lock.hcl which specifies the exact provider versions used, so that you can control when you want to update the providers used for your project.

Format and validate the configuration

Before applying your configuration you can verify what will be deployed and also make sure your configuration is syntactically valid and internally consistent by using the terraform plan command.

$ terraform plan

Create infrastructure

Apply the configuration now with the terraform apply command. Terraform will print output similar to what is shown below. We have truncated some of the output to save space.

$ terraform apply 

The output format is similar to the diff format generated by tools such as Git. The output has a + next to flexibleengine_compute_instance_v2.myinstance, meaning that Terraform will create this resource.

Terraform will now pause and wait for your approval before proceeding. If anything in the plan seems incorrect or dangerous, it is safe to abort here with no changes made to your infrastructure.

In this case the plan is acceptable, so type yes at the confirmation prompt to proceed. Executing the plan will take a few minutes since Terraform waits for the ECS instance to become available.

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.

Enter a value: yes

flexibleengine_compute_instance_v2.myinstance: Creating...
flexibleengine_compute_instance_v2.myinstance: Still creating... [10s elapsed]
flexibleengine_compute_instance_v2.myinstance: Still creating... [20s elapsed]
flexibleengine_compute_instance_v2.myinstance: Creation complete after 27s [id=886eb986-c5f9-4bf3-8b64-4100ee671097]

Inspect state

When you applied your configuration, Terraform wrote data into a file called terraform.tfstate. Terraform stores the IDs and properties of the resources it manages in this file, so that it can update or destroy those resources going forward.

The Terraform state file is the only way Terraform can track which resources it manages, and often contains sensitive information, so you must store your state file securely and restrict access to only trusted team members who need to manage your infrastructure. In production, we recommend storing your state remotely with Flexible Engine Object Storage Service.

Inspect the current state using terraform show.

$ terraform show
# flexibleengine_compute_instance_v2.myinstance:
resource "flexibleengine_compute_instance_v2" "myinstance" {
  access_ip_v4        = "192.168.0.38"
  all_metadata        = {
       "charging_mode"             = "0"
       "image_name"                = "OBS Ubuntu 20.04"
       "metering.image_id"         = "c2280a5f-159f-4489-a107-7cf0c7efdb21"
       "metering.imagetype"        = "gold"
       "metering.resourcespeccode" = "s3.large.2.linux"
       "os_bit"                    = "64"
       "vpc_id"                    = "ff8ef2c5-8eec-4853-9680-425bf73f8dda"
  }
  auto_recovery       = true
  availability_zone   = "eu-west-0b"
  flavor_id           = "s3.large.2"
  flavor_name         = "s3.large.2"
  id                  = "886eb986-c5f9-4bf3-8b64-4100ee671097"
  image_id            = "c2280a5f-159f-4489-a107-7cf0c7efdb21"
  image_name          = "OBS Ubuntu 20.04"
  key_pair            = "my_KeyPair"
  name                = "myinstance"
  region              = "eu-west-0"
  security_groups     = [
       "a85055fa-857a-4cfd-b317-c4091577aa73",
  ]
  status              = "ACTIVE"
  stop_before_destroy = false
  system_disk_id      = "2167f49f-3d79-4440-8bba-88e099b64bf3"
  volume_attached     = [
      {
          boot_index  = 0
          pci_address = ""
          size        = 40
          type        = "SATA"
          uuid        = "2167f49f-3d79-4440-8bba-88e099b64bf3"
      },
  ]

  network {
      access_network = false
      fixed_ip_v4    = "192.168.0.38"
      mac            = "fa:16:3e:72:07:96"
      port           = "a1f53b8b-6af8-4026-9f64-5a60bd88b54a"
      uuid           = "37c7ac09-e9e4-4527-b3e4-64f04841f9d3"
  }
}

Manually Managing State

Terraform has a built-in command called terraform state for advanced state management. Use the list subcommand to list of the resources in your project’s state.

$ terraform state list
flexibleengine_compute_instance_v2.myinstance

Change Infrastructure

Now update the image_name of your instance. Change the flexibleengine_compute_instance_v2.myinstance resource under the provider block in main.tf by replacing the current IMS image name with a new one.

# main.tf

# Create an Elastic Cloud Server resource
resource "flexibleengine_compute_instance_v2" "myinstance" {
name            = "myinstance"
image_name      = "OBS Ubuntu 18.04"
flavor_id       = "s3.large.2"
key_pair        = "my_KeyPair"
security_groups = ["a85055fa-857a-4cfd-b317-c4091577aa73"]

network {
  uuid = "37c7ac09-e9e4-4527-b3e4-64f04841f9d3"
}
}

This update changes the IMS to an Ubuntu 18.04 AMI. The FE provider knows that it cannot change the IMS of an instance after it has been created, so Terraform will destroy the old instance and create a new one. You can verify what will be changed using terraform plan command :

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

 # flexibleengine_compute_instance_v2.myinstance must be replaced
    ~ image_name          = "OBS Ubuntu 20.04" -> "OBS Ubuntu 18.04" # forces replacement

Apply Changes

After verifying the configuration, run terraform apply again to see how Terraform will apply this change to the existing resources.

Plan: 1 to add, 0 to change, 1 to destroy.

Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.

Enter a value: yes

flexibleengine_compute_instance_v2.myinstance: Destroying... [id=886eb986-c5f9-4bf3-8b64-4100ee671097]
flexibleengine_compute_instance_v2.myinstance: Still destroying... [id=886eb986-c5f9-4bf3-8b64-4100ee671097, 10s elapsed]
flexibleengine_compute_instance_v2.myinstance: Destruction complete after 11s
flexibleengine_compute_instance_v2.myinstance: Creating...
flexibleengine_compute_instance_v2.myinstance: Still creating... [10s elapsed]
flexibleengine_compute_instance_v2.myinstance: Still creating... [20s elapsed]
flexibleengine_compute_instance_v2.myinstance: Creation complete after 20s [id=6994d7ac-1519-403b-8c1c-680afa3f1fd6]

Apply complete! Resources: 1 added, 0 changed, 1 destroyed.

As indicated by the execution plan, Terraform first destroyed the existing instance and then created a new one in its place. You can use terraform show again to have Terraform print out the new values associated with this instance.

Destroy Infrastructure

You have now created and updated an ECS instance on FE with Terraform. Now, you will use Terraform to destroy this infrastructure.

Once you no longer need infrastructure, you may want to destroy it to reduce your security exposure and costs. For example, you may remove a production environment from service, or manage short-lived environments like build or testing systems. In addition to building and modifying infrastructure, Terraform can destroy or recreate the infrastructure it manages.

Destroy

The terraform destroy command terminates resources managed by your Terraform project. This command is the inverse of terraform apply in that it terminates all the resources specified in your Terraform state. It does not destroy resources running elsewhere that are not managed by the current Terraform project.

Destroy the resources you created.

$ terraform destroy

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
 - destroy

Terraform will perform the following actions:

 # flexibleengine_compute_instance_v2.myinstance will be destroyed
 - resource "flexibleengine_compute_instance_v2" "myinstance" {
     - access_ip_v4        = "192.168.0.40" -> null
     - all_metadata        = {
         - "charging_mode"             = "0"
         - "image_name"                = "OBS Ubuntu 18.04"
         - "metering.image_id"         = "0249222b-c9be-419b-a953-f47e91c3fc81"
         - "metering.imagetype"        = "gold"
         - "metering.resourcespeccode" = "s3.large.2.linux"
         - "os_bit"                    = "64"
         - "vpc_id"                    = "ff8ef2c5-8eec-4853-9680-425bf73f8dda"
      } -> null
     - auto_recovery       = true -> null
     - availability_zone   = "eu-west-0b" -> null
     - flavor_id           = "s3.large.2" -> null
     - flavor_name         = "s3.large.2" -> null
     - id                  = "6994d7ac-1519-403b-8c1c-680afa3f1fd6" -> null
     - image_id            = "0249222b-c9be-419b-a953-f47e91c3fc81" -> null
     - image_name          = "OBS Ubuntu 18.04" -> null
     - key_pair            = "KeyPair-ae7a" -> null
     - metadata            = {} -> null
     - name                = "myinstance" -> null
     - region              = "eu-west-0" -> null
     - security_groups     = [
         - "sg-front",
      ] -> null
     - status              = "ACTIVE" -> null
     - stop_before_destroy = false -> null
     - system_disk_id      = "b1f22f7b-7c0c-484e-a3bd-6d748ecbbc78" -> null
     - tags                = {} -> null
     - volume_attached     = [
         - {
             - boot_index  = 0
             - pci_address = ""
             - size        = 40
             - type        = "SATA"
             - uuid        = "b1f22f7b-7c0c-484e-a3bd-6d748ecbbc78"
          },
      ] -> null

     - network {
         - access_network = false -> null
         - fixed_ip_v4    = "192.168.0.40" -> null
         - mac            = "fa:16:3e:36:d9:49" -> null
         - port           = "c896b636-d8ea-4cdd-9087-c948cd12871e" -> null
         - uuid           = "37c7ac09-e9e4-4527-b3e4-64f04841f9d3" -> null
      }
  }

The - prefix indicates that the instance will be destroyed. As with apply, Terraform shows its execution plan and waits for approval before making any changes.

Answer yes to execute this plan and destroy the infrastructure.

Enter a value: yes

flexibleengine_compute_instance_v2.myinstance: Destroying... [id=6994d7ac-1519-403b-8c1c-680afa3f1fd6]
flexibleengine_compute_instance_v2.myinstance: Still destroying... [id=6994d7ac-1519-403b-8c1c-680afa3f1fd6, 10s elapsed]
flexibleengine_compute_instance_v2.myinstance: Destruction complete after 10s

Destroy complete! Resources: 1 destroyed.

Just like with apply, Terraform determines the order to destroy your resources. In this case, Terraform identified a single instance with no other dependencies, so it destroyed the instance. In more complicated cases with multiple resources, Terraform will destroy them in a suitable order to respect dependencies.