Configurazione completa con Terraform & Ansible
Questa guida propone due esempi completi di infrastructure-as-code, pronti da copiare e incollare, che distribuiscono una VM con rete, gruppo di sicurezza, volume dati e IP flottante: uno usa Terraform, l'altro Ansible. Entrambi creano la stessa architettura; è possibile scegliere lo strumento più adatto al proprio flusso di lavoro. Il playbook Ansible configura inoltre la VM (formatta il volume dati, installa Nginx).
Panoramica dell'architettura
%%{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;
Prerequisiti
- Terraform installato e configurato, vedere Installazione di Terraform
- Ansible installato e configurato, vedere Installazione di Ansible
- Application Credentials (
clouds.yaml), vedere Application Credentials - Una coppia di chiavi SSH caricata in OpenStack, vedere Coppie di chiavi SSH
Struttura del progetto
openstack-demo/
├── main.tf # Terraform: all infrastructure + VM
├── playbook.yml # Ansible: all infrastructure + VM + configuration (standalone)
└── clouds.yaml # application credentials (not in version control)
Approcci autonomi
main.tf e playbook.yml sono indipendenti: è possibile usare l'uno senza l'altro. Distribuiscono le stesse risorse.
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
}
Distribuzione
cd openstack-demo
terraform init
terraform plan
terraform apply
Terraform mostra l'IP flottante al termine di un apply riuscito:
floating_ip = "185.123.45.67"
vm_name = "demo-vm"
volume_id = "71bd47d8-b51e-4f12-9c51-0445fc36c9e4"
Ansible — playbook.yml
Questo playbook crea l'intera infrastruttura (rete, router, gruppo di sicurezza, volume dati, VM, IP flottante) e poi configura la VM, il tutto in un'unica esecuzione. Terraform non è necessario.
---
- 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
Esecuzione
ansible-playbook -i localhost, playbook.yml
Il playbook mostra l'IP flottante al termine del primo play:
TASK [Show floating IP] ****
ok: [localhost] => {
"msg": "VM floating IP: 185.123.45.67"
}
Note
Il primo play viene eseguito su localhost e comunica con l'API OpenStack tramite clouds.yaml. Il secondo play si connette all'IP flottante della VM via SSH. L'attività add_host collega i due play aggiungendo la VM all'inventario in memoria.
Verifica
Dopo la distribuzione con uno dei due strumenti, verifichi la configurazione:
Terraform
ssh ubuntu@$(terraform output -raw floating_ip)
Ansible
ssh ubuntu@<floating-ip-from-playbook-output>
Sulla VM
lsblk
df -h /mnt/data
curl -s http://localhost | head -5
Pulizia
Terraform
terraform destroy
Questo comando rimuove tutte le risorse create da Terraform: istanza, volume, IP flottante, rete, router e gruppo di sicurezza.
Ansible
Elimini le risorse in ordine inverso con 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 allocata da Ansible viene dissociata quando il server viene eliminato, ma non viene rilasciata. È possibile trovarla e rilasciarla con openstack floating ip list e openstack floating ip release <ADDRESS>.