Aller au contenu

OpenTofu

Outil open-source d’infrastructure as code et fork communautaire de Terraform.

CommandeDescription
brew install opentofuInstaller sur macOS avec Homebrew
snap install --classic opentofuInstaller sur Linux avec Snap
choco install opentofuInstaller sur Windows avec Chocolatey
curl -fsSL https://get.opentofu.org/install-opentofu.sh | shInstaller via script shell
docker run -it ghcr.io/opentofu/opentofuExécuter via Docker
tofu --versionAfficher la version installée
tofu -helpAfficher les commandes disponibles
# 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
CommandeDescription
tofu initInitialiser le répertoire de travail et télécharger les fournisseurs
tofu init -upgradeRé-initialiser et mettre à jour les versions des fournisseurs/modules
tofu init -backend-config=prod.hclInitialiser avec un fichier de configuration backend
tofu planPrévisualiser les changements d’infrastructure
tofu plan -out=plan.tfplanSauvegarder le plan dans un fichier pour application ultérieure
tofu plan -target=aws_instance.webPlanifier les changements pour une ressource spécifique uniquement
tofu plan -var-file=prod.tfvarsPlanifier avec un fichier de variables spécifique
tofu applyAppliquer les changements d’infrastructure (demande confirmation)
tofu apply -auto-approveAppliquer les changements sans confirmation
tofu apply plan.tfplanAppliquer un fichier de plan sauvegardé
tofu destroyDétruire toute l’infrastructure gérée
tofu destroy -auto-approveDétruire sans confirmation
tofu destroy -target=aws_instance.webDétruire une ressource spécifique uniquement
CommandeDescription
tofu validateValider la syntaxe de la configuration
tofu fmtFormater les fichiers de configuration
tofu fmt -checkVérifier si les fichiers sont formatés (adapté CI)
tofu fmt -recursiveFormater tous les fichiers dans les sous-répertoires
tofu outputAfficher toutes les valeurs de sortie
tofu output -jsonAfficher les sorties en format JSON
tofu output db_passwordAfficher une valeur de sortie spécifique
tofu showAfficher l’état actuel sous forme lisible
tofu show -jsonAfficher l’état en format JSON
tofu show plan.tfplanAfficher les détails d’un plan sauvegardé
tofu graphGénérer le graphe de dépendances des ressources (format DOT)
tofu graph | dot -Tpng > graph.pngRendre le graphe de dépendances en PNG
tofu refreshRéconcilier l’état avec l’infrastructure réelle
tofu providersLister les fournisseurs utilisés dans la configuration
# 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"
}
# 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
}
CommandeDescription
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
CommandeDescription
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 planPasser via une variable d’environnement
Créer un fichier terraform.tfvarsValeurs de variables chargées automatiquement
Créer un fichier *.auto.tfvarsFichiers de variables chargés automatiquement
# 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
}
# 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
}
CommandeDescription
tofu state listLister toutes les ressources dans l’état
tofu state show aws_instance.webAfficher les détails d’une ressource spécifique
tofu state mv aws_instance.old aws_instance.newRenommer une ressource dans l’état
tofu state rm aws_instance.webSupprimer une ressource de l’état (conserve l’infra réelle)
tofu state pullRécupérer l’état distant vers stdout
tofu state pushPousser l’état local vers le backend distant
tofu import aws_instance.web i-1234567Importer une ressource existante dans l’état
tofu state replace-provider hashicorp/aws registry.opentofu.org/hashicorp/awsRemplacer le fournisseur dans l’état
tofu force-unlock LOCK_IDLibérer un verrou d’état bloqué
# 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 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
    }
  }
}
CommandeDescription
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 getTélécharger et mettre à jour les modules
tofu get -updateForcer la mise à jour de tous les modules
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
# 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
}
CommandeDescription
Remplacer terraform par tofu dans les commandesLes commandes CLI sont directement compatibles
tofu init -upgradeRé-initialiser avec les fournisseurs OpenTofu
Conserver les fichiers .tf existants inchangésLa syntaxe HCL est entièrement compatible
Conserver les fichiers .tfstate existantsLe format d’état est compatible
Conserver le fichier .terraform.lock.hcl existantLe fichier de verrouillage est compatible
Mettre à jour le CI/CD pour utiliser l’action opentofu/setup-opentofuChanger les GitHub Actions
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é)
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 }}

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.

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.

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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. É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.

  6. Valider les variables avec des contraintes — Ajoutez des blocs validation aux variables pour détecter les valeurs invalides avant qu’elles n’atteignent le fournisseur cloud.

  7. Sauvegarder les plans avant d’appliquer — Utilisez tofu plan -out=plan.tfplan puis tofu apply plan.tfplan pour vous assurer d’appliquer exactement ce que vous avez vérifié.

  8. Utiliser default_tags sur les fournisseurs — Définissez les tags à l’échelle de l’organisation (ManagedBy, Environment, Project) au niveau du fournisseur pour assurer un tagging cohérent.

  9. Importer les ressources existantes — Utilisez tofu import pour placer l’infrastructure non gérée sous le contrôle d’OpenTofu plutôt que de la recréer.

  10. Exécuter tofu fmt -check dans 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.

  11. 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 data au lieu de coder en dur les identifiants.

  12. Examiner les plans attentivement — Lisez toujours la sortie du plan avant d’appliquer. Faites attention aux actions destroy et replace qui pourraient causer des temps d’arrêt.