Komplettes Setup mit Terraform & Ansible
Dieser Leitfaden bietet zwei vollständige, kopierfähige Infrastructure-as-Code-Beispiele, die eine VM mit Netzwerk, Security Group, Data-Volume und Floating IP bereitstellen — eines mit Terraform, das andere mit Ansible. Beide erstellen dieselbe Architektur; wählen Sie das Werkzeug, das zu Ihrem Workflow passt. Das Ansible-Playbook konfiguriert die VM zusätzlich (formatiert das Data-Volume, installiert Nginx).
Architektur-Übersicht
%%{init: {'themeVariables': {'textColor': '#333333', 'edgeLabelBackground': '#ffffff', 'clusterBkg': 'transparent', 'clusterBorder': '#999999'}}}%%
flowchart TD
Internet([Internet]) <-->|FIP| FIP["Floating IP"]
FIP <-->|NAT| Router
subgraph Provider ["Provider-Netzwerk"]
ExtNet["Externes Netzwerk (public)"]
end
subgraph Project ["Projekt"]
Router["Router"]
Router <-->|Gateway| ExtNet
subgraph Net ["Privates Netzwerk"]
Subnet["Subnetz 192.168.100.0/24"]
Router <-->|Interface| Subnet
subgraph SG ["Security Group"]
VM["VM-Instanz"]
end
end
Vol["Data-Volume (100 GB)"]
Vol <-.->|Angehängt| 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-Netzwerk"]
ExtNet["Externes Netzwerk (public)"]
end
subgraph Project ["Projekt"]
Router["Router"]
Router <-->|Gateway| ExtNet
subgraph Net ["Privates Netzwerk"]
Subnet["Subnetz 192.168.100.0/24"]
Router <-->|Interface| Subnet
subgraph SG ["Security Group"]
VM["VM-Instanz"]
end
end
Vol["Data-Volume (100 GB)"]
Vol <-.->|Angehängt| 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;
Voraussetzungen
- Terraform installiert und konfiguriert — siehe Terraform-Installation
- Ansible installiert und konfiguriert — siehe Ansible-Installation
- Application Credentials (
clouds.yaml) — siehe Application Credentials - Ein SSH-Key-Paar in OpenStack hochgeladen — siehe SSH-Key-Paare
Projekt-Struktur
openstack-demo/
├── main.tf # Terraform: gesamte Infrastruktur + VM
├── playbook.yml # Ansible: gesamte Infrastruktur + VM + Konfiguration (eigenständig)
└── clouds.yaml # Application Credentials (nicht in der Versionsverwaltung)
Eigenständige Ansätze
main.tf und playbook.yml sind unabhängig — Sie können entweder das eine oder das andere verwenden. Sie stellen dieselben Ressourcen bereit.
Terraform — main.tf
terraform {
required_providers {
openstack = {
source = "terraform-provider-openstack/openstack"
version = "~> 1.53"
}
}
}
provider "openstack" {
cloud = "openstack"
}
# ── Netzwerk ─────────────────────────────────────────────
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 = "SSH und ICMP erlauben"
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"
}
# ── Instanz ──────────────────────────────────────────────
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
}
Bereitstellen
cd openstack-demo
terraform init
terraform plan
terraform apply
Terraform gibt nach einem erfolgreichen Apply die Floating IP aus:
floating_ip = "185.123.45.67"
vm_name = "demo-vm"
volume_id = "71bd47d8-b51e-4f12-9c51-0445fc36c9e4"
Ansible — playbook.yml
Dieses Playbook erstellt die gesamte Infrastruktur (Netzwerk, Router, Security Group, Data-Volume, VM, Floating IP) und konfiguriert anschliessend die VM — alles in einem einzigen Lauf. Terraform wird nicht benötigt.
---
- name: Infrastruktur und VM erstellen
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: Privates Netzwerk erstellen
openstack.cloud.os_network:
name: demo-network
- name: Subnetz erstellen
openstack.cloud.os_subnet:
name: demo-subnet
network_name: demo-network
cidr: "{{ cidr }}"
dns_nameservers:
- 8.8.8.8
- 8.8.4.4
- name: Router mit externem Gateway und Interface erstellen
openstack.cloud.os_router:
name: demo-router
network: public
interfaces:
- subnet: demo-subnet
- name: Security Group erstellen
openstack.cloud.os_security_group:
name: demo-secgroup
description: SSH und ICMP erlauben
- name: SSH erlauben
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: ICMP erlauben
openstack.cloud.os_security_group_rule:
security_group: demo-secgroup
protocol: icmp
remote_ip_prefix: 0.0.0.0/0
- name: Data-Volume erstellen
openstack.cloud.os_volume:
name: demo-data-vol
size: "{{ volume_size }}"
volume_type: "{{ volume_type }}"
availability_zone: "{{ availability_zone }}"
- name: VM-Instanz starten
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: Data-Volume an VM anhängen
openstack.cloud.os_server_volume:
server: "{{ vm_name }}"
volume: demo-data-vol
- name: Floating IP zuweisen und zuordnen
openstack.cloud.os_floating_ip:
server: "{{ vm_name }}"
network: public
wait: true
register: fip
- name: VM zum In-Memory-Inventory hinzufügen
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: Warten bis SSH verfügbar ist
ansible.builtin.wait_for:
host: "{{ fip.floating_ip.floating_ip_address }}"
port: 22
timeout: 300
- name: Floating IP anzeigen
ansible.builtin.debug:
msg: "VM Floating IP: {{ fip.floating_ip.floating_ip_address }}"
- name: VM konfigurieren
hosts: demo-vm
become: true
tasks:
- name: Angehängtes Volume identifizieren
ansible.builtin.shell: lsblk -o NAME,SIZE -n | awk '$2 ~ /100G/ {print $1; exit}'
register: volume_device
changed_when: false
- name: Partition auf dem Data-Volume erstellen
ansible.builtin.shell: |
parted -s /dev/{{ volume_device.stdout }} mklabel gpt mkpart primary ext4 0% 100%
when: volume_device.stdout != ""
- name: Partition formatieren
ansible.builtin.filesystem:
fstype: ext4
dev: "/dev/{{ volume_device.stdout }}1"
opts: -L datavol
- name: Mount-Point erstellen
ansible.builtin.file:
path: /mnt/data
state: directory
mode: "0755"
- name: Volume einhängen
ansible.builtin.mount:
path: /mnt/data
src: LABEL=datavol
fstype: ext4
opts: noatime,nodiratime,nofail
state: mounted
- name: Nginx installieren
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Sicherstellen, dass Nginx läuft
ansible.builtin.service:
name: nginx
state: started
enabled: true
Ausführen
ansible-playbook -i localhost, playbook.yml
Das Playbook gibt am Ende des ersten Plays die Floating IP aus:
TASK [Floating IP anzeigen] ****
ok: [localhost] => {
"msg": "VM Floating IP: 185.123.45.67"
}
Note
Der erste Play läuft gegen localhost und kommuniziert über clouds.yaml mit der OpenStack-API. Der zweite Play verbindet sich über SSH mit der Floating IP der VM. Die add_host-Aufgabe verbindet die beiden Plays, indem sie die VM zum In-Memory-Inventory hinzufügt.
Verifizieren
Nach dem Deployment mit einem der beiden Werkzeuge kann das Setup verifiziert werden:
Terraform
ssh ubuntu@$(terraform output -raw floating_ip)
Ansible
ssh ubuntu@<floating-ip-aus-playbook-output>
Auf der VM
lsblk
df -h /mnt/data
curl -s http://localhost | head -5
Aufräumen
Terraform
terraform destroy
Dadurch werden alle von Terraform erstellten Ressourcen entfernt: Instanz, Volume, Floating IP, Netzwerk, Router und Security Group.
Ansible
Ressourcen in umgekehrter Reihenfolge mit dem OpenStack-CLI löschen:
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
Die von Ansible zugewiesene Floating IP wird beim Löschen des Servers disassoziiert, aber nicht freigegeben. Finden und freigeben mit openstack floating ip list und openstack floating ip release <ADRESSE>.