Configuring Terraform for Scaleway/OVHcloud

Configuring Terraform for Scaleway/OVHcloud
Jérémie Kassianoff
May 24, 2022
10 min read

Terraform lets you automate resource deployment.

Certified inHashiCorp Terraform Associate

Real-world use case

You need to deploy cloud resources in a reproducible way on Scaleway or OVHcloud: here's how to configure Terraform to automate this deployment.

Terraform

Terraform is a tool I use to provision my resources hosted with certain providers such as OVH, Scaleway, Azure, or AWS. This is what's known as Infrastructure As Code (IaC): it lets you automate certain tasks. The goal is to provision resources with a provider. More information: Browse Providers | Terraform Registry.

All the commands below are compatible with all clients (Windows, Mac, Linux). In our example, we'll deploy infrastructure on Scaleway with Terraform and run actions on an OVHcloud server.

Installing Terraform on macOS

You can install the package manually on macOS, or via the Homebrew package manager:

bash
brew tap hashicorp/tap
Running `brew update --preinstall`...
==> Auto-updated Homebrew!
d 3 taps (hashicorp/tap, homebrew/core and homebrew/cask).
==> New Formulae
dbml-cli             dotdrop              [email protected]           goctl                libabw               lndir                octosql              release-it           sdl2_sound           sftpgo               xcode-kotlin
==> Updated Formulae
Updated 554 formulae.
==> New Casks
aethersx2                                       jquake                                          plus42-binary                                   ti-smartview-ce-for-the-ti-84-plus-family       vieb
bike                                            manila                                          plus42-decimal                                  tqsl                                            yandex-music-unofficial
ferdium                                         mega                                            podman-desktop                                  trivial
groestlcoin-core                                oxwu                                            protokol                                        universal-android-debloater
==> Updated Casks
Updated 731 casks.
==> Deleted Casks
entropy                 gloomhaven-helper       indigo                  kite                    megax                   nbtexplorer             shortery                tifig                   twitch                  xoctave
brew install hashicorp/tap/terraform
==> Downloading https://releases.hashicorp.com/terraform/1.1.9/terraform_1.1.9_darwin_arm64.zip
######################################################################## 100.0%
==> Installing terraform from hashicorp/tap
????  /opt/homebrew/Cellar/terraform/1.1.9: 3 files, 71.4MB, built in 2 seconds
==> Running `brew cleanup terraform`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).

Next I check the registry of existing providers and look for Scaleway: scaleway/scaleway | Terraform Registry and OVHcloud.

Configuring Terraform

To get started, you need to create a folder dedicated to each Terraform project. Then you need to create a main.tf file inside it. There are two ways to write our configuration: using the HashiCorp language or the structured JSON format. In my case, I prefer using JSON formatting.

Terraform with Scaleway

We'll look at an integration with the Scaleway provider (the init, plan, apply commands are explained further below in the OVH provider example if needed).

Here's a configuration example on Scaleway:

bash
terraform {
  required_providers {
    scaleway = {
      source = "scaleway/scaleway"
    }
  }
  required_version = ">= 0.13"
}

provider "scaleway" {
  alias = "production"
  profile = "kassianoff-blog"
}

resource "scaleway_account_ssh_key" "jérémie" {
  provider = scaleway.production
  name = "Jérémie"
  public_key = "ssh-ed25519 AABBCCDDEEFFGGAABBCCDDEEFFGGAABBCCDDEEFFGGAABBCCDDEEFFGGAABBCCDDEEFF"
}

resource "scaleway_instance_ip" "public_ip" {
  provider = scaleway.production
}

locals {
  ip_source = [
    { ip = "my_routable_ip_01/32", port = "22" },
    { ip = "my_routable_ip_02/32", port = "22" },
  ]

  any_in = [
    { ip_range = "0.0.0.0/0", port = "80" },
    { ip_range = "0.0.0.0/0", port = "443" },
  ]

  any_out = [
    { ip_range = "0.0.0.0/0", port = "53", protocol = "TCP" },
    { ip_range = "0.0.0.0/0", port = "53", protocol = "UDP" },
    { ip_range = "0.0.0.0/0", port = "80", protocol = "TCP" },
    { ip_range = "0.0.0.0/0", port = "443", protocol = "TCP" },
  ]
}

resource "scaleway_instance_security_group" "prod" {
  provider = scaleway.production
  description = "scaleway-firewall"
  name = "Kassianoff scaleway firewall"
  inbound_default_policy = "drop"
  outbound_default_policy = "drop"
  enable_default_security = false

  dynamic "inbound_rule" {
    for_each = local.ip_source && local.any_in

    content {
      action = "accept"
      ip = inbound_rule.value.ip
      ip_range = inbound_rule.value.ip_range
      port = inbound_rule.value.port
    }
  }

  dynamic "outbound_rule" {
    for_each = local.any_out

    content {
      action = "accept"
      ip_range = outbound_rule.value.ip_range
      port = outbound_rule.value.port
      protocol = outbound_rule.value.protocol
    }
  }
}

resource "scaleway_instance_server" "blog" {
  provider = scaleway.production
  name = "kassianoff blog"
  type = "DEV1-L"
  image = "ubuntu_jammy"
  tags = [ "blog" ]
  ip_id = scaleway_instance_ip.public_ip.id
  security_group_id = scaleway_instance_security_group.blog.id
  private_network {
    pn_id = scaleway_vpc_private_network.blog.id
  }
}

resource "scaleway_rdb_database" "blog" {
  instance_id = scaleway_rdb_instance.blog.id
  name = "kassianoff-database"
  node_type = "DB-DEV-S"
  engine = "PostgreSQL-14"
  is_ha_cluster = true
  disable_backup = true
  user_name = "kassianoff"
  password = "thiZ_is_v&ry_s3cret"
  region= "fr-par"
  tags = [ "db", "blog" ]
  volume_type = "bssd"
  volume_size_in_gb = 5
  private_network { 
    ip_net = "192.168.1.254/24" 
    pn_id = "${scaleway_vpc_private_network.blog.id}" 
  }
}

resource scaleway_vpc_private_network blog { 
name = "production" 
}

Let's not forget to configure a profile for our Scaleway provider:

yaml
profiles:
  kassianoff-blog:
    access_key: XXXXXXXXXXXXXXXXXXXX
    secret_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 
    default_organization_id: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    default_project_id: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    default_zone: fr-par-1
    default_region: fr-par
    api_url: https://api.scaleway.com
    insecure: false
  1. We specify the provider we want to interact with, i.e. Scaleway.
  2. We defined a profile named "kassianoff-blog" to contact our provider. This refers to a configuration file (which isn't the default one). It contains our secrets, our deployment zone, and Scaleway's endpoint:
  3. We add an SSH key to our Scaleway profile so we can connect to our next instance.
  4. We add a public_ip resource so we can get an IP address on our next instance.
  5. We declare a "locals" variable in a JSON array with several values: ip_source, any_in, any_out, so we can reuse them when editing the Scaleway firewall configuration.
  6. We'll then create a Scaleway security group by adding our firewall rules in a dynamic expression so we can reuse our variables.
  7. We'll then create our server instance by choosing its name, its type, and its characteristics. We notice that the integration with the public IP and the security group is clearly visible.
  8. Furthermore, we want to create a database managed by Scaleway so we don't have to manage it ourselves. We also connect a private network to our database and configure its interface on the private network.
  9. A private network will be created so the instance and the database can communicate over a private network (you'll need to configure your ens5(eth1) interface on the linux server to access the database).

Terraform with OVHcloud

Here's another small example to switch my kimsufi server, dating from 2013, into rescue mode and reboot it:

bash
terraform {
  required_providers {
    ovh = {
      source  = "ovh/ovh"
    }
  }
}

provider "ovh" {
endpoint = "ovh-eu"
application_key = "namJkXXXXXXXXXXX"
application_secret = "rXXXXXXXXXXXX"
consumer_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}

data ovh_dedicated_server_boots "rescue" {
service_name = "ksXXXXXXX.kimsufi.com"
boot_type = "rescue"
kernel = "rescue64-pro"
}

resource ovh_dedicated_server_update "server_on_rescue" {
service_name = "ksXXXXXXX.kimsufi.com"
boot_id = data.ovh_dedicated_server_boots.rescue.result[0]
monitoring = true
state = "ok"
}

resource ovh_dedicated_server_reboot_task "server_reboot" {
service_name = data.ovh_dedicated_server_boots.rescue.service_name
keepers = [
ovh_dedicated_server_update.server_on_rescue.boot_id,
]

}

One of the first Terraform commands is "terraform init". It's used to initialize a working directory containing Terraform configuration files. It's the first command to run after writing a new Terraform configuration.

bash
terraform init

Initializing the backend...

Initializing provider plugins...

- Finding ovh/ovh versions matching "~> 0.11.0"...

- Installing ovh/ovh v0.11.0...

- Installed ovh/ovh v0.11.0 (signed by a HashiCorp partner, key ID F56D1A6CBDAAADA5)

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.

The second command is "terraform plan". This command creates an execution plan, which lets you preview the changes Terraform plans to make to your infrastructure.

By default, when Terraform creates a plan, it reads the current state of all already-existing remote objects to make sure Terraform's state is up to date. It then compares the current configuration to the previous state and notes any differences. It then proposes a set of change actions that should, if applied, make the remote objects match the current configuration:

bash
terraform plan

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

  + create

Terraform will perform the following actions:

  # ovh_dedicated_server_reboot_task.server_reboot will be created

  + resource "ovh_dedicated_server_reboot_task" "server_reboot" {
      + comment      = (known after apply)
      + done_date    = (known after apply)
      + function     = (known after apply)
      + id           = (known after apply)
      + keepers      = [
          + "1122",
        ]

      + last_update  = (known after apply)
      + service_name = "ksxxxxxxx.kimsufi.com"
      + start_date   = (known after apply)
      + status       = (known after apply)

    }

  # ovh_dedicated_server_update.server_on_rescue will be created
  + resource "ovh_dedicated_server_update" "server_on_rescue" {

      + boot_id      = 1122
      + id           = (known after apply)
      + monitoring   = true
      + service_name = "ksxxxxxxx.kimsufi.com"

      + state        = "ok"

    }

Plan: 2 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

ovh_dedicated_server_update.server_on_rescue: Creating...
ovh_dedicated_server_update.server_on_rescue: Still creating... [10s elapsed]
ovh_dedicated_server_update.server_on_rescue: Creation complete after 11s [id=ksxxxxxxx.kimsufi.com]
ovh_dedicated_server_reboot_task.server_reboot: Creating...
ovh_dedicated_server_reboot_task.server_reboot: Still creating... [10s elapsed]
ovh_dedicated_server_reboot_task.server_reboot: Still creating... [20s elapsed]
ovh_dedicated_server_reboot_task.server_reboot: Still creating... [30s elapsed]
ovh_dedicated_server_reboot_task.server_reboot: Still creating... [40s elapsed]
ovh_dedicated_server_reboot_task.server_reboot: Still creating... [50s elapsed]
ovh_dedicated_server_reboot_task.server_reboot: Still creating... [1m0s elapsed]
ovh_dedicated_server_reboot_task.server_reboot: Still creating... [1m10s elapsed]
ovh_dedicated_server_reboot_task.server_reboot: Still creating... [1m20s elapsed]
ovh_dedicated_server_reboot_task.server_reboot: Still creating... [1m30s elapsed]
ovh_dedicated_server_reboot_task.server_reboot: Creation complete after 1m31s [id=251909160]

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

An email from OVH will automatically arrive in your mailbox to send you the connection information in rescue mode. I invite you to check out the OVH documentation to deploy other kinds of configuration, and also the Terraform documentation to learn more.

Conclusion

I've presented installing Terraform on macOS as well as two concrete configuration examples: provisioning a complete infrastructure on Scaleway (SSH key, firewall, instance, database, and private network) and driving an OVHcloud dedicated server to switch it into rescue mode. The essential commands terraform init, plan, and apply are detailed with their actual outputs. This infrastructure as code approach lets you automate and document deployments otherwise done manually. It requires properly securing the secrets (API keys, passwords) used in the configuration files.