Salta ai contenuti

OpenTofu

Strumento open-source per l’infrastructure as code e fork guidato dalla community di Terraform.

ComandoDescrizione
brew install opentofuInstalla su macOS con Homebrew
snap install --classic opentofuInstalla su Linux con Snap
choco install opentofuInstalla su Windows con Chocolatey
curl -fsSL https://get.opentofu.org/install-opentofu.sh | shInstalla tramite script shell
docker run -it ghcr.io/opentofu/opentofuEsegui via Docker
tofu --versionMostra la versione installata
tofu -helpMostra i comandi disponibili
# Usando tofuenv (come tfenv per OpenTofu)
git clone https://github.com/tofuutils/tofuenv.git ~/.tofuenv
echo 'export PATH="$HOME/.tofuenv/bin:$PATH"' >> ~/.bashrc

# Elenca versioni disponibili
tofuenv list-remote

# Installa versione specifica
tofuenv install 1.8.0
tofuenv use 1.8.0

# Fissa la versione nel progetto
echo "1.8.0" > .opentofu-version
ComandoDescrizione
tofu initInizializza la directory di lavoro e scarica i provider
tofu init -upgradeRe-inizializza e aggiorna le versioni di provider/moduli
tofu init -backend-config=prod.hclInizializza con file di configurazione backend
tofu planAnteprima delle modifiche all’infrastruttura
tofu plan -out=plan.tfplanSalva il plan su file per applicazione successiva
tofu plan -target=aws_instance.webPianifica modifiche solo per una risorsa specifica
tofu plan -var-file=prod.tfvarsPianifica con file di variabili specifico
tofu applyApplica le modifiche all’infrastruttura (chiede conferma)
tofu apply -auto-approveApplica le modifiche senza conferma
tofu apply plan.tfplanApplica un file di plan salvato
tofu destroyDistruggi tutta l’infrastruttura gestita
tofu destroy -auto-approveDistruggi senza conferma
tofu destroy -target=aws_instance.webDistruggi solo una risorsa specifica
ComandoDescrizione
tofu validateValida la sintassi della configurazione
tofu fmtFormatta i file di configurazione
tofu fmt -checkControlla se i file sono formattati (adatto per CI)
tofu fmt -recursiveFormatta tutti i file nelle sottodirectory
tofu outputVisualizza tutti i valori di output
tofu output -jsonVisualizza gli output in formato JSON
tofu output db_passwordVisualizza un valore di output specifico
tofu showMostra lo stato corrente in formato leggibile
tofu show -jsonMostra lo stato in formato JSON
tofu show plan.tfplanMostra i dettagli del plan salvato
tofu graphGenera il grafico delle dipendenze delle risorse (formato DOT)
tofu graph | dot -Tpng > graph.pngRenderizza il grafico delle dipendenze come PNG
tofu refreshRiconcilia lo stato con l’infrastruttura reale
tofu providersElenca i provider usati nella configurazione
# Blocco dei provider richiesti
terraform {
  required_version = ">= 1.6.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    random = {
      source  = "hashicorp/random"
      version = "~> 3.5"
    }
  }

  # Crittografia dello stato (funzionalita esclusiva di 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
    }
  }
}

# Provider con configurazione predefinita
provider "aws" {
  region = "us-east-1"

  default_tags {
    tags = {
      ManagedBy   = "opentofu"
      Environment = var.environment
      Project     = var.project_name
    }
  }
}

# Alias del provider per multi-regione
provider "aws" {
  alias  = "west"
  region = "us-west-2"
}

# Usa il provider con alias su una risorsa
resource "aws_instance" "west_server" {
  provider      = aws.west
  ami           = "ami-0abcdef1234567890"
  instance_type = "t3.micro"
}
# Risorsa base
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type

  tags = {
    Name = "${var.project_name}-web"
  }
}

# Data source (leggi risorse esistenti)
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # Canonical

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }
}

# Valori locali
locals {
  common_tags = {
    Project     = var.project_name
    Environment = var.environment
    ManagedBy   = "opentofu"
  }

  az_count = length(data.aws_availability_zones.available.names)
}

# Count e 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
}
ComandoDescrizione
variable "name" { type = string }Definisci una variabile stringa
variable "port" { type = number default = 8080 }Variabile con valore predefinito
variable "tags" { type = map(string) }Variabile mappa
variable "cidrs" { type = list(string) }Variabile lista
variable "config" { type = object({ ... }) }Variabile oggetto con struttura
variable "db_pass" { sensitive = true }Segna la variabile come sensibile (nascosta nell’output)
variable "env" { validation { ... } }Variabile con validazione personalizzata
ComandoDescrizione
tofu plan -var="name=value"Passa variabile via CLI
tofu plan -var-file="prod.tfvars"Passa variabili da file
TF_VAR_name=value tofu planPassa via variabile d’ambiente
Crea il file terraform.tfvarsValori delle variabili caricati automaticamente
Crea il file *.auto.tfvarsFile di variabili caricati automaticamente
# 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
}
ComandoDescrizione
tofu state listElenca tutte le risorse nello stato
tofu state show aws_instance.webMostra dettagli di una risorsa specifica
tofu state mv aws_instance.old aws_instance.newRinomina una risorsa nello stato
tofu state rm aws_instance.webRimuovi una risorsa dallo stato (mantiene l’infrastruttura reale)
tofu state pullRecupera lo stato remoto su stdout
tofu state pushInvia lo stato locale al backend remoto
tofu import aws_instance.web i-1234567Importa una risorsa esistente nello stato
tofu state replace-provider hashicorp/aws registry.opentofu.org/hashicorp/awsSostituisci provider nello stato
tofu force-unlock LOCK_IDRilascia un lock bloccato sullo stato
# Backend S3 (il piu comune)
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 (funziona con GitLab, alternative a 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 locale (predefinito, per sviluppo)
terraform {
  backend "local" {
    path = "terraform.tfstate"
  }
}
# Crittografia dello stato con 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
    }
  }
}
ComandoDescrizione
module "vpc" { source = "./modules/vpc" }Usa un modulo locale
module "vpc" { source = "github.com/org/repo//modules/vpc" }Usa un modulo da GitHub
module "vpc" { source = "registry.opentofu.org/..." }Usa un modulo dal registry OpenTofu
tofu getScarica e aggiorna i moduli
tofu get -updateForza l’aggiornamento di tutti i moduli
modules/
└── vpc/
    ├── main.tf          # Risorse
    ├── variables.tf     # Variabili di input
    ├── outputs.tf       # Valori di output
    ├── versions.tf      # Requisiti del provider
    └── README.md        # Documentazione d'uso
# 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 }

# Utilizzo del modulo
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
}
ComandoDescrizione
Sostituisci terraform con tofu nei comandiI comandi CLI sono compatibili drop-in
tofu init -upgradeRe-inizializza con i provider OpenTofu
Mantieni i file .tf esistenti invariatiLa sintassi HCL e completamente compatibile
Mantieni i file .tfstate esistentiIl formato dello stato e compatibile
Mantieni i file .terraform.lock.hcl esistentiIl file di lock e compatibile
Aggiorna CI/CD per usare l’azione opentofu/setup-opentofuCambia GitHub Actions
Migrazione da Terraform a OpenTofu:

1. Installa OpenTofu accanto a Terraform
2. Nella directory del tuo progetto, esegui: tofu init
3. Esegui: tofu plan
   - Confronta l'output con: terraform plan
   - Dovrebbe essere identico (nessuna modifica)
4. Sostituisci 'terraform' con 'tofu' nelle pipeline CI/CD
5. Aggiorna GitHub Actions:
   - Vecchio: hashicorp/setup-terraform
   - Nuovo: opentofu/setup-opentofu
6. Aggiorna le sorgenti dei provider nel file di lock (se necessario):
   - tofu providers lock
7. Abilita la crittografia dello stato (esclusiva OpenTofu):
   - Aggiungi il blocco encryption al blocco terraform {}
8. Cambia i riferimenti al registry dei moduli:
   - registry.terraform.io → registry.opentofu.org
   - (Entrambi funzionano ancora, ma il registry OpenTofu e preferito)
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 }}

Crittografa i file di stato a riposo — non disponibile in Terraform. Supporta provider di chiavi PBKDF2, AWS KMS, GCP KMS e Azure Key Vault.

A differenza della crittografia lato server di Terraform Cloud, OpenTofu crittografa lo stato prima che lasci la tua macchina, assicurando che nessuna terza parte possa leggere lo stato della tua infrastruttura.

OpenTofu mantiene il proprio registry di provider e moduli su registry.opentofu.org, garantendo la disponibilita a lungo termine indipendentemente dalle decisioni di licenza di HashiCorp.

  1. Usa stato remoto con locking — configura un backend S3 + DynamoDB (o equivalente) per prevenire modifiche concorrenti e abilitare la collaborazione in team.

  2. Abilita la crittografia dello stato — usa la funzionalita esclusiva di crittografia di OpenTofu con KMS o PBKDF2 per proteggere i dati sensibili nei file di stato.

  3. Fissa le versioni dei provider e dei moduli — usa i vincoli ~> (es. ~> 5.0) per consentire aggiornamenti patch prevenendo modifiche che rompono la compatibilita.

  4. Usa workspace o separazione per directory per gli ambienti — mantieni lo stato di dev, staging e produzione separato per prevenire modifiche accidentali tra ambienti.

  5. Scrivi moduli per pattern riutilizzabili — estrai pattern comuni (VPC, database, cluster Kubernetes) in moduli con input e output chiari.

  6. Valida le variabili con vincoli — aggiungi blocchi validation alle variabili per catturare valori non validi prima che raggiungano il provider cloud.

  7. Salva i plan prima di applicarli — usa tofu plan -out=plan.tfplan poi tofu apply plan.tfplan per assicurarti di applicare esattamente cio che hai revisionato.

  8. Usa default_tags sui provider — imposta tag a livello organizzativo (ManagedBy, Environment, Project) a livello di provider per garantire un tagging coerente.

  9. Importa risorse esistenti — usa tofu import per portare l’infrastruttura non gestita sotto il controllo di OpenTofu piuttosto che ricrearla.

  10. Esegui tofu fmt -check in CI — imponi la formattazione coerente tra tutti i membri del team facendo fallire le build CI su file non formattati.

  11. Usa i data source per i riferimenti esterni — fai riferimento all’infrastruttura esistente (AMI, VPC, zone DNS) con blocchi data invece di codificare gli ID.

  12. Revisiona i plan con attenzione — leggi sempre l’output del plan prima di applicare. Presta attenzione alle azioni destroy e replace che potrebbero causare downtime.