Skip to content

Terraform Provider OpenStack

Introduction

Le provider Terraform OpenStack permet de provisionner des ressources cloud de manière déclarative et reproductible. C'est l'outil standard pour l'Infrastructure as Code (IaC) sur OpenStack.

Prérequis

  • OpenStack fonctionnel avec APIs accessibles
  • Phase 3 - Kolla-Ansible
  • Terraform installé (v1.5+)
  • Credentials OpenStack (clouds.yaml ou variables d'environnement)

Points à apprendre

Architecture IaC avec Terraform

graph TB
    subgraph External
        DevOps["DevOps/Platform Engineer<br/>Gère l'infrastructure"]
        CI["CI/CD Pipeline<br/>GitLab CI, GitHub Actions"]
    end

    subgraph "IaC Stack"
        TF["Terraform<br/>HCL<br/>Déclaration ressources"]
        State["State Backend<br/>S3/Swift<br/>État de l'infrastructure"]
        Git["Git Repository<br/>GitLab/GitHub<br/>Versioning configs"]
    end

    subgraph "OpenStack Cloud"
        API["OpenStack APIs<br/>Nova, Neutron, Cinder..."]
        Resources["Resources<br/>VMs, Networks, Volumes"]
    end

    DevOps -->|Push code<br/>git| Git
    Git -->|Trigger<br/>webhook| CI
    CI -->|Execute<br/>terraform apply| TF
    TF -->|Read/Write| State
    TF -->|API calls<br/>HTTPS| API
    API -->|Provision| Resources

Installation

# Installation Terraform
wget https://releases.hashicorp.com/terraform/1.6.6/terraform_1.6.6_linux_amd64.zip
unzip terraform_1.6.6_linux_amd64.zip
sudo mv terraform /usr/local/bin/

# Vérifier
terraform version

Configuration du provider

# versions.tf
terraform {
  required_version = ">= 1.5.0"

  required_providers {
    openstack = {
      source  = "terraform-provider-openstack/openstack"
      version = "~> 1.54"
    }
  }

  # Backend pour le state (recommandé en équipe)
  backend "swift" {
    container         = "terraform-state"
    archive_container = "terraform-state-archive"
    state_name        = "production.tfstate"
  }
}

# provider.tf
provider "openstack" {
  # Authentification via clouds.yaml (recommandé)
  cloud = "openstack"

  # Ou via variables d'environnement OS_*
  # auth_url    = var.auth_url
  # user_name   = var.user_name
  # password    = var.password
  # tenant_name = var.tenant_name
  # region      = var.region
}

Configuration clouds.yaml

# ~/.config/openstack/clouds.yaml
clouds:
  openstack:
    auth:
      auth_url: https://10.0.0.10:5000/v3
      username: admin
      password: ${OS_PASSWORD}
      project_name: admin
      project_domain_name: Default
      user_domain_name: Default
    region_name: RegionOne
    interface: internal
    verify: false  # Si certificat self-signed

Variables et outputs

# variables.tf
variable "environment" {
  description = "Environnement (dev, staging, prod)"
  type        = string
  default     = "dev"
}

variable "network_cidr" {
  description = "CIDR du réseau privé"
  type        = string
  default     = "192.168.100.0/24"
}

variable "instance_count" {
  description = "Nombre d'instances à créer"
  type        = number
  default     = 1
}

variable "flavor_name" {
  description = "Flavor pour les instances"
  type        = string
  default     = "m1.small"
}

variable "image_name" {
  description = "Image de base"
  type        = string
  default     = "ubuntu-22.04"
}

# outputs.tf
output "instance_ips" {
  description = "Adresses IP des instances"
  value       = openstack_compute_instance_v2.app[*].access_ip_v4
}

output "floating_ips" {
  description = "Floating IPs assignées"
  value       = openstack_networking_floatingip_v2.app[*].address
}

output "network_id" {
  description = "ID du réseau créé"
  value       = openstack_networking_network_v2.app.id
}

Ressources de base

# main.tf

# Réseau privé
resource "openstack_networking_network_v2" "app" {
  name           = "${var.environment}-app-network"
  admin_state_up = true
}

# Sous-réseau
resource "openstack_networking_subnet_v2" "app" {
  name       = "${var.environment}-app-subnet"
  network_id = openstack_networking_network_v2.app.id
  cidr       = var.network_cidr
  ip_version = 4

  dns_nameservers = ["8.8.8.8", "8.8.4.4"]

  allocation_pool {
    start = cidrhost(var.network_cidr, 10)
    end   = cidrhost(var.network_cidr, 200)
  }
}

# Routeur
resource "openstack_networking_router_v2" "app" {
  name                = "${var.environment}-router"
  external_network_id = data.openstack_networking_network_v2.external.id
}

resource "openstack_networking_router_interface_v2" "app" {
  router_id = openstack_networking_router_v2.app.id
  subnet_id = openstack_networking_subnet_v2.app.id
}

# Security group
resource "openstack_networking_secgroup_v2" "app" {
  name        = "${var.environment}-app-sg"
  description = "Security group for application"
}

resource "openstack_networking_secgroup_rule_v2" "ssh" {
  security_group_id = openstack_networking_secgroup_v2.app.id
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 22
  port_range_max    = 22
  remote_ip_prefix  = "0.0.0.0/0"
}

resource "openstack_networking_secgroup_rule_v2" "http" {
  security_group_id = openstack_networking_secgroup_v2.app.id
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 80
  port_range_max    = 80
  remote_ip_prefix  = "0.0.0.0/0"
}

# Instance
resource "openstack_compute_instance_v2" "app" {
  count           = var.instance_count
  name            = "${var.environment}-app-${count.index + 1}"
  flavor_name     = var.flavor_name
  image_name      = var.image_name
  key_pair        = openstack_compute_keypair_v2.app.name
  security_groups = [openstack_networking_secgroup_v2.app.name]

  network {
    uuid = openstack_networking_network_v2.app.id
  }

  metadata = {
    environment = var.environment
    managed_by  = "terraform"
  }

  user_data = file("${path.module}/cloud-init.yaml")
}

# Keypair
resource "openstack_compute_keypair_v2" "app" {
  name       = "${var.environment}-keypair"
  public_key = file("~/.ssh/id_rsa.pub")
}

# Floating IP
resource "openstack_networking_floatingip_v2" "app" {
  count = var.instance_count
  pool  = "external"
}

resource "openstack_compute_floatingip_associate_v2" "app" {
  count       = var.instance_count
  floating_ip = openstack_networking_floatingip_v2.app[count.index].address
  instance_id = openstack_compute_instance_v2.app[count.index].id
}

# Data sources
data "openstack_networking_network_v2" "external" {
  name = "external"
}

Cloud-init

# cloud-init.yaml
#cloud-config
package_update: true
package_upgrade: true

packages:
  - nginx
  - docker.io
  - python3-pip

users:
  - name: deploy
    groups: docker, sudo
    shell: /bin/bash
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    ssh_authorized_keys:
      - ${file("~/.ssh/id_rsa.pub")}

runcmd:
  - systemctl enable nginx
  - systemctl start nginx
  - systemctl enable docker
  - systemctl start docker

final_message: "Instance ready after $UPTIME seconds"

Workflow Terraform

flowchart TD
    Start([Start]) --> Init["terraform init<br/><i>Télécharge providers<br/>Configure backend</i>"]
    Init --> Validate["terraform validate<br/><i>Vérifie syntaxe HCL</i>"]
    Validate --> Plan["terraform plan<br/><i>Calcule les changements<br/>Sans modifier</i>"]
    Plan --> PlanOK{Plan OK?}

    PlanOK -->|yes| Apply["terraform apply<br/><i>Applique les changements<br/>Demande confirmation</i>"]
    PlanOK -->|no| Fix[Corriger configuration]

    Apply --> ApplyOK{Apply réussi?}
    ApplyOK -->|yes| Created[Resources created]
    ApplyOK -->|no| Rollback[Rollback partiel<br/>Vérifier state]

    Created --> Show["terraform show<br/><i>Affiche l'état actuel</i>"]
    Rollback --> Show
    Fix --> Show

    Show --> Stop([Stop])

Commandes essentielles

# Initialiser le projet
terraform init

# Valider la syntaxe
terraform validate

# Formater le code
terraform fmt -recursive

# Planifier les changements
terraform plan -out=tfplan

# Appliquer les changements
terraform apply tfplan

# Afficher l'état
terraform show
terraform state list

# Importer une ressource existante
terraform import openstack_compute_instance_v2.existing <instance-id>

# Détruire l'infrastructure
terraform destroy

# Workspace (environnements)
terraform workspace new production
terraform workspace select production

Exemples pratiques

Structure de projet recommandée

terraform-openstack/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── terraform.tfvars
│   │   └── backend.tf
│   ├── staging/
│   └── production/
├── modules/
│   ├── network/
│   ├── compute/
│   └── security/
├── .gitignore
├── .terraform-version
└── README.md

Fichier .gitignore

# .gitignore
.terraform/
*.tfstate
*.tfstate.*
*.tfvars
!example.tfvars
.terraform.lock.hcl
crash.log
*.pem
*.key

Test rapide

# Créer une infrastructure minimale
cat > main.tf << 'EOF'
terraform {
  required_providers {
    openstack = {
      source  = "terraform-provider-openstack/openstack"
      version = "~> 1.54"
    }
  }
}

provider "openstack" {}

resource "openstack_compute_instance_v2" "test" {
  name        = "terraform-test"
  flavor_name = "m1.small"
  image_name  = "cirros"
  network {
    name = "provider-net"
  }
}
EOF

# Exécuter
source ~/openrc
terraform init
terraform apply -auto-approve

# Vérifier
openstack server list | grep terraform-test

# Nettoyer
terraform destroy -auto-approve

Ressources

Checkpoint

  • Terraform installé et configuré
  • Provider OpenStack fonctionnel
  • Création de ressources de base (VM, réseau)
  • Variables et outputs utilisés
  • State backend configuré (Swift ou S3)
  • Structure de projet organisée