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.

Commenti
Caricamento commenti...
Accedi per commentare