Podman, KVM e container: una guida pratica alla virtualizzazione sicura

Ultimo aggiornamento: 9 marzo 2026
  • I container condividono il kernel con l'host, quindi il loro isolamento dipende dagli spazi dei nomi, dai cgroup e dal rafforzamento del sistema.
  • Podman offre maggiore sicurezza rispetto al classico Docker, evitando un demone root centrale e facilitando l'esecuzione senza root.
  • Le reti bridge, host e rootless (slirp4netns, paste) in Podman consentono di regolare l'equilibrio tra connettività e isolamento.
  • La combinazione di KVM, container e best practice (rootless, MAC, seccomp, scansione delle immagini) fornisce una piattaforma solida per la produzione.

Podman KVM virtualizzazione sicura

Quando si inizia a configurare un ambiente container complesso su Linux, sorge spontanea la stessa domanda: fino a che punto è sicuro affidarsi esclusivamente ai container e quando è consigliabile passare a KVM o micro-VM? Negli ambienti aziendali, dove Docker, Podman, LXC, macchine virtuali KVM e vari hypervisor coesistono, una solida comprensione del modello di isolamento è fondamentale per evitare errori.

Inoltre, non sono rari scenari piuttosto complessi, come ad esempio il tentativo di utilizzare Docker Desktop o Podman all'interno di una macchina virtuale in VirtualBox o KVM , dove entrano in gioco diversi livelli di virtualizzazione (annidati o meno) ed è facile imbattersi in errori come "KVM non è abilitato sull'host". Organizziamo tutti questi elementi – container, macchine virtuali, Podman, KVM, rete e sicurezza – e vediamo come combinarli per ottenere una virtualizzazione realmente sicura e pratica.

Virtualizzazione classica con KVM e macchine virtuali

Il fondamento di questa configurazione rimane la completa virtualizzazione dei sistemi operativi tramite hypervisor come KVM, Xen o ESXi . In questo caso, l'hardware fisico esegue un hypervisor responsabile della "suddivisione" della macchina in diverse macchine virtuali indipendenti.

In questo modello, ogni macchina virtuale avvia il proprio kernel e sistema operativo guest , completamente separati dall'host. L'hypervisor (ad esempio, KVM, integrato nel kernel Linux) funge da guardiano tra l'hardware e le macchine virtuali ed è responsabile del forte isolamento tra di essi.

Generalmente si distinguono due tipi di hypervisor: gli hypervisor di tipo 1 o bare metal, che vengono eseguiti direttamente sull'hardware, e gli hypervisor di tipo 2, ospitati su un sistema operativo esistente. VirtualBox o VMware Workstation sono esempi di hypervisor di tipo 2 , mentre un KVM gestito da libvirt su un server Linux è più simile a uno scenario di tipo 1, anche se condivide lo spazio con l'host stesso.

Il vantaggio principale di questo approccio è che ogni macchina virtuale possiede un proprio stack completo: kernel, spazio utente, servizi, rete e storage . Ciò offre grande flessibilità e la possibilità di eseguire diversi sistemi operativi e versioni del kernel sulla stessa macchina fisica.

Per gestire queste piattaforme virtualizzate in GNU/Linux, si utilizza quasi sempre libvirt come API di orchestrazione , sulla quale sono supportati strumenti grafici e da riga di comando, oppure progetti più ampi come OpenStack, che crea cloud completi combinando macchine virtuali, server bare metal e container.

Requisiti hardware e problemi comuni con KVM

Prima di eseguire KVM o utilizzare soluzioni che dipendono da esso (come alcune configurazioni di Docker Desktop o micro-VM), è necessario assicurarsi che il processore supporti la virtualizzazione hardware (VT-x su Intel o SVM/AMD-V su AMD) e che sia abilitata nel BIOS/UEFI.

In Linux, abbiamo un controllo rapido: se il comando `grep -E 'svm|vmx' /proc/cpuinfo` non restituisce nulla, o se l'opzione è ancora disabilitata nel firmware, KVM non funzionerà. Inoltre, negli scenari in cui eseguiamo macchine virtuali all'interno di un'altra macchina virtuale (virtualizzazione nidificata), l'hypervisor di livello superiore deve esporre esplicitamente tali estensioni alla macchina virtuale guest.

Quando qualcosa va storto, compaiono i soliti messaggi, come il famigerato popup di Docker Desktop in RHEL 9 all'interno di VirtualBox: "KVM non è abilitato sull'host ". Il problema in questo caso non risiede direttamente in Docker o RHEL, ma piuttosto nel fatto che la macchina virtuale di VirtualBox non riceve le estensioni di virtualizzazione necessarie per eseguire KVM al suo interno.

Comandi come `modprobe kvm` o `modprobe kvm_amd / kvm_intel` dovrebbero caricare i moduli del kernel senza messaggi di errore. Se `dmesg` non mostra nulla di rilevante, o `lsmod | grep kvm` mostra solo `kvm` e `irqbypass`, e mancano i moduli specifici della CPU, è molto probabile che la virtualizzazione hardware non sia esposta al sistema guest o sia disabilitata nel firmware.

In questi scenari, anche se tutte le funzionalità possibili sono abilitate all'interno della macchina virtuale, se VirtualBox non offre la virtualizzazione nidificata o è configurata in modo errato, KVM semplicemente non funzionerà . La soluzione consiste nel rivedere la configurazione dell'hypervisor host (VirtualBox, VMware, ecc.) e abilitare la virtualizzazione nidificata, se disponibile.

Container: virtualizzazione leggera a livello di sistema operativo

A differenza delle macchine virtuali tradizionali, i container offrono un approccio diverso: non emulano tutto l'hardware, ma riutilizzano il kernel dell'host e isolano processi e risorse utilizzando le funzioni del kernel Linux.

Un container è essenzialmente un insieme di processi incapsulati utilizzando namespace e cgroup . All'interno di questo "ambiente", l'applicazione crede di essere in esecuzione sulla propria macchina, con il proprio file system, interfacce di rete e PID, ma in realtà condivide il kernel con l'host.

I container offrono prestazioni quasi native perché non è coinvolto un secondo kernel né un'emulazione hardware completa . L'overhead è minimo: sufficiente per montare i namespace, limitare le risorse con i cgroup e applicare le policy di sicurezza (seccomp, AppArmor, SELinux, ecc.).

Tuttavia, questa architettura ha una conseguenza significativa: tutti i carichi di lavoro condividono lo stesso kernel fisico . Una grave vulnerabilità in quel kernel può improvvisamente diventare un potenziale vettore di fuga per tutti i container in esecuzione su quel nodo.

Ecco perché si dice spesso che l'isolamento dei container non è "assoluto" come quello di una macchina virtuale . Se qualcuno riesce a sfruttare un bug critico del kernel, i namespace cessano di essere una barriera efficace e i confini tra container e host possono essere infranti.

Namespace e cgroup: il fondamento dell'isolamento dei container

In Linux, l'isolamento dei container si basa su due pilastri principali: i namespace per separare le viste del sistema e i cgroup per limitare e gestire le risorse.

  Configurazione delle ventole del PC: una guida completa alle curve e al flusso d'aria

I namespace isolano elementi come PID, rete, punti di mount, IPC, hostname (UTS) e utenti . In questo modo, un processo "vede" solo i processi, le interfacce di rete, il file system e l'hostname del proprio namespace, dandogli la sensazione di trovarsi su un sistema diverso.

I cgroup, a loro volta, consentono di specificare la quantità di CPU, memoria, I/O del disco, numero di processi, ecc., che un container può consumare . Ciò impedisce che un carico incontrollato sovraccarichi l'intero host, aspetto fondamentale quando si condividono i nodi tra team o progetti.

Tuttavia, è fondamentale comprendere che i namespace e i cgroup da soli non costituiscono un sistema di sicurezza completo . Non impediscono lo sfruttamento delle vulnerabilità del kernel, non filtrano le chiamate di sistema né gestiscono politiche di accesso granulari. Sono le fondamenta, ma devono essere rafforzate.

Il modo più sensato per rafforzare l'ambiente è combinare filtri per le chiamate di sistema con seccomp, policy MAC (AppArmor o SELinux) e una drastica riduzione delle capacità dei processi, idealmente insieme a namespace utente e modalità senza privilegi di root per minimizzare i danni nel caso in cui qualcuno riesca a uscire dal container.

Minacce alla sicurezza sulle piattaforme container

In una vera piattaforma container, la superficie di attacco è distribuita su diversi livelli: il kernel e il runtime (runc, containerd, CRI-O), la catena di fornitura delle immagini, la configurazione del container/orchestratore e l'infrastruttura sottostante (cloud, storage, IAM, rete).

Tra i vettori di attacco più comuni si annoverano le vulnerabilità del software nel kernel o nell'ambiente di runtime dei container, l'utilizzo di immagini non sicure o non aggiornate, errori di configurazione in Kubernetes o Docker (container con privilegi elevati, mount host pericolosi, modalità di rete eccessivamente aperte) e falle di sicurezza nei registri o nelle pipeline CI/CD.

In molti casi reali, non esiste un singolo "bug magico", ma piuttosto una combinazione di vulnerabilità note e configurazioni inadeguate : container in esecuzione come root, utilizzo del flag --privileged, montaggio del file system dell'host all'interno del container e assenza di seccomp o MAC.

A tutto ciò si aggiunge il problema della catena di fornitura: le immagini di base pubbliche spesso contengono CVE, pacchetti obsoleti e dipendenze gestite in modo inadeguato . È relativamente facile che un malware si insinui o che una libreria compromessa rimanga inattiva fino a quando non si verificano le condizioni ideali per la sua esecuzione.

Pertanto, in produzione non è facoltativo: è necessario scansionare le immagini alla ricerca di vulnerabilità con strumenti come Trivy, Clair o Grype , controllare quali registri vengono utilizzati (Harbor, Quay, GHCR con policy) e firmare/verificare le immagini prima di distribuirle in un cluster.

Docker vs Podman: impatto sul modello di minaccia

Nel mondo dei container per applicazioni, Docker e Podman svolgono funzioni simili, ma la loro architettura differisce considerevolmente, e ciò modifica il modello di sicurezza con cui operiamo su un host.

Docker, nella sua classica modalità rootful, si basa su un demone centrale (dockerd) che viene eseguito come root . La CLI comunica con questo demone tramite /var/run/docker.sock o tramite TCP, ed è questo processo privilegiato che crea e distrugge i container, gestisce immagini, reti e volumi e interagisce con i registri.

Questo significa che chiunque abbia accesso al socket Docker ha di fatto accesso root all'host , poiché può avviare container con privilegi elevati, montare filesystem arbitrari e modificare configurazioni critiche. Il demone diventa quindi un singolo punto di errore.

Podman, d'altro canto, è nato con l'idea di essere un motore di container senza daemon, il più possibile "nativo" di Linux . Non esiste un processo centrale permanente; i container sono controllati dall'utente o da systemd, e l'integrazione con quest'ultimo permette di gestirli tramite unità di servizio.

Questo approccio si allinea meglio al modello Unix secondo cui "ogni processo appartiene all'utente che lo avvia" ed evita di dipendere da un demone con privilegi elevati . Inoltre, Podman offre compatibilità con i comandi Docker, semplificando la migrazione (è persino possibile creare un alias `docker=podman` in ambienti in cui non si desidera installare Docker CE).

Modalità rootless e namespace utente

Uno dei principali progressi pratici per ridurre l'impatto di una potenziale fuga del container è la modalità senza privilegi di root combinata con i namespace utente . La domanda a cui si propone di rispondere è: "Cosa succede se il container riesce a fuggire?"

I namespace utente consentono di rimappare l'UID 0 del container a un UID non privilegiato sull'host . In altre parole, all'interno del container l'applicazione crede di essere root, ma dal punto di vista dell'host, quel processo è in realtà un utente normale con un intervallo di subuid/subgid assegnato.

In questo modo, anche se un utente malintenzionato ottiene i privilegi di root all'interno del container e riesce a concatenare un exploit del kernel o del runtime, al ritorno all'host lo fa come utente non privilegiato . Non può sovrascrivere i binari di root, montare file system sensibili o manipolare dispositivi per i quali non dispone dei privilegi necessari.

Podman è stato progettato fin dall'inizio con questo modello in mente: containerizzazione senza privilegi di root per impostazione predefinita, ove possibile , utilizzo di SELinux/AppArmor e cgroups anche senza privilegi di root, e una forte enfasi sulla minimizzazione dei privilegi strutturali.

Docker ora offre anche una vera modalità senza privilegi di root , in cui dockerd e i container risiedono all'interno di un namespace utente, a differenza della precedente modalità userns-remap in cui il demone rimaneva root. Dal punto di vista della sicurezza, questo impedisce che un exploit contro dockerd conceda automaticamente l'accesso root all'host.

Reti di container con Podman: interfacce bridge, host, macvlan e rootless

Un altro elemento importante per la sicurezza e il funzionamento pratico è il livello di rete dei container . Podman offre diversi meccanismi per fornire connettività, con particolare attenzione agli scenari senza privilegi di root.

  Porta M.2 nativa o adattatore PCIe: qual è la scelta giusta per te?

Podman 4 ha introdotto netavark, un driver di rete che segue il modello CNI per fornire ai container indirizzi IP su reti bridge, macvlan, ecc. Con netavark, i container con privilegi di root si connettono in genere per impostazione predefinita a una rete chiamata "podman" associata a un bridge Linux (podman0) sull'host, con indirizzi 10.88.0.0/16.

Questa rete bridge predefinita fornisce connettività Internet di base tramite regole SNAT e consente di esporre le porte dei container all'host utilizzando `-po --publish`, che genera regole DNAT in iptables/nftables. Per garantire la compatibilità con Docker, questa rete non esegue un server DNS interno.

Possiamo anche creare reti bridge definite dall'utente , che isolano gruppi di container l'uno dall'altro e forniscono un DNS interno. Ciò consente ai container di risolvere i nomi all'interno della propria rete privata, semplifica la separazione dei servizi che non dovrebbero potersi vedere a vicenda e offre un maggiore controllo su MTU, firewall e altre funzionalità.

In ambienti più avanzati, è possibile utilizzare reti macvlan o ipvlan per connettere i container direttamente alla rete fisica dell'host. Macvlan consente ai container di comunicare tra loro, mentre ipvlan tende a isolarli maggiormente; queste opzioni sono utili in implementazioni in cui ogni container deve essere visto come un host reale dal resto della rete.

Reti senza radici: slirp4netns e pasta

Quando l'utente non è root, la situazione si complica: un utente senza privilegi non può modificare liberamente lo spazio dei nomi di rete globale o creare interfacce arbitrarie, quindi Podman deve ricorrere a soluzioni a livello utente per fornire la connettività.

È qui che entra in gioco slirp4netns , il meccanismo classico utilizzato da Podman rootless . Questo progetto crea un ambiente di rete isolato all'interno del container e utilizza il modulo slirp del kernel per eseguire la traduzione degli indirizzi (NAT) e consentire al container di accedere a Internet attraverso la rete dell'host.

In pratica, slirp4netns crea un'interfaccia TAP all'interno dello spazio dei nomi di rete del container (ad esempio, tap0 con IP 10.0.2.100 e gateway 10.0.2.2) e instrada il traffico tramite uno stack TCP/IP implementato nello spazio utente. È funzionale, ma aggiunge un certo overhead e alcune limitazioni.

Una di queste limitazioni è che gli utenti non privilegiati non possono utilizzare porte inferiori a 1024. Il parametro sysctl net.ipv4.ip_unprivileged_port_start indica da quale porta sono considerati "non privilegiati" (per impostazione predefinita, 1024), sebbene questo valore possa essere modificato con attenzione se il modello di sicurezza lo consente.

In Podman 5, slirp4netns è stato sostituito da pasta come meccanismo rootless predefinito . Anche pasta opera interamente nello spazio utente, ma grazie all'interfaccia tap e alle tecniche zero-copy, raggiunge prestazioni di rete quasi native, riducendo l'impatto su latenza e throughput rispetto a slirp4netns.

Reti bridge e host in contenitori rootless

Anche quando si lavora in modalità non root, è possibile connettere i container senza privilegi di root alla rete bridge predefinita "podman" utilizzando `--network=podman`. Questo sfrutta le funzionalità di netavark e mappa le porte all'host con `-p`, proprio come in modalità root.

È anche possibile creare reti bridge definite dall'utente in ambienti senza privilegi di root . In questo caso, i bridge e i dispositivi associati non vengono creati nello spazio dei nomi di rete globale dell'host, ma nello spazio dei nomi dell'utente, quindi queste interfacce non saranno visibili dall'host con un semplice comando `sudo ip a`.

Per ispezionare questi bridge senza privilegi di root, Podman offre il comando `podman unshare --rootless-netns` , che apre una shell nello spazio dei nomi di rete dell'utente. Da lì, è possibile osservare i bridge interni e verificare la connettività diretta ai container.

Questo isolamento ha un effetto curioso: Potrebbe non esserci connettività diretta dall'host agli IP di quei container rootless.ma funziona con porte mappate sull'indirizzo IP dell'host, che fungono da punto di ingresso al servizio.
Per la comunicazione tra i container, il server DNS integrato nella rete bridge definita dall'utente consente loro di comunicare per nome, semplificando notevolmente la configurazione delle applicazioni distribuite.

All'altra estremità si trova la rete host (--network=host) , dove il container condivide lo stack di rete con l'host senza un proprio indirizzo IP. In questa modalità, se un container senza privilegi di root avvia un server sulla porta 8080/tcp, il servizio sarà direttamente disponibile sulla porta 8080 dell'host, evitando la mappatura delle porte ma riducendo l'isolamento.

Podman Desktop, strumenti di imaging e orchestrazione locale

Per chi preferisce un approccio più visivo, Podman Desktop offre un ambiente grafico per la gestione di container, pod, immagini e configurazioni di rete su macchine di sviluppo, fungendo da alternativa diretta a Docker Desktop ma basandosi su un motore senza daemon.

Le immagini vengono ottenute tramite il comando `podman pull`, che le scarica dai registri definiti in `/etc/containers/registries.conf` . Se l'immagine viene specificata senza un nome di registro, Podman tenta di individuarla nell'ordine dei registri configurato, utilizzando per impostazione predefinita il tag `latest`.

Oltre alla CLI di Podman, possiamo utilizzare skopeo come strumento dedicato per la gestione, l'ispezione e la copia di immagini tra registri e repository locali. Comandi come copy, delete e sync ci consentono di automatizzare la sincronizzazione tra repository, contrassegnare le immagini per la raccolta dei rifiuti e spostare artefatti tra ambienti.

Una volta scaricata l'immagine, il comando `podman run` consente di avviare i container specificando il trasporto e il percorso (per impostazione predefinita, il trasporto Docker e la ricerca nel registro configurato). Le opzioni standard (-d, -p, --name, --pod, ecc.) coprono sia scenari di laboratorio che implementazioni più complesse.

La gestione quotidiana viene completata con `podman ps` per elencare i container, `podman stop/start` per arrestare o avviare le istanze esistenti e `podman rm` per rimuovere i container arrestati . Se un container è stato modificato e si desidera convertirlo in una nuova immagine, `podman commit` salva tali modifiche con un nuovo nome.

  Crontab Linux: Introduzione alla pianificazione delle attività

Pod e modelli di esecuzione avanzati con Podman

Ispirato a Kubernetes, Podman permette di raggruppare i container in pod che condividono lo stesso stack di rete e determinate risorse . È un modello perfetto per elementi come un database e il suo client, o più microservizi che necessitano di comunicare con bassa latenza.

Il comando `podman pod create` crea un nuovo pod e ne restituisce l'ID, ma per impostazione predefinita il pod rimane arrestato finché non viene avviato un container associato o non viene avviato esplicitamente con `podman pod start`.

Per visualizzare i pod presenti sul sistema, utilizzare il comando `podman pod ls` , che elencherà anche il container INFRA associato a ciascuno di essi. Questo container speciale mantiene attive la rete e le altre risorse condivise del pod, pertanto il numero di container non è mai pari a zero.

Podman offre comandi specifici per avviare, arrestare o riavviare interi pod (podman pod start/stop/restart) , ma possiamo anche agire solo su un container specifico all'interno del pod senza modificare gli altri, mantenendo l'isolamento dei processi a livello di applicazione.

Per aggiungere altri container a un pod esistente, usa il comando `podman run --pod POD_NAME` , seguendo la stessa sintassi utilizzata per i singoli container. Non è possibile rimuovere un container da un pod senza distruggerlo, poiché l'appartenenza al pod è legata al suo ciclo di vita.

Il monitoraggio viene completato con podman ps –pod o comandi specifici per monitorare i processi di tutti i container in tutti i pod , identificando rapidamente a quale gruppo appartiene ciascun elemento e come sono distribuiti i servizi.

Contenitori, LXC e virtualizzazione delle applicazioni

Sebbene Docker e Podman dominino il dibattito, non sono le uniche opzioni di containerizzazione in GNU/Linux . Soluzioni come LXC/LXD, systemd-nspawn e Kata Containers affrontano il problema da prospettive leggermente diverse.

LXC offre container di sistema estremamente leggeri, dotati di nome host, indirizzo IP, file system e sistema di init completo . In combinazione con LXD, che funge da hypervisor per container e macchine virtuali, offre un'esperienza molto simile a quella di più macchine leggere con prestazioni quasi pari a quelle di un sistema bare-metal.

Mentre Docker/Podman si concentrano sui container di applicazioni conformi a OCI , LXC eccelle quando sono necessari ambienti persistenti con più servizi e processi, comportandosi come una macchina virtuale ma con un overhead inferiore e un'integrazione molto naturale con il kernel Linux.

Dal punto di vista delle prestazioni, LXC è in grado di eseguire applicazioni aziendali ad alta intensità di dati praticamente senza penalizzazioni , il che lo rende una scelta efficace per carichi di lavoro sensibili alla latenza che non richiedono il forte isolamento di una macchina virtuale tradizionale.

Parallelamente a ciò, hanno guadagnato terreno anche le tecnologie orientate alle micro-VM e a un maggiore isolamento, come Kata Containers , che avvia ogni pod all'interno di una piccola macchina virtuale con il proprio kernel, o gVisor, che inserisce una "sandbox del kernel" scritta in Go tra l'applicazione e Linux. Entrambe riducono la dipendenza dal kernel condiviso e si avvicinano al modello di sicurezza delle VM, pur mantenendo la facilità d'uso dei container.

Best practice per rafforzare Docker e Podman

Considerando tutto quanto sopra, l'approccio più ragionevole per una piattaforma container robusta è quello di lavorare a livelli: segmentazione con namespace/cgroup, politiche di filtraggio con seccomp e MAC e restrizione strutturale dei privilegi tramite la modalità senza privilegi di root.

In pratica, è consigliabile adottare la prassi standard di eseguire i container in modalità non-root ogni volta che sia possibile , soprattutto in ambienti multi-tenant o all'interno di cloud condivisi. Podman facilita questa operazione per impostazione predefinita, e Docker rootless segue la stessa filosofia quando è richiesta la compatibilità.

Il kernel host deve essere mantenuto aggiornato con le patch di sicurezza . Vulnerabilità come Dirty COW, Dirty Pipe e altre vengono risolte con aggiornamenti che devono essere applicati relativamente in fretta se non vogliamo lasciare la porta aperta a possibili fughe dai container.

Un'altra misura estremamente efficace consiste nell'utilizzare distribuzioni immutabili e orientate ai container, come openSUSE MicroOS o Flatcar Linux . La loro directory root di sola lettura e il sistema di aggiornamento atomico riducono notevolmente la superficie di attacco e facilitano i rollback in caso di problemi dopo l'applicazione di una patch.

Per quanto riguarda la configurazione: nessun container con privilegi elevati in produzione, se non in casi estremamente controllati , non eseguire applicazioni come root all'interno del container, utilizzare file system di sola lettura ove possibile e limitare le funzionalità al minimo indispensabile. SELinux/AppArmor e profili seccomp personalizzati in base alle chiamate effettive dell'applicazione sono praticamente obbligatori.

Infine, non dobbiamo dimenticare l' aspetto del monitoraggio in tempo reale . Strumenti come Falco, Sysdig Secure o Aqua aiutano a rilevare anomalie, tentativi di evasione (come accessi insoliti a /proc o /sys), traffico di rete atipico o comandi sospetti, dando il tempo di reagire prima che l'attacco abbia successo.

Combinando la virtualizzazione classica con KVM, container gestiti da Podman (preferibilmente in modalità rootless), reti ben segmentate e solide pratiche di hardening, è possibile creare una piattaforma di virtualizzazione altamente flessibile e sicura. Comprendere che il kernel condiviso è il punto più critico, affidarsi a VM o micro-VM quando il rischio o la conformità normativa lo richiedono e gestire attentamente la supply chain delle immagini fa la differenza tra un ambiente di laboratorio e un'infrastruttura pronta per la produzione.

Tipi di virtualizzazione del server
Articolo correlato:
Tipi di virtualizzazione del server