Configuration complète avec Terraform & Ansible
Ce guide propose deux exemples complets d'infrastructure-as-code, prêts à copier-coller, qui déploient une VM avec réseau, groupe de sécurité, volume de données et IP flottante : l'un repose sur Terraform, l'autre sur Ansible. Les deux créent la même architecture ; choisissez l'outil qui correspond à votre flux de travail. Le playbook Ansible configure en outre la VM (formatage du volume de données, installation de Nginx).
Vue d'ensemble de l'architecture
%%{init: {'themeVariables': {'textColor': '#333333', 'edgeLabelBackground': '#ffffff', 'clusterBkg': 'transparent', 'clusterBorder': '#999999'}}}%%
flowchart TD
Internet([Internet]) <-->|FIP| FIP["Floating IP"]
FIP <-->|NAT| Router
subgraph Provider ["Provider Network"]
ExtNet["External Network (public)"]
end
subgraph Project ["Project"]
Router["Router"]
Router <-->|Gateway| ExtNet
subgraph Net ["Private Network"]
Subnet["Subnet 192.168.100.0/24"]
Router <-->|Interface| Subnet
subgraph SG ["Security Group"]
VM["VM Instance"]
end
end
Vol["Data Volume (100 GB)"]
Vol <-.->|Attached| VM
end
classDef vm fill:#d4edda,stroke:#28a745,color:#1b5e20,stroke-width:2px;
classDef router fill:#fff3cd,stroke:#ffc107,color:#6d4c00,stroke-width:2px;
classDef ext fill:#f8d7da,stroke:#dc3545,color:#8b0000,stroke-width:2px;
classDef vol fill:#e3f2fd,stroke:#1976d2,color:#0d47a1,stroke-width:2px;
classDef net fill:#eeeeee,stroke:#757575,stroke-dasharray: 5 5;
class VM vm;
class Router router;
class ExtNet ext;
class Vol vol;
class Subnet net;
%%{init: {'themeVariables': {'textColor': '#e0e0e0', 'edgeLabelBackground': '#2a2a2a', 'clusterBkg': 'transparent', 'clusterBorder': '#666666'}}}%%
flowchart TD
Internet([Internet]) <-->|FIP| FIP["Floating IP"]
FIP <-->|NAT| Router
subgraph Provider ["Provider Network"]
ExtNet["External Network (public)"]
end
subgraph Project ["Project"]
Router["Router"]
Router <-->|Gateway| ExtNet
subgraph Net ["Private Network"]
Subnet["Subnet 192.168.100.0/24"]
Router <-->|Interface| Subnet
subgraph SG ["Security Group"]
VM["VM Instance"]
end
end
Vol["Data Volume (100 GB)"]
Vol <-.->|Attached| VM
end
classDef vm fill:#1b3a28,stroke:#66bb6a,color:#c8e6c9,stroke-width:2px;
classDef router fill:#4d3a00,stroke:#ffb300,color:#ffe082,stroke-width:2px;
classDef ext fill:#4a1a1a,stroke:#ef5350,color:#ffcdd2,stroke-width:2px;
classDef vol fill:#0d2944,stroke:#42a5f5,color:#bbdefb,stroke-width:2px;
classDef net fill:#2a2a2a,stroke:#9e9e9e,stroke-dasharray: 5 5;
class VM vm;
class Router router;
class ExtNet ext;
class Vol vol;
class Subnet net;
Prérequis
- Terraform installé et configuré, voir Installation de Terraform
- Ansible installé et configuré, voir Installation d'Ansible
- Application Credentials (
clouds.yaml), voir Application Credentials - Une paire de clés SSH importée dans OpenStack, voir Paires de clés SSH
Structure du projet
openstack-demo/
├── main.tf # Terraform: all infrastructure + VM
├── playbook.yml # Ansible: all infrastructure + VM + configuration (standalone)
└── clouds.yaml # application credentials (not in version control)
Approches autonomes
main.tf et playbook.yml sont indépendants : vous pouvez utiliser l'un sans l'autre. Ils déploient les mêmes ressources.
Terraform — main.tf
terraform {
required_providers {
openstack = {
source = "terraform-provider-openstack/openstack"
version = "~> 1.53"
}
}
}
provider "openstack" {
cloud = "openstack"
}
# ── Network ──────────────────────────────────────────────
resource "openstack_networking_network_v2" "private" {
name = "demo-network"
admin_state_up = true
}
resource "openstack_networking_subnet_v2" "subnet" {
name = "demo-subnet"
network_id = openstack_networking_network_v2.private.id
cidr = "192.168.100.0/24"
ip_version = 4
dns_nameservers = ["8.8.8.8", "8.8.4.4"]
}
resource "openstack_networking_router_v2" "router" {
name = "demo-router"
external_network_id = data.openstack_networking_network_v2.public.id
}
resource "openstack_networking_router_interface_v2" "router_iface" {
router_id = openstack_networking_router_v2.router.id
subnet_id = openstack_networking_subnet_v2.subnet.id
}
# ── Security Group ───────────────────────────────────────
resource "openstack_compute_secgroup_v2" "secgroup" {
name = "demo-secgroup"
description = "Allow SSH and ICMP"
rule {
from_port = 22
to_port = 22
ip_protocol = "tcp"
cidr = "0.0.0.0/0"
}
rule {
from_port = -1
to_port = -1
ip_protocol = "icmp"
cidr = "0.0.0.0/0"
}
}
# ── Data Sources ─────────────────────────────────────────
data "openstack_networking_network_v2" "public" {
name = "public"
}
data "openstack_images_image_v2" "ubuntu" {
name = "Ubuntu 24.04"
most_recent = true
}
# ── Data Volume ──────────────────────────────────────────
resource "openstack_blockstorage_volume_v3" "data" {
name = "demo-data-vol"
size = 100
volume_type = "v-ssd-std"
availability_zone = "ch-zh1-az2"
}
# ── Instance ─────────────────────────────────────────────
resource "openstack_compute_instance_v2" "vm" {
name = "demo-vm"
image_id = data.openstack_images_image_v2.ubuntu.id
flavor_name = "g1.2c4m"
key_pair = "my-key"
security_groups = [openstack_compute_secgroup_v2.secgroup.name]
availability_zone = "ch-zh1-az2"
network {
uuid = openstack_networking_network_v2.private.id
}
}
resource "openstack_compute_volume_attach_v2" "attach" {
instance_id = openstack_compute_instance_v2.vm.id
volume_id = openstack_blockstorage_volume_v3.data.id
}
# ── Floating IP ──────────────────────────────────────────
resource "openstack_networking_floatingip_v2" "fip" {
pool = data.openstack_networking_network_v2.public.name
}
resource "openstack_compute_floatingip_associate_v2" "fip_assoc" {
floating_ip = openstack_networking_floatingip_v2.fip.address
instance_id = openstack_compute_instance_v2.vm.id
}
# ── Outputs ──────────────────────────────────────────────
output "floating_ip" {
value = openstack_networking_floatingip_v2.fip.address
}
output "vm_name" {
value = openstack_compute_instance_v2.vm.name
}
output "volume_id" {
value = openstack_blockstorage_volume_v3.data.id
}
Déploiement
cd openstack-demo
terraform init
terraform plan
terraform apply
Terraform affiche l'IP flottante après un apply réussi :
floating_ip = "185.123.45.67"
vm_name = "demo-vm"
volume_id = "71bd47d8-b51e-4f12-9c51-0445fc36c9e4"
Ansible — playbook.yml
Ce playbook crée toute l'infrastructure (réseau, routeur, groupe de sécurité, volume de données, VM, IP flottante), puis configure la VM, le tout en une seule exécution. Terraform n'est pas nécessaire.
---
- name: Create infrastructure and VM
hosts: localhost
vars:
vm_name: demo-vm
image: "Ubuntu 24.04"
flavor: g1.2c4m
key_name: my-key
availability_zone: ch-zh1-az2
volume_size: 100
volume_type: v-ssd-std
cidr: 192.168.100.0/24
tasks:
- name: Create private network
openstack.cloud.os_network:
name: demo-network
- name: Create subnet
openstack.cloud.os_subnet:
name: demo-subnet
network_name: demo-network
cidr: "{{ cidr }}"
dns_nameservers:
- 8.8.8.8
- 8.8.4.4
- name: Create router with external gateway and interface
openstack.cloud.os_router:
name: demo-router
network: public
interfaces:
- subnet: demo-subnet
- name: Create security group
openstack.cloud.os_security_group:
name: demo-secgroup
description: Allow SSH and ICMP
- name: Allow SSH
openstack.cloud.os_security_group_rule:
security_group: demo-secgroup
protocol: tcp
port_range_min: 22
port_range_max: 22
remote_ip_prefix: 0.0.0.0/0
- name: Allow ICMP
openstack.cloud.os_security_group_rule:
security_group: demo-secgroup
protocol: icmp
remote_ip_prefix: 0.0.0.0/0
- name: Create data volume
openstack.cloud.os_volume:
name: demo-data-vol
size: "{{ volume_size }}"
volume_type: "{{ volume_type }}"
availability_zone: "{{ availability_zone }}"
- name: Launch VM instance
openstack.cloud.os_server:
name: "{{ vm_name }}"
image: "{{ image }}"
flavor: "{{ flavor }}"
key_name: "{{ key_name }}"
security_groups: demo-secgroup
availability_zone: "{{ availability_zone }}"
nics:
- net-name: demo-network
delete_fip: false
register: vm
- name: Attach data volume to VM
openstack.cloud.os_server_volume:
server: "{{ vm_name }}"
volume: demo-data-vol
- name: Allocate and associate floating IP
openstack.cloud.os_floating_ip:
server: "{{ vm_name }}"
network: public
wait: true
register: fip
- name: Add VM to in-memory inventory
ansible.builtin.add_host:
name: demo-vm
ansible_host: "{{ fip.floating_ip.floating_ip_address }}"
ansible_user: ubuntu
ansible_ssh_private_key_file: ~/.ssh/id_rsa
- name: Wait for SSH to be available
ansible.builtin.wait_for:
host: "{{ fip.floating_ip.floating_ip_address }}"
port: 22
timeout: 300
- name: Show floating IP
ansible.builtin.debug:
msg: "VM floating IP: {{ fip.floating_ip.floating_ip_address }}"
- name: Configure VM
hosts: demo-vm
become: true
tasks:
- name: Identify the attached volume
ansible.builtin.shell: lsblk -o NAME,SIZE -n | awk '$2 ~ /100G/ {print $1; exit}'
register: volume_device
changed_when: false
- name: Create partition on the data volume
ansible.builtin.shell: |
parted -s /dev/{{ volume_device.stdout }} mklabel gpt mkpart primary ext4 0% 100%
when: volume_device.stdout != ""
- name: Format the partition
ansible.builtin.filesystem:
fstype: ext4
dev: "/dev/{{ volume_device.stdout }}1"
opts: -L datavol
- name: Create mount point
ansible.builtin.file:
path: /mnt/data
state: directory
mode: "0755"
- name: Mount the volume
ansible.builtin.mount:
path: /mnt/data
src: LABEL=datavol
fstype: ext4
opts: noatime,nodiratime,nofail
state: mounted
- name: Install Nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Ensure Nginx is running
ansible.builtin.service:
name: nginx
state: started
enabled: true
Exécution
ansible-playbook -i localhost, playbook.yml
Le playbook affiche l'IP flottante à la fin du premier play :
TASK [Show floating IP] ****
ok: [localhost] => {
"msg": "VM floating IP: 185.123.45.67"
}
Note
Le premier play s'exécute sur localhost et communique avec l'API OpenStack via clouds.yaml. Le second play se connecte à l'IP flottante de la VM par SSH. La tâche add_host fait le lien entre les deux plays en ajoutant la VM à l'inventaire en mémoire.
Vérification
Après un déploiement avec l'un ou l'autre des outils, vérifiez l'installation :
Terraform
ssh ubuntu@$(terraform output -raw floating_ip)
Ansible
ssh ubuntu@<floating-ip-from-playbook-output>
Sur la VM
lsblk
df -h /mnt/data
curl -s http://localhost | head -5
Nettoyage
Terraform
terraform destroy
Cette commande supprime toutes les ressources créées par Terraform : instance, volume, IP flottante, réseau, routeur et groupe de sécurité.
Ansible
Supprimez les ressources dans l'ordre inverse avec la CLI OpenStack :
openstack server delete demo-vm
openstack volume delete demo-data-vol
openstack router remove subnet demo-router demo-subnet
openstack router delete demo-router
openstack network delete demo-network
openstack security group delete demo-secgroup
Warning
L'IP flottante allouée par Ansible est dissociée lorsque le serveur est supprimé, mais n'est pas libérée. Retrouvez-la et libérez-la avec openstack floating ip list et openstack floating ip release <ADDRESS>.