Salta el contingut

El Llenguatge HCL: Declarant Infraestructura

3. El Llenguatge HCL: Declarant Infraestructura

HCL (HashiCorp Configuration Language) és el llenguatge que utilitza Terraform. És un llenguatge declaratiu dissenyat per ser llegible per humans i fàcil d'escriure. Vegem els seus components principals.

Sintaxi Bàsica: Blocks, Arguments i Expressions

HCL es compon principalment de blocks (blocs). Un bloc té un tipus, zero o més etiquetes, i un cos que conté arguments i altres blocs niats:

block_type "label1" "label2" {
  argument1 = value1
  argument2 = value2

  nested_block {
    nested_argument = nested_value
  }
}

Els tipus de blocs més comuns que utilitzaràs són:

  • terraform: Configuració de Terraform mateix
  • provider: Configuració de providers
  • resource: Definició de recursos a crear
  • data: Consulta d'informació existent
  • variable: Declaració de variables d'entrada
  • output: Definició de valors de sortida
  • module: Crida a mòduls reutilitzables
  • locals: Definició de valors locals (com variables internes)

Variables: Parametritzant la Configuració

Les variables permeten parametritzar la teva configuració, fent-la reutilitzable en diferents entorns. Declares variables així:

variable "environment" {
  description = "L'entorn on desplegar (dev, staging, production)"
  type        = string
  default     = "dev"
}

variable "instance_count" {
  description = "Nombre d'instàncies a crear"
  type        = number
  default     = 1
}

variable "enable_monitoring" {
  description = "Si s'ha d'activar la monitorització"
  type        = bool
  default     = false
}

variable "allowed_ips" {
  description = "IPs permeses per SSH"
  type        = list(string)
  default     = []
}

variable "tags" {
  description = "Tags comuns per a tots els recursos"
  type        = map(string)
  default     = {
    Managed_by = "Terraform"
  }
}

Pots utilitzar les variables en la teva configuració amb la sintaxi var.nom_variable:

resource "aws_instance" "server" {
  count         = var.instance_count
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = var.environment == "production" ? "t3.medium" : "t3.micro"

  tags = merge(var.tags, {
    Environment = var.environment
    Name        = "server-${var.environment}-${count.index + 1}"
  })
}

Pots passar valors a les variables de diverses maneres:

  1. Fitxer terraform.tfvars:

    environment      = "production"
    instance_count   = 3
    enable_monitoring = true
    allowed_ips      = ["203.0.113.0/24", "198.51.100.0/24"]
    

  2. Línia de comandes:

    terraform apply -var="environment=production" -var="instance_count=3"
    

  3. Variables d'entorn:

    export TF_VAR_environment="production"
    export TF_VAR_instance_count=3
    terraform apply
    

Data Sources: Consultant Informació Existent

Els data sources et permeten consultar informació que ja existeix, sigui en el teu cloud provider o en altres llocs:

# Obtenir l'AMI més recent d'Ubuntu
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]  # Canonical

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

# Obtenir la VPC per defecte
data "aws_vpc" "default" {
  default = true
}

# Obtenir les zones de disponibilitat
data "aws_availability_zones" "available" {
  state = "available"
}

# Utilitzar les dades
resource "aws_instance" "server" {
  ami               = data.aws_ami.ubuntu.id
  instance_type     = "t3.micro"
  availability_zone = data.aws_availability_zones.available.names[0]
}

Els data sources són extremadament útils perquè et permeten fer la teva configuració més flexible i adaptable. Per exemple, en lloc d'hardcodejar l'ID d'una AMI (que canvia amb cada regió i amb cada actualització), pots cercar dinàmicament la AMI més recent que compleixi certs criteris.

Outputs: Exportant Informació

Els outputs et permeten exportar informació sobre la infraestructura creada:

output "server_public_ip" {
  description = "IP pública del servidor"
  value       = aws_instance.server.public_ip
}

output "server_private_ip" {
  description = "IP privada del servidor"
  value       = aws_instance.server.private_ip
  sensitive   = false
}

output "database_endpoint" {
  description = "Endpoint de la base de dades"
  value       = aws_db_instance.main.endpoint
  sensitive   = true
}

Després d'executar terraform apply, pots veure els outputs:

$ terraform output
server_public_ip = "203.0.113.45"
server_private_ip = "10.0.1.23"
database_endpoint = <sensitive>

$ terraform output database_endpoint
mysql-instance.abc123.eu-west-1.rds.amazonaws.com:3306

Els outputs són útils per: - Veure informació important després de crear recursos - Passar informació entre diferents configuracions de Terraform - Integrar amb altres eines (per exemple, passar IPs a Ansible)

Locals: Variables Intermèdies

Els locals són com variables, però les calcules dins de la configuració en lloc de passar-les des de fora:

locals {
  # Combinar nom del projecte i entorn
  name_prefix = "${var.project_name}-${var.environment}"

  # Tags comuns per a tots els recursos
  common_tags = {
    Project     = var.project_name
    Environment = var.environment
    ManagedBy   = "Terraform"
    Owner       = var.team_email
  }

  # Calcular la mida de la base de dades segons l'entorn
  db_instance_class = var.environment == "production" ? "db.t3.large" : "db.t3.micro"

  # Determinar si s'ha d'activar multi-AZ
  db_multi_az = var.environment == "production" ? true : false
}

resource "aws_instance" "server" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"

  tags = merge(local.common_tags, {
    Name = "${local.name_prefix}-server"
  })
}

resource "aws_db_instance" "main" {
  instance_class = local.db_instance_class
  multi_az       = local.db_multi_az

  tags = merge(local.common_tags, {
    Name = "${local.name_prefix}-database"
  })
}

Els locals són ideals per evitar repetició en el teu codi i per fer càlculs que no vols exposar com variables d'entrada.

Expressions i Funcions: Lògica en HCL

HCL suporta expressions i funcions que et permeten fer la teva configuració dinàmica i intel·ligent.

Expressions condicionals:

instance_type = var.environment == "production" ? "t3.large" : "t3.micro"

Bucles amb for_each:

variable "users" {
  default = {
    alice = { role = "admin" }
    bob   = { role = "developer" }
    carol = { role = "developer" }
  }
}

resource "aws_iam_user" "users" {
  for_each = var.users
  name     = each.key

  tags = {
    Role = each.value.role
  }
}

Bucles amb count:

resource "aws_instance" "server" {
  count         = 3
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"

  tags = {
    Name = "server-${count.index + 1}"
  }
}

Funcions útils:

HCL inclou desenes de funcions integrades. Algunes de les més útils:

# Funcions de strings
upper("hello")                    # "HELLO"
lower("WORLD")                    # "world"
format("server-%02d", 5)          # "server-05"
join(", ", ["a", "b", "c"])       # "a, b, c"
split(",", "a,b,c")               # ["a", "b", "c"]

# Funcions de col·leccions
length([1, 2, 3])                 # 3
concat([1, 2], [3, 4])            # [1, 2, 3, 4]
merge({a=1}, {b=2})               # {a=1, b=2}
keys({a=1, b=2})                  # ["a", "b"]
values({a=1, b=2})                # [1, 2]

# Funcions de xarxa
cidrsubnet("10.0.0.0/16", 8, 1)   # "10.0.1.0/24"
cidrhost("10.0.1.0/24", 5)        # "10.0.1.5"

# Funcions de fitxers
file("path/to/file.txt")          # Llegir un fitxer
templatefile("path/to/template", vars) # Processar un template

# Funcions de codificació
base64encode("hello")             # "aGVsbG8="
jsonencode({a = 1, b = 2})        # '{"a":1,"b":2}'
yamlencode({a = 1, b = 2})        # "a: 1\nb: 2\n"

Referència oficial: La documentació completa de funcions està disponible a: https://www.terraform.io/language/functions

Dynamic Blocks: Configuració Repetitiva

Quan necessites crear múltiples blocs similars dins d'un recurs, pots utilitzar dynamic blocks:

variable "ingress_rules" {
  default = [
    { port = 22, protocol = "tcp", cidr = "203.0.113.0/24" },
    { port = 80, protocol = "tcp", cidr = "0.0.0.0/0" },
    { port = 443, protocol = "tcp", cidr = "0.0.0.0/0" },
  ]
}

resource "aws_security_group" "web" {
  name = "web-sg"

  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      from_port   = ingress.value.port
      to_port     = ingress.value.port
      protocol    = ingress.value.protocol
      cidr_blocks = [ingress.value.cidr]
    }
  }
}

Això és molt més net que haver de repetir el bloc ingress múltiples vegades.