Salta al contenuto principale

05 - Infrastructure as Code Patterns: Terraform, Pulumi e Policy as Code

Infrastructure as Code: Il Fondamento della Piattaforma L' Infrastructure as Code (IaC) e il principio secondo cui l'infrastruttura viene definita,...

Cosa ne pensi di questo articolo?

05 - Infrastructure as Code Patterns: Terraform, Pulumi e Policy as Code
Platform EngineeringFoto:panumas nikhomkhaiPexels

In Breve

Infrastructure as Code: Il Fondamento della Piattaforma L' Infrastructure as Code (IaC) e il principio secondo cui l'infrastruttura viene definita,...

Infrastructure as Code: Il Fondamento della Piattaforma

L'Infrastructure as Code (IaC) e il principio secondo cui l'infrastruttura viene definita, versionata e gestita attraverso codice dichiarativo o imperativo, invece che tramite configurazioni manuali. Nel contesto del Platform Engineering, IaC non e solo una best practice: e un requisito fondamentale per costruire una IDP che sia ripetibile, scalabile e auditabile.

In questo articolo confronteremo i due approcci dominanti - Terraform (dichiarativo) e Pulumi (imperativo con linguaggi general-purpose) - e esploreremo come implementare Policy as Code con OPA e Sentinel per garantire compliance e sicurezza automatizzata.

Cosa Imparerai

  • Paradigmi IaC: dichiarativo (Terraform/HCL) vs imperativo (Pulumi/TypeScript)
  • Gestione dello state: remote state, locking, disaster recovery
  • Moduli Terraform riutilizzabili e composizione
  • Pulumi: IaC con TypeScript, Python e Go
  • Policy as Code: OPA/Rego per compliance e Sentinel per Terraform Cloud
  • Strategie di migrazione verso IaC per infrastruttura esistente

Terraform: L'Approccio Dichiarativo

Terraform di HashiCorp e lo strumento IaC più diffuso al mondo. Utilizza un linguaggio dichiarativo chiamato HCL (HashiCorp Configuration Language) per descrivere lo stato desiderato dell'infrastruttura. Terraform confronta lo stato desiderato con lo stato attuale e calcola un piano di esecuzione per raggiungere la configurazione target.

I punti di forza di Terraform sono:

  • Provider ecosystem: supporta centinaia di provider cloud e SaaS (AWS, Azure, GCP, Kubernetes, GitHub, Datadog...)
  • Plan before apply: mostra le modifiche prima di eseguirle, riducendo il rischio di errori
  • State management: traccia lo stato reale dell'infrastruttura per gestire aggiornamenti incrementali
  • Moduli: componenti riutilizzabili che incapsulano configurazioni complesse

# Terraform: modulo riutilizzabile per microservice infrastructure
# modules/microservice-infra/main.tf

variable "service_name" {
  type        = string
  description = "Nome del microservizio"
}

variable "environment" {
  type        = string
  description = "Environment (dev, staging, production)"
}

variable "cpu_limit" {
  type    = string
  default = "500m"
}

variable "memory_limit" {
  type    = string
  default = "512Mi"
}

# Kubernetes namespace con labels standard
resource "kubernetes_namespace" "service" {
  metadata {
    name = "${var.service_name}-${var.environment}"
    labels = {
      "app.kubernetes.io/name"       = var.service_name
      "app.kubernetes.io/env"        = var.environment
      "platform.company.io/managed"  = "true"
    }
  }
}

# Database PostgreSQL (RDS)
resource "aws_db_instance" "database" {
  identifier     = "${var.service_name}-${var.environment}"
  engine         = "postgres"
  engine_version = "15.4"
  instance_class = var.environment == "production" ? "db.r6g.large" : "db.t3.micro"

  allocated_storage     = 20
  max_allocated_storage = var.environment == "production" ? 100 : 50
  storage_encrypted     = true

  db_name  = replace(var.service_name, "-", "_")
  username = "app_user"
  password = random_password.db_password.result

  backup_retention_period = var.environment == "production" ? 30 : 7
  deletion_protection     = var.environment == "production"

  tags = {
    Service     = var.service_name
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

# Redis cache (ElastiCache)
resource "aws_elasticache_cluster" "cache" {
  cluster_id           = "${var.service_name}-${var.environment}"
  engine               = "redis"
  node_type            = var.environment == "production" ? "cache.r6g.large" : "cache.t3.micro"
  num_cache_nodes      = 1
  parameter_group_name = "default.redis7"
  port                 = 6379
}

output "namespace" {
  value = kubernetes_namespace.service.metadata[0].name
}

output "database_endpoint" {
  value = aws_db_instance.database.endpoint
}

output "cache_endpoint" {
  value = aws_elasticache_cluster.cache.cache_nodes[0].address
}
      

Pulumi: IaC con Linguaggi General-Purpose

Pulumi adotta un approccio diverso: invece di un linguaggio dedicato (come HCL), permette di definire l'infrastruttura usando linguaggi di programmazione standard come TypeScript, Python, Go, C# e Java. Questo significa avere accesso a costrutti come loop, condizionali, funzioni, classi e l'intero ecosistema di librerie del linguaggio scelto.

I vantaggi di Pulumi sono:

  • Linguaggi familiari: non e necessario imparare un nuovo linguaggio; si usa quello che il team già conosce
  • Testing nativo: unit test e integration test dell'infrastruttura con framework di test standard
  • Abstrazioni potenti: classi, interfacce e composizione per creare componenti IaC complessi
  • IDE support: autocomplete, type checking e refactoring con i tool del linguaggio

// Pulumi: componente riutilizzabile in TypeScript
// microservice-infra.ts

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as k8s from "@pulumi/kubernetes";

interface MicroserviceInfraArgs {
  serviceName: string;
  environment: string;
  cpuLimit?: string;
  memoryLimit?: string;
  enableDatabase?: boolean;
  enableCache?: boolean;
}

export class MicroserviceInfra extends pulumi.ComponentResource {
  public readonly namespace: k8s.core.v1.Namespace;
  public readonly databaseEndpoint?: pulumi.Output<string>;
  public readonly cacheEndpoint?: pulumi.Output<string>;

  constructor(
    name: string,
    args: MicroserviceInfraArgs,
    opts?: pulumi.ComponentResourceOptions
  ) {
    super("company:platform:MicroserviceInfra", name, {}, opts);

    const isProd = args.environment === "production";

    // Kubernetes namespace
    this.namespace = new k8s.core.v1.Namespace(
      `${args.serviceName}-ns`,
      {
        metadata: {
          name: `${args.serviceName}-${args.environment}`,
          labels: {
            "app.kubernetes.io/name": args.serviceName,
            "app.kubernetes.io/env": args.environment,
            "platform.company.io/managed": "true",
          },
        },
      },
      { parent: this }
    );

    // Database condizionale
    if (args.enableDatabase !== false) {
      const db = new aws.rds.Instance(
        `${args.serviceName}-db`,
        {
          identifier: `${args.serviceName}-${args.environment}`,
          engine: "postgres",
          engineVersion: "15.4",
          instanceClass: isProd ? "db.r6g.large" : "db.t3.micro",
          allocatedStorage: 20,
          storageEncrypted: true,
          deletionProtection: isProd,
        },
        { parent: this }
      );
      this.databaseEndpoint = db.endpoint;
    }

    this.registerOutputs({
      namespaceName: this.namespace.metadata.name,
    });
  }
}
      

State Management

La gestione dello state e uno degli aspetti più critici dell'IaC. Lo state file contiene la mappatura tra le risorse definite nel codice e le risorse reali nel cloud. Una gestione errata dello state può portare a drift, conflitti e persino alla perdita di risorse.

  • Remote state: lo state deve essere salvato in un backend remoto (S3, GCS, Azure Blob, Terraform Cloud) per essere condiviso tra i membri del team
  • State locking: meccanismo di locking (DynamoDB per AWS, GCS per GCP) per prevenire modifiche concorrenti
  • State encryption: lo state contiene informazioni sensibili e deve essere crittografato at rest e in transit
  • Disaster recovery: backup regolari dello state con versioning abilitato sul bucket

Regola Fondamentale

Non committare mai lo state file nel repository Git. Lo state contiene informazioni sensibili (password, connection strings, chiavi) e deve essere gestito esclusivamente tramite backend remoto con encryption e access control appropriati.

Policy as Code con OPA e Sentinel

Policy as Code e il principio di definire le policy organizzative come codice versionato, testabile e automaticamente enforceable. Nel contesto IaC, le policy verificano che l'infrastruttura definita rispetti requisiti di sicurezza, compliance e costi prima del deployment.

  • OPA (Open Policy Agent): engine di policy general-purpose con linguaggio Rego. Può validare Terraform plans, Kubernetes manifests, API requests
  • Sentinel: framework di policy di HashiCorp integrato in Terraform Cloud/Enterprise. Usa un linguaggio dedicato ottimizzato per policy infrastrutturali
  • Kyverno: engine di policy nativo Kubernetes che usa YAML per definire le policy (più semplice di OPA per use case K8s-specifici)

# OPA/Rego: policy per validare Terraform plans
# policy/terraform/mandatory_tags.rego

package terraform.mandatory_tags

import rego.v1

# Tutti i resource devono avere i tag obbligatori
mandatory_tags := ["Environment", "Service", "ManagedBy", "Team"]

# Trova risorse senza tag obbligatori
deny contains msg if {
  resource := input.planned_values.root_module.resources[_]
  tags := object.get(resource.values, "tags", {})
  required_tag := mandatory_tags[_]
  not tags[required_tag]
  msg := sprintf(
    "Resource '%s' manca il tag obbligatorio '%s'",
    [resource.address, required_tag]
  )
}

# Database in produzione deve avere encryption abilitata
deny contains msg if {
  resource := input.planned_values.root_module.resources[_]
  resource.type == "aws_db_instance"
  resource.values.storage_encrypted != true
  msg := sprintf(
    "Database '%s' deve avere storage_encrypted = true",
    [resource.address]
  )
}

# Nessuna risorsa pubblica senza approvazione
deny contains msg if {
  resource := input.planned_values.root_module.resources[_]
  resource.type == "aws_s3_bucket"
  acl := object.get(resource.values, "acl", "private")
  acl == "public-read"
  msg := sprintf(
    "S3 bucket '%s' non può essere pubblico senza approvazione",
    [resource.address]
  )
}
      

GitOps e PR-Based Workflows

L'integrazione dell'IaC con workflow GitOps e essenziale per una IDP matura. Il pattern più efficace e il PR-based workflow: ogni modifica all'infrastruttura avviene tramite una Pull Request che viene validata automaticamente prima del merge.

  • Atlantis: tool che automatizza il workflow Terraform su Pull Request (plan automatico, apply dopo approvazione)
  • Spacelift: piattaforma SaaS per gestione IaC con policy, drift detection e module registry
  • Env0: piattaforma che aggiunge governance, costi e TTL agli ambienti IaC

Il vantaggio del PR-based workflow e la traceability completa: ogni modifica all'infrastruttura e documentata in una PR con review, commenti e approvazione. Questo soddisfa requisiti di audit e compliance in modo naturale.

Strategie di Migrazione

Migrare un'infrastruttura esistente (creata manualmente o con script ad-hoc) verso IaC e un processo che richiede pianificazione e gradualità:

  • Fase 1 - Import: importare le risorse esistenti nello state Terraform senza modificarle
  • Fase 2 - Codify: scrivere il codice IaC che descrive le risorse importate, verificando che il plan non mostri modifiche
  • Fase 3 - Standardize: refactoring progressivo verso moduli condivisi e naming conventions
  • Fase 4 - Automate: integrazione con CI/CD per apply automatico e policy enforcement

Consiglio per la Migrazione

Non cercare di migrare tutta l'infrastruttura in un colpo solo. Parti dai componenti più semplici e a basso rischio (bucket S3, DNS records, IAM policies), acquisisci esperienza e confidenza, poi procedi con le risorse più critiche (database, cluster Kubernetes, networking).

Domande frequenti

Qual è il fondamento della piattaforma di Infrastructure as Code?

L'Infrastructure as Code (IaC) è il principio secondo cui l'infrastruttura viene definita, versionata e gestita attraverso codice dichiarativo o imperativo.

Quali sono i due approcci dominanti per implementare l'IaC?

I due approcci dominanti sono Terraform (dichiarativo) e Pulumi (imperativo con linguaggi general-purpose).

Perché è importante non committare lo stato file nel repository Git?

Lo stato file contiene informazioni sensibili e deve essere gestito esclusivamente tramite backend remoto con encryption e access control appropriati.

Quali sono i vantaggi del PR-based workflow in un'IDP matura?

I vantaggi includono la traceability completa, ovvero ogni modifica all'infrastruttura è documentata in una Pull Request con review, commenti e approvazione.

Fonti e approfondimenti

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti