El curs fa quatre mòduls que troba a faltar el mateix. Al Mòdul 4, el release 3.3.0 no va arrencar i desplegar.sh va fer una reversió automàtica —correcte— però ningú no va arribar a diagnosticar per què no arrencava, perquè no hi havia on reproduir-ho. A 07-01 vas practicar la recuperació de l'arrencada trencant el fstab del servidor real, amb instantània prèvia i creuant els dits. A 07-03, l'últim exercici demanava buidar la memòria cau de pàgina, treure l'ionice i aturar l'aplicació per mesurar el planificador d'E/S: tres coses que no es fan en producció. I va quedar pendent comprovar si la CPU exposa AES-NI a l'hoste, que és un canvi de configuració de la màquina virtual.

Tot això apunta a la mateixa mancança: falta un entorn de proves. Aquesta lliçó el construeix, i pel camí t'ensenya la pila de virtualització des de dins — que és el que farà que la lliçó següent, contenidors, s'entengui per contrast i no per analogia equivocada.

Contingut

  1. Què és virtualitzar: els tres models
  2. Hipervisors de tipus 1 i de tipus 2
  3. Virtualització davant de contenidors
  4. KVM, QEMU i libvirt: qui fa què
  5. Instal·lació i comprovació del suport
  6. virsh: gestionar màquines des de la línia d'ordres
  7. Crear una màquina amb virt-install
  8. Aprovisionament desatès amb cloud-init
  9. Emmagatzematge: formats, pools i instantànies
  10. Xarxa: NAT, aïllada i pont
  11. Rendiment: virtio i configuració de CPU
  12. Clonar i crear srv-tramontana-proves

Què és virtualitzar: els tres models

Virtualitzar és executar un sistema operatiu complet dins d'un altre, fent-li creure que té el seu propi maquinari. El problema tècnic de fons: certes instruccions de la CPU són privilegiades i només les pot executar el nucli. Si el sistema hoste es pensa que és el nucli i n'executa una, cal interceptar-la i fer-hi alguna cosa. Les tres maneres de resoldre-ho defineixen els tres models:

Model Com maneja les instruccions privilegiades Rendiment Requereix modificar l'hoste
Emulació Tradueix cada instrucció per programari 10-100 vegades més lent No
Paravirtualització L'hoste sap que està virtualitzat i demana els serveis a l'hipervisor per una API 90-95 % del natiu Sí
Assistida per maquinari La CPU té un mode específic (VT-x/AMD-V) que les intercepta tota sola 95-99 % del natiu No

L'emulació encara té el seu ús: és el que permet executar ARM sobre x86 (qemu-system-aarch64) o un sistema de 1985. Per virtualitzar Linux sobre Linux a la mateixa arquitectura, és absurdament lenta.

La paravirtualització va ser la tècnica dominant abans del 2006 (Xen la va popularitzar) i exigia un nucli modificat. Avui sobreviu en una cosa molt més important del que sembla: els controladors paravirtualitzats, virtio, que veuràs més endavant i que són la diferència entre una VM lenta i una de ràpida.

La virtualització assistida per maquinari és la que fa servir srv-tramontana i la que es fa servir arreu avui. Intel l'anomena VT-x i AMD, AMD-V; totes dues afegeixen un nivell de privilegi per sota del nucli on viu l'hipervisor.

Hipervisors de tipus 1 i de tipus 2

Tipus 1 (natiu) Tipus 2 (allotjat)
On corre Directament sobre el maquinari Com a aplicació sobre un SO
Exemples VMware ESXi, Xen, Hyper-V VirtualBox, VMware Workstation
Rendiment Major: sense SO intermedi Menor, encara que poc amb VT-x
Ús típic Centres de dades, núvol Escriptori, desenvolupament, laboratori

I KVM desafia aquella classificació, que és l'interessant: és un mòdul del nucli de Linux, així que tècnicament hi ha un sistema operatiu a sota (tipus 2), però aquell sistema operatiu es converteix en l'hipervisor en carregar-lo, amb accés directe a les extensions de virtualització (tipus 1). La resposta honesta és que és un híbrid, i a la pràctica rendeix com un tipus 1.

El teu portàtil està executant VirtualBox, que és de tipus 2. srv-tramontana és una VM a dins seu. I en aquesta lliçó crearàs una VM dins de srv-tramontana, cosa que s'anomena virtualització imbricada i requereix que VirtualBox exposi les extensions de CPU a l'hoste. Hi tornarem a l'apartat d'instal·lació.

Virtualització davant de contenidors

Aquesta comparació és el pont cap a la lliçó següent, i convé veure-la abans de tocar contenidors per no adquirir l'analogia equivocada:

graph TB
    subgraph VM["Maquina virtual"]
        H1["Maquinari fisic"] --> K1["Nucli amfitrio + KVM"]
        K1 --> Q1["QEMU"] & Q2["QEMU"]
        Q1 --> KG1["Nucli hoste 1"] --> A1["Biblioteques + app"]
        Q2 --> KG2["Nucli hoste 2"] --> A2["Biblioteques + app"]
    end
    subgraph CT["Contenidors"]
        H2["Maquinari fisic"] --> K2["Nucli amfitrio UNIC"]
        K2 --> N1["namespace + cgroup"] --> B1["Biblioteques + app"]
        K2 --> N2["namespace + cgroup"] --> B2["Biblioteques + app"]
    end

La diferència estructural és en una sola fila del diagrama: cada VM té el seu propi nucli; els contenidors comparteixen el de l'amfitrió. D'aquí se'n deriven totes les altres diferències:

Màquina virtual Contenidor
Nucli Propi Compartit amb l'amfitrió
Arrencada 10-60 segons Mil·lisegons
Pes al disc GB (sistema complet) MB (només l'aplicació i les seves biblioteques)
Sobrecàrrega de memòria 200 MB - 1 GB per VM Pràcticament nul·la
Aïllament Fort: frontera de maquinari virtual Més feble: frontera de nucli
Pot executar un altre SO Sí (Windows, BSD, un altre nucli Linux) No: només Linux, i el de l'amfitrió
Densitat típica Desenes per amfitrió Centenars o milers

I les dues conseqüències que cal retenir:

  • Per a aïllament de seguretat, la VM és superior. Una fuga de contenidor és una fallada del nucli compartit i dona accés a l'amfitrió. Una fuga de VM requereix vulnerar l'hipervisor, que és una superfície molt menor. Per això els proveïdors de núvol executen càrregues de clients diferents en VM separades, no en contenidors del mateix nucli.
  • Per a densitat i velocitat, el contenidor guanya per ordres de magnitud. Arrencar cent contenidors és qüestió de segons; cent VM són cent nuclis.

No són alternatives: es combinen. L'habitual avui és executar contenidors dins de màquines virtuals, aprofitant l'aïllament de la VM entre inquilins i la densitat del contenidor dins de cadascuna.

KVM, QEMU i libvirt: qui fa què

Tres peces que es confonen constantment. Cadascuna resol un problema diferent:

Peça Què és Què resol
KVM Un mòdul del nucli (/dev/kvm) Donar accés a VT-x/AMD-V: executar la CPU virtual a velocitat nativa
QEMU Un programa d'espai d'usuari Emular tota la resta: disc, xarxa, teclat, gràfics, BIOS
libvirt Un dimoni i una biblioteca Gestió: definir, arrencar, aturar, xarxa, emmagatzematge, API estable

La divisió de feina entre les dues primeres és la clau: KVM s'encarrega de la CPU i la memòria, que és on el rendiment importa; QEMU emula els dispositius, als quals s'accedeix molt menys. Sense KVM, QEMU emularia també la CPU i tindries el model lent del primer apartat. La combinació se sol escriure «QEMU/KVM».

I libvirt aporta una cosa que no és òbvia fins que gestiones més de dues màquines: una capa d'abstracció estable. La línia d'ordres de QEMU és llarguíssima i canvia entre versions; libvirt defineix la màquina en XML, i virsh parla sempre igual. A més gestiona xarxes virtuals, pools d'emmagatzematge i permisos, i és l'API que fan servir virt-manager, Vagrant, OpenStack i Terraform.

Instal·lació i comprovació del suport

Primer, comprovar que hi ha suport de maquinari. I aquí apareix la complicació de la virtualització imbricada:

$ sudo apt install cpu-checker
$ kvm-ok
INFO: /dev/kvm does not exist
HINT:   sudo modprobe kvm_intel
INFO: Your CPU supports KVM extensions
INFO: KVM (vmx) is disabled by your BIOS

srv-tramontana és una VM de VirtualBox, i per defecte VirtualBox no exposa les extensions de virtualització a l'hoste. Cal activar-ho des de l'amfitrió, amb la màquina apagada:

# Al portatil amfitrio, amb srv-tramontana APAGADA
$ VBoxManage modifyvm srv-tramontana --nested-hw-virt on
$ VBoxManage showvm srv-tramontana --machinereadable | grep -i nested
nestedHWVirt="on"

A VMware l'opció equivalent és Virtualize Intel VT-x/EPT, i en un servidor físic n'hi ha prou d'activar VT-x/AMD-V a la UEFI. Després d'encendre de nou:

$ kvm-ok
INFO: /dev/kvm exists
KVM acceleration can be used

$ grep -o -m1 -E 'vmx|svm' /proc/cpuinfo
vmx
$ lsmod | grep kvm
kvm_intel             376832  0
kvm                  1146880  1 kvm_intel
irqbypass              12288  1 kvm

Si kvm-ok continua fallant, la virtualització funcionarà per emulació pura: utilitzable per provar la mecànica d'aquesta lliçó, inutilitzable per treballar de debò.

$ sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients \
      virtinst bridge-utils libguestfs-tools cloud-image-utils

$ systemctl is-active libvirtd
active

# El grup libvirt permet gestionar VM sense sudo.
# AVIS de seguretat: qui pot definir una VM pot muntar qualsevol disc
# de l amfitrio dins seu i llegir-lo. Pertanyer a aquest grup es
# equivalent a root a la practica. Es el mateix advertiment que caldra
# amb el grup docker a 07-05.
$ sudo usermod -aG libvirt operador
$ newgrp libvirt
$ virsh --connect qemu:///system version
Compiled against library: libvirt 10.0.0
Using library: libvirt 10.0.0
Using API: QEMU 10.0.0
Running hypervisor: QEMU 8.2.2

Fixa't en qemu:///system davant de qemu:///session: el primer són màquines del sistema, gestionades pel dimoni com a root, amb accés a les xarxes i pools compartits. El segon són màquines de l'usuari, sense privilegis, amb xarxa limitada. Per a un servidor, sempre system, i convé fixar-ho per no haver-ho d'escriure:

$ echo 'export LIBVIRT_DEFAULT_URI="qemu:///system"' >> ~/.bashrc
$ source ~/.bashrc

I una comprovació important que tanca el cercle amb 07-01: en instal·lar libvirt apareix virbr0, la interfície de la xarxa NAT per defecte, i és exactament la que costava 35 segons d'arrencada perquè systemd-networkd-wait-online esperava que tingués enllaç:

$ ip -brief addr show virbr0
virbr0           DOWN           192.168.122.1/24
$ systemd-analyze | tail -1
Startup finished in 3.402s (kernel) + 9.118s (userspace) = 12.520s

Continua en 12 segons perquè a l'exercici de 07-01 vas limitar systemd-networkd-wait-online a enp0s3 amb un drop-in. Sense aquell arranjament, instal·lar libvirt hauria tornat a afegir 35 segons a l'arrencada, i probablement ningú no hauria relacionat les dues coses.

virsh: gestionar màquines des de la línia d'ordres

virsh és l'eina principal. Les seves operacions essencials:

$ virsh list --all
 Id   Name   State
--------------------

$ virsh net-list --all
 Name      State    Autostart   Persistent
------------------------------------------------
 default   active   yes         yes

$ virsh pool-list --all
 Name       State    Autostart
-------------------------------
 default    active   yes
Ordre Què fa
virsh list --all Totes les màquines definides, amb el seu estat
virsh dominfo <vm> CPU, memòria, autoarrencada, seguretat
virsh start <vm> Arrenca
virsh shutdown <vm> Aturada ordenada: demana a l'hoste que s'apagui (ACPI)
virsh destroy <vm> Tall de corrent: immediat i sense avisar l'hoste
virsh reboot <vm> Reinici ordenat
virsh autostart <vm> Arrenca en iniciar l'amfitrió
virsh console <vm> Consola sèrie: la via quan no hi ha xarxa
virsh edit <vm> Edita l'XML amb validació
virsh dumpxml <vm> Aboca la definició
virsh undefine <vm> Esborra la definició (no els discos, llevat de --remove-all-storage)
virsh domifaddr <vm> Adreces IP de la màquina

La distinció entre shutdown i destroy mereix èmfasi perquè el nom enganya: destroy no esborra res, és l'equivalent a estirar el cable. No destrueix la definició ni els discos, però sí que pot deixar el sistema de fitxers de l'hoste inconsistent. És la mateixa lògica del shutdown davant d'estirar el cable de 01-05, i l'ordre correcte és sempre intentar shutdown primer i esperar:

$ virsh shutdown srv-tramontana-proves
Domain 'srv-tramontana-proves' is being shutdown
$ for i in {1..30}; do
      [[ "$(virsh domstate srv-tramontana-proves)" == "shut off" ]] && break
      sleep 2
  done
$ virsh domstate srv-tramontana-proves
shut off

És exactament el patró SIGTERM → esperar → verificar → SIGKILL que vas aprendre a 03-06, aplicat a màquines senceres.

I virsh console és l'eina que fa practicable tot el de 07-01: dona accés a la consola sèrie de l'hoste, així que pots veure el menú de GRUB, entrar en mode emergència i recuperar un fstab trencat sense necessitat d'interfície gràfica. Se'n surt amb Ctrl+].

Crear una màquina amb virt-install

virt-install és la manera no interactiva de definir i arrencar una màquina. L'exemple complet, comentat línia a línia:

$ sudo virt-install \
    --name srv-tramontana-proves \
    --memory 2048 \
    --vcpus 2 \
    --cpu host-passthrough \
    --disk path=/var/lib/libvirt/images/proves.qcow2,size=20,format=qcow2,bus=virtio \
    --network network=default,model=virtio \
    --os-variant ubuntu24.04 \
    --graphics none \
    --console pty,target_type=serial \
    --location 'http://archive.ubuntu.com/ubuntu/dists/noble/main/installer-amd64/' \
    --extra-args 'console=ttyS0,115200n8'
Opció Per què aquell valor
--cpu host-passthrough Exposa la CPU real a l'hoste, inclòs AES-NI — el pendent de 07-03
bus=virtio Controlador paravirtualitzat de disc: imprescindible per al rendiment
model=virtio El mateix per a la xarxa
--os-variant Permet a libvirt triar els valors òptims per a aquell sistema
--graphics none Un servidor no necessita pantalla virtual
--console pty,target_type=serial Consola sèrie, que és el que fa servir virsh console
console=ttyS0,115200n8 Diu al nucli de l'hoste que parli pel port sèrie

Els dos últims van junts i són la parella que fa que virsh console funcioni: sense console=ttyS0 a la línia d'ordres del nucli, l'hoste escriuria en una pantalla virtual que ningú no mira, i veuries una consola en blanc. És una fallada desconcertant i molt comuna.

Una instal·lació interactiva des de l'instal·lador d'Ubuntu triga vint minuts i cal respondre preguntes. Per a un entorn de proves que vols poder recrear, allò no serveix. L'alternativa és la secció següent.

Aprovisionament desatès amb cloud-init

Les distribucions publiquen imatges de núvol: discos ja instal·lats, amb cloud-init a dins, que es configuren sols en la primera arrencada a partir d'unes dades que els lliures.

$ cd /var/lib/libvirt/images
$ sudo wget -q https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
$ sudo qemu-img info noble-server-cloudimg-amd64.img
image: noble-server-cloudimg-amd64.img
file format: qcow2
virtual size: 3.5 GiB (3758096384 bytes)
disk size: 588 MiB

Fixa't en la diferència entre virtual size i disk size: 3,5 GiB declarats, 588 MiB ocupats. És l'aprovisionament fi de qcow2, que s'explica a l'apartat següent.

Les dades de configuració es lliuren en dos fitxers YAML:

$ mkdir -p ~/cloud-init && cd ~/cloud-init
$ cat > user-data <<'EOF'
#cloud-config
hostname: srv-tramontana-proves
fqdn: srv-tramontana-proves.tramontana.example
manage_etc_hosts: true

users:
  - name: operador
    groups: [sudo, adm]
    shell: /bin/bash
    sudo: 'ALL=(ALL) NOPASSWD:ALL'
    lock_passwd: false
    ssh_authorized_keys:
      - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... operador@portatil-alumne

# Coherent amb el que vas aprendre a 06-02: res de contrasenyes per SSH
ssh_pwauth: false
disable_root: true

package_update: true
package_upgrade: true
packages:
  - postgresql-16
  - restic
  - ufw
  - auditd
  - libpam-pwquality
  - sysstat
  - linux-tools-generic

write_files:
  - path: /etc/tramontana/app.conf
    owner: root:root
    permissions: '0640'
    content: |
      db_host=127.0.0.1
      db_port=5432
      db_name=tramontana_reserves
      max_connexions=80
      timeout_consulta=30
      log_nivell=debug
      escolta=127.0.0.1
  - path: /etc/sysctl.d/70-rendiment.conf
    owner: root:root
    permissions: '0644'
    content: |
      # Copiat de produccio perque les proves siguin representatives
      net.core.default_qdisc = fq
      net.ipv4.tcp_congestion_control = bbr
      vm.swappiness = 10
      vm.dirty_background_ratio = 5
      vm.dirty_ratio = 10

runcmd:
  - [ install, -d, -m, '0750', -o, root, -g, root, /opt/tramontana ]
  - [ install, -d, -m, '2770', /srv/tramontana/backups ]
  - [ sysctl, --system ]
  - [ systemctl, enable, --now, sysstat ]

final_message: "srv-tramontana-proves llest despres de $UPTIME segons"
EOF

$ cat > network-config <<'EOF'
version: 2
ethernets:
  enp1s0:
    dhcp4: true
EOF

Els dos fitxers s'empaqueten en una imatge ISO minúscula que la màquina llegeix com si fos un CD:

$ cloud-localds -N network-config llavor.iso user-data
$ ls -lh llavor.iso
-rw-rw-r-- 1 operador operador 366K ago 18 18:04 llavor.iso
$ sudo mv llavor.iso /var/lib/libvirt/images/

I ara es crea la màquina, amb un detall important: la imatge descarregada no es fa servir directament, es crea un disc nou que la fa servir com a suport:

$ cd /var/lib/libvirt/images
# El disc de la VM te com a suport la imatge base, que queda intacta
$ sudo qemu-img create -f qcow2 -F qcow2 \
      -b noble-server-cloudimg-amd64.img proves.qcow2 20G
Formatting 'proves.qcow2', fmt=qcow2 cluster_size=65536 ...

$ sudo virt-install \
    --name srv-tramontana-proves \
    --memory 2048 --vcpus 2 --cpu host-passthrough \
    --disk path=/var/lib/libvirt/images/proves.qcow2,device=disk,bus=virtio \
    --disk path=/var/lib/libvirt/images/llavor.iso,device=cdrom \
    --network network=default,model=virtio \
    --os-variant ubuntu24.04 \
    --graphics none --console pty,target_type=serial \
    --import --noautoconsole

Domain creation completed.

$ virsh list
 Id   Name                    State
---------------------------------------
 1    srv-tramontana-proves   running

$ sleep 60 && virsh domifaddr srv-tramontana-proves
 Name       MAC address          Protocol     Address
-------------------------------------------------------------------------------
 vnet0      52:54:00:8a:c1:f2    ipv4         192.168.122.104/24

$ ssh [email protected] 'hostname; sysctl -n net.ipv4.tcp_congestion_control'
srv-tramontana-proves
bbr

Menys de dos minuts, sense ni una sola pregunta, i amb la configuració de producció ja aplicada. I —això és el que fa valuós el mètode— el user-data és un fitxer de text que es versiona a git: la màquina de proves és reproduïble. És la mateixa idea d'infraestructura com a codi que veuràs amb Ansible a 07-06, en la seva forma més simple.

Emmagatzematge: formats, pools i instantànies

raw davant de qcow2

$ sudo qemu-img create -f raw prova.raw 10G
$ sudo qemu-img create -f qcow2 prova.qcow2 10G
$ ls -lh prova.raw prova.qcow2
-rw-r--r-- 1 root root  10G ago 18 18:12 prova.raw
-rw-r--r-- 1 root root 193K ago 18 18:12 prova.qcow2
$ du -h prova.raw prova.qcow2
0	prova.raw
196K	prova.qcow2

Fixa't en la diferència entre ls i du per a prova.raw: ls diu 10 GB, du diu 0. És un fitxer dispers (sparse), així que raw també fa aprovisionament fi en sistemes de fitxers que ho admeten. La diferència real entre els formats és en una altra banda:

raw qcow2
Rendiment El màxim possible 95-98 % del de raw
Aprovisionament fi Sí, amb fitxers dispersos Sí, natiu
Instantànies internes No Sí
Discos de suport No Sí
Compressió i xifratge No Sí
Es pot muntar a l'amfitrió Directament amb losetup Requereix qemu-nbd

La decisió pràctica: qcow2 llevat que mesuris que el 2-5 % de rendiment importa. Les instantànies i els discos de suport valen molt més que aquell marge en un entorn de proves, i per a producció hi ha alternatives millors que totes dues (un volum LVM directe, que és el que fan servir molts hipervisors).

$ sudo qemu-img info proves.qcow2
image: proves.qcow2
file format: qcow2
virtual size: 20 GiB (21474836480 bytes)
disk size: 1.24 GiB
backing file: noble-server-cloudimg-amd64.img
backing file format: qcow2

# Ampliar un disc: primer el fitxer, DESPRES el sistema de fitxers de dins
$ sudo qemu-img resize proves.qcow2 +10G
Image resized.
# I dins de l hoste, amb el que vas aprendre a 05-04:
$ ssh [email protected] 'sudo growpart /dev/vda 1 && sudo resize2fs /dev/vda1'

# Convertir entre formats
$ sudo qemu-img convert -f qcow2 -O raw proves.qcow2 proves.raw

El disc de suport (backing file) és la peça que fa eficient un laboratori: la imatge base es comparteix i cada VM només guarda les seves diferències. Deu màquines de proves ocupen la base més deu petits fitxers de canvis, no deu sistemes complets. Amb una regla que cal respectar: si modifiques la imatge base, tots els discos que la tenen com a suport es corrompen. La base és de només lectura, a la pràctica.

# Aplanar un disc: incorporar el contingut de la base i trencar la dependencia
$ sudo qemu-img rebase -p -b "" proves.qcow2

Pools d'emmagatzematge

libvirt organitza l'emmagatzematge en pools, que abstreuen on viuen els discos:

$ virsh pool-list --all --details
 Name      State    Autostart  Persistent  Capacity  Allocation  Available
-------------------------------------------------------------------------------
 default   running  yes        yes         14.51 GiB    1.31 GiB   13.20 GiB

$ virsh pool-dumpxml default | grep -E '<path>|<type|name>'
<pool type='dir'>
  <name>default</name>
      <path>/var/lib/libvirt/images</path>

# Crear un pool al volum de dades, que te mes lloc
$ virsh pool-define-as proves dir --target /srv/tramontana/vm
$ virsh pool-build proves && virsh pool-start proves
$ virsh pool-autostart proves
$ virsh vol-list proves --details

Un pool pot ser un directori, un grup de volums LVM, un dispositiu iSCSI, NFS o Ceph. El de tipus logical sobre LVM és interessant aquí: vg-dades ja existeix des de 05-04 i fer servir volums lògics directes com a discos evita la capa de fitxer.

Instantànies, i l'advertiment

$ virsh snapshot-create-as srv-tramontana-proves \
    --name neta-24.04 \
    --description "Acabada d aprovisionar per cloud-init, abans de proves" \
    --atomic
Domain snapshot neta-24.04 created

$ virsh snapshot-list srv-tramontana-proves
 Name         Creation Time               State
---------------------------------------------------
 neta-24.04   2026-08-18 18:22:41 +0200   shutoff

# Trencar alguna cosa, provar, i tornar enrere
$ virsh snapshot-revert srv-tramontana-proves --snapshotname neta-24.04
$ virsh snapshot-delete srv-tramontana-proves --snapshotname neta-24.04

I l'advertiment, amb el mateix pes que el de 05-04 sobre RAID:

Una instantània no és una còpia de seguretat. Viu al mateix fitxer (o al seu costat), al mateix disc, a la mateixa màquina. Si el disc falla, si el fitxer es corromp o si algú esborra el directori, se'n van la instantània i l'original junts. No compleix cap dels tres requisits de la regla 3-2-1 de 05-08.

El que una instantània sí que és: un punt de retorn ràpid per a una operació arriscada. Exactament el que necessites abans de provar un canvi de nucli. I té dos costos que cal conèixer: cada instantània activa degrada el rendiment d'escriptura, perquè cada bloc modificat exigeix copiar l'original primer; i acumular desenes d'instantànies encadenades pot fer el disc impossible de manejar.

Les instantànies amb la màquina en marxa (--live) capturen també la memòria i són molt més delicades: si l'aplicació té dades a mig escriure, l'estat restaurat pot ser inconsistent. L'opció --quiesce demana a l'agent de l'hoste que buidi els sistemes de fitxers abans, i requereix qemu-guest-agent instal·lat a dins. Per a un entorn de proves, apagar la màquina i fer la instantània en fred és més simple i més fiable.

Xarxa: NAT, aïllada i pont

Els tres modes, i quan cadascun:

Mode La VM veu La xarxa veu la VM Entre VM Quan
NAT (default) Internet, sí No Sí Per defecte; laboratori
Aïllada Res de fora No Sí Proves sense sortida; programari maliciós
Pont Tot Sí, amb IP pròpia Sí Servidors que han de ser accessibles
$ virsh net-dumpxml default
<network>
  <name>default</name>
  <forward mode='nat'/>
  <bridge name='virbr0' stp='on' delay='0'/>
  <ip address='192.168.122.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='192.168.122.2' end='192.168.122.254'/>
    </dhcp>
  </ip>
</network>

En NAT, libvirt munta un pont (virbr0), un servidor DHCP i DNS (dnsmasq) i les regles de traducció d'adreces. La VM surt a Internet amb la IP de l'amfitrió, i des de la xarxa ningú no pot iniciar una connexió cap a ella. Per a un entorn de proves és el correcte: aïllament per defecte sense configurar res.

I aquí convé tancar l'assumpte de virbr0 i l'arrencada, perquè ara es veu el mecanisme complet: la interfície existeix des que libvirt arrenca, però no té enllaç fins que una VM s'hi connecta. systemd-networkd-wait-online, en la seva configuració per defecte, esperava que totes les interfícies gestionades estiguessin en línia, i aquella no ho estaria mai amb les màquines apagades. D'aquí els 35 segons.

$ virsh net-define /dev/stdin <<'EOF'
<network>
  <name>aillada</name>
  <bridge name='virbr1' stp='on' delay='0'/>
  <ip address='192.168.200.1' netmask='255.255.255.0'>
    <dhcp><range start='192.168.200.10' end='192.168.200.100'/></dhcp>
  </ip>
</network>
EOF
$ virsh net-start aillada && virsh net-autostart aillada

Sense element <forward>, la xarxa no té sortida: les màquines es veuen entre si i amb l'amfitrió, i res més. És el que es fa servir per provar una configuració de tallafocs o analitzar alguna cosa sospitosa.

El pont és el mode que cal quan la VM ha de ser un servidor accessible. Requereix reconfigurar la xarxa de l'amfitrió amb netplan, aplicant 06-01 — i amb la mateixa cura, perquè et pot deixar sense accés a la màquina:

$ sudo cp -p /etc/netplan/50-cloud-init.yaml \
      /etc/netplan/50-cloud-init.yaml.bak-$(date +%F)
$ sudo tee /etc/netplan/60-pont.yaml >/dev/null <<'EOF'
network:
  version: 2
  ethernets:
    enp0s3:
      dhcp4: false
      dhcp6: false
  bridges:
    br0:
      interfaces: [enp0s3]
      addresses: [10.0.2.15/24]
      routes:
        - to: default
          via: 10.0.2.2
      nameservers:
        addresses: [10.0.2.2, 1.1.1.1]
      parameters:
        stp: false
        forward-delay: 0
EOF
$ sudo chmod 600 /etc/netplan/60-pont.yaml

# netplan try amb reversio automatica als 120 s: la xarxa de seguretat de 06-01
$ sudo netplan try
Do you want to keep these settings?
Press ENTER before the timeout to accept the new configuration

Aquell netplan try no és opcional. Configurar un pont per SSH és exactament el cas per al qual existeix: si el pont surt malament, perds la sessió, i la reversió automàtica als 120 segons et torna la màquina.

I un advertiment específic: el pont reassigna la IP de la interfície física al pont. Durant la transició hi ha un tall de xarxa d'uns segons, i si la configuració és incorrecta el tall és permanent. En un servidor remot sense consola, això es fa amb moltíssima cura.

$ virsh attach-interface srv-tramontana-proves bridge br0 \
      --model virtio --persistent

Rendiment: virtio i configuració de CPU

virtio: la diferència entre lenta i ràpida

Els dispositius que QEMU presenta a l'hoste poden ser emulats (imita un dispositiu real, com una targeta Intel e1000) o paravirtualitzats (virtio: un dispositiu que no existeix al món real, dissenyat per parlar directament amb l'hipervisor).

La diferència és gran i mereix veure's mesurada:

Dispositiu Emulat virtio
Disc ~40 % del rendiment natiu 95-98 %
Xarxa ~1 Gbit/s amb molta CPU 10 Gbit/s+ amb poca CPU
$ virsh dumpxml srv-tramontana-proves | grep -A2 -E "<disk|<interface"
    <disk type='file' device='disk'>
      <driver name='qemu' type='qcow2' cache='none' io='native' discard='unmap'/>
      <source file='/var/lib/libvirt/images/proves.qcow2'/>
      <target dev='vda' bus='virtio'/>
    <interface type='network'>
      <mac address='52:54:00:8a:c1:f2'/>
      <source network='default'/>
      <model type='virtio'/>

Les pistes que virtio està actiu: el disc s'anomena vda (no sda) i el bus és virtio. Si veus sda amb bus sata, estàs fent servir emulació i perdent més de la meitat del rendiment de disc.

Els tres atributs del <driver> també importen:

Atribut Valor Per què
cache none Evita la doble memòria cau amfitrió/hoste; més segur davant de talls
io native E/S asíncrona del nucli; millor amb cache=none
discard unmap Propaga el TRIM de l'hoste: el fitxer qcow2 es redueix en esborrar

discard=unmap és el que evita que un disc qcow2 creixi indefinidament: sense ell, esborrar dades dins de l'hoste no allibera espai a l'amfitrió.

Configuració de CPU

$ virsh dumpxml srv-tramontana-proves | grep -A3 '<cpu'
  <cpu mode='host-passthrough' check='none' migratable='on'/>

Els tres modes:

Mode Què exposa Migració en viu
host-passthrough La CPU real, totes les seves extensions Només a amfitrions idèntics
host-model Un model equivalent al de l'amfitrió A amfitrions similars
custom Un model concret que tries tu A qualsevol amfitrió amb aquell model o superior

I aquí es resol el pendent de 07-03, el d'AES-NI:

$ ssh [email protected] 'grep -o -m1 aes /proc/cpuinfo'
aes

$ ssh [email protected] 'openssl speed -evp aes-256-cbc 2>&1 | tail -2'
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
aes-256-cbc    1284412.10k  3841204.88k  4218844.12k  4402118.42k  4441204.88k

4,4 GB/s de xifratge AES: allò només és possible amb acceleració per maquinari. Amb host-model en lloc de host-passthrough, o amb una CPU virtual genèrica, el mateix openssl speed donaria entre 200 i 600 MB/s, i aquí està el factor de cinc a deu que es plantejava com a hipòtesi a l'últim exercici de 07-03 per a la lentitud de la còpia xifrada.

La contrapartida de host-passthrough és la migració en viu: una màquina que veu les extensions exactes d'un processador no es pot migrar a un altre amfitrió que no les tingui, perquè l'hoste les està fent servir. Per a un entorn de proves en un únic amfitrió, host-passthrough és l'elecció correcta.

Dos ajustos més de memòria, esmentats per completesa:

$ virsh dumpxml srv-tramontana-proves | grep -A3 -E '<memballoon|<memoryBacking'
    <memballoon model='virtio'>

El ballooning permet a l'amfitrió recuperar memòria d'un hoste que no la fa servir, mitjançant un controlador dins de l'hoste que la «infla». És útil per a densitat, i contraproduent per a una base de dades, que reserva memòria compartida i no espera perdre-la. Les hugepages (<memoryBacking><hugepages/></memoryBacking>) redueixen les fallades de TLB de l'hoste, amb la mateixa lògica que les THP de 07-03, i són habituals en màquines grans de base de dades.

Clonar i crear srv-tramontana-proves

La màquina de cloud-init és neta i reproduïble, però per reproduir un problema de producció de vegades cal una còpia de l'estat real. virt-clone la fa:

# La maquina origen ha d estar APAGADA
$ virsh shutdown srv-tramontana-proves
$ virt-clone --original srv-tramontana-proves \
             --name srv-tramontana-proves-2 \
             --auto-clone
Allocating 'proves-2.qcow2'   |  20 GB  00:00:04
Clone 'srv-tramontana-proves-2' created successfully.

virt-clone copia els discos i genera adreces MAC i UUID nous, que és el que evita el conflicte que produeix copiar el fitxer a mà. Però deixa intacte l'interior de l'hoste: mateix nom de màquina, mateixes claus d'amfitrió d'SSH, mateix machine-id. I això dona problemes reals:

$ sudo virt-sysprep -d srv-tramontana-proves-2 \
    --hostname proves-2 \
    --operations defaults,-ssh-userdir
[   0.0] Examining the guest ...
[   4.2] Performing "abrt-data" ...
[   4.2] Performing "bash-history" ...
[   4.3] Performing "machine-id" ...
[   4.4] Performing "ssh-hostkeys" ...
[   4.5] Performing "logfiles" ...
[   4.8] Setting a random seed
[   4.9] Setting the machine ID in /etc/machine-id
[   5.0] Setting the hostname: proves-2

virt-sysprep neteja el que fa única una màquina: machine-id —que systemd i el journal fan servir com a identificador—, claus d'amfitrió d'SSH, registres, historial de shell, i configuració de xarxa associada a la MAC anterior. Sense aquest pas, dues màquines amb el mateix machine-id produeixen entrades de journal barrejades si es centralitzen, i dues amb les mateixes claus d'amfitrió provoquen l'avís d'empremta canviada de 06-02.

I libguestfs permet a més treballar amb el disc d'una màquina apagada sense arrencar-la, cosa que resol l'escenari de 07-01 sense necessitat d'un mitjà de rescat:

# Inspeccionar un disc sense arrencar la maquina
$ sudo virt-ls -d srv-tramontana-proves /etc/tramontana/
app.conf

# Llegir un fitxer
$ sudo virt-cat -d srv-tramontana-proves /etc/fstab

# I ARREGLAR un fstab trencat sense arrencar ni fer servir un Live CD
$ sudo guestfish -d srv-tramontana-proves -i edit /etc/fstab

Això és el que converteix la recuperació de 07-01 en una operació de dos minuts quan la màquina és virtual: en lloc d'arrencar des d'un ISO, muntar i fer chroot, s'edita el fitxer directament al disc apagat.

L'entorn de proves, i per a què serveix

Amb srv-tramontana-proves en marxa, els quatre deutes del principi de la lliçó es poden liquidar:

# 1. El release 3.3.0 que no arrencava (deute del Modul 4)
$ virsh snapshot-create-as srv-tramontana-proves --name abans-de-3.3.0 --atomic
$ scp /srv/tramontana/backups/enviaments/tramontana-3.3.0.tar.gz \
      [email protected]:/tmp/
$ ssh [email protected] \
      'sudo ~/scripts/desplegar.sh 3.3.0; sudo journalctl -u tramontana -n 40'
# I amb el registre complet al davant, per fi es pot diagnosticar

# 2. El planificador d E/S (exercici 3 de 07-03), sense degradar produccio
$ ssh [email protected] \
      'sync; echo 3 | sudo tee /proc/sys/vm/drop_caches; \
       echo mq-deadline | sudo tee /sys/block/vda/queue/scheduler'

# 3. La recuperacio de l arrencada de 07-01, amb virsh console
$ virsh console srv-tramontana-proves
# (Ctrl+] per sortir)

# 4. I AES-NI, ja comprovat
$ ssh [email protected] 'grep -c -m1 aes /proc/cpuinfo'
1

# Tornar al punt de partida quan s acabi
$ virsh snapshot-revert srv-tramontana-proves --snapshotname abans-de-3.3.0

Sobre la migració en viu, per tancar: libvirt permet moure una màquina en execució d'un amfitrió a un altre sense interrompre-la, copiant la memòria en diverses passades fins que en queda tan poca per transferir que es pot pausar uns mil·lisegons i completar el canvi.

$ virsh migrate --live srv-tramontana-proves qemu+ssh://amfitrio2/system

Requereix emmagatzematge compartit (o --copy-storage-all, molt més lent) i CPU compatibles —d'aquí la contrapartida de host-passthrough—. És la base del manteniment sense talls: es buiden els amfitrions un a un per actualitzar-los. I és un avançament de 07-07: moure una màquina no és el mateix que tenir alta disponibilitat, perquè la migració requereix que l'amfitrió origen continuï funcionant. Davant d'una fallada sobtada, no serveix.

Eines gràfiques i de més alt nivell, esmentades: virt-manager és la interfície gràfica de libvirt, còmoda per explorar i per accedir a la consola d'una màquina amb entorn gràfic; Vagrant defineix màquines de desenvolupament en un Vagrantfile versionable i funciona sobre libvirt, VirtualBox o VMware, i és l'eina indicada quan un equip sencer necessita entorns idèntics; i Terraform i OpenStack operen a escala d'infraestructura, parlant també amb libvirt per sota.

Errors Comuns i Consells

  • No activar la virtualització imbricada. kvm-ok diu KVM is disabled by your BIOS i tot funciona per emulació, deu vegades més lent. A VirtualBox, VBoxManage modifyvm <vm> --nested-hw-virt on amb la màquina apagada.
  • Oblidar console=ttyS0 a la línia d'ordres del nucli. virsh console mostra una pantalla en blanc i sembla que la màquina no arrenca. Va aparellat amb --console pty,target_type=serial.
  • Fer servir dispositius emulats en lloc de virtio. Si el disc de l'hoste s'anomena sda i no vda, estàs perdent més de la meitat del rendiment de disc. Comprova bus='virtio' a l'XML.
  • Confondre destroy amb esborrar. virsh destroy és estirar el cable: no esborra res, però pot deixar el sistema de fitxers de l'hoste inconsistent. Fes servir shutdown i espera.
  • Modificar una imatge base que té discos que la fan servir de suport. Corromp tots els discos derivats. La base és de només lectura a la pràctica; fes servir qemu-img rebase si necessites independitzar-ne un.
  • Confiar en una instantània com a còpia de seguretat. Viu al mateix disc i a la mateixa màquina. No compleix cap dels tres requisits de la regla 3-2-1.
  • Acumular instantànies encadenades. Cadascuna activa degrada l'escriptura, i una cadena llarga fa el disc impossible de manejar. Consolida o esborra.
  • Clonar sense virt-sysprep. Dues màquines amb el mateix machine-id i les mateixes claus d'amfitrió d'SSH provoquen journals barrejats i avisos d'empremta canviada.
  • Configurar un pont per SSH sense netplan try. Un pont mal definit et deixa fora de la màquina de forma permanent. La reversió automàtica als 120 segons existeix exactament per a això.
  • Ometre discard=unmap. El fitxer qcow2 creix indefinidament perquè esborrar dins de l'hoste no allibera espai a l'amfitrió.
  • Activar ballooning en una màquina de base de dades. La base de dades reserva memòria compartida i no espera que se li tregui. Provoca degradació difícil de diagnosticar.
  • Consell de mètode. Versiona el user-data de cloud-init a git juntament amb els scripts. Una màquina de proves que es recrea amb una ordre en dos minuts es fa servir; una que cal reinstal·lar a mà s'abandona així que es desconfigura.

Exercicis

Exercici 1

Dissenya el user-data de cloud-init per a una màquina srv-tramontana-proves que reprodueixi la configuració de seguretat de producció el més fidelment possible, i explica què no es pot reproduir i per què. Justifica cada decisió.

Exercici 2

El release 3.3.0 no arrenca. Dissenya el procediment complet per diagnosticar-lo a l'entorn de proves, fent servir el que has après a 07-01, 07-02 i aquesta lliçó, de manera que puguis repetir l'intent tantes vegades com calgui.

Exercici 3

La Marta pregunta si convé moure Tramontana Reserves a màquines virtuals gestionades per KVM en un servidor propi, en lloc del VPS actual. Redacta l'anàlisi: què s'hi guanya, què s'hi perd, i què recomanes.

Solucions

Solució 1

#cloud-config
# user-data per a srv-tramontana-proves
# Objectiu: reproduir produccio prou perque les proves siguin valides,
# SENSE copiar res que sigui un secret real.
hostname: srv-tramontana-proves
fqdn: srv-tramontana-proves.tramontana.example
manage_etc_hosts: true

# --- Identitats: mateixos uid/gid que produccio ---
# Motiu: els permisos, ACL i SGID nomes son representatius si els
# identificadors numerics coincideixen. Un 2770 sobre el gid 1002 nomes es
# comporta igual si el grup tramontana ES el 1002.
groups:
  - tramontana: [operador]

users:
  - name: operador
    uid: 1000
    groups: [sudo, adm, tramontana]
    shell: /bin/bash
    lock_passwd: false
    sudo: 'ALL=(ALL) NOPASSWD:ALL'
    ssh_authorized_keys:
      - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... operador@portatil-alumne
  - name: luis
    uid: 1001
    groups: [tramontana]
    shell: /bin/bash
    lock_passwd: true
  - name: svc-tramontana
    uid: 997
    system: true
    shell: /usr/sbin/nologin
    homedir: /opt/tramontana
    create_groups: false
    no_create_home: true

ssh_pwauth: false
disable_root: true

# --- Paquets: els mateixos que produccio, mes els de diagnostic ---
package_update: true
package_upgrade: true
packages:
  - postgresql-16
  - restic
  - ufw
  - fail2ban
  - auditd
  - aide
  - apparmor-utils
  - libpam-pwquality
  - needrestart
  - sysstat
  # Eines de diagnostic: en proves SI, en produccio no calen
  - linux-tools-generic
  - strace
  - bpfcc-tools
  - bpftrace
  - qemu-guest-agent

write_files:
  # Configuracio de l aplicacio, amb log_nivell=debug (unica diferencia)
  - path: /etc/tramontana/app.conf
    owner: root:tramontana
    permissions: '0640'
    content: |
      db_host=127.0.0.1
      db_port=5432
      db_name=tramontana_reserves
      max_connexions=80
      timeout_consulta=30
      log_nivell=debug
      escolta=127.0.0.1

  # sysctl de rendiment: identic a produccio. Sense aixo, qualsevol
  # mesura comparativa seria invalida.
  - path: /etc/sysctl.d/70-rendiment.conf
    owner: root:root
    permissions: '0644'
    content: |
      net.core.default_qdisc = fq
      net.ipv4.tcp_congestion_control = bbr
      vm.swappiness = 10
      vm.dirty_background_ratio = 5
      vm.dirty_ratio = 10
      fs.inotify.max_user_watches = 524288

  # sysctl de seguretat: identic, INCLOS ptrace_scope.
  # Decisio deliberada: si en proves relaxem ptrace_scope, no
  # reproduiriem l "Operation not permitted" de 07-02, i les proves
  # de diagnostic no serien representatives.
  - path: /etc/sysctl.d/60-enfortiment.conf
    owner: root:root
    permissions: '0644'
    content: |
      kernel.dmesg_restrict = 1
      kernel.kptr_restrict = 2
      kernel.randomize_va_space = 2
      fs.protected_hardlinks = 1
      fs.protected_symlinks = 1
      fs.protected_fifos = 2
      fs.protected_regular = 2
      kernel.unprivileged_bpf_disabled = 1
      kernel.yama.ptrace_scope = 1

  - path: /etc/modprobe.d/blacklist-tramontana.conf
    owner: root:root
    permissions: '0644'
    content: |
      install cramfs /bin/true
      install freevxfs /bin/true
      install jffs2 /bin/true
      install udf /bin/true
      install dccp /bin/true
      install sctp /bin/true

  # Dades de prova SINTETIQUES, generades, mai copiades de produccio
  - path: /home/operador/dades/cases.txt
    owner: operador:operador
    permissions: '0640'
    content: |
      mas-figueres
      can-ventos
      la-solana
      cal-ferrer
      el-moli

runcmd:
  # Rutes amb els mateixos permisos i SGID que produccio
  - [ install, -d, -m, '0755', -o, root, -g, root, /opt/tramontana ]
  - [ install, -d, -m, '2770', -o, svc-tramontana, -g, tramontana,
      /opt/tramontana/shared/uploads ]
  - [ install, -d, -m, '0750', -o, svc-tramontana, -g, adm,
      /var/log/tramontana ]
  - [ install, -d, -m, '2770', -o, operador, -g, tramontana,
      /srv/tramontana/backups ]
  # Tallafocs amb la mateixa politica de llista blanca
  - [ ufw, --force, default, deny, incoming ]
  - [ ufw, --force, default, allow, outgoing ]
  - [ ufw, limit, '22/tcp' ]
  - [ ufw, allow, from, 192.168.122.0/24, to, any, port, '5432', proto, tcp ]
  - [ ufw, --force, enable ]
  # Aplicar i activar
  - [ sysctl, --system ]
  - [ update-initramfs, -u, -k, all ]
  - [ systemctl, enable, --now, sysstat ]
  - [ systemctl, enable, --now, qemu-guest-agent ]
  # Generar reserves.csv sintetiques amb la mateixa estructura
  - [ bash, -c, 'printf "id;data;casa;hoste;nits;import\n" > /home/operador/dades/reserves.csv' ]
  - [ bash, -c, 'for i in $(seq 1001 1025); do printf "%s;2026-08-%02d;mas-figueres;Hoste %s;2;250,00\n" "$i" "$((i-1000))" "$i"; done >> /home/operador/dades/reserves.csv' ]
  - [ chown, 'operador:operador', /home/operador/dades/reserves.csv ]

final_message: "srv-tramontana-proves llest despres de $UPTIME segons"

Què NO es pot reproduir, i per què. Aquesta és la part important de l'exercici, perquè determina quines proves són vàlides:

No reproduïble Per què Conseqüència per a les proves
Els secrets reals (db_password, contrasenya de restic, clau GPG de pass) Copiar-los multiplicaria per dos la seva exposició i violaria la regla de 06-05 Les proves fan servir credencials pròpies. No es pot provar la rotació real
El fitxer .cred de systemd-creds Està xifrat amb una clau derivada de la màquina de producció; és indesxifrable aquí Cal generar-ne un de nou. És la limitació que ja es va documentar a 06-05
El certificat de Let's Encrypt Requereix el domini públic i el desafiament ACME. A més, cada emissió consumeix quota Se'n fa servir un d'autosignat. No es pot provar la renovació real
Les dades personals d'hostes RGPD: un entorn de proves té menys controls, més accessos i més còpies. Copiar-les seria una infracció Dades sintètiques amb la mateixa estructura i volum
El volum LUKS amb la seva clau La clau és un secret i el disc és físic Es pot crear un LUKS nou amb clau de proves. Vàlid per mesurar rendiment, no per restaurar còpies reals
La base de dades d'AIDE Depèn de les sumes dels fitxers reals d'aquesta màquina S'inicialitza aquí. Vàlid per provar el mecanisme, no per comparar amb producció
El maquinari exacte És una VM imbricada, amb dues capes d'hipervisor Les mesures absolutes de rendiment no són comparables; només les relatives abans/després dins de la mateixa màquina

I les tres decisions que convé justificar perquè són contraintuïtives:

  1. Mantenir ptrace_scope = 1 en proves. La temptació és relaxar-lo per diagnosticar còmodament. Seria un error: aleshores no reproduiríem l'Operation not permitted de 07-02 i les proves de diagnòstic no valdrien. Un entorn de proves que difereix en seguretat no prova la seguretat.
  2. Mateixos UID i GID numèrics. És fàcil deixar que cloud-init assigni els que vulgui, i aleshores el 2770 sobre el grup tramontana no es comporta igual, i les proves de permisos són invàlides. Els identificadors numèrics són part de la configuració.
  3. log_nivell=debug com a única diferència intencionada, més les eines de diagnòstic. Totes dues coses són més del que hi ha en producció, no menys, així que no invaliden res — i cal anotar-les, perquè el volum de registres sí que difereix.

L'últim advertiment va al manual d'operació: aquest user-data conté la clau pública d'SSH i els identificadors de l'organització, així que es versiona al repositori intern, no en un de públic. I mai no ha de contenir un secret real: per a això hi ha pass i l'aprovisionament posterior.

Solució 2

La clau del disseny és que el diagnòstic ha de ser repetible: cada intent parteix del mateix estat, perquè les diferències observades es deguin al canvi i no a residus de l'intent anterior.

# --- Fase 0: punt de retorn net i repetible ---
$ virsh shutdown srv-tramontana-proves
$ for i in {1..30}; do
      [[ "$(virsh domstate srv-tramontana-proves)" == "shut off" ]] && break
      sleep 2
  done
$ virsh snapshot-create-as srv-tramontana-proves \
    --name base-3.2.1 \
    --description "3.2.1 activa i verificada, abans d intentar 3.3.0" \
    --atomic
$ virsh start srv-tramontana-proves

La instantània en fred és deliberada: una --live capturaria la memòria i, sense --quiesce, podria deixar PostgreSQL inconsistent. Per a un punt de retorn, apagada és més simple i més fiable.

# --- Fase 1: verificar l estat de partida ---
$ VM=192.168.122.104
$ ssh operador@$VM '~/scripts/revisio_salut.sh; echo "estat: $?"'
estat: 0
$ ssh operador@$VM 'readlink /opt/tramontana/app'
releases/3.2.1

Sense aquesta verificació, una fallada de 3.3.0 es podria confondre amb un entorn de proves que ja estava trencat.

# --- Fase 2: preparar l observacio ABANS de provocar la fallada ---
# L error pot durar mil·lisegons: cal estar mirant quan passi.
$ ssh operador@$VM 'sudo journalctl -f -u tramontana' > /tmp/journal-3.3.0.log &
$ ssh operador@$VM 'sudo execsnoop-bpfcc -T' > /tmp/processos-3.3.0.log &
$ ssh operador@$VM 'sudo journalctl -f -k | grep -i apparmor' > /tmp/apparmor.log &

execsnoop-bpfcc és l'elecció informada aquí: si el binari arrenca i mor en 200 ms, ps no el veurà mai, i aquesta eina captura cada execve amb el seu codi de sortida. És exactament el cas d'ús de la taula de 07-02.

# --- Fase 3: l intent, amb el mode de simulacio primer ---
$ scp /srv/tramontana/backups/enviaments/tramontana-3.3.0.tar.gz \
      /srv/tramontana/backups/enviaments/tramontana-3.3.0.tar.gz.sha256 \
      operador@$VM:/tmp/
$ ssh operador@$VM 'cd /tmp && sha256sum -c tramontana-3.3.0.tar.gz.sha256'
tramontana-3.3.0.tar.gz: OK

# --dry-run primer: convencio del curs, i descarta problemes de l script
$ ssh operador@$VM 'sudo ~/scripts/desplegar.sh --dry-run 3.3.0'
$ ssh operador@$VM 'sudo ~/scripts/desplegar.sh 3.3.0'
# --- Fase 4: recollir l evidencia, del general a l especific ---

# 4a. El journal. La causa hi es el 70% de les vegades.
$ ssh operador@$VM 'sudo journalctl -u tramontana -n 60 --no-pager -o short-precise'

# 4b. Com va morir? El codi de sortida i el senyal ho diuen tot.
$ ssh operador@$VM 'systemctl show tramontana -p ExecMainStatus -p ExecMainCode \
    -p Result -p StatusErrno'

I aquí està l'arbre de decisió que fa útil aquest procediment, perquè cada símptoma té una eina diferent:

Evidència Hipòtesi Eina que la confirma
signal=SYS Filtre seccomp de SystemCallFilter ausyscall <n> sobre el syscall= del journal
apparmor="DENIED" El perfil d'AppArmor no contempla una ruta nova journalctl -k | grep DENIED
status=127 o not found Falta una biblioteca compartida ldd sobre el binari nou
status=1 amb missatge propi Configuració: falta una clau a app.conf strace -e trace=%file | grep ENOENT
Killed / oom-kill Excedeix MemoryMax=512M journalctl -k | grep -i oom
Arrenca i mor sense missatge Qualsevol de les anteriors, silenciada execsnoop + strace -f des de l'arrencada
# 4c. Biblioteques: la causa mes comuna d un binari nou que no arrenca
$ ssh operador@$VM 'ldd /opt/tramontana/releases/3.3.0/tramontana | grep -i "not found"'
	libpq.so.6 => not found

# I si apareix, la confirmacio i la causa arrel
$ ssh operador@$VM 'apt-cache policy libpq5; dpkg -l | grep libpq'
# 4d. Si el journal no diu res util: tracar l arrencada des del principi
$ ssh operador@$VM 'sudo -u svc-tramontana strace -f -o /tmp/traca.txt \
    /opt/tramontana/releases/3.3.0/tramontana --config /etc/tramontana/app.conf; \
    grep -E "ENOENT|EACCES|EPERM" /tmp/traca.txt | grep -v "lib\|locale" | tail -20'

# 4e. Comparar 3.2.1 amb 3.3.0: que ha canviat de debo
$ ssh operador@$VM 'diff <(ldd /opt/tramontana/releases/3.2.1/tramontana | sort) \
                         <(ldd /opt/tramontana/releases/3.3.0/tramontana | sort)'
$ ssh operador@$VM 'sudo systemd-analyze security tramontana.service | tail -1'

El pas 4e és el que més rendiment dona i el que se sol oblidar: comparar la versió que funciona amb la que no. Si l'única diferència és una biblioteca nova, ja tens la resposta sense traçar res.

# --- Fase 5: tornar al punt de partida i repetir ---
$ kill %1 %2 %3 2>/dev/null    # tancar els observadors
$ virsh shutdown srv-tramontana-proves
$ virsh snapshot-revert srv-tramontana-proves --snapshotname base-3.2.1
$ virsh start srv-tramontana-proves
$ ssh operador@$VM '~/scripts/revisio_salut.sh; echo "estat: $?"'
estat: 0

Tres decisions de disseny que justifiquen el procediment:

  1. Els observadors es llancen abans de l'intent. Un binari que mor en 200 ms no es pot observar a posteriori. És la diferència entre tenir l'evidència i haver de repetir.
  2. L'arbre de decisió està escrit abans de començar. Sota pressió, la temptació és llançar strace a tot i ofegar-se en sortida. Amb la taula al davant, cada símptoma porta a una eina concreta.
  3. La instantània permet intents il·limitats. I aquella és la raó de ser de la lliçó: aquest diagnòstic no es pot fer en producció. Cada intent fallit deixa un release a mig desplegar, un servei caigut i una reversió. En proves, snapshot-revert i una altra vegada.

Nota final: quan es trobi la causa, la correcció s'aplica i es prova en proves fins que revisio_salut.sh retorni 0, i només aleshores es desplega en producció. Que desplegar.sh tingués reversió automàtica des de 04-07 va evitar el desastre en el seu moment; tenir on diagnosticar és el que permet arreglar-ho.

Solució 3

Anàlisi: migrar Tramontana Reserves a virtualització pròpia amb KVM Per a: Marta Vidal · De: Operacions de sistemes · 18 d'agost de 2026

Recomanació breu: no ara. Mantenir el VPS i fer servir KVM únicament per a l'entorn de proves, que és on el benefici és immediat i el risc nul. En detallo el raonament.


Què hi guanyaríem

  1. Control complet de la pila. Podríem ajustar la CPU de la màquina (host-passthrough), la configuració de discos i el planificador d'E/S. Recorda que a les proves de rendiment d'aquesta setmana no vam poder determinar si el xifratge de les còpies anava accelerat per maquinari, precisament perquè no controlem aquella capa.
  2. Aïllament fort entre serveis. Podríem separar la base de dades de l'aplicació en màquines diferents, amb una frontera de seguretat real entre elles. Avui comparteixen sistema operatiu: un compromís de l'aplicació arriba directament a la base de dades.
  3. Instantànies abans de cada canvi. Un punt de retorn de segons, en lloc de dependre de la restauració de còpies amb el seu RTO de 8 hores.
  4. Cost predictible a partir de cert volum. Un servidor propi amb capacitat per a diverses màquines pot sortir més barat que quatre o cinc VPS.
  5. Sense dependència del proveïdor quant a versions de nucli, característiques disponibles o canvis de preu.

Què hi perdríem, i això és el determinant

  1. El maquinari passa a ser problema nostre. Discos, fonts, memòria, ventiladors. Un disc que falla a les tres de la matinada avui el resol el proveïdor; amb servidor propi el resolem nosaltres, i necessitaríem discos en RAID —recordant que RAID no és una còpia de seguretat— i recanvis.
  2. L'energia, la refrigeració i la xarxa deixen d'estar garantides. Un servidor en una oficina depèn d'un sol circuit elèctric i d'una sola línia de dades. Un tall de llum de dues hores és una caiguda de dues hores. Un allotjament amb SAI redundant i doble escomesa costa diners.
  3. Un únic punt de fallada, i un de nou: l'amfitrió. Avui tenim un servidor que pot fallar. Amb virtualització pròpia tindríem les màquines virtuals més l'amfitrió, i si l'amfitrió cau, cauen totes alhora. Això és important: la virtualització no dona alta disponibilitat, i creure el contrari és l'error de disseny més comú de l'àrea. Amb un sol amfitrió, el risc de caiguda total augmenta.
  4. Augmenta la càrrega de treball, no disminueix. Avui administrem un sistema operatiu. Amb virtualització pròpia administraríem l'amfitrió, l'hipervisor, la xarxa virtual, l'emmagatzematge i cada màquina hoste. Amb la plantilla actual, això és temps que surt d'un altre lloc.
  5. Cost inicial real. Servidor amb capacitat i redundància de discos, més allotjament adequat, més els recanvis. No és menyspreable, i cal comparar-ho amb el cost anual del VPS a diversos anys, no a un.
  6. Implicacions de compliment. Tractem dades personals d'hostes. Un servidor propi ens converteix en responsables de la seguretat física de l'equip, que avui és del proveïdor i que al nostre model d'amenaces figura explícitament com a fora d'abast. Allò s'hauria de revisar amb el responsable de protecció de dades, i probablement exigiria xifratge complet del disc arrel i control d'accés a l'espai físic.

El que sí que proposo, i ja està en marxa

Fer servir KVM per a l'entorn de proves, a la mateixa màquina d'administració o al VPS actual. El benefici és immediat i el risc, cap:

  • Aquesta setmana ha permès, per primera vegada, poder diagnosticar el release 3.3.0 que va fallar al juliol i que va quedar sense explicació.
  • Permet provar canvis de configuració i actualitzacions de nucli abans d'aplicar-los en producció.
  • Permet assajar el procediment de recuperació del manual d'operació sense tocar el servidor real.
  • Es recrea de zero en dos minuts amb un fitxer de configuració versionat a git.

Quan reconsiderar-ho. L'anàlisi canvia si es donen almenys dues d'aquestes condicions:

Condició Per què canvia la decisió
Necessitem quatre o més servidors El cost per màquina del servidor propi baixa molt
Hi ha pressupost per a dos amfitrions Només aleshores la virtualització aporta disponibilitat, no la resta
Hi ha allotjament professional disponible Resol energia, refrigeració i xarxa
Hi ha una persona dedicada a infraestructura La càrrega de treball deixa de sortir d'un altre lloc
El cost del VPS supera clarament l'alternativa a tres anys És quan la inversió s'amortitza de debò

I una alternativa intermèdia que mereix considerar-se abans. Si l'objectiu real és separar la base de dades de l'aplicació per seguretat, o tenir un entorn de preproducció permanent, contractar un segon VPS aconsegueix això mateix per una fracció del cost i sense assumir la responsabilitat del maquinari. És un pas reversible; comprar un servidor no ho és.

Resum per decidir. La virtualització pròpia és la resposta correcta quan el problema és escala i hi ha recursos per fer-la bé. El nostre problema avui no és l'escala: és que no teníem entorn de proves —resolt— i que tenim un únic punt de fallada, que és un problema de redundància i no de virtualització. Aquell segon assumpte l'estic estudiant i et portaré una proposta amb números.

Conclusió

Ja tens l'entorn de proves que el curs feia quatre mòduls que trobava a faltar, i entens la pila que el sosté. Saps què virtualitza cada peça: KVM dona accés a les extensions de la CPU, QEMU emula els dispositius, libvirt gestiona el conjunt amb una API estable, i virsh és l'eina del dia a dia. Saps que destroy és estirar el cable i no esborrar, que virsh console és la via per recuperar una arrencada trencada sense interfície gràfica, i que virtio al disc i a la xarxa és la diferència entre una màquina lenta i una de ràpida. Aprovisiones sense intervenció amb cloud-init a partir d'un fitxer versionat a git, tries qcow2 per les seves instantànies i els seus discos de suport sabent el que costa aquell 2-5 % de rendiment, i tens clar que una instantània no és una còpia de seguretat.

I de passada has tancat tres assumptes pendents. El virbr0 que costava 35 segons d'arrencada a 07-01 té ara una explicació completa: una interfície que existeix però no té enllaç fins que arrenca una màquina. El dubte de 07-03 sobre AES-NI està resolt: amb host-passthrough, l'hoste veu les extensions de la CPU i xifra a 4,4 GB/s, cosa que confirma el factor de cinc a deu que es plantejava com a hipòtesi. I el release 3.3.0 que va fallar al Mòdul 4 té per fi un lloc on reproduir-se tantes vegades com calgui, amb una instantània com a punt de retorn i un arbre de decisió escrit abans de començar.

Fixa't ara en el preu que has pagat per l'aïllament fort de les màquines virtuals. Cada VM porta el seu propi nucli, triga mig minut a arrencar, ocupa gigabytes al disc i reserva centenars de megabytes de memòria només per existir. Per a un entorn de proves allò és perfectament raonable. Per desplegar una aplicació i la seva base de dades, i poder-les recrear en segons, és caríssim. I al diagrama del tercer apartat ja vas veure l'alternativa: compartir el nucli de l'amfitrió i aïllar només l'imprescindible.

A la lliçó 07-05: Contenidors de Linux i Docker es construeix això, i comença per la idea que cal tenir clara abans d'escriure el primer docker run: un contenidor no és una màquina petita, és un procés aïllat. Veuràs les tres primitives del nucli que ho fan possible —els namespaces, que aïllen el que un procés veu; els cgroups v2, que són literalment la mateixa tecnologia del MemoryMax=512M que vas posar a 05-05; i les capabilities de 05-02—, i reconeixeràs als namespaces de muntatge el chroot amb què vas reparar GRUB a 07-01. Després arriba Docker: imatges i capes, un Dockerfile amb construcció en diverses etapes per no enviar el compilador a producció, volums davant de bind mounts, xarxes amb resolució per nom, i un compose.yaml que aixeca l'aplicació i PostgreSQL junts amb comprovacions de salut. Amb dos advertiments que la lliçó es pren seriosament: pertànyer al grup docker equival a la pràctica a tenir root, i aquell kernel.unprivileged_userns_clone que vas deixar comentat a 06-06 amb una nota explicant per què — ha arribat el moment d'entendre-la del tot.

Curs de Linux: De Principiant a Administrador de Sistemes

Mòdul 1: Introducció a Linux

Mòdul 2: Comandes Bàsiques de Linux

Mòdul 3: Habilitats Avançades en la Línia de Comandes

Mòdul 4: Scripting en Shell

Mòdul 5: Administració del Sistema

Mòdul 6: Xarxes i Seguretat

Mòdul 7: Temes Avançats

Mòdul 8: Projectes Pràctics

© Copyright 2026. Tots els drets reservats