Tanquem el mòdul 5 amb una pregunta incòmoda: durant quatre lliçons hem estat fingint aïllament. ProtectSystem=strict fingeix un sistema de fitxers de només lectura, PrivateTmp fingeix un /tmp propi, les capabilities fingeixen un root retallat. Tot això funciona, però tot això ho decideix el mateix nucli que executa el procés que volem contenir: si aquest nucli cau, cau tot amb ell.

La virtualització hi respon amb una idea que el 1974 ja estava formalitzada i que avui sosté tota la informàtica al núvol: donar a un sistema operatiu sencer la il·lusió que té una màquina per a ell sol. No pas un directori propi: una CPU pròpia, una memòria física pròpia, un disc propi, una targeta de xarxa pròpia i el seu propi nucli en mode privilegiat. Si meteo-api s'executa dins d'una màquina virtual i algú aconsegueix root i trenca el nucli d'aquesta màquina virtual, encara li queda una frontera sencera al davant.

En aquesta lliçó entendràs què significa exactament virtualitzar, per què durant vint anys x86 va ser la pitjor arquitectura possible per fer-ho i les tres solucions que es van inventar, com es virtualitza cadascun dels tres recursos que vam estudiar al mòdul 2 —CPU, memòria i E/S— i quin preu es paga per tot plegat, amb xifres. Acabarem aixecant una rèplica de proves de meteo-01 amb KVM i libvirt, i mesurant la sobrecàrrega real. Els contenidors, que són una altra resposta al mateix problema amb un compromís completament diferent, són la lliçó següent; aquí només direm per què una màquina virtual aïlla més.

Contingut

  1. Què significa virtualitzar una màquina completa
  2. El problema històric: consolidació, aïllament i aprofitament
  3. Els criteris de Popek i Goldberg
  4. Per què x86 no era virtualitzable, i les tres respostes
  5. Hipervisors de tipus 1 i de tipus 2
  6. Virtualització de la CPU: vCPU, sobresubscripció i steal time
  7. Virtualització de la memòria: EPT/NPT, ballooning i sobrecompromís
  8. Virtualització de l'E/S: emulació, virtio i assignació directa
  9. Xarxes virtuals: pont, NAT i xarxa interna
  10. El disc virtual: formats, instantànies i clonatge
  11. Migració en calent
  12. Pràctica amb KVM i libvirt: una rèplica de meteo-01
  13. Seguretat de l'aïllament: superfície de l'hipervisor i escapades
  14. Quan cal virtualitzar i quan no

Què significa virtualitzar una màquina completa

A 01-01 vam definir el sistema operatiu com una màquina estesa: converteix el maquinari incòmode en abstraccions còmodes (processos, fitxers, sockets). Un hipervisor fa una cosa diferent i, en certa manera, més humil: no ofereix abstraccions noves, sinó més còpies de la mateixa màquina.

Capa Què multiplexa Què ofereix al seu client
Sistema operatiu El maquinari entre processos Abstraccions: processos, fitxers, sockets
Hipervisor El maquinari entre sistemes operatius Més maquinari (virtual): CPU, RAM, discos, NIC

D'aquí ve el vocabulari:

  • Amfitrió (host): la màquina física i el programari que gestiona el repartiment.
  • Hipervisor o VMM (Virtual Machine Monitor): la capa que crea i controla les màquines virtuals.
  • Convidat (guest): el sistema operatiu que s'executa dins d'una màquina virtual, sense saber que ho fa.

Aquesta darrera frase és la clau de tot: el convidat no està portat ni modificat. Un Debian instal·lat en una màquina virtual és el mateix Debian que instal·laries en ferro. Executa el seu propi procés init, el seu propi planificador, les seves pròpies taules de pàgines i els seus propis controladors. Es pensa que /dev/vda és un disc. Es pensa que té 4 CPU. Es pensa que l'arrencada de la BIOS l'ha feta una placa base.

graph TD
    subgraph Amfitrio["Màquina física meteo-host"]
        HW["Maquinari: 16 nuclis, 64 GB RAM, NVMe, NIC 10G"]
        HV["Hipervisor (KVM + QEMU)"]
        subgraph VM1["VM: meteo-01"]
            K1["Nucli Linux convidat"]
            P1["meteo-api · ingestor · agregador"]
        end
        subgraph VM2["VM: meteo-01-proves"]
            K2["Nucli Linux convidat"]
            P2["Processos de proves"]
        end
        HW --> HV
        HV --> K1
        HV --> K2
        K1 --> P1
        K2 --> P2
    end

Fixa't en la diferència essencial amb el que veurem a 06-02: aquí hi ha dos nuclis convidats independents, cadascun amb la seva pròpia gestió de memòria, el seu propi planificador i la seva pròpia taula de processos. En un contenidor només hi ha un nucli, el de l'amfitrió, compartit per tots.

El problema històric: consolidació, aïllament i aprofitament

La virtualització no va néixer per elegància teòrica, sinó per diners. A finals dels 90 i principis dels 2000, el model dominant era un servei, un servidor físic, per una raó molt sensata que ja coneixes del mòdul 5: si dues aplicacions comparteixen màquina, comparteixen nucli, comparteixen sistema de fitxers i comparteixen destí. Una fallada de l'aplicació A tombava l'aplicació B.

El resultat mesurat en centres de dades reals de l'època era demolidor: utilització mitjana de CPU d'entre el 5 % i el 15 %. És a dir, es compraven, alimentaven, refrigeraven i administraven màquines que estaven aturades el 90 % del temps, perquè calia dimensionar-les per al pic i aïllar-les per seguretat.

Aplicat a Meteora, el càlcul és fàcil de veure:

Escenari Màquines físiques Utilització mitjana Cost relatiu
Un servei per servidor (meteo-api, ingestor, agregador, proves, CI, rèplica) 6 ~8 % 100 %
Sis màquines virtuals sobre un amfitrió potent 1 (+1 de reserva) ~45 % ~35 %

La virtualització va resoldre simultàniament tres coses que fins llavors estaven en conflicte:

  • Consolidació. Moltes càrregues en menys ferro, amb un estalvi directe en compra, espai de bastidor, electricitat i refrigeració.
  • Aïllament. Sense renunciar a la separació: cada càrrega continua tenint el seu propi sistema operatiu, i un nucli convidat que entra en pànic no arrossega els altres.
  • Aprofitament i flexibilitat. Redimensionar una màquina virtual és editar un fitxer XML; afegir un disc és una ordre; clonar un servidor sencer són segons. Amb ferro, tot això són setmanes i una ordre de compra.

A això s'hi va sumar un quart benefici que va resultar ser gairebé tan important: la màquina va passar a ser un fitxer. Una còpia de seguretat completa, una instantània abans d'una actualització arriscada, un entorn de proves idèntic al de producció, moure una càrrega d'un servidor a un altre sense apagar-la... tot això només és possible quan el "servidor" és un conjunt de fitxers i una mica d'estat.

Els criteris de Popek i Goldberg

El 1974, Gerald Popek i Robert Goldberg van publicar l'article que encara avui defineix què és un hipervisor de debò. Van establir tres propietats que ha de complir:

Criteri Què exigeix Què passa si falla
Equivalència El convidat s'ha de comportar igual que sobre maquinari real (llevat de temps i recursos disponibles) Caldria modificar el sistema operatiu convidat: ja no és virtualització pura
Control de recursos L'hipervisor manté el control absolut dels recursos: el convidat mai no pot prendre pel seu compte allò que no se li ha donat Un convidat podria acaparar o accedir a memòria d'un altre: adéu a l'aïllament
Eficiència La majoria de les instruccions s'han d'executar directament a la CPU física, sense intervenció de l'hipervisor Ja no és virtualització, és emulació: de 10 a 100 vegades més lent

El tercer criteri és el que separa la virtualització de l'emulació. QEMU en mode emulació pura pot executar un binari ARM sobre un x86 traduint cada instrucció: compleix equivalència i control, però és lentíssim. Un hipervisor ha d'aconseguir que el codi del convidat s'executi sobre el silici real, i només intervenir en els moments perillosos.

Instruccions privilegiades i sensibles

Per formalitzar quan això és possible, Popek i Goldberg van classificar les instruccions d'una arquitectura. Recupera de 01-06 la distinció entre mode usuari i mode nucli:

  • Instruccions privilegiades: provoquen una excepció (trap) si s'executen en mode usuari. Per exemple, carregar la taula de pàgines o deshabilitar interrupcions.
  • Instruccions sensibles a la configuració: canvien l'estat dels recursos del sistema (activar interrupcions, modificar la taula de descriptors, canviar de mode).
  • Instruccions sensibles al comportament: el seu resultat depèn de l'estat del sistema, per exemple de en quin anell de privilegi s'està executant.

I llavors van enunciar el seu teorema, que és d'una senzillesa preciosa:

Es pot construir un hipervisor per a una arquitectura si el conjunt d'instruccions sensibles és un subconjunt de les instruccions privilegiades.

El mecanisme que hi ha al darrere es diu trap-and-emulate (atrapar i emular) i funciona així:

  1. El sistema operatiu convidat s'executa desanellat: no pas a l'anell 0 que ell es pensa que té, sinó en un anell menys privilegiat.
  2. Mentre executa instruccions innòcues (aritmètica, salts, accessos a la seva memòria), va a velocitat nativa. Aquí es compleix l'eficiència.
  3. Quan executa una instrucció sensible —que per hipòtesi és privilegiada—, la CPU llança una excepció que captura l'hipervisor.
  4. L'hipervisor emula l'efecte d'aquesta instrucció sobre l'estat virtual del convidat i li retorna el control. Aquí es compleixen l'equivalència i el control de recursos.

Si alguna instrucció és sensible però no privilegiada, es trenca tot: el convidat l'executa sense que ningú se n'assabenti i obté un resultat que revela o altera l'estat real de la màquina.

Per què x86 no era virtualitzable, i les tres respostes

L'any 2000, John Scott Robin i Cynthia Irvine van publicar una anàlisi del conjunt d'instruccions d'Intel Pentium i hi van trobar 17 instruccions sensibles no privilegiades. x86 no passava el teorema.

L'exemple canònic és POPF (pop flags): treu un valor de la pila i el carrega al registre de banderes, que inclou el bit IF d'habilitació d'interrupcions. En mode nucli, POPF canvia IF. En mode usuari, POPF ignora silenciosament aquest bit i no provoca cap excepció. És sensible (afecta la configuració) però no privilegiada (no llança trap). Un nucli convidat desanellat que intenta deshabilitar interrupcions amb POPF es pensa que ho ha fet, i no ha passat res. A partir d'aquí el convidat funciona malament de maneres impossibles de depurar.

Un altre exemple encara més directe: SGDT, SIDT, SLDT i STR llegeixen els registres de taules de descriptors i funcionen en mode usuari. Un convidat pot llegir-los i descobrir que les seves taules no són on ell les havia posades: sap que està virtualitzat i, encara pitjor, veu dades reals de l'amfitrió.

La indústria va donar tres respostes, cronològicament i amb filosofies oposades.

Resposta 1: traducció binària dinàmica (VMware, 1999)

VMware Workstation va resoldre el problema sense tocar el maquinari ni el convidat: en lloc d'executar directament el codi del nucli convidat, el tradueix sobre la marxa a un bloc equivalent i segur, que es desa en una memòria cau de traducció.

  • El codi de mode usuari del convidat s'executa directament, sense traduir: no és sensible.
  • El codi de mode nucli del convidat es tradueix bloc a bloc. Les instruccions innòcues es copien tal qual; les 17 problemàtiques se substitueixen per crides a l'hipervisor.
  • Els blocs traduïts es desen a la memòria cau, de manera que un bucle es tradueix un cop i s'executa milers.

Va ser una gesta d'enginyeria i va funcionar, amb una penalització del 5 % al 20 % segons la càrrega; el seu cost va ser una complexitat brutal i molta sensibilitat a les càrregues amb codi de nucli abundant.

Resposta 2: paravirtualització (Xen, 2003)

Xen va prendre el camí contrari: si el problema és que certes instruccions no atrapen, no les fem servir. Es modifica el sistema operatiu convidat perquè, allà on faria una operació privilegiada, cridi explícitament l'hipervisor mitjançant una hipercall —exactament el mateix concepte que una crida al sistema de 01-06, però un nivell més avall—.

Concepte Qui crida A qui Mecanisme
Crida al sistema Procés d'usuari Nucli syscall
Hipercall Nucli convidat Hipervisor Instrucció de trap explícita

Avantatges: rendiment excel·lent (sobrecàrrega de l'1 % al 5 %) i disseny més simple. Inconvenient decisiu: cal modificar el convidat. Amb Linux era viable (codi obert), amb Windows no. Xen paravirtualitzat exigia nuclis específics.

La paravirtualització va desaparèixer com a tècnica de virtualització de CPU, però va sobreviure amb un èxit enorme a l'E/S: virtio, que veuràs més avall, és exactament aquesta idea aplicada a discos i targetes de xarxa, i avui és l'estàndard de facto.

Resposta 3: virtualització assistida per maquinari (Intel VT-x i AMD-V, 2005-2006)

La solució definitiva va ser arreglar l'arquitectura. Intel VT-x i AMD-V (SVM) van afegir un nou eix de privilegi perpendicular als anells:

  • Mode arrel VMX (root): on s'executa l'hipervisor. Té els seus quatre anells 0-3.
  • Mode no arrel VMX (non-root): on s'executa el convidat. També té els seus quatre anells 0-3.

Això és l'important i el que se sol explicar malament: el nucli convidat s'executa a l'anell 0, com ell espera, però en mode no arrel. No està desanellat. No cal traduir res. I en mode no arrel, les instruccions sensibles sí que provoquen una sortida a l'hipervisor, incloses les 17 traïdores. El teorema de Popek i Goldberg es compleix per decret del silici.

Al mode arrel se l'anomena col·loquialment "anell -1", l'expressió que ja vam avançar a 01-06: un nivell de privilegi per sota de l'anell 0 del convidat.

L'estat de cada màquina virtual viu en una estructura en memòria anomenada VMCS (Virtual Machine Control Structure) a Intel, o VMCB a AMD. Conté tres coses:

  • L'estat del convidat: registres, punters de taules de pàgines, estat de control. Es desa en sortir i es restaura en entrar.
  • L'estat de l'amfitrió: on tornar quan el convidat surti.
  • Els camps de control: quins esdeveniments han de provocar una sortida. Aquí es decideix, per exemple, si CPUID surt a l'hipervisor o si les escriptures a CR3 es gestionen soles.

El cicle de vida és un bucle entre dues instruccions i un esdeveniment:

sequenceDiagram
    participant HV as Hipervisor (mode arrel)
    participant CPU as CPU
    participant G as Convidat (mode no arrel)
    HV->>CPU: VMLAUNCH / VMRESUME
    CPU->>G: Carrega estat des de la VMCS i executa
    Note over G: Codi natiu a tota velocitat
    G->>CPU: Instrucció sensible / E/S / interrupció
    CPU->>HV: VM exit (desa l'estat a la VMCS)
    Note over HV: Emula l'operació
    HV->>CPU: VMRESUME

El VM exit és la unitat de cost de tota la virtualització moderna, i convé tenir-ne el número al cap:

Operació Cost aproximat
Instrucció aritmètica < 1 cicle (amb paral·lelisme)
Fallada de memòria cau a RAM ~200-300 cicles
Crida al sistema (syscall) ~100-150 cicles
VM exit + VM entry ~1.000-1.500 cicles (va baixar des de ~4.000 a les primeres generacions)

Amb una CPU a 3 GHz, 1.200 cicles són 0,4 microsegons. Sembla poc, però si una càrrega provoca 100.000 sortides per segon, s'està gastant un 4 % d'un nucli sencer només a entrar i sortir. Per això tot el disseny de la virtualització moderna consisteix a eliminar sortides: EPT les elimina per a les fallades de pàgina, virtio les redueix agrupant operacions d'E/S, i SR-IOV les elimina del tot per a la xarxa.

Hipervisors de tipus 1 i de tipus 2

La classificació clàssica, també de Goldberg, distingeix segons què hi ha sota l'hipervisor.

Aspecte Tipus 1 (bare-metal) Tipus 2 (allotjat)
S'executa sobre El maquinari nu Un sistema operatiu amfitrió
Controladors de dispositiu Propis (o d'un domini de servei) Els del sistema amfitrió
Rendiment Màxim Bo, amb una capa més
Superfície d'atac Mínima La de l'amfitrió sencer
Arrencada És el primer que arrenca Es llança com una aplicació més
Ús típic Producció, centres de dades, núvol Escriptori, desenvolupament, laboratori
Exemples VMware ESXi, Xen, Microsoft Hyper-V, KVM VirtualBox, VMware Workstation, Parallels, QEMU sense KVM

A la pràctica, aquesta frontera s'ha tornat borrosa, i KVM n'és l'exemple perfecte. KVM (Kernel-based Virtual Machine) no és un hipervisor separat: és un mòdul del nucli Linux (kvm.ko més kvm-intel.ko o kvm-amd.ko) que converteix el mateix nucli Linux en hipervisor de tipus 1.

La jugada és elegantíssima, i encaixa amb tot el que vam veure al mòdul 2: Linux ja sap planificar tasques sobre CPU, ja sap gestionar memòria virtual, ja sap parlar amb els discos i les targetes de xarxa. Un hipervisor necessita exactament això. Per què reescriure-ho? KVM només afegeix el que falta: la gestió de la VMCS i el mode arrel.

Amb KVM, una màquina virtual és un procés normal de Linux, i cada vCPU és un fil d'aquest procés. Això té conseqüències que es poden comprovar:

# Una VM en execució apareix com un procés més
ps -eo pid,comm,nlwp,pcpu --sort=-pcpu | head -5
#   PID COMMAND         NLWP %CPU
#  4211 qemu-system-x86    7  185

# Els seus fils: un per vCPU més els d'E/S
ps -L -p 4211 -o tid,comm | head

Què mostra. nlwp 7 indica set fils: quatre vCPU i tres auxiliars. El %CPU de 185 significa que la VM està fent servir l'equivalent a 1,85 nuclis físics. I com que és un procés normal, se li poden aplicar totes les eines del curs: nice i chrt per a la seva prioritat (02-02), cgroups per limitar-li la memòria i la CPU (06-02), taskset per fixar-lo a uns nuclis concrets, i fins i tot l'OOM killer el pot matar, amb l'efecte dramàtic d'apagar de cop un servidor virtual sencer.

Sota KVM, el complement habitual és QEMU, que aporta el que KVM no fa: emular la placa base, la BIOS/UEFI, el rellotge, el bus PCI i els dispositius. La divisió del treball és neta: KVM executa les instruccions del convidat a velocitat nativa; QEMU atén els VM exits que corresponen a dispositius.

Virtualització de la CPU: vCPU, sobresubscripció i steal time

Cada vCPU d'una màquina virtual és, a KVM, un fil del procés QEMU. I com qualsevol fil, el planifica el planificador de l'amfitrió, el CFS-EEVDF que vam estudiar a 02-02. Hi ha llavors dos nivells de planificació superposats:

  1. El planificador del convidat reparteix els seus processos entre les seves 4 vCPU. Es pensa que són CPU reals i sempre disponibles.
  2. El planificador de l'amfitrió reparteix les vCPU de totes les màquines virtuals entre els 16 nuclis físics.

Com que el convidat no sap res del segon nivell, pot prendre decisions dolentes: per exemple, fer spin en un spinlock (03-04) esperant una altra vCPU que l'amfitrió ha desallotjat i no es tornarà a executar en 10 mil·lisegons. Aquest problema, el lock holder preemption, és la raó que els nuclis moderns facin servir spinlocks paravirtualitzats que cedeixen a l'hipervisor.

Sobresubscripció

Sobresubscriure és assignar més vCPU en total que nuclis físics hi ha. Amb 16 nuclis físics i 6 màquines virtuals de 4 vCPU cadascuna, hi ha 24 vCPU sobre 16 nuclis: una ràtio d'1,5:1.

Funciona per la mateixa raó per la qual funciona la multiprogramació: gairebé cap càrrega no fa servir la seva CPU tota l'estona. meteo-api està la major part del temps blocat a accept() esperant peticions.

Ràtio vCPU:pCPU Situació típica Risc
1:1 o menys Càrregues crítiques i sensibles a la latència Cap, però es desaprofita
2:1 a 4:1 Servidors generals, entorns de proves Acceptable si es vigila el steal time
> 8:1 Escriptoris virtuals, càrregues molt ocioses Latències erràtiques

La memòria, en canvi, no se sobresubscriu amb la mateixa alegria, perquè un procés que no fa servir CPU sí que continua ocupant la seva RAM.

Steal time: el rellotge que delata

Com sap un convidat que està esperant CPU? L'hipervisor l'hi diu. Linux exposa el temps robat: el temps durant el qual una vCPU estava a punt per executar però l'amfitrió no li va donar nucli físic.

top -bn1 | head -3
# %Cpu(s): 12,3 us,  3,1 sy,  0,0 ni, 68,2 id,  1,4 wa,  0,0 hi,  0,0 si, 15,0 st

Què significa st. Aquest 15,0 st és la xifra més important per diagnosticar una màquina virtual lenta. Diu que el 15 % del temps de CPU que el convidat es pensava que tenia se l'ha quedat un altre. I la conseqüència pràctica és brutal per al diagnòstic:

  • Si dins de la VM veus latències altes i %us baix, %wa baix i st alt, el problema no és dins de la teva màquina: és a l'amfitrió, sobresubscrit. Res del que optimitzis al teu codi no ho arreglarà.
  • Un st sostingut per damunt del 5 % ja és senyal de contenció; per damunt del 10 % és un problema.
  • Al núvol públic, un st alt en instàncies barates sovint és el producte que has comprat: els tipus d'instància amb CPU "ampliable" funcionen així.

Tres peces més que convé conèixer: la fixació (pinning) lliga cada vCPU a un nucli físic concret, elimina la migració entre nuclis, conserva la localitat de memòria cau i de NUMA i redueix el jitter, per la qual cosa és la norma en càrregues sensibles a la latència; el CPUID emmascarat filtra les capacitats que anuncia la CPU perquè una VM no descobreixi instruccions (AVX-512, per exemple) que no existiran a la destinació si es migra; i el rellotge ha de ser paravirtualitzat (kvm-clock), perquè un convidat que és desallotjat no es pot refiar de comptar cicles.

Virtualització de la memòria: EPT/NPT, ballooning i sobrecompromís

Aquest és, conceptualment, el punt més bonic de la lliçó, i s'aguanta directament sobre la paginació de 02-04.

Recorda l'esquema normal: un procés fa servir adreces virtuals, i la MMU les tradueix a adreces físiques recorrent una taula de pàgines de quatre nivells, amb la TLB desant el resultat a la memòria cau.

Ara apareix un problema nou: el convidat té les seves pròpies taules de pàgines i tradueix de virtual-convidat a físic-convidat. Però el "físic" del convidat no és físic de debò: és una ficció que l'hipervisor ha de traduir a físic-amfitrió o màquina. Hi ha dues traduccions encadenades:

Adreça virtual del procés convidat
        ↓ (taules de pàgines del convidat)
Adreça "física" del convidat  ← fictícia
        ↓ (traducció de l'hipervisor)
Adreça física real de la màquina

Taules ombra: la solució històrica

Abans que el maquinari hi ajudés, l'hipervisor mantenia taules de pàgines ombra (shadow page tables): taules reals que traduïen directament de virtual-convidat a físic-real, combinant les dues traduccions per endavant. La MMU feia servir la taula ombra i no s'assabentava de res.

El cost era terrible: l'hipervisor havia de marcar les taules del convidat com a només lectura per assabentar-se de cada modificació. Cada vegada que el convidat creava o modificava una entrada —cosa que un sistema operatiu fa constantment—, es produïa una fallada de pàgina, un VM exit i una feina de sincronització. En càrregues amb moltes creacions de processos, la penalització podia superar el 40 %.

EPT i NPT: la traducció imbricada en maquinari

Intel EPT (Extended Page Tables) i AMD NPT/RVI van resoldre el problema afegint a la MMU una segona taula de pàgines, gestionada per l'hipervisor, que tradueix de físic-convidat a físic-real. Ara la MMU fa les dues traduccions tota sola, sense intervenció de ningú.

L'avantatge és enorme: el convidat pot modificar les seves taules de pàgines lliurement, sense VM exits. Les fallades de pàgina del convidat les resol el convidat. L'hipervisor només intervé quan falla la segona taula.

El preu és el nombre d'accessos a memòria quan falla la TLB. Amb paginació de quatre nivells, un recorregut normal costa 4 accessos. Amb dos nivells imbricats, cadascun d'aquests 4 accessos requereix al seu torn el seu propi recorregut de 4 nivells:

Escenari Accessos a memòria per fallada de TLB
Sense virtualitzar (4 nivells) 4
Amb EPT (4 × 4 + 4) fins a 24

Per això el consell pràctic de 02-04 es torna més important dins d'una màquina virtual: fer servir pàgines enormes (huge pages) de 2 MB redueix els nivells d'ambdues taules i multiplica l'abast de la TLB. En una base de dades virtualitzada, activar hugepages pot donar entre un 5 % i un 15 % de millora.

Ballooning: recuperar memòria d'un convidat

Un convidat que ha fet servir memòria no la retorna: el seu nucli la conserva a la seva memòria cau de pàgina, perquè des del seu punt de vista és gratis. L'amfitrió veu aquesta memòria com a usada encara que ningú no la necessiti.

La solució és astuta i es diu globus de memòria (balloon driver). És un controlador paravirtualitzat dins del convidat que, a petició de l'hipervisor, demana memòria al seu propi nucli i no la fa servir per a res. En "inflar-se", el nucli convidat pateix pressió de memòria i allibera memòries cau o fa swap, exactament com vam estudiar a 02-04; les pàgines que el globus obté es comuniquen a l'hipervisor, que les reassigna a altres màquines virtuals. En desinflar-se, el globus les allibera i el convidat recupera memòria. És una manera que l'hipervisor demani cooperació al convidat en lloc de robar-li pàgines a cegues.

Deduplicació: KSM

KSM (Kernel Samepage Merging) recorre la memòria buscant pàgines idèntiques entre màquines virtuals diferents i les fusiona en una sola còpia física marcada com a còpia en escriure (el mateix COW de fork que vam veure a 02-01). Si deu VM executen el mateix Debian, els seus binaris en memòria són idèntics i s'estalvia molt: en granges homogènies s'han mesurat estalvis del 30 % al 50 %.

Dos advertiments seriosos. Costa CPU: el dimoni ksmd escaneja contínuament, i es controla amb /sys/kernel/mm/ksm/pages_to_scan i sleep_millisecs. I té implicacions de seguretat: la fusió obre un canal encobert mesurable per temps —escriure en una pàgina fusionada triga més, perquè cal copiar-la—, cosa que permet a una VM deduir quines pàgines té una altra. En entorns multiinquilí, KSM es desactiva.

Sobrecompromís i el seu risc

Igual que amb la CPU, es pot prometre més RAM de la que hi ha: 8 VM de 8 GB sobre un amfitrió de 48 GB. Funciona mentre les VM no facin servir tot el que se'ls ha promès alhora.

El risc és qualitativament pitjor que amb la CPU. Si falta CPU, tot va lent. Si falta RAM, l'amfitrió comença a fer swap de pàgines de convidats, i això és catastròfic: el convidat no sap que la seva "RAM" és en un disc, de manera que les seves pròpies decisions de gestió de memòria es tornen absurdes —pot estar fent swap dels seus processos alhora que l'amfitrió en fa swap d'ell, el fenomen anomenat double paging—. I si s'esgota del tot, l'OOM killer de l'amfitrió mata un procés QEMU: una màquina virtual sencera desapareix de cop, com si li haguessin tret el cable d'alimentació.

Regla pràctica: sobrecomprometre CPU sí, amb vigilància del steal time; sobrecomprometre memòria només amb marge ampli, ballooning actiu i monitoratge, i mai en màquines crítiques.

Virtualització de l'E/S: emulació, virtio i assignació directa

Reprenem aquí el de 02-07: controladors, interrupcions i DMA. Hi ha tres estratègies, de pitjor a millor rendiment.

  1. Dispositiu emulat

QEMU emula un dispositiu real i conegut: una targeta de xarxa Intel e1000, un controlador IDE, una targeta gràfica Cirrus. El convidat fa servir el seu controlador estàndard, sense saber res.

El seu avantatge és la compatibilitat absoluta: qualsevol sistema operatiu, per antic que sigui, arrenca. El seu cost és desastrós: cada accés a un registre del dispositiu —cada outb, cada escriptura a memòria mapada— provoca un VM exit, i enviar un paquet de xarxa pot costar 10 o 15 sortides.

  1. Paravirtualitzat: virtio

Aquí reviu la idea de Xen. virtio defineix una interfície explícitament virtual: el convidat sap que està virtualitzat i fa servir un controlador dissenyat per parlar amb un hipervisor de la manera més barata possible.

El mecanisme central és la virtqueue: un anell de descriptors en memòria compartida entre convidat i hipervisor, amb la mateixa filosofia que els anells de DMA de 02-07.

  1. El convidat escriu diverses peticions a l'anell. Sense cap VM exit: és memòria compartida.
  2. Quan vol avisar, fa una sola notificació (kick), que sí que provoca una sortida.
  3. L'hipervisor processa el lot sencer i notifica la finalització amb una interrupció virtual.

Aquest "una sortida per lot en lloc d'una per operació" és tot el truc, i és exactament la mateixa lògica d'amortització que NAPI a 02-07. Els dispositius habituals són virtio-net, virtio-blk, virtio-scsi i virtio-balloon. A Linux vénen al nucli des de fa més de quinze anys, i per això els discos d'una VM moderna es diuen /dev/vda i no /dev/sda.

Una volta de rosca més és vhost: moure el processament de l'anell de l'espai d'usuari (QEMU) al nucli de l'amfitrió (vhost-net), eliminant canvis de context addicionals.

  1. Assignació directa: passthrough, IOMMU i SR-IOV

L'opció extrema: donar a la màquina virtual el dispositiu físic. El convidat parla amb el maquinari real amb el seu controlador natiu, sense capa intermèdia.

Això només és segur gràcies a la IOMMU que vam estudiar a 02-07. Sense ella, el dispositiu fa DMA a adreces físiques reals i una VM el podria programar perquè escrivís a la memòria de l'amfitrió: una escapada total. Amb IOMMU (VT-d o AMD-Vi), el dispositiu té la seva pròpia traducció d'adreces i només pot tocar la memòria de la seva VM. Com vam avançar llavors, aquesta és la peça que fa viable el passthrough.

El problema del passthrough pur és que un dispositiu es dona a una sola VM. SR-IOV (Single Root I/O Virtualization) ho resol al mateix maquinari: una targeta de xarxa compatible es presenta com una funció física (PF) i fins a 64 o 128 funcions virtuals (VF), cadascuna amb les seves cues i la seva adreça MAC. Cada VF s'assigna a una VM diferent, i el repartiment el fa el silici de la targeta.

Estratègia Sobrecàrrega Rendiment típic (xarxa 10G) Migració en calent Quan fer-la servir
Emulat (e1000) Molt alta 1-2 Gbit/s, CPU alta Compatibilitat, sistemes antics
virtio + vhost Baixa (~5-10 %) 8-9,5 Gbit/s Cas general: per defecte
Passthrough / SR-IOV Gairebé nul·la (<2 %) ~9,9 Gbit/s, latència mínima No (o molt complicada) NFV, alt rendiment, baixa latència

La columna de la migració és la que decideix a la pràctica: l'assignació directa lliga la VM a aquell maquinari concret, i amb això es perd la flexibilitat que és la meitat del valor de virtualitzar.

Xarxes virtuals: pont, NAT i xarxa interna

L'hipervisor implementa un commutador virtual en programari, al qual es connecten les interfícies de les VM. Hi ha tres topologies bàsiques.

Mode Què fa Adreça IP de la VM Visible des de la xarxa física Ús
Pont (bridge) La VM es connecta al mateix segment L2 que l'amfitrió Del DHCP de la xarxa real Servidors en producció
NAT L'amfitrió tradueix adreces; xarxa privada interna Privada (p. ex. 192.168.122.x) No (només sortida) Escriptori, desenvolupament
Xarxa interna / aïllada Només comunicació entre VM del mateix amfitrió Privada, sense sortida No Laboratoris, xarxes de proves

Per a una rèplica de meteo-01 que ha de rebre lectures de les estacions, el mode pont és el correcte: la VM apareix a la xarxa com una màquina més, amb la seva pròpia IP.

I aquí un advertiment de seguretat que enllaça amb 05-03: el tallafocs de l'amfitrió no veu el trànsit entre VM del mateix pont. Dues màquines virtuals connectades al mateix pont es parlen a nivell d'enllaç sense passar per nftables de l'amfitrió. Les conseqüències:

  • El tallafocs de cada convidat continua sent obligatori; no n'hi ha prou amb un de perimetral.
  • Per filtrar al pont calen mecanismes específics (ebtables, nftables a la família bridge, filtres de libvirt o grups de seguretat del proveïdor).
  • Segmentar amb xarxes virtuals separades per funció és millor que confiar en regles: la xarxa de gestió no ha de compartir pont amb la xarxa de dades.

El disc virtual: formats, instantànies i clonatge

El disc d'una màquina virtual és normalment un fitxer a l'amfitrió, encara que també pot ser un volum LVM (04-03) o un LUN d'una cabina.

Format Aprovisionament prim Instantànies Rendiment Notes
RAW Només amb fitxers dispersos No (llevat d'LVM/btrfs a sota) Màxim Simple; el més ràpid
QCOW2 , internes i encadenades Bo (5-10 % menys) Estàndard a KVM; compressió i xifratge
VMDK Bo VMware
VHDX Bo Hyper-V

L'aprovisionament prim (thin provisioning) significa que un disc declarat de 500 GB ocupa al principi uns pocs megabytes i creix a mesura que s'hi escriu. Estalvia moltíssim espai, però introdueix un risc operatiu real: es poden crear deu discos de 500 GB sobre un magatzem de 2 TB, i el dia que tots creixin, el magatzem s'omple i totes les VM s'aturen alhora. És exactament el mateix sobrecompromís d'abans, aplicat al disc, i requereix la mateixa vigilància.

Instantànies i el seu cost

Una instantània (snapshot) congela l'estat del disc en un instant. A QCOW2 s'implementa creant un fitxer nou que només desa els blocs modificats i encadenant-lo sobre l'anterior, que passa a ser de només lectura.

Això té dues conseqüències que sempre es menystenen. La primera és que cada lectura pot requerir recórrer la cadena: amb 5 instantànies encadenades, llegir un bloc pot implicar mirar en 5 fitxers, i el rendiment es degrada de manera acumulativa. La segona és que una instantània no és una còpia de seguretat: depèn del fitxer base, de manera que si es corromp el disc original la instantània no serveix de res, i si es desa al mateix emmagatzematge una fallada del magatzem se les endú totes dues. Les còpies 3-2-1 de 05-03 continuen sent obligatòries.

La regla operativa és clara: les instantànies són per a finestres curtes —"actualitzaré el nucli, si falla reverteixo"— i es consoliden o s'esborren en hores, no en mesos.

Plantilles i clonatge enllaçat

Una plantilla és una imatge base preparada (sistema instal·lat, actualitzat, enfortit i "netejat" d'identificadors únics, amb virt-sysprep). A partir d'ella:

  • Clonatge complet: es copia el disc sencer. Independent, ocupa el que ocupa, triga.
  • Clonatge enllaçat (linked clone): es crea un QCOW2 nou amb la plantilla com a base de només lectura. Ocupa megabytes i es crea en un segon; només s'escriuen les diferències.

Atura't un moment en el clonatge enllaçat, perquè és exactament la mateixa idea que veuràs a 06-02 amb les imatges de contenidors i overlayfs: una capa base compartida de només lectura i una capa d'escriptura per instància. La tècnica és tan bona que apareix als dos mons.

Migració en calent

Moure una màquina virtual en execució d'un amfitrió a un altre, sense apagar-la i amb una interrupció de desenes de mil·lisegons, és probablement la característica més impressionant de la virtualització, i la que fa viable el manteniment d'un centre de dades sense finestres d'aturada.

El que cal traslladar és: l'estat de la CPU (registres, uns quants kilobytes: trivial), l'estat dels dispositius (petit), el disc (si l'emmagatzematge és compartit, no hi ha res a copiar: és el motiu que existeixin les SAN i les cabines) i la memòria, que és el repte: 16 GB per una xarxa de 10 Gbit/s són uns 13 segons en el millor dels casos, i durant aquests 13 segons la VM continua modificant pàgines.

La tècnica estàndard és la precòpia iterativa:

  1. Ronda 0: es copien totes les pàgines de memòria mentre la VM continua executant-se.
  2. Rondes 1..n: es copien només les pàgines embrutades durant la ronda anterior (l'hipervisor les rastreja amb bits de "brut").
  3. Cada ronda és més petita que l'anterior, perquè hi ha menys temps per embrutar.
  4. Aturada final (stop-and-copy): quan el conjunt pendent baixa d'un llindar, es pausa la VM, es copien les darreres pàgines i l'estat de CPU, i s'arrenca a la destinació. És l'única interrupció real: típicament 50-300 ms.

El cas patològic s'anomena conjunt de treball calent: si la VM embruta pàgines més de pressa del que la xarxa les copia —una base de dades amb escriptures intensives—, les rondes no convergeixen i la migració no acaba mai. Les solucions són estrangular la CPU del convidat perquè embruti menys, o canviar d'estratègia a postcòpia: moure la VM immediatament i portar les pàgines de l'origen sota demanda, cosa que redueix el temps total però introdueix un risc greu —si la xarxa cau a mitges, la VM queda partida entre dues màquines i es perd—.

Requisits que cal tenir presents: CPU compatibles entre origen i destinació (d'aquí l'emmascarament de CPUID), emmagatzematge compartit o migració de disc afegida, xarxa comuna, i res de passthrough, pel que ja hem vist.

Pràctica amb KVM i libvirt: una rèplica de meteo-01

libvirt és la capa de gestió que unifica el tracte amb hipervisors diferents (KVM, Xen, LXC, ESXi) darrere d'una mateixa API, un dimoni (libvirtd) i una eina de línia de comandes (virsh). Defineix cada VM com un XML de domini.

Primer, comprovar que la màquina pot virtualitzar:

# La CPU té extensions de virtualització?
grep -c -E 'vmx|svm' /proc/cpuinfo      # >0 → sí (vmx=Intel, svm=AMD)

# Estan carregats els mòduls de KVM?
lsmod | grep kvm
# kvm_intel   372736  0
# kvm        1028096  1 kvm_intel

# Diagnòstic complet de libvirt
virt-host-validate qemu
#   QEMU: Checking for hardware virtualization    : PASS
#   QEMU: Checking if device /dev/kvm exists      : PASS
#   QEMU: Checking for cgroup 'cpu' controller    : PASS
#   QEMU: Checking for secure guest support       : WARN

Què fa. vmx/svm a /proc/cpuinfo indica suport de VT-x o AMD-V; si no hi apareix, gairebé sempre està desactivat a la BIOS, no absent. virt-host-validate recorre tots els requisits —mòduls, permisos de /dev/kvm, controladors de cgroup, IOMMU— i és el primer que cal executar quan alguna cosa no arrenca.

Ara creem la rèplica de proves. La idea és tenir un meteo-01-proves idèntic en configuració al de producció, on assajar actualitzacions i restauracions sense tocar el servei real:

sudo virt-install \
  --name meteo-01-proves \
  --memory 4096 \
  --vcpus 2,maxvcpus=4 \
  --cpu host-passthrough \
  --disk path=/var/lib/libvirt/images/meteo-01-proves.qcow2,size=60,format=qcow2,bus=virtio,cache=none,discard=unmap \
  --disk path=/var/lib/libvirt/images/meteo-01-dades.qcow2,size=200,format=qcow2,bus=virtio,cache=none \
  --network bridge=br0,model=virtio \
  --os-variant debian12 \
  --graphics none \
  --console pty,target_type=serial \
  --location 'https://deb.debian.org/debian/dists/stable/main/installer-amd64/' \
  --extra-args 'console=ttyS0,115200n8'

Opció per opció, i per què:

  • --memory 4096 i --vcpus 2,maxvcpus=4: 4 GB i 2 vCPU, amb marge per arribar a 4 en calent sense recrear la VM.
  • --cpu host-passthrough: exposa la CPU real de l'amfitrió tal qual, amb totes les seves instruccions. Dona el màxim rendiment i impedeix migrar a un amfitrió amb una CPU diferent. Per a producció amb migració s'hi faria servir un model genèric.
  • Dos discos: un de sistema (60 GB) i un altre de dades (200 GB) per a /var/lib/meteora. La separació és la mateixa decisió de 04-03: un ompliment de les dades no ha d'impedir arrencar.
  • bus=virtio: els discos paravirtualitzats dels quals hem parlat; apareixeran com a /dev/vda i /dev/vdb.
  • cache=none: crític per a la integritat. Desactiva la memòria cau de pàgina de l'amfitrió per a aquest disc, de manera que un fsync() del convidat (04-05) arribi de debò al maquinari. Amb cache=writeback va més ràpid, però un tall elèctric de l'amfitrió pot corrompre el sistema de fitxers del convidat i fer inútil el journaling.
  • discard=unmap: propaga el TRIM del convidat al fitxer QCOW2, de manera que esborrar dins de la VM allibera espai de debò a l'amfitrió. Sense això, un disc prim només creix.
  • --network bridge=br0,model=virtio: pont perquè la rèplica rebi lectures de les estacions, amb NIC paravirtualitzada.
  • --graphics none + --console pty,target_type=serial: sense escriptori; consola sèrie, que és el que vols en un servidor i el que permet veure els missatges d'arrencada.

I les operacions quotidianes:

virsh list --all                          # dominis definits i el seu estat
virsh start meteo-01-proves               # arrencar
virsh console meteo-01-proves             # entrar per consola sèrie (sortir: Ctrl+])
virsh shutdown meteo-01-proves            # apagada endreçada (ACPI, el convidat hi col·labora)
virsh destroy meteo-01-proves             # tall de corrent! només si no respon
virsh dumpxml meteo-01-proves > repl.xml  # exportar la definició completa
virsh setmem meteo-01-proves 6G --live    # ajustar memòria en calent (ballooning)
virsh domblkstat meteo-01-proves vda      # estadístiques d'E/S del disc
virsh snapshot-create-as meteo-01-proves abans-actualitzar --disk-only --atomic

Dos advertiments importants. virsh destroy no esborra la VM: l'apaga bruscament, equivalent a estirar el cable, amb el risc de corrupció consegüent; esborrar la definició és virsh undefine. I el virsh shutdown requereix que el convidat tingui instal·lat qemu-guest-agent o com a mínim respongui a ACPI; sense això, no passa res i hi ha gent que conclou erròniament que la VM està penjada.

Per a l'aprovisionament inicial sense instal·lador interactiu, l'estàndard és partir d'una imatge de núvol oficial i configurar-la amb cloud-init: usuaris, claus SSH, paquets i fitxers es declaren en un YAML que s'injecta al primer arrencada. És l'eina que converteix una plantilla genèrica en un meteo-01-proves ja configurat, i la veurem amb un exemple complet a El Sistema Operatiu al Núvol.

Seguretat de l'aïllament: superfície de l'hipervisor i escapades

La promesa de la virtualització és que un compromís total del convidat no arriba a l'amfitrió. Fins a quin punt és certa?

La superfície d'atac de l'hipervisor és tot allò que un convidat maliciós pot tocar:

Superfície Exemple Mitigació
Instruccions que provoquen VM exit Gestors de CPUID, MSR, E/S Codi molt auditat; poc freqüent
Dispositius emulats El controlador de disquet, USB, xarxa, VGA de QEMU La font més gran de CVE: treure el que no es faci servir
Interfícies paravirtualitzades virtio, balloon, canals de l'agent Validació estricta de descriptors
Canals laterals del maquinari Spectre, Meltdown, L1TF, MDS, Foreshadow Microcodi, l1tf=flush, desactivar SMT
Gestió API de libvirt, consoles, pla de control Autenticació i xarxa de gestió separada

El cas històric que tothom cita és VENOM (CVE-2015-3456), un desbordament al controlador de disquet virtual de QEMU. Un convidat amb root podia escapar-se a l'amfitrió, i la lliçó va ser humiliant i valuosíssima: la vulnerabilitat era en un dispositiu que ningú no feia servir des de feia vint anys i que hi era "per compatibilitat". És el principi de superfície mínima de 05-01, aplicat a la definició de la VM: treu el disquet, l'USB, l'àudio i la VGA dels teus servidors.

Les mesures d'enfortiment de l'amfitrió són, al capdavall, les mateixes del mòdul 5 aplicades al procés QEMU: s'executa com a usuari libvirt-qemu sense privilegis, amb sVirt (SELinux o AppArmor) etiquetant cada domini de manera que un QEMU compromès no pugui tocar els discos d'una altra VM, amb seccomp filtrant-li les syscalls i amb la IOMMU acotant el DMA. La defensa en profunditat no desapareix per virtualitzar: s'aplica un nivell més avall.

Per què una VM aïlla més que un contenidor

Aquest és el punt que cal endur-se de la lliçó, i que prepara la següent:

  • En una màquina virtual, la frontera és l'hipervisor. Un atacant que aconsegueix root al convidat i trenca el nucli convidat sencer encara té al davant una interfície estreta i molt auditada: uns quants gestors de VM exit i uns dispositius virtio.
  • En un contenidor, la frontera és el nucli de l'amfitrió, i la seva interfície és la taula de crides al sistema: entre 300 i 400 punts d'entrada, molts amb historial de fallades.

La comparació quantitativa és brutal: centenars de syscalls davant d'un grapat de gestors. Per això, quan cal executar codi de tercers no fiable —el cas típic del núvol públic, o d'un sistema que compila codi de clients—, la resposta correcta continua sent una màquina virtual, o una de les tecnologies intermèdies que veurem a 06-03.

Quan cal virtualitzar i quan no

La sobrecàrrega real, mesurada sobre KVM modern amb virtio i EPT:

Recurs Sobrecàrrega típica Condició
CPU (càlcul pur) 1-3 % Sense sobresubscripció
Memòria (accés) 2-8 % Pitjor amb moltes fallades de TLB; millora amb huge pages
Memòria (consum) +200-500 MB per VM El nucli convidat i la seva memòria cau
Disc seqüencial 5-10 % Amb virtio-blk i cache=none
Disc aleatori (IOPS) 10-25 % El més castigat
Xarxa (throughput) 5-15 % Gairebé nul amb SR-IOV
Latència de xarxa +20-50 µs Rellevant només en càrregues molt sensibles

Amb aquests números a la mà:

Virtualitza quan necessitis aïllament fort entre càrregues, executar sistemes operatius diferents, consolidar servidors infrautilitzats, poder migrar en calent per mantenir el ferro sense aturades, tenir instantànies i entorns de proves idèntics a producció, o executar codi que no controles.

No virtualitzis quan la càrrega necessiti tot el maquinari (una base de dades que esprem un NVMe, un node de càlcul científic), quan la latència sigui crítica fins al microsegon (trading, telecomunicacions sense SR-IOV), quan l'accés al maquinari sigui especial (targetes d'adquisició, GPU sense virtualització), o quan l'objectiu real sigui empaquetar i desplegar una aplicació: per a això els contenidors són incomparablement més lleugers, i és justament la lliçó següent.

I un consell d'arquitectura: no és una elecció binària. El patró més estès avui és contenidors dins de màquines virtuals: la VM aporta la frontera de seguretat forta entre inquilins o entorns, i el contenidor aporta la densitat i la velocitat de desplegament dins d'aquesta frontera.

Errors Habituals i Consells

Creure que la virtualització és "lenta". Ho era el 2004, amb traducció binària i taules ombra. Avui, una càrrega de CPU pura perd un 1-3 %. El que sí que continua costant és l'E/S mal configurada: fer servir dispositius emulats en lloc de virtio pot multiplicar per cinc el consum de CPU de la xarxa.

Ignorar el st de top. És el primer indicador que cal mirar en una VM lenta i l'únic que diu "el problema no és aquí dins". Moltes hores d'optimització s'han perdut afinant una aplicació quan el que hi havia era un amfitrió sobresubscrit.

Sobrecomprometre memòria com se sobrecompromet CPU. No són equivalents. Falta de CPU és lentitud; falta de RAM és l'OOM killer de l'amfitrió matant una VM sencera. Deixa sempre marge per al mateix amfitrió (uns 2-4 GB) i monitora.

Fer servir cache=writeback en producció. És temptador perquè els bancs de proves milloren molt, però trenca la garantia de durabilitat de fsync() que vam estudiar a 04-05: el convidat es pensa que ha escrit i la dada és a la RAM de l'amfitrió. Un tall elèctric corromp el sistema de fitxers del convidat. Fes servir cache=none (o directsync) per a dades que importen.

Acumular instantànies. Cada baula de la cadena afegeix indirecció a cada lectura, i una cadena de deu instantànies pot reduir el rendiment a la meitat. I no són còpies de seguretat: depenen del fitxer base.

Confondre virsh destroy amb esborrar. destroy és "estirar el cable"; undefine és esborrar la definició. Aprendre-ho per les males amb un domini de producció és un ritu de pas que convé evitar.

Deixar dispositius emulats innecessaris. El disquet, l'USB, l'àudio i la targeta gràfica d'un servidor virtual només aporten superfície d'atac (recorda VENOM). Revisa l'XML del domini i treu-ne tot el que no facis servir.

Oblidar el tallafocs dins de la VM. El trànsit entre VM del mateix pont no passa per nftables de l'amfitrió. Cada convidat necessita la seva pròpia política, amb policy drop com a 05-03.

Consell: mesura sempre a dins i a fora. Quan una VM va lenta, compara les seves mètriques amb les de l'amfitrió (vmstat, iostat de l'amfitrió, virsh domstats). La meitat dels problemes de rendiment en virtualització es diagnostiquen mirant al nivell equivocat.

Consell: activa huge pages per a càrregues amb molta memòria. Amb EPT, una fallada de TLB pot costar fins a 24 accessos a memòria. Les pàgines de 2 MB redueixen dràsticament aquesta pressió, i en bases de dades virtualitzades la millora és mesurable.

Exercicis

Exercici 1: aplicar el teorema de Popek i Goldberg

Una arquitectura fictícia NOVA-32 té aquestes instruccions:

Instrucció Què fa Llança excepció en mode usuari?
LDTAB Carrega el registre de taula de pàgines
RDTAB Llegeix el registre de taula de pàgines No
SETINT Habilita/deshabilita interrupcions
RDMODE Retorna l'anell actual d'execució No
ADDW Suma dos registres No
IOWR Escriu en un port d'E/S

(a) Classifica cada instrucció com a privilegiada, sensible (i de quin tipus) o innòcua. (b) Compleix NOVA-32 el teorema? Justifica-ho. (c) Per a cada instrucció problemàtica, descriu un escenari concret en què trenqui l'equivalència o el control de recursos. (d) Proposa les tres solucions històriques aplicades a aquest cas i digues quina triaries si el convidat és un sistema propietari del qual no tens el codi.

Exercici 2: dimensionar i diagnosticar un amfitrió per a Meteora

Un amfitrió té 16 nuclis físics, 64 GB de RAM i un NVMe. Cal allotjar-hi: meteo-01 (producció: 8 vCPU, 24 GB), meteo-01-proves (4 vCPU, 8 GB), un servidor d'integració contínua (4 vCPU, 8 GB, a ràfegues), un servidor de mètriques (2 vCPU, 4 GB) i dues VM de laboratori (2 vCPU, 4 GB cadascuna).

(a) Calcula la ràtio de sobresubscripció de CPU i de memòria, i digues si et semblen acceptables, justificant-ho. (b) Dins de meteo-01 observes %Cpu(s): 9,1 us, 2,0 sy, 71,0 id, 0,6 wa, 17,3 st: interpreta'n el diagnòstic i digues quines tres accions concretes prendries, en ordre. (c) Quina configuració de disc i de xarxa donaries a meteo-01 davant de les VM de laboratori, i per què? (d) Activaries KSM? Raona la resposta considerant l'estalvi esperable i el risc.

Exercici 3: decidir l'estratègia d'E/S i de migració

Meteora vol que la rèplica de meteo-01 es pugui migrar en calent entre dos amfitrions per poder actualitzar el maquinari sense aturar el servei. L'ingestor rep unes 8.000 lectures per segon (24 bytes cadascuna) i meteo-api serveix trànsit HTTPS.

(a) Tria l'estratègia d'E/S de xarxa i justifica-la davant de les altres dues, considerant el requisit de migració. (b) Estima el temps de la fase de precòpia si la VM té 24 GB de RAM i la xarxa de migració és de 10 Gbit/s, i explica de què depèn que la migració convergeixi. (c) Enumera els requisits que han de complir els dos amfitrions perquè la migració sigui possible. (d) Descriu què passaria si a més s'hagués assignat una VF d'SR-IOV a la VM, i com ho resoldries.

Solucions

Solució 1

(a) Classificació:

Instrucció Classificació
LDTAB Privilegiada i sensible a la configuració (canvia l'estat del sistema). Correcta: atrapa.
RDTAB Sensible al comportament, no privilegiada. Problemàtica.
SETINT Privilegiada i sensible a la configuració. Correcta.
RDMODE Sensible al comportament, no privilegiada. Problemàtica.
ADDW Innòcua.
IOWR Privilegiada i sensible (control de recursos). Correcta.

(b) No compleix el teorema. El conjunt d'instruccions sensibles {LDTAB, RDTAB, SETINT, RDMODE, IOWR} no està contingut en el de privilegiades {LDTAB, SETINT, IOWR}: RDTAB i RDMODE se'n surten. Amb trap-and-emulate pur, aquestes dues s'executarien sense que l'hipervisor se n'assabentés.

(c) Escenaris concrets:

  • RDTAB: el nucli convidat carrega la seva taula de pàgines amb LDTAB (que atrapa i l'hipervisor emula, apuntant en realitat a la taula ombra). Després la llegeix amb RDTAB per comprovar-la i obté l'adreça de la taula real de l'amfitrió, no la que ell hi havia escrit. Trenca l'equivalència (el comportament difereix del maquinari real) i, a més, filtra informació de l'amfitrió, debilitant el control de recursos.
  • RDMODE: el nucli convidat es pensa que és a l'anell 0 i comprova el seu nivell amb RDMODE; obté, per exemple, "anell 1". Un nucli que verifica el seu propi privilegi abans de fer alguna cosa crítica entrarà en una branca d'error o entrarà en pànic. Trenca l'equivalència i, a més, permet al convidat detectar que està virtualitzat, cosa rellevant en anàlisi de programari maliciós.

(d) Les tres solucions aplicades:

  • Traducció binària dinàmica: s'escaneja el codi de nucli del convidat i se substitueixen RDTAB i RDMODE per salts a l'hipervisor, que retorna els valors virtuals que el convidat espera. Funciona sense tocar el convidat ni el maquinari, amb cost de complexitat i de rendiment.
  • Paravirtualització: es modifica el nucli convidat perquè no faci servir RDTAB ni RDMODE, sinó hipercalls equivalents. Rendiment òptim, però exigeix el codi font del convidat.
  • Assistència per maquinari: s'afegeix un mode no arrel en què RDTAB i RDMODE provoquen sortida a l'hipervisor. És la solució neta, però requereix una revisió de l'arquitectura.

Elecció amb un convidat propietari sense codi font: traducció binària dinàmica, que és l'única de les dues primeres que no exigeix modificar-lo. Si es pot redissenyar el silici, l'assistència per maquinari és superior en tot (més simple, més ràpida i més segura), i és exactament el camí que va seguir x86 entre 1999 i 2006.

Solució 2

(a) Ràtios.

  • CPU: 8 + 4 + 4 + 2 + 2 + 2 = 22 vCPU sobre 16 nuclis → 1,375:1. Perfectament raonable: és dins del rang conservador, i diverses d'aquestes càrregues (CI, laboratori) són intermitents.
  • Memòria: 24 + 8 + 8 + 4 + 4 + 4 = 52 GB compromesos sobre 64 GB. No hi ha sobrecompromís, però el marge és de 12 GB, del qual cal descomptar el mateix amfitrió (2-4 GB) i les estructures de QEMU (uns 200-500 MB per VM, és a dir 1,2-3 GB). El marge real queda al voltant de 5-8 GB: és just però viable; no admet afegir-hi més VM sense sobrecomprometre.

(b) Interpretació del top. st 17,3 amb us 9,1 i id 71,0 és un diagnòstic de llibre: el convidat no està saturat, però no rep la CPU que es pensa que té. El wa 0,6 descarta que sigui disc. Per tant el problema és a l'amfitrió, no a meteo-01: hi ha contenció de CPU per part d'altres VM. Accions, en ordre:

  1. Mirar l'amfitrió, no el convidat: vmstat 1, virsh domstats --cpu-total de totes les VM, per identificar qui està consumint. El més probable és que sigui el servidor de CI, que treballa a ràfegues i satura.
  2. Contenir el culpable limitant-li la CPU: virsh schedinfo ci --set cpu_quota=... o, millor, CPUQuota via cgroups (06-02). L'objectiu és que la càrrega elàstica no faci mal a la crítica.
  3. Prioritzar i fixar producció: apujar el pes de CPU de meteo-01 (cpu_shares) i considerar pinning de les seves 8 vCPU a nuclis concrets, reservant la resta per a les altres, per eliminar la contenció de manera estructural en lloc de reactiva.

(c) Configuració diferenciada.

meteo-01 (producció) VM de laboratori
Disc RAW o QCOW2 amb cache=none, io=native, bus=virtio, discos separats sistema/dades QCOW2 amb clonatge enllaçat, cache=writeback
Xarxa virtio + vhost-net en pont, per rebre de les estacions NAT o xarxa interna aïllada
Instantànies Només finestres curtes; còpies 3-2-1 reals Lliures, és la seva utilitat principal

La justificació és la integritat davant de la velocitat: en producció, cache=none garanteix que fsync() arribi al maquinari i que el journaling d'ext4 compleixi la seva promesa; al laboratori, la pèrdua de dades davant d'un tall és irrellevant i el clonatge enllaçat estalvia espai i temps.

(d) KSM: no, o amb matisos. L'estalvi esperable és alt perquè les sis VM comparteixen distribució (Debian estable) i per tant moltes pàgines de binaris idèntiques: caldria esperar entre un 20 % i un 40 % de reducció del consum, que aquí seria valuós atès el marge just. Però: l'amfitrió no està sobrecompromès, de manera que el benefici no és necessari ara mateix; ksmd consumeix CPU precisament en una màquina que ja mostra contenció de CPU; i KSM obre un canal encobert entre VM. Decisió: no activar-lo per defecte. Si en el futur calgués sobrecomprometre memòria, s'activaria amb pages_to_scan baix i només entre VM del mateix nivell de confiança, mai si hi hagués càrregues de tercers.

Solució 3

(a) Estratègia d'E/S de xarxa: virtio-net amb vhost-net.

  • Davant del dispositiu emulat (e1000): amb 8.000 lectures/s més el trànsit HTTPS, l'emulació provocaria de l'ordre de desenes de milers de VM exits per segon, amb un cost de ~1.200 cicles cadascun; es malbarataria una fracció notable d'un nucli només en sortides. Descartat per rendiment.
  • Davant d'SR-IOV / passthrough: donaria el millor rendiment i la latència més baixa, però impedeix la migració en calent, que és el requisit explícit de l'enunciat. Descartat per requisit funcional.
  • virtio + vhost-net deixa la sobrecàrrega en el 5-15 % amb migració plenament suportada. I el volum no és exigent: 8.000 × 24 B = 192 KB/s de dades útils, ridícul per a qualsevol enllaç modern; el que importa aquí és la taxa de paquets i d'interrupcions, que és justament el que virtio amortitza en agrupar a la virtqueue.

(b) Temps de precòpia i convergència. 24 GB = 192 Gbit. A 10 Gbit/s teòrics, amb un aprofitament realista del 80 % (8 Gbit/s), la ronda 0 triga uns 24 segons. Les rondes següents copien només les pàgines embrutades durant l'anterior.

La convergència depèn de comparar dues velocitats: la taxa d'embrutiment (pàgines modificades per segon × 4 KB) davant de l'amplada de banda de migració. Si meteo-01 embruta 200 MB/s i la xarxa transfereix 1 GB/s, cada ronda és cinc vegades menor que l'anterior i en 4-5 rondes es baixa del llindar: convergeix i l'aturada final és de desenes de mil·lisegons. Si meteo-01 estigués reescrivint contínuament una memòria cau gran a /dev/shm/meteora-cache a més velocitat que la xarxa, no convergiria mai. En aquest cas: augmentar l'amplada de banda de migració, estrangular la CPU del convidat (auto-converge), activar compressió, o passar a postcòpia assumint-ne el risc.

(c) Requisits dels dos amfitrions:

  1. CPU compatible: mateix fabricant i model presentat al convidat. Amb --cpu host-passthrough la migració només funciona entre CPU idèntiques; per migrar de debò cal fer servir un model de CPU comú que emmascari CPUID al mínim comú denominador.
  2. Emmagatzematge compartit (SAN, NFS, Ceph) accessible per tots dos amb la mateixa ruta; si no, cal afegir-hi migració de disc, que multiplica el temps.
  3. Xarxa comuna entre tots dos amfitrions, idealment una xarxa de migració dedicada per no competir amb el trànsit de servei, i el pont br0 definit amb el mateix nom a tots dos.
  4. Versions compatibles de QEMU i libvirt (es migra cap endavant, no cap enrere), i comunicació autenticada entre els libvirtd.
  5. Sense dispositius assignats directament i sense recursos locals de l'amfitrió (ISO muntades des de rutes locals, per exemple).

(d) Amb una VF d'SR-IOV assignada, la migració en calent no és possible directament, perquè l'estat del dispositiu físic és dins de la targeta i no és transferible, i perquè el convidat té carregat un controlador específic d'aquell maquinari. Solucions, de més simple a més complexa:

  • No fer servir SR-IOV i quedar-se a virtio: és la decisió correcta aquí, perquè el volum de Meteora no ho exigeix.
  • Vinculació (bonding) de la VF amb una virtio-net dins del convidat: abans de migrar es desenganxa la VF, el trànsit continua per virtio durant la migració, i a la destinació es torna a enganxar una VF. Funciona, però afegeix complexitat de configuració.
  • Acceptar una migració amb aturada (apagar, moure, arrencar), cosa que incompleix el requisit.

Conclusió

Virtualitzar és donar a un sistema operatiu sencer la il·lusió d'una màquina pròpia, i la diferència amb el que fa un sistema operatiu és que l'hipervisor no ofereix abstraccions noves, sinó més còpies de la mateixa màquina. Va néixer per diners —utilitzacions del 5-15 % en centres de dades on calia dedicar un servidor per servei per aïllar-los— i va resoldre alhora la consolidació, l'aïllament i la flexibilitat, amb un efecte col·lateral que va resultar decisiu: la màquina va passar a ser un fitxer, i amb això van arribar les instantànies, les plantilles, el clonatge i la migració.

El marc teòric són els criteris de Popek i Goldberg —equivalència, control de recursos i eficiència, que és el que separa la virtualització de l'emulació— i el seu teorema: cal que les instruccions sensibles siguin un subconjunt de les privilegiades per poder aplicar trap-and-emulate. x86 no el complia amb 17 instruccions sensibles no privilegiades, de les quals POPF és l'exemple canònic: en mode usuari ignora el bit d'interrupcions sense llançar excepció. Les tres respostes van ser la traducció binària dinàmica de VMware (sense tocar maquinari ni convidat, amb cost de complexitat), la paravirtualització de Xen (hipercalls, rendiment excel·lent, però cal modificar el convidat; avui sobreviu a l'E/S) i l'assistència per maquinari de VT-x i AMD-V, que va afegir el mode arrel i no arrel —l'"anell -1"— amb la VMCS desant l'estat i el VM exit com a unitat de cost, uns 1.000-1.500 cicles que tot el disseny posterior s'ha dedicat a evitar.

La classificació en tipus 1 i tipus 2 continua sent útil, però KVM la difumina: en ser un mòdul del nucli Linux, converteix Linux en hipervisor reutilitzant-ne el planificador, el gestor de memòria i els controladors, de manera que una VM és un procés i cada vCPU és un fil —amb tot el que això implica: nice, cgroups, taskset i fins i tot l'OOM killer se li apliquen—. Sobre els tres recursos: en CPU, dos nivells de planificació superposats, sobresubscripció raonable de 2:1 a 4:1 i el st de top com l'indicador que diu "el problema no és aquí dins"; en memòria, la doble traducció resolta històricament amb taules ombra i avui amb EPT/NPT, que elimina VM exits a canvi de fins a 24 accessos per fallada de TLB —d'aquí la importància de les huge pages—, més ballooning, KSM amb el seu canal encobert i un sobrecompromís molt més perillós que el de CPU; i en E/S, l'escala d'emulació, virtio (per defecte, perquè amortitza sortides agrupant a la virtqueue) i passthrough/SR-IOV amb IOMMU, que dona el màxim rendiment a canvi de perdre la migració.

Al voltant, les peces pràctiques: xarxes en pont, NAT o aïllades —amb l'advertiment que nftables de l'amfitrió no veu el trànsit entre VM del mateix pont—, discos amb aprovisionament prim i el seu risc d'ompliment simultani, instantànies que degraden el rendiment i no són còpies de seguretat, clonatge enllaçat que anticipa exactament el model de capes dels contenidors, i la migració en calent per precòpia iterativa, el repte de la qual és la memòria i l'enemic de la qual és un conjunt de treball que s'embruta més de pressa del que la xarxa copia. A KVM i libvirt, tot això es maneja amb virsh i virt-install, on les decisions que de debò importen són bus=virtio, cache=none per integritat i discard=unmap per espai.

I la conclusió de seguretat, que és la que enllaça amb el que ve: la superfície de l'hipervisor és estreta però real —VENOM era al controlador de disquet que ningú no feia servir—, s'enforteix amb les mateixes armes del mòdul 5 aplicades a QEMU (usuari sense privilegis, sVirt, seccomp, IOMMU), i aïlla més que un contenidor per una raó quantificable: la interfície que un convidat hostil pot atacar són uns pocs gestors de VM exit, davant de les 300-400 crides al sistema que exposa un nucli compartit.

Ara bé: tot això té un preu que no és el percentatge de CPU, sinó el pes. Cada màquina virtual carrega amb un nucli sencer, un init, un sistema de fitxers complet i 200-500 MB de RAM només per existir; arrenca en desenes de segons; i la seva imatge ocupa gigabytes. Si el que vols no és executar un altre sistema operatiu, sinó simplement empaquetar la teva aplicació amb les seves dependències i limitar el que veu i el que consumeix, estàs pagant per un nucli que no necessites.

I si en lloc de duplicar el nucli, aprofitéssim que el nucli que ja tens sap mentir a un procés sobre quins fitxers existeixen, quins processos hi ha, quina xarxa veu i quanta memòria pot fer servir? Això no requereix hipervisor: requereix espais de noms i grups de control, dos mecanismes que ja són dins de Linux i que anem citant des del mòdul 3. És la lliçó següent: Contenidors: Namespaces i cgroups.

Fonaments de Sistemes Operatius

Mòdul 1: Introducció als Sistemes Operatius

Mòdul 2: Gestió de Recursos

Mòdul 3: Concurrència

Mòdul 4: Estructures de Fitxers

Mòdul 5: Protecció i Seguretat del Sistema

Mòdul 6: Virtualització i Contenidors

Mòdul 7: Administració i Diagnòstic a la Pràctica

© Copyright 2026. Tots els drets reservats