OpenTofu
Outil open-source d’infrastructure as code et fork communautaire de Terraform.
Installation
Section intitulée « Installation »Installation par plateforme
Section intitulée « Installation par plateforme »| Commande | Description |
|---|---|
brew install opentofu | Installer sur macOS avec Homebrew |
snap install --classic opentofu | Installer sur Linux avec Snap |
choco install opentofu | Installer sur Windows avec Chocolatey |
curl -fsSL https://get.opentofu.org/install-opentofu.sh | sh | Installer via script shell |
docker run -it ghcr.io/opentofu/opentofu | Exécuter via Docker |
tofu --version | Afficher la version installée |
tofu -help | Afficher les commandes disponibles |
Installation du gestionnaire de versions
Section intitulée « Installation du gestionnaire de versions »# Utilisation de tofuenv (comme tfenv pour OpenTofu)
git clone https://github.com/tofuutils/tofuenv.git ~/.tofuenv
echo 'export PATH="$HOME/.tofuenv/bin:$PATH"' >> ~/.bashrc
# Lister les versions disponibles
tofuenv list-remote
# Installer une version spécifique
tofuenv install 1.8.0
tofuenv use 1.8.0
# Fixer la version dans le projet
echo "1.8.0" > .opentofu-version
Commandes principales
Section intitulée « Commandes principales »Commandes de workflow
Section intitulée « Commandes de workflow »| Commande | Description |
|---|---|
tofu init | Initialiser le répertoire de travail et télécharger les fournisseurs |
tofu init -upgrade | Ré-initialiser et mettre à jour les versions des fournisseurs/modules |
tofu init -backend-config=prod.hcl | Initialiser avec un fichier de configuration backend |
tofu plan | Prévisualiser les changements d’infrastructure |
tofu plan -out=plan.tfplan | Sauvegarder le plan dans un fichier pour application ultérieure |
tofu plan -target=aws_instance.web | Planifier les changements pour une ressource spécifique uniquement |
tofu plan -var-file=prod.tfvars | Planifier avec un fichier de variables spécifique |
tofu apply | Appliquer les changements d’infrastructure (demande confirmation) |
tofu apply -auto-approve | Appliquer les changements sans confirmation |
tofu apply plan.tfplan | Appliquer un fichier de plan sauvegardé |
tofu destroy | Détruire toute l’infrastructure gérée |
tofu destroy -auto-approve | Détruire sans confirmation |
tofu destroy -target=aws_instance.web | Détruire une ressource spécifique uniquement |
Commandes d’inspection
Section intitulée « Commandes d’inspection »| Commande | Description |
|---|---|
tofu validate | Valider la syntaxe de la configuration |
tofu fmt | Formater les fichiers de configuration |
tofu fmt -check | Vérifier si les fichiers sont formatés (adapté CI) |
tofu fmt -recursive | Formater tous les fichiers dans les sous-répertoires |
tofu output | Afficher toutes les valeurs de sortie |
tofu output -json | Afficher les sorties en format JSON |
tofu output db_password | Afficher une valeur de sortie spécifique |
tofu show | Afficher l’état actuel sous forme lisible |
tofu show -json | Afficher l’état en format JSON |
tofu show plan.tfplan | Afficher les détails d’un plan sauvegardé |
tofu graph | Générer le graphe de dépendances des ressources (format DOT) |
tofu graph | dot -Tpng > graph.png | Rendre le graphe de dépendances en PNG |
tofu refresh | Réconcilier l’état avec l’infrastructure réelle |
tofu providers | Lister les fournisseurs utilisés dans la configuration |
Configuration (HCL)
Section intitulée « Configuration (HCL) »Configuration du fournisseur
Section intitulée « Configuration du fournisseur »# Bloc des fournisseurs requis
terraform {
required_version = ">= 1.6.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.5"
}
}
# Chiffrement de l'état (fonctionnalité exclusive OpenTofu)
encryption {
method "aes_gcm" "default" {
keys = key_provider.pbkdf2.mykey
}
state {
method = method.aes_gcm.default
enforced = true
}
plan {
method = method.aes_gcm.default
enforced = true
}
key_provider "pbkdf2" "mykey" {
passphrase = var.state_passphrase
}
}
}
# Fournisseur avec configuration par défaut
provider "aws" {
region = "us-east-1"
default_tags {
tags = {
ManagedBy = "opentofu"
Environment = var.environment
Project = var.project_name
}
}
}
# Alias de fournisseur pour multi-région
provider "aws" {
alias = "west"
region = "us-west-2"
}
# Utiliser un fournisseur aliasé sur une ressource
resource "aws_instance" "west_server" {
provider = aws.west
ami = "ami-0abcdef1234567890"
instance_type = "t3.micro"
}
Définitions de ressources
Section intitulée « Définitions de ressources »# Ressource basique
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
tags = {
Name = "${var.project_name}-web"
}
}
# Source de données (lire les ressources existantes)
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
}
# Valeurs locales
locals {
common_tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "opentofu"
}
az_count = length(data.aws_availability_zones.available.names)
}
# Count et for_each
resource "aws_subnet" "public" {
count = local.az_count
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = merge(local.common_tags, {
Name = "${var.project_name}-public-${count.index}"
Type = "public"
})
}
resource "aws_security_group_rule" "ingress" {
for_each = {
http = { port = 80, cidrs = ["0.0.0.0/0"] }
https = { port = 443, cidrs = ["0.0.0.0/0"] }
ssh = { port = 22, cidrs = [var.admin_cidr] }
}
type = "ingress"
from_port = each.value.port
to_port = each.value.port
protocol = "tcp"
cidr_blocks = each.value.cidrs
security_group_id = aws_security_group.main.id
}
Variables et sorties
Section intitulée « Variables et sorties »Définitions de variables
Section intitulée « Définitions de variables »| Commande | Description |
|---|---|
variable "name" { type = string } | Définir une variable de type chaîne |
variable "port" { type = number default = 8080 } | Variable avec valeur par défaut |
variable "tags" { type = map(string) } | Variable de type map |
variable "cidrs" { type = list(string) } | Variable de type liste |
variable "config" { type = object({ ... }) } | Variable objet avec structure |
variable "db_pass" { sensitive = true } | Marquer la variable comme sensible (masquée dans la sortie) |
variable "env" { validation { ... } } | Variable avec validation personnalisée |
Passage de variables
Section intitulée « Passage de variables »| Commande | Description |
|---|---|
tofu plan -var="name=value" | Passer une variable via le CLI |
tofu plan -var-file="prod.tfvars" | Passer des variables depuis un fichier |
TF_VAR_name=value tofu plan | Passer via une variable d’environnement |
Créer un fichier terraform.tfvars | Valeurs de variables chargées automatiquement |
Créer un fichier *.auto.tfvars | Fichiers de variables chargés automatiquement |
Exemples de variables et sorties
Section intitulée « Exemples de variables et sorties »# variables.tf
variable "environment" {
type = string
description = "Deployment environment"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "instance_config" {
type = object({
instance_type = string
volume_size = number
encrypted = bool
})
default = {
instance_type = "t3.micro"
volume_size = 20
encrypted = true
}
}
variable "database_password" {
type = string
sensitive = true
}
# outputs.tf
output "vpc_id" {
description = "The ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "List of public subnet IDs"
value = aws_subnet.public[*].id
}
output "web_url" {
description = "URL of the web server"
value = "https://${aws_lb.web.dns_name}"
}
output "db_connection_string" {
description = "Database connection string"
value = "postgresql://${var.db_username}:${var.database_password}@${aws_db_instance.main.endpoint}/${var.db_name}"
sensitive = true
}
Fichiers de variables
Section intitulée « Fichiers de variables »# prod.tfvars
environment = "prod"
instance_config = {
instance_type = "t3.large"
volume_size = 100
encrypted = true
}
# dev.tfvars
environment = "dev"
instance_config = {
instance_type = "t3.micro"
volume_size = 20
encrypted = false
}
Gestion de l’état
Section intitulée « Gestion de l’état »Commandes d’état
Section intitulée « Commandes d’état »| Commande | Description |
|---|---|
tofu state list | Lister toutes les ressources dans l’état |
tofu state show aws_instance.web | Afficher les détails d’une ressource spécifique |
tofu state mv aws_instance.old aws_instance.new | Renommer une ressource dans l’état |
tofu state rm aws_instance.web | Supprimer une ressource de l’état (conserve l’infra réelle) |
tofu state pull | Récupérer l’état distant vers stdout |
tofu state push | Pousser l’état local vers le backend distant |
tofu import aws_instance.web i-1234567 | Importer une ressource existante dans l’état |
tofu state replace-provider hashicorp/aws registry.opentofu.org/hashicorp/aws | Remplacer le fournisseur dans l’état |
tofu force-unlock LOCK_ID | Libérer un verrou d’état bloqué |
Configuration du backend
Section intitulée « Configuration du backend »# Backend S3 (le plus courant)
terraform {
backend "s3" {
bucket = "my-tofu-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "tofu-locks"
encrypt = true
}
}
# Backend PostgreSQL
terraform {
backend "pg" {
conn_str = "postgres://user:pass@db.example.com/tofu_state?sslmode=require"
}
}
# Backend HTTP (fonctionne avec GitLab, alternatives Terraform Cloud)
terraform {
backend "http" {
address = "https://gitlab.example.com/api/v4/projects/1/terraform/state/prod"
lock_address = "https://gitlab.example.com/api/v4/projects/1/terraform/state/prod/lock"
unlock_address = "https://gitlab.example.com/api/v4/projects/1/terraform/state/prod/lock"
username = "gitlab-ci-token"
password = var.ci_token
}
}
# Backend local (par défaut, pour le développement)
terraform {
backend "local" {
path = "terraform.tfstate"
}
}
Chiffrement de l’état (exclusif OpenTofu)
Section intitulée « Chiffrement de l’état (exclusif OpenTofu) »# Chiffrement de l'état avec AWS KMS
terraform {
encryption {
key_provider "aws_kms" "main" {
kms_key_id = "arn:aws:kms:us-east-1:123456789:key/abcd-1234"
region = "us-east-1"
key_spec = "AES_256"
}
method "aes_gcm" "default" {
keys = key_provider.aws_kms.main
}
state {
method = method.aes_gcm.default
enforced = true
}
}
}
Utilisation des modules
Section intitulée « Utilisation des modules »| Commande | Description |
|---|---|
module "vpc" { source = "./modules/vpc" } | Utiliser un module local |
module "vpc" { source = "github.com/org/repo//modules/vpc" } | Utiliser un module GitHub |
module "vpc" { source = "registry.opentofu.org/..." } | Utiliser un module du registre OpenTofu |
tofu get | Télécharger et mettre à jour les modules |
tofu get -update | Forcer la mise à jour de tous les modules |
Structure d’un module
Section intitulée « Structure d’un module »modules/
└── vpc/
├── main.tf # Ressources
├── variables.tf # Variables d'entrée
├── outputs.tf # Valeurs de sortie
├── versions.tf # Exigences des fournisseurs
└── README.md # Documentation d'utilisation
Exemple de module
Section intitulée « Exemple de module »# modules/vpc/main.tf
resource "aws_vpc" "this" {
cidr_block = var.cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = merge(var.tags, { Name = var.name })
}
resource "aws_subnet" "public" {
count = length(var.public_subnets)
vpc_id = aws_vpc.this.id
cidr_block = var.public_subnets[count.index]
availability_zone = var.azs[count.index]
map_public_ip_on_launch = true
tags = merge(var.tags, {
Name = "${var.name}-public-${count.index}"
})
}
# modules/vpc/variables.tf
variable "name" { type = string }
variable "cidr" { type = string }
variable "azs" { type = list(string) }
variable "public_subnets" { type = list(string) }
variable "tags" { type = map(string) default = {} }
# modules/vpc/outputs.tf
output "vpc_id" { value = aws_vpc.this.id }
output "public_subnet_ids" { value = aws_subnet.public[*].id }
# Utilisation du module
module "vpc" {
source = "./modules/vpc"
name = "production"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
tags = local.common_tags
}
Migration depuis Terraform
Section intitulée « Migration depuis Terraform »Compatibilité directe
Section intitulée « Compatibilité directe »| Commande | Description |
|---|---|
Remplacer terraform par tofu dans les commandes | Les commandes CLI sont directement compatibles |
tofu init -upgrade | Ré-initialiser avec les fournisseurs OpenTofu |
Conserver les fichiers .tf existants inchangés | La syntaxe HCL est entièrement compatible |
Conserver les fichiers .tfstate existants | Le format d’état est compatible |
Conserver le fichier .terraform.lock.hcl existant | Le fichier de verrouillage est compatible |
Mettre à jour le CI/CD pour utiliser l’action opentofu/setup-opentofu | Changer les GitHub Actions |
Liste de contrôle de migration
Section intitulée « Liste de contrôle de migration »Migration de Terraform vers OpenTofu :
1. Installer OpenTofu aux côtés de Terraform
2. Dans votre répertoire de projet, exécuter : tofu init
3. Exécuter : tofu plan
- Comparer la sortie avec : terraform plan
- Devrait être identique (aucun changement)
4. Remplacer 'terraform' par 'tofu' dans les pipelines CI/CD
5. Mettre à jour les GitHub Actions :
- Ancien : hashicorp/setup-terraform
- Nouveau : opentofu/setup-opentofu
6. Mettre à jour les sources des fournisseurs du fichier de verrouillage (si nécessaire) :
- tofu providers lock
7. Activer le chiffrement de l'état (exclusif OpenTofu) :
- Ajouter le bloc encryption au bloc terraform {}
8. Changer les références du registre de modules :
- registry.terraform.io → registry.opentofu.org
- (Les deux fonctionnent encore, mais le registre OpenTofu est préféré)
CI/CD GitHub Actions
Section intitulée « CI/CD GitHub Actions »name: OpenTofu Plan & Apply
on:
push:
branches: [main]
pull_request:
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: opentofu/setup-opentofu@v1
with:
tofu_version: 1.8.0
- name: Init
run: tofu init
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Plan
run: tofu plan -out=plan.tfplan -no-color
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Apply (main only)
if: github.ref == 'refs/heads/main'
run: tofu apply -auto-approve plan.tfplan
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
Fonctionnalités exclusives OpenTofu
Section intitulée « Fonctionnalités exclusives OpenTofu »Chiffrement de l’état
Section intitulée « Chiffrement de l’état »Chiffrement des fichiers d’état au repos — indisponible dans Terraform. Supporte les fournisseurs de clés PBKDF2, AWS KMS, GCP KMS et Azure Key Vault.
Chiffrement de l’état côté client
Section intitulée « Chiffrement de l’état côté client »Contrairement au chiffrement côté serveur de Terraform Cloud, OpenTofu chiffre l’état avant qu’il ne quitte votre machine, garantissant qu’aucun tiers ne peut lire l’état de votre infrastructure.
Indépendance du registre
Section intitulée « Indépendance du registre »OpenTofu maintient son propre registre de fournisseurs et de modules à registry.opentofu.org, assurant une disponibilité à long terme indépendante des décisions de licence de HashiCorp.
Bonnes pratiques
Section intitulée « Bonnes pratiques »-
Utiliser un état distant avec verrouillage — Configurez un backend S3 + DynamoDB (ou équivalent) pour empêcher les modifications concurrentes et permettre la collaboration en équipe.
-
Activer le chiffrement de l’état — Utilisez la fonctionnalité exclusive de chiffrement d’OpenTofu avec KMS ou PBKDF2 pour protéger les données sensibles dans les fichiers d’état.
-
Fixer les versions des fournisseurs et modules — Utilisez les contraintes
~>(par exemple~> 5.0) pour permettre les mises à jour de correctifs tout en empêchant les changements cassants. -
Utiliser des workspaces ou la séparation par répertoire pour les environnements — Gardez les états dev, staging et production séparés pour éviter les changements accidentels entre environnements.
-
Écrire des modules pour les patterns réutilisables — Extrayez les patterns courants (VPCs, bases de données, clusters Kubernetes) dans des modules avec des entrées et sorties claires.
-
Valider les variables avec des contraintes — Ajoutez des blocs
validationaux variables pour détecter les valeurs invalides avant qu’elles n’atteignent le fournisseur cloud. -
Sauvegarder les plans avant d’appliquer — Utilisez
tofu plan -out=plan.tfplanpuistofu apply plan.tfplanpour vous assurer d’appliquer exactement ce que vous avez vérifié. -
Utiliser
default_tagssur les fournisseurs — Définissez les tags à l’échelle de l’organisation (ManagedBy, Environment, Project) au niveau du fournisseur pour assurer un tagging cohérent. -
Importer les ressources existantes — Utilisez
tofu importpour placer l’infrastructure non gérée sous le contrôle d’OpenTofu plutôt que de la recréer. -
Exécuter
tofu fmt -checkdans le CI — Imposez un formatage cohérent à tous les membres de l’équipe en faisant échouer les builds CI sur les fichiers non formatés. -
Utiliser des sources de données pour les références externes — Référencez l’infrastructure existante (AMIs, VPCs, zones DNS) avec des blocs
dataau lieu de coder en dur les identifiants. -
Examiner les plans attentivement — Lisez toujours la sortie du plan avant d’appliquer. Faites attention aux actions
destroyetreplacequi pourraient causer des temps d’arrêt.