Terraform et Ansible
Terraform et Ansible sont complémentaires, pas concurrents. Terraform provisionne l'infrastructure (VMs, réseau, DNS, stockage). Ansible configure les OS et déploie les applications. Articuler les deux correctement évite les zones grises où chaque outil essaie de faire le travail de l'autre.
Le pattern recommandé
- Terraform : créer la VM, configurer le réseau, les security groups, les volumes, les DNS records
- Ansible : configurer l'OS, installer les packages, déployer les applications, gérer les configurations
- Interface : les outputs Terraform alimentent l'inventaire Ansible
Générer l'inventaire Ansible depuis les outputs
# outputs.tf
output "web_ips" {
value = { for k, v in aws_instance.web : k => v.public_ip }
}
output "ansible_inventory" {
value = templatefile("${path.module}/templates/inventory.tpl", {
web_hosts = { for k, v in aws_instance.web : k => v.public_ip }
db_host = aws_db_instance.main.address
})
}# templates/inventory.tpl
[web]
%{ for name, ip in web_hosts ~}
${name} ansible_host=${ip} ansible_user=debian
%{ endfor ~}
[db]
db01 ansible_host=${db_host} ansible_user=admin# Générer l'inventaire et lancer Ansible
terraform output -raw ansible_inventory > inventory/hosts.ini
ansible-playbook -i inventory/hosts.ini playbooks/site.ymlnull_resource + local-exec
resource "null_resource" "configure_servers" {
triggers = {
# Relancer Ansible si les instances changent
instance_ids = join(",", [for v in aws_instance.web : v.id])
}
provisioner "local-exec" {
command = <<-EOT
sleep 30 # attendre que SSH soit disponible
ansible-playbook -i "${join(",", [for v in aws_instance.web : "${v.public_ip}"])}, " -u debian --private-key ~/.ssh/id_ed25519 playbooks/site.yml
EOT
}
depends_on = [aws_instance.web]
}Cette approche fonctionne mais est fragile : la sortie Terraform reste bloquée pendant l'exécution d'Ansible, les erreurs Ansible ne propagent pas toujours proprement, et le re-run du playbook est difficile à isoler.
remote-exec : exécuter sur la cible
resource "aws_instance" "web" {
# ...
connection {
type = "ssh"
user = "debian"
private_key = file(var.private_key_path)
host = self.public_ip
}
provisioner "remote-exec" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y python3", # préparer pour Ansible
]
}
}Pattern pipeline recommandé
# .gitlab-ci.yml
stages: [terraform, ansible]
terraform_apply:
stage: terraform
script:
- terraform init
- terraform apply -auto-approve
- terraform output -json > terraform_outputs.json
artifacts:
paths: [terraform_outputs.json]
ansible_deploy:
stage: ansible
dependencies: [terraform_apply]
script:
- python3 scripts/generate_inventory.py terraform_outputs.json > inventory/hosts.ini
- ansible-playbook -i inventory/hosts.ini playbooks/site.ymlCe pattern est propre : les deux étapes sont séparées, les outputs Terraform transitent via un artefact JSON, et chaque étape peut être rejouée indépendamment.