Terraform + Ansible : automatiser une infrastructure OpenStack de A à Z

November 8, 2024

Terraform + Ansible : automatiser une infrastructure OpenStack de A à Z

L'Infrastructure as Code (IaC) est au cœur du DevOps moderne. Dans ce post, je partage mon approche pour automatiser entièrement une infrastructure OpenStack en combinant Terraform (pour le provisioning) et Ansible (pour la configuration).

Vue d'ensemble de la stack

┌────────────────────────────────────────┐ │ Votre machine locale │ │ │ │ terraform plan / apply │ │ │ │ │ ▼ │ │ ┌─────────────┐ ┌──────────────┐ │ │ │ Terraform │──▶│ OpenStack │ │ │ │ (IaC) │ │ API │ │ │ └─────────────┘ └──────┬───────┘ │ │ │ │ │ ┌───────▼────────┐ │ │ │ VMs créées │ │ │ └───────┬────────┘ │ │ │ │ │ ┌─────────────┐ │ │ │ │ Ansible │◀──────────┘ │ │ │ (Config) │ (inventory dynamique)│ │ └─────────────┘ │ └────────────────────────────────────────┘

Phase 1 : Provisioning avec Terraform

Structure du projet

infrastructure/ ├── main.tf ├── variables.tf ├── outputs.tf ├── terraform.tfvars └── modules/ ├── network/ ├── compute/ └── security/

Provider OpenStack

# main.tf terraform { required_providers { openstack = { source = "terraform-provider-openstack/openstack" version = "~> 1.53" } } # State stocké dans un backend S3 (ou Swift pour OpenStack) backend "s3" { bucket = "terraform-state" key = "infrastructure/terraform.tfstate" region = "us-east-1" } } provider "openstack" { auth_url = var.openstack_auth_url tenant_name = var.openstack_project user_name = var.openstack_user password = var.openstack_password region = var.openstack_region }

Réseau et sécurité

# modules/network/main.tf resource "openstack_networking_network_v2" "private_net" { name = "${var.env}-private-network" admin_state_up = true } resource "openstack_networking_subnet_v2" "private_subnet" { name = "${var.env}-subnet" network_id = openstack_networking_network_v2.private_net.id cidr = "10.0.1.0/24" ip_version = 4 dns_nameservers = ["8.8.8.8", "8.8.4.4"] } # Security Group minimaliste resource "openstack_networking_secgroup_v2" "app_sg" { name = "${var.env}-app-sg" } resource "openstack_networking_secgroup_rule_v2" "http" { direction = "ingress" ethertype = "IPv4" protocol = "tcp" port_range_min = 80 port_range_max = 80 security_group_id = openstack_networking_secgroup_v2.app_sg.id } resource "openstack_networking_secgroup_rule_v2" "ssh" { direction = "ingress" ethertype = "IPv4" protocol = "tcp" port_range_min = 22 port_range_max = 22 remote_ip_prefix = var.admin_cidr # SSH uniquement depuis votre IP security_group_id = openstack_networking_secgroup_v2.app_sg.id }

Instances de calcul

# modules/compute/main.tf resource "openstack_compute_instance_v2" "web" { count = var.web_count name = "${var.env}-web-${count.index + 1}" image_id = data.openstack_images_image_v2.ubuntu.id flavor_id = data.openstack_compute_flavor_v2.medium.id key_pair = openstack_compute_keypair_v2.deployer.name network { uuid = var.network_id } security_groups = [var.security_group] # User data pour bootstrap minimal user_data = <<-EOF #!/bin/bash apt-get update -y apt-get install -y python3 python3-pip EOF metadata = { env = var.env role = "web" } } # Output des IPs pour Ansible output "web_ips" { value = openstack_compute_instance_v2.web[*].access_ip_v4 }

Phase 2 : Configuration avec Ansible

Inventaire dynamique depuis Terraform

# inventory.py - génère l'inventaire depuis l'output Terraform import json import subprocess def get_terraform_output(): result = subprocess.run( ["terraform", "output", "-json"], capture_output=True, text=True ) return json.loads(result.stdout) outputs = get_terraform_output() web_ips = outputs["web_ips"]["value"] inventory = { "web": { "hosts": web_ips, "vars": { "ansible_user": "ubuntu", "ansible_ssh_private_key_file": "~/.ssh/deployer" } } } print(json.dumps(inventory))

Playbook de configuration

# playbook.yml --- - name: Configuration des serveurs web hosts: web become: true roles: - role: common # Base OS hardening - role: docker # Installation Docker - role: nginx # Reverse proxy - role: app # Déploiement application - name: Configuration du monitoring hosts: web become: true tasks: - name: Installer Node Exporter (Prometheus) community.general.docker_container: name: node-exporter image: prom/node-exporter:latest restart_policy: always network_mode: host volumes: - /proc:/host/proc:ro - /sys:/host/sys:ro

Role de durcissement OS

# roles/common/tasks/main.yml - name: Désactiver login root SSH lineinfile: path: /etc/ssh/sshd_config regexp: "^PermitRootLogin" line: "PermitRootLogin no" notify: restart sshd - name: Désactiver authentification par mot de passe lineinfile: path: /etc/ssh/sshd_config regexp: "^PasswordAuthentication" line: "PasswordAuthentication no" notify: restart sshd - name: Configurer fail2ban copy: content: | [sshd] enabled = true port = ssh maxretry = 3 bantime = 3600 dest: /etc/fail2ban/jail.local notify: restart fail2ban

CI/CD pour l'infra

# .github/workflows/infra.yml name: Infrastructure CI/CD on: push: paths: ["infrastructure/**"] branches: [main] jobs: terraform-plan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: hashicorp/setup-terraform@v3 - name: Terraform Plan run: | cd infrastructure terraform init terraform plan -out=tfplan env: OS_AUTH_URL: ${{ secrets.OS_AUTH_URL }} OS_PASSWORD: ${{ secrets.OS_PASSWORD }} - name: Upload plan uses: actions/upload-artifact@v4 with: name: tfplan path: infrastructure/tfplan terraform-apply: needs: terraform-plan runs-on: ubuntu-latest environment: production # Requiert approbation manuelle steps: - name: Download plan uses: actions/download-artifact@v4 with: name: tfplan path: infrastructure/ - name: Terraform Apply run: terraform apply infrastructure/tfplan

Bonnes pratiques retenues

  1. Toujours versionner le state — un state perdu = infrastructure orpheline
  2. terraform plan avant tout apply — intégrez-le dans votre code review
  3. Séparer les rôles Ansible — réutilisabilité et testabilité
  4. Vaults Ansible pour les secrets, jamais en clair dans les playbooks
  5. Tagguer les ressources — indispensable pour le suivi des coûts

Article basé sur mon projet d'infrastructure personnelle (2024).

GitHub
LinkedIn
X