Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Infrastructure as Code

Introducció

Tradicionalment, configurar un servidor era un procés manual: connectar-se per SSH, instal·lar paquets, editar fitxers de configuració, reiniciar serveis… Si tot anava bé, el servidor funcionava. Si no, calia repetir el procés — sovint de memòria o seguint unes notes desactualitzades.

Aquest model té problemes greus:

  • Configuration drift: amb el temps, els servidors acumulen canvis manuals que ningú ha documentat. Dos servidors que haurien de ser idèntics acaben sent diferents.
  • Snowflake servers: cada servidor és únic i irreproduïble. Si falla, reconstruir-lo és un projecte en si mateix.
  • Falta de traçabilitat: no hi ha registre de qui va canviar què, ni quan, ni per què.
  • Errors humans: un pas oblidat, una comanda equivocada, un fitxer sobreescrit… i el servei cau.

La Infrastructure as Code (IaC) resol aquests problemes aplicant al món de la infraestructura les mateixes pràctiques que ja utilitzem per al codi: la infraestructura es descriu en fitxers de text, es versiona amb Git, es revisa amb pull requests i s’aplica automàticament mitjançant eines.

A DevOps hem vist que la infraestructura es pot definir com a codi. Aquest document desenvolupa aquesta idea en profunditat.

Els beneficis són directes:

  • Reproduïbilitat: qualsevol persona (o pipeline) pot reconstruir un entorn des de zero executant el codi.
  • Consistència: tots els entorns es configuren de manera idèntica, eliminant el configuration drift.
  • Traçabilitat: cada canvi d’infraestructura queda registrat a l’historial de Git.
  • Col·laboració: els canvis d’infraestructura passen pel mateix flux de revisió que el codi de l’aplicació.

IaC es divideix en dos grans pilars:

  • Aprovisionament (provisioning): crear els recursos — màquines virtuals, xarxes, bases de dades al núvol. L’eina de referència és Terraform.
  • Gestió de configuració (configuration management): configurar el que ja existeix — instal·lar paquets, desplegar aplicacions, gestionar serveis. L’eina de referència és Ansible.

Una analogia: l’aprovisionament és construir la casa; la gestió de configuració és moblar-la i connectar-hi els serveis.

Aquests dos pilars operen a nivell d’infraestructura: servidors, xarxes, sistemes operatius. Però hi ha un segon nivell — el de l’aplicació — on Docker Compose fa un paper equivalent: aprovisiona (crea contenidors, xarxes, volums) i configura (variables d’entorn, ports, dependències) els serveis de l’aplicació dins d’un servidor ja preparat.

NivellAprovisionamentConfiguració
InfraestructuraTerraform crea VMs, xarxesAnsible instal·la paquets, configura serveis
AplicacióCompose crea contenidors, volums, xarxesCompose configura variables, ports, dependències

Un escenari molt habitual combina les tres eines — Terraform, Ansible i Docker Compose — en un flux natural (tot i que no és l’únic possible, com veurem a la secció de patrons d’ús):

Terraform crea les màquines i n’exporta les IPs. Ansible utilitza aquestes IPs per connectar-s’hi, configurar-les i desplegar-hi l’aplicació amb Docker Compose. Tot definit en fitxers, versionat a Git, i executable amb un pipeline.

Conceptes clau

Abans d’entrar a les eines concretes, cal entendre tres conceptes fonamentals que travessen tota la disciplina d’IaC.

Declaratiu vs imperatiu

Hi ha dues maneres de descriure la infraestructura:

  • Imperativa: una seqüència de comandes que s’executen en ordre. Com arribar a l’estat desitjat.

    apt update apt install -y nginx systemctl start nginx systemctl enable nginx
  • Declarativa: una descripció de l’estat final desitjat. Què volem, no com arribar-hi.

    - name: Nginx instal·lat i actiu apt: name: nginx state: present - name: Servei nginx actiu service: name: nginx state: started enabled: true

La majoria d’eines d’IaC (Terraform, Ansible, Docker Compose) segueixen el model declaratiu: l’eina s’encarrega de calcular els passos necessaris per arribar a l’estat descrit.

Idempotència

Una operació és idempotent si executar-la múltiples vegades produeix el mateix resultat que executar-la un cop. Si Ansible detecta que nginx ja està instal·lat, no fa res. Si detecta que falta, l’instal·la.

Això és fonamental perquè permet reexecutar el codi d’infraestructura amb seguretat: si alguna cosa falla a mitges, es pot tornar a executar sense por de duplicar o trencar res.

Infraestructura mutable vs immutable

  • Mutable: els servidors es modifiquen in place. Ansible actualitza paquets, canvia configuracions, reinicia serveis sobre una màquina existent.
  • Immutable: en lloc de modificar un servidor, se’n crea un de nou amb la configuració actualitzada i es destrueix l’antic. Docker segueix aquest model: no es modifiquen contenidors, es recreen.
ConcepteExemple mutableExemple immutable
ActualitzacióAnsible actualitza nginx al servidorEs construeix una nova imatge Docker amb nginx actualitzat
RollbackAnsible reverteix la configuracióEs redesplega la imatge anterior
RiscAcumulació de canvis no previstosCal reconstruir la imatge sencera

A la pràctica, la majoria d’entorns combinen ambdós models: els servidors es gestionen de forma mutable amb Ansible, però les aplicacions s’hi despleguen com a contenidors immutables amb Docker.

Aprovisionament: Terraform

Terraform és l’eina d’aprovisionament per excel·lència. Permet crear i gestionar recursos — màquines virtuals, xarxes, DNS, bases de dades gestionades — mitjançant fitxers de configuració declaratius. Funciona tant amb plataformes de virtualització autogestionades (Proxmox, OpenStack) com amb proveïdors de núvol públic: qualsevol sistema que exposi una API pot tenir un provider de Terraform.

Funcionament bàsic

Terraform utilitza un llenguatge propi anomenat HCL (HashiCorp Configuration Language). La configuració es defineix en fitxers .tf:

provider "proxmox" { endpoint = var.proxmox_url api_token = var.proxmox_token } resource "proxmox_virtual_environment_vm" "web" { name = "web-server" node_name = "pve1" clone { vm_id = 9000 # template Ubuntu 22.04 } cpu { cores = 2 } memory { dedicated = 2048 } network_device { bridge = "vmbr0" } }

Conceptes essencials

  • Providers: connectors que parlen amb les APIs de plataformes externes (Proxmox, OpenStack, proveïdors de núvol, etc.).
  • Resources: els recursos que es creen (proxmox_virtual_environment_vm, openstack_compute_instance, etc.). Terraform analitza les dependències entre recursos i els crea en l’ordre correcte — si una VM necessita una xarxa, primer crea la xarxa.
  • State: Terraform manté un fitxer d’estat (terraform.tfstate) que registra quins recursos existeixen i les seves propietats. Gràcies a aquest registre, Terraform pot calcular la diferència entre l’estat actual i el desitjat, i aplicar només els canvis necessaris — sense tocar el que ja és correcte.
  • Cicle plan/apply: terraform plan mostra els canvis que s’aplicaran sense executar-los, permetent revisar i validar abans d’actuar. Només quan es confirma amb terraform apply s’executen els canvis reals. Això redueix el risc d’errors en producció.
EnfocamentProcés
ManualEntrar al panell web → clicar “Crear servidor” → configurar opcions → repetir per cada recurs
TerraformEscriure .tfterraform plan → revisar → terraform apply → tots els recursos creats

Altres enfocaments d’aprovisionament

Terraform no és l’única manera d’aprovisionar infraestructura. Depenent del context, es poden utilitzar altres enfocaments:

  • Ansible com a aprovisionador: Ansible també pot crear recursos — contenidors LXC, instàncies al núvol (amb mòduls com community.general.lxc_container), etc. Això elimina la necessitat d’una eina addicional, però perd els avantatges de Terraform: el fitxer d’estat, el cicle plan/apply, i la gestió automàtica de dependències entre recursos.
  • Imatges preconfigurades: en lloc de crear una màquina buida i configurar-la després, es pot construir una imatge base (amb Packer, per exemple) que ja inclou el sistema operatiu configurat, Docker instal·lat, i les eines necessàries. Terraform crea instàncies a partir d’aquesta imatge, reduint el treball d’Ansible al mínim. Això s’apropa al model d’infraestructura immutable.

A la secció de Patrons d’ús es veuran exemples concrets d’aquests enfocaments.

Gestió de configuració: Ansible

Ansible és l’eina principal de gestió de configuració. Permet definir l’estat desitjat dels servidors — quins paquets han d’estar instal·lats, quins serveis actius, quins fitxers configurats — i aplicar-lo automàticament.

Què és Ansible

La filosofia d’Ansible es basa en tres principis:

  • Agentless: no cal instal·lar cap programari als servidors gestionats. Ansible es connecta per SSH des d’una màquina de control (el portàtil del desenvolupador o un servidor de CI/CD) i executa les tasques remotament.
  • Declaratiu: es descriu l’estat desitjat, no els passos per arribar-hi.
  • Idempotent: executar el mateix codi dues vegades no canvia res si l’estat ja és el correcte.

Per què Ansible interessa als desenvolupadors? Perquè utilitza YAML — el mateix format que Docker Compose i els pipelines de CI/CD —, no requereix agents, i permet automatitzar el desplegament de les aplicacions que escriuen.

Inventari

L’inventari és la llista de màquines que Ansible gestiona, organitzades en grups. Es defineix en un fitxer YAML:

all: children: webservers: hosts: web1: ansible_host: 192.168.1.10 web2: ansible_host: 192.168.1.11 databases: hosts: db1: ansible_host: 192.168.1.20

Els grups permeten aplicar configuracions diferents a cada tipus de servidor. Les variables específiques de cada grup o host es defineixen en directoris separats:

project/ ├── inventory.yml ├── group_vars/ │ ├── all.yml # Variables per a tots els hosts │ ├── webservers.yml # Variables per als servidors web │ └── databases.yml # Variables per a les bases de dades └── host_vars/ └── web1.yml # Variables específiques de web1

En entorns amb moltes màquines o infraestructura canviant, mantenir l’inventari a mà no escala. Ansible suporta inventaris dinàmics: plugins que consulten l’API de la plataforma i generen la llista de màquines automàticament. Per exemple, amb el plugin de Proxmox, Ansible pot descobrir totes les VMs amb una etiqueta concreta (role: webserver) i afegir-les al grup corresponent. Si Terraform crea o destrueix màquines, l’inventari dinàmic s’actualitza sol a la propera execució.

Quan s’utilitza Terraform per aprovisionar, una opció senzilla és passar les IPs directament a Ansible:

# Terraform genera les IPs, Ansible les consumeix terraform output -json ips | jq -r '.[]' > hosts.txt ansible-playbook -i hosts.txt playbook.yml

Amb inventaris dinàmics, ni tan sols cal aquest pas intermedi: Ansible consulta directament la plataforma.

Playbooks i tasques

Un playbook és un fitxer YAML que descriu un conjunt de tasques a aplicar a un grup de hosts. L’estructura és: plays (quins hosts + quines tasques) i tasks (accions individuals que utilitzen mòduls).

--- - name: Configurar servidor web hosts: webservers become: true tasks: - name: Instal·lar nginx apt: name: nginx state: present update_cache: true - name: Copiar configuració template: src: nginx.conf.j2 dest: /etc/nginx/sites-available/default notify: Reiniciar nginx - name: Activar el servei service: name: nginx state: started enabled: true handlers: - name: Reiniciar nginx service: name: nginx state: restarted

Cada tasca utilitza un mòdul d’Ansible: apt per gestionar paquets, template per generar fitxers, service per gestionar serveis, copy per copiar fitxers, docker_compose per gestionar contenidors, etc.

La idempotència es veu a cada tasca: apt: state=present instal·la nginx només si no està instal·lat. Si ja ho està, Ansible reporta ok i passa a la següent tasca. Executar el playbook dues vegades produeix el mateix resultat.

La sortida d’Ansible mostra l’estat de cada tasca:

  • ok: l’estat ja era el correcte, no s’ha canviat res
  • changed: s’ha aplicat un canvi
  • failed: la tasca ha fallat

Handlers

Els handlers són tasques que només s’executen quan una altra tasca els notifica. El cas d’ús clàssic: reiniciar un servei només quan el seu fitxer de configuració canvia.

Al playbook anterior, la tasca “Copiar configuració” inclou notify: Reiniciar nginx. Si Ansible detecta que el fitxer ha canviat, marca el handler per executar-se. Si el fitxer ja era idèntic, el handler no s’executa.

Característiques importants:

  • Els handlers s’executen un cop al final del play, encara que siguin notificats múltiples vegades.
  • Això evita reinicis innecessaris: si tres tasques modifiquen la configuració de nginx, el servei només es reinicia un cop.

Templates amb Jinja2

Les templates permeten generar fitxers de configuració dinàmicament, substituint variables pels seus valors. Ansible utilitza el motor de templates Jinja2.

Un fitxer nginx.conf.j2:

server { listen {{ http_port | default(80) }}; server_name {{ server_name }}; location / { proxy_pass http://localhost:{{ app_port }}; } }

Quan Ansible processa aquesta template amb les variables http_port: 8080, server_name: example.com i app_port: 3000, genera:

server { listen 8080; server_name example.com; location / { proxy_pass http://localhost:3000; } }

Jinja2 suporta condicionals ({% if %}) i bucles ({% for %}), cosa que permet generar configuracions complexes:

{% for backend in app_backends %} upstream {{ backend.name }} { server {{ backend.host }}:{{ backend.port }}; } {% endfor %}

Variables i jerarquia de precedència

Les variables es poden definir en molts llocs. Ansible aplica una jerarquia de precedència per determinar quin valor s’utilitza quan una variable es defineix en més d’un lloc.

De menys a més prioritat (simplificat):

PrioritatOrigen
1 (més baixa)defaults/main.yml del rol
2Variables d’inventari (group_vars/, host_vars/)
3vars: al playbook
4vars/main.yml del rol
5 (més alta)Línia de comandes (-e "variable=valor")

La bona pràctica és:

  • Valors per defecte als defaults/ dels rols (es poden sobreescriure fàcilment)
  • Configuració per entorn a group_vars/ (producció, staging)
  • Configuració específica per host a host_vars/
  • Sobreescriptures puntuals a la línia de comandes

Rols

Els rols són el mecanisme d’Ansible per organitzar i reutilitzar codi, de manera similar a les funcions o mòduls en programació. Un rol encapsula totes les tasques, handlers, templates i variables necessaris per configurar un component concret.

Estructura estàndard d’un rol:

roles/ └── nginx/ ├── tasks/ │ └── main.yml # Tasques principals ├── handlers/ │ └── main.yml # Handlers ├── templates/ │ └── nginx.conf.j2 # Templates Jinja2 ├── files/ │ └── index.html # Fitxers estàtics ├── defaults/ │ └── main.yml # Variables per defecte └── meta/ └── main.yml # Dependències del rol

Utilitzar un rol al playbook és senzill:

--- - name: Configurar servidor web hosts: webservers become: true roles: - common - nginx

Ansible executa els rols en ordre. Cada rol és independent i autocontingut, cosa que permet reutilitzar-lo en projectes diferents.

Ansible Galaxy és el repositori de la comunitat on es publiquen rols reutilitzables. Per exemple, geerlingguy.docker instal·la i configura Docker — en lloc d’escriure les tasques des de zero, es pot aprofitar el treball de la comunitat.

Altres enfocaments de configuració

Ansible configura servidors de forma mutable: modifica l’estat d’una màquina existent. Però no és l’únic enfocament possible:

  • Contenidors Docker: en lloc de configurar un servidor amb Ansible perquè executi nginx, es desplega un contenidor nginx ja configurat. La configuració es trasllada al Dockerfile i al compose.yml, i el servidor només necessita tenir Docker instal·lat. Això redueix el paper d’Ansible a la preparació del host.
  • Imatges immutables: es construeix una imatge de servidor (amb Packer o eines similars) que ja inclou tot el programari i la configuració. Cada canvi genera una imatge nova. Ansible no intervé en producció — només en la construcció de la imatge.

A la pràctica, molts entorns combinen enfocaments: Ansible configura el servidor base (usuaris, firewall, Docker) i Docker gestiona les aplicacions. La secció de Patrons d’ús mostra exemples concrets d’aquestes combinacions.

Patrons d’ús

Les eines d’IaC es combinen de maneres diferents segons el context. Per situar cada patró, recordem la matriu de la introducció: aprovisionament i configuració operen a dos nivells (infraestructura i aplicació), i cada patró cobreix una combinació diferent d’aquests quatre quadrants.

A nivell d’aplicació, Docker Compose és l’eina dominant i apareix en gairebé tots els patrons. Però no és l’única opció: Ansible pot desplegar aplicacions directament — instal·lar el runtime (Node, Python…), copiar el codi, configurar el servei amb systemd — sense contenidors.

Quan l’aplicació creix més enllà d’un sol servidor, Kubernetes substitueix Compose en aquest nivell. Kubernetes orquestra contenidors a través de múltiples nodes i ofereix funcionalitats que Compose no pot donar: escalat automàtic (ajustar el nombre de rèpliques segons la càrrega), desplegaments sense downtime (rolling updates, canaris), auto-reparació (reiniciar contenidors que fallen, redistribuir càrrega si un node cau), i service discovery integrat entre serveis distribuïts. El preu és una complexitat considerable: Kubernetes requereix un clúster dedicat, un coneixement profund de la seva arquitectura (pods, services, deployments, ingress…) i un esforç operacional significatiu. Per a la majoria d’aplicacions — un servidor o uns pocs — Compose amb Ansible és suficient i molt més senzill. Kubernetes queda fora de l’abast d’aquest document.

Cada patró cobreix una combinació diferent d’aquests quatre quadrants. Els patrons següents van de menys a més complets.

Docker Compose com a IaC d’aplicació

Nivell: aplicació — aprovisionament (crea contenidors, xarxes, volums) i configuració (variables, ports, dependències). No cobreix el nivell d’infraestructura.

Si has treballat amb Docker Compose, ja has fet IaC sense saber-ho. Un fitxer compose.yml és una descripció declarativa de la infraestructura de l’aplicació: quins serveis s’executen, com es connecten entre ells, quines dades persisteixen.

Per als fonaments de Docker i Compose, consulteu Docker bàsic. Aquí ampliem el concepte cap a arquitectures multi-servei més realistes.

Un entorn de desenvolupament típic pot incloure quatre serveis: l’aplicació, una base de dades, un sistema de cache i un reverse proxy:

services: app: build: . environment: - DATABASE_URL=postgresql://db:5432/app - REDIS_URL=redis://cache:6379 depends_on: db: condition: service_healthy cache: condition: service_started db: image: postgres:16 volumes: - db_data:/var/lib/postgresql/data environment: - POSTGRES_DB=app - POSTGRES_PASSWORD=${DB_PASSWORD} healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 3s retries: 5 cache: image: redis:7-alpine proxy: image: nginx:alpine ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf depends_on: - app volumes: db_data:

Aquest fitxer defineix tota la topologia de l’aplicació: serveis, xarxes, volums, dependències i health checks. Està versionat al repositori, qualsevol membre de l’equip pot aixecar l’entorn amb un sol docker compose up, i el resultat és idèntic a tot arreu.

Compose fa d’eina d’IaC al nivell de l’aplicació: aprovisiona (crea contenidors, xarxes, volums) i configura (variables, ports, dependències) els serveis. Però opera dins d’una màquina que ja existeix i ja té Docker instal·lat. No crea la màquina virtual, no configura el firewall, no instal·la el sistema operatiu. Per al nivell d’infraestructura necessitem Terraform i Ansible.

Ansible + Docker

Nivell d’infraestructura: configuració (Ansible instal·la Docker, firewall, certificats). Nivell d’aplicació: aprovisionament i configuració (Compose desplega els serveis).

Un patró molt habitual en entorns de producció és combinar Ansible per configurar el servidor i Docker per desplegar l’aplicació. Ansible s’encarrega de tot el que el contenidor no pot fer per si sol: instal·lar Docker, configurar el firewall, gestionar certificats TLS, crear directoris de dades… I després desplega l’aplicació com a contenidors.

El flux típic és:

  1. Ansible instal·la Docker al servidor (mòdul apt o rol geerlingguy.docker)
  2. Ansible genera el compose.yml a partir d’una template Jinja2, injectant les variables de l’entorn
  3. Ansible executa docker compose up al servidor remot

Exemple d’un playbook que desplega una aplicació amb Docker Compose:

--- - name: Desplegar aplicació hosts: webservers become: true tasks: - name: Instal·lar Docker apt: name: - docker.io - docker-compose-v2 state: present update_cache: true - name: Crear directori de l'aplicació file: path: /opt/app state: directory - name: Generar docker-compose.yml template: src: docker-compose.yml.j2 dest: /opt/app/docker-compose.yml notify: Reiniciar aplicació - name: Generar fitxer d'entorn template: src: env.j2 dest: /opt/app/.env mode: '0600' notify: Reiniciar aplicació handlers: - name: Reiniciar aplicació command: docker compose up -d args: chdir: /opt/app

La template docker-compose.yml.j2 pot ser:

services: app: image: {{ app_image }}:{{ app_version }} env_file: .env ports: - "{{ app_port }}:8080" restart: unless-stopped db: image: postgres:16 volumes: - db_data:/var/lib/postgresql/data env_file: .env restart: unless-stopped volumes: db_data:

Ansible també pot interactuar amb Docker directament mitjançant els mòduls de la col·lecció community.docker: docker_image per gestionar imatges, docker_container per a contenidors individuals, o docker_compose_v2 per a Compose.

L’avantatge d’aquesta combinació és doble:

  • Docker proporciona portabilitat i aïllament de l’aplicació
  • Ansible proporciona gestió de configuració, reproductibilitat i automatització del servidor sencer

Terraform + Ansible + Docker

Nivell d’infraestructura: aprovisionament (Terraform crea VMs, xarxes) i configuració (Ansible instal·la paquets, configura serveis). Nivell d’aplicació: aprovisionament i configuració (Compose desplega els serveis).

És el patró més complet: cobreix els quatre quadrants de la matriu. Cada eina fa el que millor sap fer:

  1. Terraform aprovisiona la infraestructura: crea les VMs, xarxes i recursos necessaris
  2. Ansible configura els servidors: instal·la Docker, configura el firewall, gestiona certificats, crea usuaris
  3. Docker Compose desplega l’aplicació: defineix els serveis, xarxes i volums de l’aplicació

El flux és el que hem vist a la introducció: Terraform crea les màquines i n’exporta les IPs, Ansible s’hi connecta per configurar-les, i finalment hi desplega l’aplicació amb Compose. A la pràctica, els passos 2 i 3 sovint s’executen amb un sol ansible-playbook que inclou tant la configuració del servidor com el desplegament amb Compose.

Aquest patró és habitual en entorns amb infraestructura gestionada (Proxmox, OpenStack, núvol públic) on cal crear i destruir recursos amb freqüència.

Terraform + cloud-init

Nivell d’infraestructura: aprovisionament (Terraform crea VMs) i configuració mínima (cloud-init al primer boot). Nivell d’aplicació: aprovisionament i configuració (Compose desplega els serveis).

En aquest patró, Ansible desapareix del flux. La configuració del servidor es fa amb cloud-init: un estàndard que permet executar scripts i configuració al primer arrencada d’una VM. Terraform passa les instruccions de cloud-init com a part de la definició del recurs:

resource "proxmox_virtual_environment_vm" "web" { name = "web-server" # ... initialization { user_data_file_id = proxmox_virtual_environment_file.cloud_init.id } } resource "proxmox_virtual_environment_file" "cloud_init" { content_type = "snippets" datastore_id = "local" node_name = "pve1" source_raw { data = <<-EOF #cloud-config packages: - docker.io - docker-compose-v2 runcmd: - systemctl enable docker - cd /opt/app && docker compose up -d EOF file_name = "cloud-init.yml" } }

La VM arrenca ja amb Docker instal·lat i l’aplicació en marxa. No cal una connexió SSH posterior ni un pas addicional de configuració.

L’avantatge és la simplicitat: tot queda definit a Terraform, sense una segona eina. El compromís és que cloud-init és menys potent que Ansible — funciona bé per a configuracions senzilles, però es queda curt per a escenaris complexos (múltiples rols, templates, gestió de secrets, configuració condicional). S’apropa al model d’infraestructura immutable: si cal canviar la configuració, es recrea la VM en lloc de modificar-la.

Ansible com a aprovisionador

Nivell d’infraestructura: aprovisionament (Ansible crea VMs, contenidors LXC) i configuració (Ansible instal·la paquets, configura serveis). No cobreix el nivell d’aplicació per si sol, però es pot combinar amb Docker Compose.

Com hem vist a la secció de Terraform, Ansible també pot fer d’aprovisionador: crear recursos directament amb els seus mòduls, sense necessitat d’una eina addicional. Això és especialment útil en entorns autogestionats on Ansible ja gestiona la configuració.

Per exemple, amb contenidors LXC (Linux Containers) — que són com VMs lleugeres amb sistema operatiu complet, init propi i xarxa, però compartint el kernel del host — Ansible pot crear-los i configurar-los en un sol flux. El mateix patró s’aplica a VMs de Proxmox (amb el mòdul community.general.proxmox_kvm) o instàncies d’OpenStack (amb openstack.cloud.server).

El flux és sempre el mateix:

  1. Ansible es connecta a la plataforma (host LXC, API de Proxmox, API d’OpenStack)
  2. Ansible crea els recursos (contenidors, VMs, instàncies) segons les definicions
  3. Ansible configura cada recurs (paquets, serveis, aplicacions) connectant-s’hi per SSH

Exemple amb contenidors LXC — l’inventari defineix tant els contenidors a crear com la seva configuració:

all: children: lxc_host: hosts: server1: ansible_host: 192.168.1.5 containers: - name: web template: ubuntu-22.04 ip: 10.0.3.10 role: webserver - name: db template: ubuntu-22.04 ip: 10.0.3.20 role: database

Un playbook simplificat per crear i configurar els contenidors:

--- - name: Crear contenidors LXC hosts: lxc_host become: true tasks: - name: Crear contenidor community.general.lxc_container: name: "{{ item.name }}" template: "{{ item.template }}" state: started container_config: - "lxc.net.0.ipv4.address = {{ item.ip }}/24" loop: "{{ containers }}" - name: Configurar servidors web hosts: webservers become: true roles: - common - nginx

El compromís d’aquest enfocament és el que hem vist: es perd el fitxer d’estat, el cicle plan/apply i la gestió de dependències que ofereix Terraform. A canvi, es guanya simplicitat — una sola eina per a tot.

Comparació dels patrons:

PatróAprovis. infraConfig. infraAprovis. + config. appCas d’ús típic
ComposeComposeDesenvolupament local, entorns simples
Ansible + DockerAnsibleComposeProducció amb servidor existent
Terraform + Ansible + DockerTerraformAnsibleComposeEntorns al núvol o Proxmox (flux complet)
Terraform + cloud-initTerraformcloud-initComposeInfraestructura immutable, config. senzilla
Ansible aprovisionadorAnsibleAnsible— (o Compose)Entorns autogestionats, simplicitat

Tots els patrons comparteixen els mateixos principis d’IaC: tot definit en fitxers, versionat a Git, i aplicable automàticament.

Gestió de secrets

Els fitxers d’IaC viuen al repositori de Git, però necessiten accés a contrasenyes, claus d’API i certificats. Aquests secrets no poden estar al repositori en text pla — tal com s’explica a DevOps.

Hi ha tres nivells de gestió de secrets, de menys a més sofisticat:

Ansible Vault

Ansible Vault permet encriptar fitxers o variables individuals amb una contrasenya. El fitxer encriptat es pot versionar a Git amb seguretat:

# Encriptar un fitxer de variables ansible-vault encrypt group_vars/production/secrets.yml # Editar secrets sense desencriptar manualment ansible-vault edit group_vars/production/secrets.yml # Executar un playbook amb secrets encriptats ansible-playbook playbook.yml --ask-vault-pass

El fitxer secrets.yml abans d’encriptar:

db_password: "s3cur3_p4ssw0rd" api_key: "ak_live_xxxxxxxxxxxx" tls_certificate: | -----BEGIN CERTIFICATE----- ...

Un cop encriptat, Git veu un fitxer binari que només es pot desxifrar amb la contrasenya del vault. Ansible el desencripta automàticament en temps d’execució i injecta les variables a les tasques i templates.

El flux pràctic és:

  1. El fitxer encriptat viu al repositori (segur)
  2. El desenvolupador o el pipeline de CI/CD té la contrasenya del vault
  3. Ansible desencripta en runtime → injecta valors a templates/variables
  4. Mai es guarden secrets en text pla al disc del servidor

Variables de CI/CD

Les plataformes de CI/CD (GitLab CI, GitHub Actions) ofereixen magatzems de secrets integrats. Els secrets es configuren a la interfície de la plataforma i s’injecten com a variables d’entorn durant l’execució del pipeline:

# .gitlab-ci.yml deploy: script: - ansible-playbook playbook.yml --extra-vars "db_password=$DB_PASSWORD" variables: ANSIBLE_VAULT_PASSWORD: $VAULT_PASSWORD

Això permet separar els secrets del codi completament: el repositori no conté cap secret, ni tan sols encriptat.

Gestors externs

Per a entorns més complexos, existeixen gestors de secrets dedicats com HashiCorp Vault. Ansible pot consultar-los directament amb plugins de lookup. Això és habitual en organitzacions grans, però queda fora de l’abast d’aquesta introducció.

Integració amb CI/CD

IaC tanca el cercle del pipeline de DevOps: els fitxers d’infraestructura viuen al repositori, es revisen amb pull requests i s’apliquen automàticament.

El pipeline d’infraestructura segueix el mateix patró que el de codi:

git push → validar → dry-run → [aprovació] → aplicar

En detall:

  1. Validar: comprovar la sintaxi (ansible-lint, terraform validate)
  2. Dry-run: simular l’execució sense aplicar canvis (terraform plan, ansible-playbook --check)
  3. Aprovació manual: per a producció, una persona revisa els canvis proposats
  4. Aplicar: executar els canvis (terraform apply, ansible-playbook)

El mecanisme que dispara aquest pipeline és un hook de Git, tal com s’explica a DevOps. Quan algú fa git push, un hook post-receive al servidor executa els passos del pipeline. Això funciona amb qualsevol servidor Git — des d’un repositori autogestionat amb Gitolite fins a plataformes com GitLab.

Amb hooks de Git

En un entorn autogestionat (per exemple, Gitolite), el hook post-receive pot executar directament Ansible:

#!/bin/bash # hooks/post-receive while read oldrev newrev ref; do if [ "$ref" = "refs/heads/main" ]; then echo "Desplegant infraestructura..." ansible-lint playbook.yml \ && ansible-playbook -i inventory/staging.yml playbook.yml fi done

Això és tot el que cal per tenir un pipeline bàsic: un git push a main valida i desplega automàticament. Per a producció, es pot afegir un pas manual (per exemple, un push a una branca production o una etiqueta).

Amb plataformes de CI/CD

Plataformes com GitLab CI o Jenkins afegeixen una capa d’abstracció sobre els hooks: en lloc d’escriure scripts bash, es defineix el pipeline en un fitxer YAML amb etapes, condicions i aprovacions manuals. L’avantatge respecte als hooks manuals és la interfície visual, la gestió de secrets integrada i les eines de monitorització. Però el concepte subjacent és el mateix: un event de Git que dispara una seqüència automatitzada.

Referències