devops

Déployer une infrastructure Scaleway avec Terraform

Guide complet pour déployer un serveur et configurer la sécurité sur Scaleway avec Terraform

10 minutes de lecture

Dans mon article précédent, nous avons découvert Terraform avec Docker en local. Aujourd'hui, nous passons à l'étape suivante : déployer une vraie infrastructure cloud sur Scaleway.

Pourquoi Scaleway ?

Scaleway est un cloud provider français qui offre plusieurs avantages :

  • Prix compétitifs : Une instance DEV1-S (2 vCPU, 2GB RAM) coûte environ 10€/mois
  • Datacenter en France : Latence faible et conformité RGPD
  • API simple : Provider Terraform bien maintenu

Ce qu'on va construire

À la fin de cet article, tu auras déployé :

  • Un projet Scaleway pour isoler tes ressources
  • Une clé SSH générée automatiquement par Terraform
  • Un Security Group avec règles firewall
  • Une instance DEV1-S avec Ubuntu 22.04

Prérequis

Pour créer tes credentials, rends-toi dans la console Scaleway : IAM > API Keys > Generate API Key.

Architecture des fichiers

1infra/
2├── main.tf           # Ressources principales
3├── variables.tf      # Définition des variables
4├── outputs.tf        # Valeurs de sortie
5├── versions.tf       # Configuration Terraform et providers
6└── terraform.tfvars  # Valeurs des variables (non committé)

Configuration du provider

versions.tf

Commençons par configurer Terraform et le provider Scaleway :

1terraform {
2  required_version = ">= 1.0"
3
4  required_providers {
5    scaleway = {
6      source  = "scaleway/scaleway"
7      version = "~> 2.0"
8    }
9    tls = {
10      source  = "hashicorp/tls"
11      version = "~> 4.0"
12    }
13    local = {
14      source  = "hashicorp/local"
15      version = "~> 2.0"
16    }
17    http = {
18      source  = "hashicorp/http"
19      version = "~> 3.0"
20    }
21  }
22}

Nous utilisons quatre providers :

  • scaleway/scaleway pour les ressources cloud
  • hashicorp/tls pour générer la clé SSH
  • hashicorp/local pour sauvegarder les clés SSH localement
  • hashicorp/http pour récupérer notre IP publique

variables.tf

Définissons les variables nécessaires :

1# Credentials Scaleway
2variable "scw_access_key" {
3  description = "Scaleway Access Key"
4  type        = string
5  sensitive   = true
6}
7
8variable "scw_secret_key" {
9  description = "Scaleway Secret Key"
10  type        = string
11  sensitive   = true
12}
13
14variable "scw_organization_id" {
15  description = "Scaleway Organization ID"
16  type        = string
17}
18
19# Configuration
20variable "project_name" {
21  description = "Nom du projet Scaleway"
22  type        = string
23  default     = "mon-projet"
24}
25
26variable "region" {
27  description = "Scaleway region"
28  type        = string
29  default     = "fr-par"
30}
31
32variable "zone" {
33  description = "Scaleway zone"
34  type        = string
35  default     = "fr-par-1"
36}
37
38variable "instance_type" {
39  description = "Type d'instance (DEV1-S, DEV1-M, GP1-XS...)"
40  type        = string
41  default     = "DEV1-S"
42}

L'attribut sensitive = true empêche Terraform d'afficher ces valeurs dans les logs.

Différence region/zone : Chez Scaleway, une région (fr-par) contient plusieurs zones (fr-par-1, fr-par-2, fr-par-3). Les ressources sont créées dans une zone spécifique.

terraform.tfvars

Crée ce fichier pour stocker tes valeurs (ne le committe jamais !) :

1scw_access_key      = "SCWXXXXXXXXXXXXXXXXX"
2scw_secret_key      = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
3scw_organization_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
4project_name        = "mon-super-projet"

Ajoute terraform.tfvars et *.auto.tfvars à ton .gitignore pour éviter de committer tes secrets.

Créer le projet et configurer le provider

main.tf

1# Configuration du provider
2provider "scaleway" {
3  access_key      = var.scw_access_key
4  secret_key      = var.scw_secret_key
5  organization_id = var.scw_organization_id
6  region          = var.region
7  zone            = var.zone
8}
9
10# Créer un projet pour isoler les ressources
11resource "scaleway_account_project" "main" {
12  name        = var.project_name
13  description = "Projet géré par Terraform"
14}

Un projet Scaleway permet d'isoler tes ressources et de gérer les permissions. C'est une bonne pratique d'avoir un projet par environnement (dev, staging, prod).

Générer une clé SSH automatiquement

Plutôt que de créer une clé SSH manuellement, laissons Terraform s'en charger :

1# Générer une paire de clés ED25519
2resource "tls_private_key" "ssh" {
3  algorithm = "ED25519"
4}
5
6# Enregistrer la clé publique dans Scaleway
7resource "scaleway_iam_ssh_key" "main" {
8  name       = "${var.project_name}-terraform"
9  public_key = trimspace(tls_private_key.ssh.public_key_openssh)
10  project_id = scaleway_account_project.main.id
11}
12
13# Sauvegarder la clé privée localement
14resource "local_file" "ssh_private_key" {
15  content         = tls_private_key.ssh.private_key_openssh
16  filename        = "${path.module}/${var.project_name}_ed25519"
17  file_permission = "0600"
18}
19
20# Sauvegarder la clé publique localement
21resource "local_file" "ssh_public_key" {
22  content  = tls_private_key.ssh.public_key_openssh
23  filename = "${path.module}/${var.project_name}_ed25519.pub"
24}

Quelques points importants :

  • ED25519 est plus sécurisé et plus rapide que RSA
  • trimspace() supprime les espaces/retours à la ligne parasites
  • file_permission = "0600" est obligatoire pour SSH (lecture/écriture uniquement par le propriétaire)
  • path.module référence le dossier contenant les fichiers Terraform

Configurer le Security Group

Un Security Group agit comme un firewall pour ton instance. Nous allons :

  • Bloquer tout le trafic entrant par défaut
  • Autoriser SSH uniquement depuis notre IP
  • Ouvrir les ports HTTP (80) et HTTPS (443)
1# Récupérer notre IP publique actuelle
2data "http" "my_ip" {
3  url = "https://ipv4.icanhazip.com"
4}
5
6locals {
7  my_ip = "${chomp(data.http.my_ip.response_body)}/32"
8}
9
10# Créer le Security Group
11resource "scaleway_instance_security_group" "main" {
12  project_id              = scaleway_account_project.main.id
13  name                    = "${var.project_name}-sg"
14  description             = "Security group pour ${var.project_name}"
15  inbound_default_policy  = "drop"   # Bloquer tout par défaut
16  outbound_default_policy = "accept" # Autoriser les sorties
17
18  # SSH - restreint à notre IP uniquement
19  inbound_rule {
20    action   = "accept"
21    port     = 22
22    protocol = "TCP"
23    ip_range = local.my_ip
24  }
25
26  # HTTP - ouvert à tous
27  inbound_rule {
28    action   = "accept"
29    port     = 80
30    protocol = "TCP"
31  }
32
33  # HTTPS - ouvert à tous
34  inbound_rule {
35    action   = "accept"
36    port     = 443
37    protocol = "TCP"
38  }
39}

Astuce : Le data source http appelle une API qui retourne notre IP publique. La fonction chomp() supprime le retour à la ligne. Le /32 indique un masque CIDR pour une seule IP.

Cette configuration suit le principe du deny by default : on bloque tout, puis on ouvre explicitement ce dont on a besoin.

Déployer l'instance

Réserver une IP publique

1resource "scaleway_instance_ip" "main" {
2  project_id = scaleway_account_project.main.id
3}

Réserver une IP séparément permet de la conserver même si on détruit et recrée l'instance. Pratique pour éviter de changer tes enregistrements DNS.

Créer l'instance

1resource "scaleway_instance_server" "main" {
2  project_id = scaleway_account_project.main.id
3  name       = "${var.project_name}-server"
4  type       = var.instance_type
5  image      = "ubuntu_jammy"
6
7  # Attacher l'IP et le Security Group
8  ip_id             = scaleway_instance_ip.main.id
9  security_group_id = scaleway_instance_security_group.main.id
10
11  tags = ["terraform", "production"]
12}

Types d'instances disponibles :

TypevCPURAMPrix/mois
DEV1-S22 GB~10€
DEV1-M34 GB~20€
DEV1-L48 GB~40€
GP1-XS416 GB~70€

Images disponibles : ubuntu_jammy (22.04), ubuntu_focal (20.04), debian_bookworm, debian_bullseye, etc.

Provisionner avec Cloud-init

Cloud-init permet de configurer l'instance au premier démarrage. C'est idéal pour installer des packages, créer des fichiers, ou exécuter des scripts.

Version simple (inline)

1resource "scaleway_instance_server" "main" {
2  # ... configuration précédente ...
3
4  user_data = {
5    cloud-init = <<-EOF
6      #cloud-config
7      package_update: true
8      package_upgrade: true
9
10      packages:
11        - apt-transport-https
12        - ca-certificates
13        - curl
14        - git
15        - htop
16        - docker.io
17
18      runcmd:
19        - systemctl enable docker
20        - systemctl start docker
21    EOF
22  }
23}

Note : Le user_data doit être défini avant la création de l'instance, car cloud-init s'exécute au premier démarrage.

Version avec fichier template

Pour des configurations plus complexes, utilise un fichier séparé :

1locals {
2  cloud_init_content = templatefile("${path.module}/templates/cloud-init.yml", {
3    project_name = var.project_name
4  })
5}
6
7resource "scaleway_instance_server" "main" {
8  # ... configuration précédente ...
9
10  user_data = {
11    cloud-init = local.cloud_init_content
12  }
13
14  # Recréer l'instance si cloud-init change
15  lifecycle {
16    replace_triggered_by = [terraform_data.cloud_init_hash]
17  }
18}
19
20# Hash du cloud-init pour détecter les changements
21resource "terraform_data" "cloud_init_hash" {
22  input = md5(local.cloud_init_content)
23}

Et dans templates/cloud-init.yml :

1#cloud-config
2package_update: true
3package_upgrade: true
4
5packages:
6  - apt-transport-https
7  - ca-certificates
8  - curl
9  - git
10  - htop
11  - docker.io
12
13runcmd:
14  - systemctl enable docker
15  - systemctl start docker
16  - echo "Provisioning complete for ${project_name}"

La fonction templatefile() permet d'injecter des variables dans le fichier YAML.

Outputs

Définis des outputs pour récupérer facilement les informations importantes :

1output "project_id" {
2  description = "ID du projet Scaleway"
3  value       = scaleway_account_project.main.id
4}
5
6output "instance_public_ip" {
7  description = "IP publique de l'instance"
8  value       = scaleway_instance_ip.main.address
9}
10
11output "ssh_command" {
12  description = "Commande SSH pour se connecter"
13  value       = "ssh -i ${local_file.ssh_private_key.filename} ubuntu@${scaleway_instance_ip.main.address}"
14}

Après terraform apply, tu peux afficher ces valeurs avec :

1terraform output

Déployer l'infrastructure

Initialiser Terraform

1$ terraform init
2
3Initializing the backend...
4Initializing provider plugins...
5- Finding scaleway/scaleway versions matching "~> 2.0"...
6- Installing scaleway/scaleway v2.x.x...
7
8Terraform has been successfully initialized!

Prévisualiser les changements

1$ terraform plan
2
3Plan: 8 to add, 0 to change, 0 to destroy.

Vérifie toujours le plan avant d'appliquer. C'est ton filet de sécurité.

Appliquer

1$ terraform apply
2
3Do you want to perform these actions?
4  Enter a value: yes
5
6Apply complete! Resources: 8 added, 0 changed, 0 destroyed.
7
8Outputs:
9
10instance_public_ip = "51.15.xxx.xxx"
11ssh_command = "ssh -i ./mon-projet_ed25519 ubuntu@51.15.xxx.xxx"

Se connecter à l'instance

1$ ssh -i ./mon-projet_ed25519 ubuntu@51.15.xxx.xxx
2
3Welcome to Ubuntu 22.04.x LTS
4ubuntu@mon-projet-server:~$

Gérer le state avec Terraform Cloud

Pour travailler en équipe ou depuis une CI/CD, stocke le state dans Terraform Cloud (gratuit jusqu'à 500 ressources gérées) :

1terraform {
2  cloud {
3    organization = "mon-organisation"
4
5    workspaces {
6      name = "mon-projet-prod"
7    }
8  }
9
10  # ... required_providers ...
11}

Avantages :

  • State partagé : Toute l'équipe voit le même état
  • Locking : Empêche les modifications simultanées
  • Historique : Conserve les versions précédentes du state
  • Sécurité : Le state n'est pas stocké en local

Connecte-toi avec :

1$ terraform login
2$ terraform init

Bonnes pratiques

1. Structure des fichiers

Pour un projet plus conséquent, organise tes fichiers :

1infra/
2├── main.tf           # Provider et ressources principales
3├── network.tf        # Security groups, VPC
4├── compute.tf        # Instances
5├── variables.tf
6├── outputs.tf
7├── versions.tf
8└── templates/
9    └── cloud-init.yml

2. Nommage cohérent

Préfixe tes ressources avec le nom du projet :

1resource "scaleway_instance_server" "main" {
2  name = "${var.project_name}-server"
3}

3. Utilise des tags

Les tags facilitent le filtrage et la gestion des coûts :

1tags = ["terraform", "production", var.project_name]

4. Ne jamais modifier le state manuellement

Si tu dois importer une ressource existante :

1terraform import scaleway_instance_server.main fr-par-1/instance-id

5. Détruire proprement

Pour tout supprimer :

1$ terraform destroy

Pour supprimer une ressource spécifique :

1$ terraform destroy -target=scaleway_instance_server.main

Estimation des coûts

Pour l'infrastructure décrite dans cet article :

RessourceCoût mensuel
Instance DEV1-S~10€
IP publique (attachée)Gratuit
Total~10€/mois

Note : Une IP publique attachée à une instance est gratuite. Tu ne paies (~3€/mois) que si tu réserves une IP sans l'attacher à une instance.

TLDR

1# 1. Créer les fichiers Terraform (main.tf, variables.tf, etc.)
2
3# 2. Créer terraform.tfvars avec tes credentials
4scw_access_key      = "SCWXXXXXXXXX"
5scw_secret_key      = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
6scw_organization_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
7
8# 3. Initialiser
9terraform init
10
11# 4. Prévisualiser
12terraform plan
13
14# 5. Déployer
15terraform apply
16
17# 6. Se connecter
18ssh -i ./mon-projet_ed25519 ubuntu@<IP>
19
20# 7. Détruire (quand tu n'en as plus besoin)
21terraform destroy

Pour aller plus loin

Dans un prochain article, nous verrons comment déployer une application complète avec Docker Compose, Traefik, et Let's Encrypt sur cette infrastructure.