Questo obiettivo è quasi tutto concettuale: pochi comandi, molti nomi e meccanismi da riconoscere. Vale comunque la pena leggerlo con attenzione, perché descrive la condizione in cui gira la maggior parte delle macchine Linux che amministrerai: un guest su un hypervisor, o un’istanza da un provider IaaS.

Macchine virtuali e container: due modi diversi di isolare

Una macchina virtuale è un computer intero emulato in software. L’hypervisor divide fra i guest le risorse fisiche dell’host e a ciascuno presenta BIOS, controller dischi e schede di rete simulati; il disco è di solito un file. Gli hypervisor da conoscere sono tre: Xen, di tipo 1 o bare metal, che si avvia direttamente sull’hardware; KVM, modulo del kernel Linux gestito dal demone libvirt, ibrido perché ha bisogno di un Linux ma si comporta da hypervisor a tutti gli effetti; VirtualBox, di tipo 2, che gira sopra un sistema operativo ospitante. Spostare un guest fra hypervisor è una migrazione, e a macchina accesa una live migration.

Un container non emula un computer: isola un’applicazione riusando il kernel dell’host, quindi pesa molto meno. Sotto ci sono i cgroup, che ripartiscono memoria, tempo di CPU e banda. LPI distingue il container Linux di sistema, che contiene un ambiente simile a una piccola distribuzione (LXC/LXD), dall’application container, pensato per un solo servizio (Docker e le sue orchestrazioni). L’implementazione di Docker o Kubernetes non è richiesta: bastano il concetto e la differenza rispetto alla macchina virtuale.

Full, para e ibrido: dove entrano i guest driver

Un guest completamente virtualizzato non sa di essere virtuale ed esegue le istruzioni senza modifiche: su hardware x86 servono le estensioni Intel VT-x o AMD-V, che si abilitano nel firmware BIOS o UEFI. Per vedere se la CPU le espone, cerca i flag in /proc/cpuinfo:

$ grep --color -E "vmx|svm" /proc/cpuinfo

vmx è Intel, svm è AMD. Un guest paravirtualizzato invece sa di essere virtuale: usa un kernel modificato e driver dedicati, i guest driver, che parlano direttamente con l’hypervisor e rendono l’I/O di disco e rete molto più veloce. La forma ibrida mette driver paravirtualizzati in un guest per il resto completamente virtualizzato, ed è oggi la più comune. I nomi da associare: KVM usa i driver del progetto virtio, VirtualBox le Guest Additions distribuite come immagine ISO.

Cosa cambia quando il guest sta in cloud

In un ambiente IaaS ritrovi le stesse cose con altri nomi. Le computing instance sono le macchine virtuali provisionate, e la tariffa dipende da quante ne tieni accese e per quanto. Il block storage è il disco: costa in base a capacità e velocità, con l’archiviazione lenta molto più economica. La rete si progetta da pannello web (subnet, rotte, firewall, spesso DNS pubblico) e si collega alla rete aziendale con una VPN.

Anche il disco ha due forme da distinguere. Il formato COW (copy-on-write, o thin provisioning), di cui qcow2 è l’esempio tipico, dichiara una dimensione massima ma occupa spazio reale solo per i dati scritti: il guest vede un disco da 23 GB mentre sull’host il file ne pesa 5. Il formato raw alloca subito tutto lo spazio e in cambio è più veloce.

L’accesso a un guest in cloud passa quasi sempre da OpenSSH con coppie di chiavi: ssh-keygen genera la coppia, ssh-copy-id deposita quella pubblica nel file ~/.ssh/authorized_keys del server remoto. Sui permessi non c’è margine: 0600 per la chiave privata, 0644 per quella pubblica.

Clonare un template: gli identificatori da rigenerare

È il punto pratico dell’obiettivo. Una macchina virtuale è un insieme di file, quindi conviene prepararne una con sistema operativo e configurazione di base e poi copiarla. Il problema è che alcune cose devono restare uniche, e la copia le duplica.

Il primo è il D-Bus machine ID, generato all’installazione. Vive in /var/lib/dbus/machine-id, di norma un collegamento simbolico a /etc/machine-id. Due guest con lo stesso ID si comportano in modo erratico: le risorse dell’hypervisor finiscono al sistema sbagliato. Per leggerlo e rigenerarlo:

$ dbus-uuidgen --get
$ sudo rm -f /etc/machine-id
$ sudo dbus-uuidgen --ensure=/etc/machine-id

dbus-uuidgen --ensure senza argomenti verifica soltanto che un ID esista: se non stampa errori, c’è. Se /var/lib/dbus/machine-id non è un collegamento a /etc/machine-id, va rimosso anche quello. Cambiare l’ID su un sistema in esercizio è sconsigliato: si fa sul clone.

Il secondo sono le chiavi host SSH, che stanno in /etc/ssh/ con nomi del tipo ssh_host_rsa_key e ssh_host_ed25519_key. Non confonderle con le chiavi utente in ~/.ssh/: le chiavi host identificano la macchina verso chi si collega. Due cloni con le stesse chiavi host sono indistinguibili per un client: è un problema di sicurezza, non solo di igiene. Si cancellano dal template e se ne rigenerano di nuove al primo avvio con ssh-keygen -A. Alla stessa lista appartengono l’hostname e l’indirizzo MAC della scheda di rete, da rigenerare per evitare collisioni sul segmento.

cloud-init: preconfigurare invece di ritoccare

cloud-init è lo strumento vendor-neutral per questo lavoro: al primo avvio legge un file YAML, chiamato cloud-config, e applica quanto ci trova — hostname, fuso orario, utenti, chiavi SSH, pacchetti, aggiornamenti. Un esempio minimo:

#cloud-config
timezone: Africa/Dar_es_Salaam
hostname: test-system
apt_update: true
apt_upgrade: true
packages:
  - nginx

Un dettaglio che sembra pedanteria e non lo è: la prima riga deve essere esattamente #cloud-config, senza spazio dopo il cancelletto. E cloud-init non serve solo alle macchine virtuali: preconfigura anche i container, per esempio quelli LXD.