Modules Terraform - Compute¶
Introduction¶
Le module compute gère les instances, volumes, floating IPs et keypairs. Il s'intègre avec le module network pour créer des stacks applicatives complètes.
Prérequis¶
Points à apprendre¶
Architecture du module¶
graph TB
subgraph "Module: compute"
Instance["Instances<br/>openstack_compute_instance_v2<br/>VMs avec cloud-init"]
Volume["Volumes<br/>openstack_blockstorage_volume_v3<br/>Stockage persistant"]
Attach["Volume Attach<br/>openstack_compute_volume_attach_v2<br/>Attachement volumes"]
FIP["Floating IPs<br/>openstack_networking_floatingip_v2<br/>IPs publiques"]
Keypair["Keypair<br/>openstack_compute_keypair_v2<br/>Clé SSH"]
ServerGroup["Server Group<br/>openstack_compute_servergroup_v2<br/>Anti-affinity"]
end
NetworkModule["Module: network<br/>Réseau et security groups"]
Image["Glance Image<br/>Image de base"]
Flavor["Flavor<br/>Configuration ressources"]
Instance -->|Uses<br/>network_id, security_group_id| NetworkModule
Instance -->|Boot from| Image
Instance -->|Sized by| Flavor
Instance -->|Auth| Keypair
Attach -->|Attach to| Instance
Attach -->|Attach| Volume
FIP -->|Associate| Instance
Instance -->|Member of| ServerGroup
Structure du module¶
modules/compute/
├── main.tf
├── variables.tf
├── outputs.tf
├── volumes.tf
├── cloud-init/
│ ├── base.yaml
│ └── web.yaml
└── README.md
Variables du module¶
# modules/compute/variables.tf
variable "name" {
description = "Nom de base pour les instances"
type = string
}
variable "environment" {
description = "Environnement"
type = string
default = "dev"
}
variable "instance_count" {
description = "Nombre d'instances"
type = number
default = 1
}
variable "image_name" {
description = "Nom de l'image"
type = string
default = "ubuntu-22.04"
}
variable "image_id" {
description = "ID de l'image (prioritaire sur image_name)"
type = string
default = null
}
variable "flavor_name" {
description = "Nom du flavor"
type = string
default = "m1.small"
}
variable "network_id" {
description = "ID du réseau"
type = string
}
variable "security_group_ids" {
description = "Liste des IDs de security groups"
type = list(string)
default = []
}
variable "security_group_names" {
description = "Liste des noms de security groups"
type = list(string)
default = []
}
variable "keypair_name" {
description = "Nom de la keypair existante"
type = string
default = null
}
variable "public_key" {
description = "Clé publique SSH (crée une nouvelle keypair)"
type = string
default = null
}
variable "assign_floating_ip" {
description = "Assigner une floating IP"
type = bool
default = false
}
variable "floating_ip_pool" {
description = "Pool pour les floating IPs"
type = string
default = "external"
}
variable "availability_zone" {
description = "Zone de disponibilité"
type = string
default = null
}
variable "user_data" {
description = "Cloud-init user data"
type = string
default = null
}
variable "user_data_file" {
description = "Chemin vers fichier cloud-init"
type = string
default = null
}
variable "volumes" {
description = "Volumes à créer et attacher"
type = list(object({
size = number
type = optional(string, "standard")
mount_point = optional(string)
}))
default = []
}
variable "boot_volume_size" {
description = "Taille du volume de boot (0 = boot depuis image)"
type = number
default = 0
}
variable "boot_volume_type" {
description = "Type du volume de boot"
type = string
default = "standard"
}
variable "use_anti_affinity" {
description = "Utiliser un server group anti-affinity"
type = bool
default = false
}
variable "metadata" {
description = "Metadata pour les instances"
type = map(string)
default = {}
}
variable "tags" {
description = "Tags pour les ressources"
type = map(string)
default = {}
}
Ressources principales¶
# modules/compute/main.tf
locals {
name_prefix = "${var.environment}-${var.name}"
common_tags = merge(var.tags, {
environment = var.environment
managed_by = "terraform"
module = "compute"
})
user_data = coalesce(
var.user_data,
var.user_data_file != null ? file(var.user_data_file) : null,
file("${path.module}/cloud-init/base.yaml")
)
security_groups = concat(
var.security_group_names,
[for id in var.security_group_ids : id]
)
}
# Data source pour l'image
data "openstack_images_image_v2" "this" {
count = var.image_id == null ? 1 : 0
name = var.image_name
most_recent = true
}
locals {
image_id = coalesce(var.image_id, data.openstack_images_image_v2.this[0].id)
}
# Keypair (optionnel)
resource "openstack_compute_keypair_v2" "this" {
count = var.public_key != null ? 1 : 0
name = "${local.name_prefix}-keypair"
public_key = var.public_key
}
locals {
keypair_name = coalesce(
var.keypair_name,
var.public_key != null ? openstack_compute_keypair_v2.this[0].name : null
)
}
# Server group pour anti-affinity
resource "openstack_compute_servergroup_v2" "this" {
count = var.use_anti_affinity ? 1 : 0
name = "${local.name_prefix}-servergroup"
policies = ["anti-affinity"]
}
# Volume de boot (optionnel)
resource "openstack_blockstorage_volume_v3" "boot" {
count = var.boot_volume_size > 0 ? var.instance_count : 0
name = "${local.name_prefix}-boot-${count.index + 1}"
size = var.boot_volume_size
volume_type = var.boot_volume_type
image_id = local.image_id
availability_zone = var.availability_zone
}
# Instance
resource "openstack_compute_instance_v2" "this" {
count = var.instance_count
name = "${local.name_prefix}-${count.index + 1}"
flavor_name = var.flavor_name
key_pair = local.keypair_name
security_groups = local.security_groups
availability_zone = var.availability_zone
user_data = local.user_data
# Boot depuis image ou volume
dynamic "block_device" {
for_each = var.boot_volume_size > 0 ? [1] : []
content {
uuid = openstack_blockstorage_volume_v3.boot[count.index].id
source_type = "volume"
destination_type = "volume"
boot_index = 0
delete_on_termination = false
}
}
# Si pas de volume de boot, utiliser l'image
image_id = var.boot_volume_size > 0 ? null : local.image_id
network {
uuid = var.network_id
}
# Server group anti-affinity
dynamic "scheduler_hints" {
for_each = var.use_anti_affinity ? [1] : []
content {
group = openstack_compute_servergroup_v2.this[0].id
}
}
metadata = merge(local.common_tags, var.metadata, {
instance_index = count.index + 1
})
lifecycle {
ignore_changes = [user_data]
}
}
# Floating IPs
resource "openstack_networking_floatingip_v2" "this" {
count = var.assign_floating_ip ? var.instance_count : 0
pool = var.floating_ip_pool
}
resource "openstack_compute_floatingip_associate_v2" "this" {
count = var.assign_floating_ip ? var.instance_count : 0
floating_ip = openstack_networking_floatingip_v2.this[count.index].address
instance_id = openstack_compute_instance_v2.this[count.index].id
}
Gestion des volumes¶
# modules/compute/volumes.tf
# Volumes de données
resource "openstack_blockstorage_volume_v3" "data" {
count = var.instance_count * length(var.volumes)
name = "${local.name_prefix}-data-${floor(count.index / length(var.volumes)) + 1}-${count.index % length(var.volumes) + 1}"
size = var.volumes[count.index % length(var.volumes)].size
volume_type = var.volumes[count.index % length(var.volumes)].type
availability_zone = var.availability_zone
metadata = local.common_tags
}
# Attachement des volumes
resource "openstack_compute_volume_attach_v2" "data" {
count = var.instance_count * length(var.volumes)
instance_id = openstack_compute_instance_v2.this[floor(count.index / length(var.volumes))].id
volume_id = openstack_blockstorage_volume_v3.data[count.index].id
}
Cloud-init templates¶
# modules/compute/cloud-init/base.yaml
#cloud-config
package_update: true
package_upgrade: true
packages:
- curl
- wget
- vim
- htop
- net-tools
timezone: Europe/Paris
ntp:
enabled: true
servers:
- ntp.ubuntu.com
users:
- default
- name: deploy
groups: sudo
shell: /bin/bash
sudo: ['ALL=(ALL) NOPASSWD:ALL']
write_files:
- path: /etc/motd
content: |
====================================
Managed by Terraform
Environment: ${environment}
====================================
runcmd:
- echo "Instance provisioned at $(date)" > /var/log/cloud-init-custom.log
final_message: "Instance ready after $UPTIME seconds"
# modules/compute/cloud-init/web.yaml
#cloud-config
package_update: true
packages:
- nginx
- certbot
- python3-certbot-nginx
write_files:
- path: /var/www/html/index.html
content: |
<!DOCTYPE html>
<html>
<head><title>Welcome</title></head>
<body>
<h1>Instance: ${hostname}</h1>
<p>Environment: ${environment}</p>
</body>
</html>
runcmd:
- systemctl enable nginx
- systemctl start nginx
Outputs du module¶
# modules/compute/outputs.tf
output "instance_ids" {
description = "IDs des instances créées"
value = openstack_compute_instance_v2.this[*].id
}
output "instance_names" {
description = "Noms des instances"
value = openstack_compute_instance_v2.this[*].name
}
output "private_ips" {
description = "Adresses IP privées"
value = openstack_compute_instance_v2.this[*].access_ip_v4
}
output "floating_ips" {
description = "Floating IPs assignées"
value = var.assign_floating_ip ? openstack_networking_floatingip_v2.this[*].address : []
}
output "volume_ids" {
description = "IDs des volumes de données"
value = openstack_blockstorage_volume_v3.data[*].id
}
output "keypair_name" {
description = "Nom de la keypair utilisée"
value = local.keypair_name
}
output "server_group_id" {
description = "ID du server group"
value = var.use_anti_affinity ? openstack_compute_servergroup_v2.this[0].id : null
}
# Output pour Ansible inventory
output "ansible_inventory" {
description = "Inventaire Ansible généré"
value = {
for idx, instance in openstack_compute_instance_v2.this : instance.name => {
ansible_host = var.assign_floating_ip ? openstack_networking_floatingip_v2.this[idx].address : instance.access_ip_v4
private_ip = instance.access_ip_v4
instance_id = instance.id
}
}
}
Utilisation du module¶
# environments/production/main.tf
# Réseau
module "app_network" {
source = "../../modules/network"
name = "webapp"
environment = "production"
cidr = "10.10.0.0/24"
}
# Instances web (avec anti-affinity)
module "web_servers" {
source = "../../modules/compute"
name = "web"
environment = "production"
instance_count = 3
image_name = "ubuntu-22.04"
flavor_name = "m1.medium"
network_id = module.app_network.network_id
security_group_names = [module.app_network.security_group_name]
public_key = file("~/.ssh/id_rsa.pub")
assign_floating_ip = true
use_anti_affinity = true
user_data_file = "${path.module}/cloud-init/web.yaml"
metadata = {
role = "webserver"
}
tags = {
project = "webapp"
}
}
# Instance base de données
module "database" {
source = "../../modules/compute"
name = "database"
environment = "production"
instance_count = 1
image_name = "ubuntu-22.04"
flavor_name = "m1.large"
network_id = module.app_network.network_id
security_group_names = [module.app_network.database_security_group_id]
public_key = file("~/.ssh/id_rsa.pub")
# Volume de boot persistant
boot_volume_size = 50
boot_volume_type = "ssd"
# Volumes de données
volumes = [
{ size = 100, type = "ssd", mount_point = "/var/lib/postgresql" }
]
metadata = {
role = "database"
}
}
# Outputs
output "web_floating_ips" {
value = module.web_servers.floating_ips
}
output "database_private_ip" {
value = module.database.private_ips[0]
}
output "ansible_inventory" {
value = merge(
module.web_servers.ansible_inventory,
module.database.ansible_inventory
)
}
Diagramme d'infrastructure¶
graph TB
subgraph Internet
Users[Users]
end
subgraph "OpenStack Cloud"
subgraph "External Network"
FIP1["FIP 203.0.113.10"]
FIP2["FIP 203.0.113.11"]
FIP3["FIP 203.0.113.12"]
end
subgraph "Project Network (10.10.0.0/24)"
subgraph "Web Tier (Anti-affinity)"
Web1["web-1<br/>m1.medium<br/>10.10.0.10"]
Web2["web-2<br/>m1.medium<br/>10.10.0.11"]
Web3["web-3<br/>m1.medium<br/>10.10.0.12"]
end
subgraph "Database Tier"
DB["database-1<br/>m1.large<br/>10.10.0.20<br/>+ 100GB SSD"]
end
end
end
Users -->|HTTPS<br/>443| FIP1
FIP1 --> Web1
FIP2 --> Web2
FIP3 --> Web3
Web1 -->|PostgreSQL<br/>5432| DB
Web2 -->|PostgreSQL<br/>5432| DB
Web3 -->|PostgreSQL<br/>5432| DB
Exemples pratiques¶
Génération d'inventaire Ansible¶
# Exporter l'inventaire depuis Terraform
terraform output -json ansible_inventory | jq -r '
to_entries | .[] | "\(.key) ansible_host=\(.value.ansible_host) private_ip=\(.value.private_ip)"
' > inventory.ini
Ressources¶
Checkpoint¶
- Module compute fonctionnel
- Instances créées avec cloud-init
- Volumes attachés correctement
- Floating IPs assignées
- Anti-affinity testé
- Inventaire Ansible généré