Maszyny wirtualne: KVM i libvirt
Po tej lekcji: kursant sprawdza, czy serwer nadaje się do wirtualizacji, zakłada dyski qemu-img, stawia maszynę virt-install, zarządza nią przez virsh (start, wyłączanie, konsola, migawki), rozumie różnicę między siecią NAT a mostkiem i konfiguruje nową maszynę automatycznie przez cloud-init.
Wstęp
Kontener jest świetny, dopóki wystarcza Ci jądro serwera. Ale chcesz przetestować aktualizację jądra. Albo uczysz się do RHCSA i potrzebujesz maszyny, którą można bezkarnie zepsuć, a potem w trzy sekundy cofnąć do stanu sprzed katastrofy. Albo szef chce Windowsa obok Linuksa. Kontener tu nie pomoże.
Potrzebny jest cały komputer udawany w komputerze. Maszyna wirtualna. Dziś postawimy ją bez klikania, z linii komend, tak jak na prawdziwym serwerze bez monitora. A na końcu sprawimy, że nowa maszyna przy pierwszym starcie sama ustawi sobie nazwę, użytkownika i klucz SSH.
Dom z własną elektrownią
Kontener to kontener mieszkalny na budowie, który bierze prąd z placu. Maszyna wirtualna to osobny dom z własną elektrownią, własną kuchnią i własnym zarządcą. Ma własne jądro, własny system, własny wirtualny dysk i kartę sieciową. Myśli, że jest prawdziwym komputerem.
Kto udaje dla niej sprzęt? Trzech pomocników:
- KVM — moduł jądra Linuksa, który zamienia cały system w hiperwizor, czyli zarządcę osiedla domów. Daje maszynom prawdziwy procesor na wyłączność na krótkie chwile, dzięki czemu działają prawie tak szybko jak sprzęt.
- QEMU — program, który udaje resztę sprzętu: dysk, kartę sieciową, ekran.
- libvirt — biuro zarządcy. Pamięta, jakie masz maszyny, jak są skonfigurowane, i daje jedno narzędzie do wszystkiego:
virsh.
Najpierw sprawdzamy, czy procesor w ogóle to potrafi:
grep -cE 'vmx|svm' /proc/cpuinfo
lsmod | grep kvmvmx to wsparcie wirtualizacji w procesorach Intela, svm w AMD. Wynik 0 znaczy, że wirtualizacja jest wyłączona w BIOS-ie albo jesteś już w maszynie wirtualnej, która nie przekazuje jej dalej. Pełną diagnozę robi:
sudo virt-host-validate qemu QEMU: Checking for hardware virtualization : FAIL (Host not compatible with KVM; HW virtualization CPU features not found. Only emulated CPUs are available; performance will be significantly limited)
QEMU: Checking if device /dev/net/tun exists : PASS
QEMU: Checking for cgroup 'cpu' controller support : PASSTak wygląda, kiedy się nie da. Maszyna wciąż ruszy, ale QEMU będzie emulować procesor programowo, jak symulator lotu na kalkulatorze. Na dobrze przygotowanym serwerze w każdej linijce chcesz widzieć PASS.
Instalacja
[RHEL] Według dokumentacji Red Hata:
sudo dnf install qemu-kvm libvirt virt-install virt-viewer
for drv in qemu network nodedev nwfilter secret storage interface; do sudo systemctl start virt${drv}d{,-ro,-admin}.socket; doneNowsze RHEL-e nie mają jednego wielkiego libvirtd, tylko osobne małe usługi do maszyn, sieci, dysków. Pętla z lekcji o pętlach uruchamia je wszystkie naraz.
sudo apt install qemu-system-x86 libvirt-daemon-system virtinst
sudo usermod -aG libvirt $USERGrupa libvirt pozwala zarządzać maszynami systemowymi bez sudo. Po dopisaniu do grupy wyloguj się i zaloguj ponownie.
Uwaga na jedną rzecz: libvirt ma dwa osobne biura. qemu:///system to biuro systemowe, z uprawnieniami roota, do maszyn serwerowych. qemu:///session to prywatne biurko zwykłego użytkownika. To osobne instancje z osobnymi listami maszyn. Jak „maszyna zniknęła”, sprawdź najpierw, do którego biura dzwonisz:
virsh uriqemu:///systemDyski: qemu-img
Wirtualny dysk to zwykły plik. Najpopularniejszy format to qcow2, „QEMU copy on write”. To jak nadmuchiwany materac: mówisz, że ma 10 GB, ale zajmuje tyle, ile w niego naprawdę włożysz.
qemu-img create -f qcow2 dysk.qcow2 5G
ls -lh dysk.qcow2
qemu-img resize dysk.qcow2 +5G
qemu-img info dysk.qcow2Formatting 'dysk.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=5368709120 lazy_refcounts=off refcount_bits=16
-rw-r--r-- 1 root root 193K Nov 3 16:37 dysk.qcow2
Image resized.
image: dysk.qcow2
file format: qcow2
virtual size: 10 GiB (10737418240 bytes)
disk size: 200 KiBDysk na 10 GB, a na serwerze zajmuje 200 KB. virtual size to obietnica, disk size to rzeczywistość. Uwaga: dysk można powiększyć także w drugim kierunku, ale qemu-img zażąda wtedy --shrink i ostrzeże, że wszystko za nową granicą przepadnie. Zmniejszanie dysku z danymi to jak skracanie spodni na człowieku, który w nich stoi.
Konwersja między formatami, na przykład gdy przenosisz maszynę z VMware albo do niego:
qemu-img convert -p -f qcow2 -O raw dysk.qcow2 dysk.raw
qemu-img convert -f raw -O vmdk dysk.raw dysk.vmdkI najlepsza sztuczka: dysk z podkładem (backing file). Masz jeden czysty obraz systemu i chcesz z niego dziesięć maszyn. Zamiast kopiować go dziesięć razy, robisz cienkie nakładki. Jak kalka na rysunku: oryginał pod spodem zostaje nietknięty, a każda maszyna rysuje tylko na swojej kalce.
cd /var/lib/libvirt/images
sudo qemu-img create -f qcow2 -F qcow2 -b /var/lib/libvirt/images/noble-server-cloudimg-amd64.img web01.qcow2 20G
sudo qemu-img info --backing-chain web01.qcow2 | grep -E 'image:|backing file|virtual size'image: web01.qcow2
virtual size: 20 GiB (21474836480 bytes)
backing file: /var/lib/libvirt/images/noble-server-cloudimg-amd64.img
backing file format: qcow2
image: /var/lib/libvirt/images/noble-server-cloudimg-amd64.img-b to podkład, -F jego format. Ważne: podkładu nie wolno potem zmieniać ani przesuwać, bo wszystkie kalki się rozjadą.
virt-install: nowa maszyna jedną komendą
Klasyczna instalacja z płyty ISO, z instalatorem w konsoli tekstowej:
sudo virt-install \\
--name rocky01 \\
--memory 2048 --vcpus 2 \\
--disk size=20 \\
--location /var/lib/libvirt/images/Rocky-10-x86_64-dvd.iso \\
--osinfo rocky10 \\
--network network=default \\
--graphics none \\
--extra-args 'console=ttyS0'Czytamy od góry: nazwa, pamięć w megabajtach, liczba procesorów, nowy dysk 20 GB, skąd instalator, jaki to system, do jakiej sieci, bez okienka graficznego, a instalator ma mówić przez port szeregowy, żebyś go widział w terminalu. Nazwę systemu dla --osinfo znajdziesz tak:
virt-install --osinfo list | grep -E 'rocky|rhel10|ubuntu24'Nazwa ma znaczenie: dla znanego systemu virt-install dobiera szybkie wirtualne urządzenia virtio. Dla nieznanego daje stare, wolne, za to bezpieczne. Ja na pierwszej próbie dałem generic i dysk dostał nazwę hda, jak w komputerze z poprzedniej epoki.
virsh: pilot do maszyn
virsh list --all
virsh dominfo test94 Id Name State
------------------------
1 test94 running
Id: 1
Name: test94
State: running
CPU(s): 1
Max memory: 524288 KiB
Persistent: yes
Autostart: disablelist bez --all pokazuje tylko włączone. Reszta pilota:
virsh shutdown test94
virsh destroy test94
virsh start test94
virsh autostart test94
virsh console test94Domain 'test94' destroyed
Domain 'test94' started
Domain 'test94' marked as autostarted
Connected to domain 'test94'
Escape character is ^] (Ctrl + ])Pamiętasz sygnały? shutdown to grzeczna prośba o zamknięcie lokalu: wirtualne naciśnięcie przycisku zasilania, system sam się zamyka. destroy to wyłączenie prądu. Nazwa jest straszna, ale nic nie kasuje: maszyna i jej dysk zostają, tylko gwałtownie gasną, z ryzykiem uszkodzenia otwartych plików. autostart włącza maszynę razem z serwerem. console to wejście przez kabel szeregowy, kiedy SSH nie działa. Wyjście z konsoli to Ctrl+]. Zapisz to sobie, bo exit wyloguje Cię z systemu w maszynie, ale z konsoli nie wyjdziesz.
A co z usuwaniem maszyny na dobre?
virsh undefine rocky01 --remove-all-storageTo już kasuje naprawdę, razem z dyskiem. Dwa razy sprawdź nazwę.
Migawki, czyli zapisz grę
Największa zaleta maszyny do nauki. Przed ryzykowną zmianą robisz migawkę, jak zapis stanu gry przed walką z bossem:
virsh snapshot-create-as test94 --name przed-aktualizacja --description "czysta instalacja"
virsh snapshot-list test94Domain snapshot przed-aktualizacja created
Name Creation Time State
-----------------------------------------------------------
po-starcie 2026-11-03 16:36:24 +0100 running
przed-aktualizacja 2026-11-03 16:36:23 +0100 shutoffKolumna State mówi, w jakim stanie była maszyna: migawka włączonej maszyny zapisuje też pamięć, więc po powrocie programy działają dalej, jakby nic się nie stało. Zepsułeś? Wracasz:
virsh snapshot-revert test94 przed-aktualizacja
virsh snapshot-delete test94 po-starcieTe migawki żyją wewnątrz pliku qcow2, widać je też w qemu-img info. Ważne: migawka to nie kopia zapasowa. Leży w tym samym pliku co maszyna. Padł dysk, padły migawki. O prawdziwych kopiach jest osobna lekcja.
Sieć: NAT kontra mostek
Maszyna potrzebuje sieci. Są dwie główne drogi.
NAT to osiedle zamknięte z jedną bramą. Maszyny mają swoje wewnętrzne adresy, wychodzą do internetu przez serwer, ale z zewnątrz nikt do nich nie zapuka. Sieć default w libvirt działa właśnie tak:
sudo virsh net-list --all
sudo virsh net-dumpxml default | grep -E 'forward|ip address|range' <forward mode='nat'/>
<ip address='192.168.122.1' netmask='255.255.255.0'>
<range start='192.168.122.2' end='192.168.122.254'/>Serwer to brama 192.168.122.1, maszyny dostają adresy z DHCP z puli. Świetne do nauki i testów.
Mostek (bridge) to wpuszczenie maszyn prosto na ulicę. Maszyna dostaje adres z tej samej sieci co serwer, jak kolejny komputer podłączony kablem do switcha. Tego chcesz, gdy maszyna ma być normalnym serwerem w firmie.
Mostek tworzysz w systemie serwera. [Ubuntu] przez netplan, plik /etc/netplan/01-most.yaml:
network:
version: 2
ethernets:
enp1s0:
dhcp4: false
bridges:
br0:
interfaces: [enp1s0]
dhcp4: truesudo netplan try[RHEL] przez nmcli:
sudo nmcli connection add type bridge con-name br0 ifname br0
sudo nmcli connection add type ethernet port-type bridge con-name br0-port1 ifname enp1s0 controller br0
sudo nmcli connection up br0Ostrzeżenie: robisz to na karcie sieciowej, przez którą jesteś zalogowany. Adres przenosi się z karty na mostek i przez SSH możesz stracić połączenie w połowie operacji. Miej dostęp do konsoli serwera albo ćwicz na maszynie wirtualnej. netplan try po to właśnie istnieje: jak nie potwierdzisz zmian w 120 sekund, wraca do starej konfiguracji.
Potem w virt-install zamiast --network network=default piszesz --network bridge=br0.
cloud-init: maszyna, która sama się urządza
Instalacja z ISO to klikanie przez instalator. W chmurze nikt tego nie robi. Bierze się gotowy obraz systemu, tak zwany cloud image, i przy pierwszym starcie podaje mu kartkę z instrukcjami: jak się nazywasz, kto ma konto, jaki klucz SSH, co zainstalować. Program w maszynie, który czyta tę kartkę, to cloud-init. Jak nowy pracownik, który pierwszego dnia znajduje na biurku list powitalny z loginem, hasłem do Wi-Fi i listą zadań.
Gotowe obrazy pobierasz od dystrybucji: Ubuntu z cloud-images.ubuntu.com, Rocky i Red Hat też udostępniają obrazy dla chmury (format sprawdź przez qemu-img info). Kartka składa się z dwóch plików. user-data:
#cloud-config
hostname: web01
users:
- name: eryk
groups: sudo
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
ssh_authorized_keys:
- ssh-ed25519 AAAA... eryk@laptop
package_update: true
packages:
- htop
runcmd:
- echo "Postawione przez cloud-init" > /etc/motdPierwsza linijka #cloud-config jest obowiązkowa. Bez niej cloud-init uzna plik za coś innego. Na RHEL-u grupa adminów to wheel, nie sudo. Drugi plik, meta-data:
instance-id: web01
local-hostname: web01Zanim użyjesz, sprawdź składnię. Literówka w YAML to maszyna bez Twojego konta:
cloud-init schema -c user-dataValid schema user-dataTeraz dwie drogi. Nowszy virt-install sam spakuje te pliki:
sudo virt-install --name web01 --memory 2048 --vcpus 2 \\
--disk path=/var/lib/libvirt/images/web01.qcow2 --import \\
--osinfo ubuntu24.04 --network network=default \\
--cloud-init user-data=user-data,meta-data=meta-data \\
--graphics none --noautoconsole--import znaczy: nie instaluj, dysk już ma system, po prostu uruchom. A dysk web01.qcow2 to nasza kalka na obrazie Ubuntu z poprzedniej części. Druga droga, działająca wszędzie: sam robisz małą płytę z kartką.
cloud-localds seed.iso user-data meta-data
file seed.isoseed.iso: ISO 9660 CD-ROM filesystem data 'cidata'Etykieta płyty cidata to sygnał dla cloud-init: „tu jest Twoja kartka”. Wkładasz ją jako drugi napęd: --disk path=seed.iso,device=cdrom. [Ubuntu] cloud-localds jest w paczce cloud-image-utils.
Po starcie wchodzisz ssh eryk@ADRES, bez hasła, z kluczem. W środku cloud-init status --wait czeka, aż skończy, a jak coś nie wyszło, zaglądasz do /var/log/cloud-init.log i /var/log/cloud-init-output.log.
Jak to działa pod spodem
Maszyna wirtualna z punktu widzenia serwera to… jeden proces qemu-system-x86_64. Każdy wirtualny procesor to wątek tego procesu. Kucharz udający całą restaurację. Kiedy maszyna wykonuje zwykłe instrukcje, KVM pozwala jej działać prosto na procesorze. Gdy maszyna chce dotknąć sprzętu, na przykład zapisać na dysk, procesor zatrzymuje ją i oddaje głos QEMU, które zapisuje dane do pliku qcow2.
libvirt przechowuje opis każdej maszyny jako XML. Systemowe maszyny leżą w /etc/libvirt/qemu/:
sudo ls /etc/libvirt/qemu/
virsh domblklist test94autostart networks test94.xml
Target Source
------------------------------------------------
hda /var/lib/libvirt/images/test94.qcow2Tego XML-a nie edytujesz ręcznie w edytorze. Używasz virsh edit test94, który sprawdzi poprawność przed zapisem, tak jak visudo dla sudoers. Podgląd to virsh dumpxml test94.
Ściąga flag
| Komenda | Flaga | Co robi |
|---|---|---|
qemu-img create | -f qcow2 PLIK ROZMIAR | nowy dysk |
qemu-img create | -b PODKŁAD -F FORMAT | dysk z podkładem (kalka) |
qemu-img info | --backing-chain | cały łańcuch dysków |
qemu-img resize | +5G, --shrink | powiększa, zmniejsza |
qemu-img convert | -f Z -O NA -p | zmiana formatu z paskiem postępu |
virt-install | --name --memory --vcpus | nazwa, RAM w MB, procesory |
virt-install | --disk size=20 / path=... | nowy dysk albo istniejący |
virt-install | --location, --cdrom, --import | skąd system |
virt-install | --osinfo NAZWA | typ systemu, dobór urządzeń |
virt-install | --network network=default / bridge=br0 | NAT albo mostek |
virt-install | --cloud-init user-data=... | kartka dla cloud-init |
virt-install | --graphics none --noautoconsole | bez okna, bez łączenia z konsolą |
virsh | list --all | wszystkie maszyny |
virsh | start, shutdown, destroy | włącz, grzecznie wyłącz, wyłącz prąd |
virsh | console | konsola szeregowa, wyjście Ctrl+] |
virsh | autostart | start razem z serwerem |
virsh | snapshot-create-as, snapshot-list, snapshot-revert | migawki |
virsh | undefine --remove-all-storage | usuwa maszynę i jej dyski |
⚠ Typowy błąd
Wpisujesz virsh shutdown, a maszyna… dalej działa:
virsh shutdown test94
sleep 5
virsh list --allDomain 'test94' is being shutdown
Id Name State
------------------------
1 test94 running„Is being shutdown”, a stan running. Dlaczego? shutdown tylko naciska wirtualny przycisk zasilania. Jeśli system w środku jeszcze się uruchamia, wisi albo nie ma usługi, która ten przycisk obsługuje, to nikt go nie usłyszy. U mnie maszyna miała pusty dysk, więc nie było w niej nikogo, kto mógłby usłyszeć. Jak dzwonek do pustego mieszkania.
Co robić: poczekaj chwilę i sprawdź virsh console, co się dzieje w środku. W nowoczesnych obrazach chmurowych włączony jest też agent QEMU (qemu-guest-agent), z którym zadziała virsh shutdown --mode agent. Dopiero na końcu virsh destroy, świadomie, ze świadomością, że to wyjęcie wtyczki z gniazdka.
✎ Zadanie
- Utwórz dysk
baza.qcow2o rozmiarze 10 GB, a na nim dysk z podkłademklon.qcow2o rozmiarze 20 GB. Sprawdźqemu-img info --backing-chain, ile każdy zajmuje naprawdę. - Postaw maszynę z obrazu chmurowego przez
virt-install --importz własnymuser-data, które zakłada Twoje konto z kluczem SSH. Zaloguj się po SSH. - Zrób migawkę
czysta, usuń w maszynie plik/etc/motd, a potem przywróć migawkę i sprawdź, że plik wrócił.
Pokaż rozwiązanie
Część pierwsza: dysk z podkładem.
qemu-img create -f qcow2 baza.qcow2 10G
qemu-img create -f qcow2 -F qcow2 -b baza.qcow2 klon.qcow2 20G
qemu-img info --backing-chain klon.qcow2 | grep -E '^image:|virtual size|^disk size|backing file:'image: klon.qcow2
virtual size: 20 GiB (21474836480 bytes)
disk size: 196 KiB
backing file: baza.qcow2
image: baza.qcow2
virtual size: 10 GiB (10737418240 bytes)
disk size: 196 KiBDwa dyski, razem 30 GB obietnic, a zajmują mniej niż zdjęcie kota z telefonu. Zwróć uwagę: kalka może być większa niż podkład.
Część druga: maszyna z cloud-init. Plik user-data z Twoim kontem i kluczem jak w lekcji, sprawdzony przez cloud-init schema -c user-data, i:
sudo virt-install --name lab01 --memory 2048 --vcpus 2 \\
--disk path=/var/lib/libvirt/images/klon.qcow2 --import \\
--osinfo ubuntu24.04 --network network=default \\
--cloud-init user-data=user-data,meta-data=meta-data \\
--graphics none --noautoconsole
sudo virsh domifaddr lab01
ssh eryk@192.168.122.Xdomifaddr poda adres, który maszyna dostała z DHCP sieci default. Jak SSH pyta o hasło, to znaczy, że klucz nie dotarł: zajrzyj przez virsh console do /var/log/cloud-init.log. Najczęstszy powód to brak #cloud-config w pierwszej linijce.
Część trzecia: migawka.
sudo virsh snapshot-create-as lab01 czysta
ssh eryk@192.168.122.X 'sudo rm /etc/motd'
sudo virsh snapshot-revert lab01 czysta
ssh eryk@192.168.122.X 'cat /etc/motd'Plik wrócił, razem z napisem „Postawione przez cloud-init”. Wehikuł czasu, który działa tylko w jedną stronę: do tyłu. Do przodu musisz iść sam.
Co dalej
Masz już kontenery, maszyny i usługi. Kto ich pilnuje w nocy? W następnej lekcji monitorowanie: historia obciążenia w sar, progi i alarmy, Zabbix z agentem, Netdata i Prometheus z node_exporter.