W LinuxLab nie testujemy zmian na serwerach klientów. Zanim playbook, aktualizacja systemu albo nowa konfiguracja usługi trafi na produkcję, przechodzi przez lab: kilka maszyn wirtualnych, które można postawić i skasować bez konsekwencji. Ten artykuł opisuje lab, którego realnie używamy - jak jest zbudowany, dlaczego akurat tak i co się w nim popsuło po drodze.
W tym artykule budujemy lab z czterema systemami testowymi: Debian 12, Debian 13, Ubuntu 24.04 LTS i Ubuntu 26.04 LTS. Całość opisuje kod, a nie kliknięcia w graficznym menedżerze maszyn. Pokazujemy podział na warstwy, katalog obrazów, sieć laboratoryjną, konfigurację systemu przy pierwszym starcie, odtwarzanie pojedynczej maszyny oraz sprzątanie. Nazwy kont, ścieżki kluczy i nazwa hosta są w przykładach ogólne - przed użyciem podstaw własne.
Spis treści
- Co sprawdziliśmy, a czego nie
- Po co lab opisany kodem
- Krótki słownik
- Terraform czy OpenTofu
- Architektura: warstwy i stany
- Budowa krok po kroku
- Warstwa bazowa: obrazy i sieć
- Cztery maszyny testowe
- Co dostaje maszyna przy starcie
- Pułapka: dlaczego nie bootowały
- Powtarzalność: latest czy data
- Operacje dnia drugiego
- Sprzątanie
- Czego ten kod nie robi
- Ograniczenia tego podejścia
- Checklista
Co sprawdziliśmy, a czego nie
Wszystkie liczby i wyniki poniżej pochodzą z labu, który u nas działa i jest używany na co dzień, a nie z maszyny postawionej na potrzeby tego tekstu. Stan z dnia publikacji:
| Element | Stan zmierzony 10 września 2026 |
|---|---|
| Host labu | ThinkPad T490, 38 GiB pamięci, 8 wątków - odczytane z danych identyfikacyjnych płyty |
| Narzędzia | Terraform 1.16.1 oraz OpenTofu 1.12.6 zainstalowane obok siebie na tym samym hoście |
| Ten sam kod, dwa narzędzia | konfiguracja warstwy bazowej przeszła init i validate pod oboma, bez żadnej zmiany w plikach |
| Provider | dmacvicar/libvirt w wersji 0.8.3 - dostępny w obu rejestrach |
| Jedyna różnica | host w pliku blokady: registry.terraform.io/dmacvicar/libvirt kontra registry.opentofu.org/dmacvicar/libvirt |
| Sieć laboratoryjna | sieć lab aktywna, tryb NAT, 192.168.123.0/24, zakres adresów od .2 do .254, domena lab |
| Obrazy bazowe | pula golden pod /var/lib/libvirt/images/golden; pliki źródłowe w formacie surowym: Debian po 3,0 GiB, Ubuntu po 3,5 GiB |
| Format dysku maszyny | opis maszyny deb13 zawiera <driver name='qemu' type='raw'/>, a plik dysku faktycznie jest surowy, 10 GiB - czyli zgodnie |
| Firmware | OVMF_CODE_4M.fd i OVMF_VARS_4M.fd obecne na hoście, wariant UEFI jest domyślny |
| Maszyny | deb12, deb13, ubu2404, ubu2604 zdefiniowane i zarejestrowane; lab nie musi chodzić bez przerwy |
| Pełne odtworzenie | lab został skasowany i zbudowany od nowa na potrzeby tego artykułu: obie warstwy usunięte, potem odtworzone. Czasy w sekcji o sprzątaniu; wszystkie maszyny wstały i wpuściły po SSH |
Czego nie potwierdzaliśmy przy okazji tego tekstu, żeby nie sprzedawać zalecenia jako testu:
- Przebiegu na całkiem czystym hoście. Cykl „skasuj wszystko i zbuduj od nowa” został wykonany i zmierzony - opisujemy go niżej. Był to jednak host, który ma już zainstalowany libvirt, gotowe obrazy w formacie surowym i pliki ze zmiennymi. Na maszynie bez tego dochodzą kroki, których w tym kodzie nie ma; wypisujemy je w osobnej sekcji.
- Wariantu ze starszym firmware. Opisujemy go jako alternatywę, bo u nas domyślny jest UEFI i to on jest przetestowany.
- Zachowania innych wersji providera. Wszystko dotyczy wersji
0.8.3. Nowsze wydania mogą zachowywać się inaczej, zwłaszcza w miejscu opisanym w sekcji o formacie dysku.
Po co lab opisany kodem
Maszynę testową można wyklikać w graficznym menedżerze w kwadrans. Problem pojawia się później: po miesiącu nikt nie pamięta, ile miała pamięci, jaki dokładnie obraz systemu został użyty i czy ktoś czegoś w niej ręcznie nie dołożył. Przy jednej maszynie da się z tym żyć. Przy sześciu, z których każda ma inny system, to źródło fałszywych wyników - poprawka „działa na labie” zaczyna znaczyć „działa na tej jednej maszynie, w stanie, którego nie umiemy odtworzyć”.
Opisanie labu kodem rozwiązuje trzy konkretne rzeczy:
- Odtwarzalność. Maszynę można skasować i postawić od nowa w kilka minut, zawsze w tym samym stanie wyjściowym. Test, który zostawił po sobie bałagan, przestaje być problemem.
- Przegląd zmian. Zmiana pamięci z 2 na 4 GiB jest jedną linijką w pliku, którą widać w historii repozytorium. Nie trzeba jej pamiętać ani nikomu opowiadać.
- Świadomy dobór systemów. Lista systemów testowych staje się jawną decyzją, a nie efektem tego, co kto kiedyś postawił. Kiedy system wypada ze wsparcia, wypada z labu przez usunięcie jednego wiersza - a lab sam podpowiada, kiedy ten moment nadszedł, o czym niżej.
To nie jest argument za automatyzowaniem wszystkiego. Lab jest dobrym miejscem na taki kod właśnie dlatego, że jego zawartość zmienia się często, a cena błędu jest zerowa.
Co opisuje Terraform, a co dzieje się dalej
Warto od razu ustawić granicę, bo bez niej łatwo się rozczarować. Terraform dostarcza puste maszyny: procesor, pamięć, dysk, kartę sieciową, konto i klucz publiczny. Na tym jego rola się kończy. Maszyna wstaje, da się na nią zalogować - i nie ma na niej nic poza systemem.
Tym, co czyni ją użyteczną, zajmuje się druga warstwa: narzędzie do zarządzania konfiguracją, w naszym przypadku Ansible. To ono dokłada wersje interpreterów, serwery WWW, bazę i ustawienia bezpieczeństwa. Lab jest kompletny dopiero razem - i to jest zdrowy podział, bo obie warstwy mają inny cykl życia: maszyny kasuje się i stawia, a ich zawartość zbiega się do stanu opisanego w osobnym repozytorium.
Ten artykuł opisuje wyłącznie pierwszą warstwę. Druga to osobny temat, któremu poświęcamy inny materiał; tutaj wszystko, co dalej, dotyczy Terraform.
Ile sprzętu to naprawdę wymaga
Tu jest dobra wiadomość, która wielu osobom umyka: własny lab z Linuksami nie musi nic kosztować poza prądem. Nie potrzeba serwera w szafie ani wykupionej maszyny u dostawcy rozliczanej za godzinę. Policzmy na konkretach z tego labu, bo liczby są mniejsze, niż się wydaje.
Każda maszyna testowa dostaje 2 rdzenie, 2 GiB pamięci i 10 GiB dysku. Zmierzone zapotrzebowanie czterech opisywanych maszyn wygląda tak:
| Zasób | Cztery maszyny naraz | Uwaga |
|---|---|---|
| Pamięć | 8 GiB | tylko gdy wszystkie cztery są włączone jednocześnie, a to rzadki przypadek |
| Rdzenie | 2 na maszynę | rdzenie się współdzieli, więc nie trzeba ich mieć osiem |
| Dysk na maszyny | 40 GiB | zmierzone; każdy dysk to pełna kopia obrazu, po 10 GiB |
| Dysk na obrazy bazowe | 6 GiB | zmierzone dla czterech systemów, pobierane raz |
Razem około 46 GiB miejsca i 8 GiB pamięci przy pełnym obciążeniu. Nie jest to liczba teoretyczna: opisywany lab działa na ThinkPadzie T490 - zwykłym laptopie biznesowym sprzed kilku lat, w którym do pamięci wlutowanej dołożono moduł w wolnym gnieździe. Ten sam host obsługuje przy tym nie tylko cztery maszyny testowe, ale i kilka usługowych. Przy 16 GiB pamięci uruchamiasz dwie, trzy maszyny naraz bez zastanowienia; przy 24 GiB wszystkie cztery i jeszcze zostaje na przeglądarkę.
Warto też zauważyć, że rzadko potrzebujesz wszystkiego naraz. Testujesz playbook na Debianie 13 - włączasz jedną maszynę. Sprawdzasz różnicę w zachowaniu między Ubuntu 24.04 a 26.04 - włączasz dwie. Reszta stoi wyłączona i nie kosztuje ani pamięci, ani procesora; zajmuje tylko miejsce na dysku. W chwili pisania tego artykułu wszystkie cztery maszyny naszego labu są wyłączone i to jest ich normalny stan.
Psuj do woli - o to właśnie chodzi. Największą wartością własnego labu nie jest oszczędność, tylko zniesiona blokada przed eksperymentem. Możesz zepsuć zaporę tak, że stracisz dostęp po sieci. Możesz wykasować niewłaściwy katalog i zobaczyć, co się wtedy dzieje. Możesz zrobić aktualizację między wydaniami systemu i sprawdzić, co się po niej wysypie. Każda z tych rzeczy na cudzym serwerze kończy się telefonem o drugiej w nocy, a w labie kończy się jednym poleceniem odtwarzającym maszynę i wnioskiem, który zapamiętasz na lata.
Jeżeli dopiero zaczynasz z Linuksem albo z automatyzacją, to jest najtańszy sposób, żeby uczyć się na własnych błędach zamiast na cudzej produkcji. Sprzęt, który już masz, plus wieczór na przeczytanie tego artykułu - i masz środowisko, w którym wolno wszystko.
Krótki słownik
- Terraform - narzędzie, które czyta opis infrastruktury z plików tekstowych i doprowadza rzeczywistość do zgodności z tym opisem. Nie wykonuje poleceń krok po kroku, tylko porównuje stan opisany ze stanem faktycznym i wykonuje różnicę.
- OpenTofu - odgałęzienie Terraform powstałe po zmianie jego licencji, rozwijane pod skrzydłami Linux Foundation. Czyta te same pliki i ma te same polecenia; różnicom poświęcona jest osobna sekcja.
- HCL - z angielskiego HashiCorp Configuration Language, czyli język, w którym pisze się te pliki. To on odpowiada za bloki w rodzaju
resourceczyvariable. - Provider - wtyczka, która tłumaczy opis w HCL na wywołania konkretnego systemu. Tutaj używamy providera do libvirt, czyli warstwy zarządzającej maszynami na KVM (Kernel-based Virtual Machine, wirtualizacja wbudowana w jądro Linuksa).
- Stan - plik, w którym narzędzie zapisuje, co już utworzyło i pod jakim identyfikatorem. Bez niego kolejne uruchomienie nie wiedziałoby, że maszyna o tej nazwie już istnieje, i próbowałoby ją utworzyć drugi raz.
- Katalog główny - katalog z plikami, na którym uruchamiasz polecenia. Ma własny stan. W tym labie mamy dwa takie katalogi i to jest sedno całej konstrukcji.
- Moduł - zestaw plików napisany raz i używany z wielu miejsc, żeby nie kopiować tej samej definicji. Odpowiednik funkcji.
- Plik blokady - zapis, który dokładnie provider i w jakiej wersji został pobrany, razem z sumami kontrolnymi. Trafia do repozytorium; katalog z samą wtyczką już nie.
- Cloud image - gotowy, minimalny obraz dysku z zainstalowanym systemem, przeznaczony do uruchamiania w wirtualizacji. Nie przechodzi się przez instalator: obraz startuje od razu.
- cloud-init - mechanizm obecny w takim obrazie, który przy pierwszym starcie konfiguruje system z danych podanych z zewnątrz: nazwę hosta, konto, klucz publiczny, pakiety.
- Golden image - lokalna kopia obrazu systemu, z której klonuje się dyski maszyn. Pobierana raz i używana wiele razy, zamiast ściągać ten sam plik przy każdej maszynie.
Terraform czy OpenTofu
W 2023 roku HashiCorp zmienił licencję Terraform z otwartej na Business Source License. W odpowiedzi powstało odgałęzienie kodu, dziś rozwijane jako OpenTofu pod skrzydłami Linux Foundation. Dla labu takiego jak ten wybór jest wygodniejszy, niż się wydaje, bo nie trzeba go podejmować raz na zawsze.
Sprawdziliśmy to wprost. Ta sama konfiguracja warstwy bazowej, bez żadnej zmiany w plikach, przeszła przygotowanie i walidację pod oboma narzędziami:
# Terraform 1.16.1
terraform init && terraform validate
# Success! The configuration is valid.
# OpenTofu 1.12.6 - te same pliki, nic nie zmieniane
tofu init && tofu validate
# Success! The configuration is valid.
Jedyna różnica, jaką widać po tej operacji, siedzi w pliku blokady - inny jest host rejestru, z którego pobrano wtyczkę:
| Narzędzie | Wpis w pliku blokady | Wersja providera |
|---|---|---|
| Terraform | registry.terraform.io/dmacvicar/libvirt | 0.8.3 |
| OpenTofu | registry.opentofu.org/dmacvicar/libvirt | 0.8.3 |
Praktyczny wniosek: pliki konfiguracji są wspólne, a narzędzie wybiera się jedną zmienną. W naszym pliku Makefile stoi po prostu TF ?= terraform, więc przełączenie całego labu na drugie narzędzie to make apply TF=tofu. Nie zalecamy natomiast mieszania obu na tym samym stanie - plik blokady jest wtedy przepisywany w tę i z powrotem, co zaśmieca historię repozytorium bez żadnego zysku. Wybierz jedno na dany katalog i trzymaj się go.
Czego ten test nie obejmuje: nie porównywaliśmy zachowania obu narzędzi przy realnym tworzeniu maszyn ani przy migracji istniejącego stanu między nimi. Sprawdzona jest zgodność samej konfiguracji, nie równoważność w każdym scenariuszu.
Architektura: dwie warstwy, dwa stany
Najważniejsza decyzja w całym labie nie dotyczy narzędzia, tylko podziału. Lab jest rozbity na dwa katalogi główne z osobnymi stanami:
lab/
├── modules/
│ └── vm-group/ wspólna definicja maszyny (dyski, cloud-init, domena)
├── base/ pula obrazów, obrazy bazowe, sieć lab → własny stan
├── testboxes/ maszyny testowe → własny stan
└── Makefile skróty na oba katalogi
Powód jest praktyczny. Obrazy bazowe waży się w gigabajtach i pobiera z Internetu. Maszyny testowe kasuje się i stawia po kilka razy dziennie. Gdyby jedno i drugie siedziało w tym samym stanie, każde destroy maszyn zabierałoby ze sobą obrazy, a każde odtworzenie labu zaczynałoby się od kilkunastu minut pobierania. Przy osobnych stanach kasowanie maszyn nie rusza obrazów.
Katalog z maszynami musi jednak wiedzieć, jakie obrazy powstały w warstwie bazowej i jak nazywa się sieć. Służy do tego odczyt cudzego stanu:
data "terraform_remote_state" "base" {
backend = "local"
config = {
path = var.base_state_path
}
}
Dzięki temu warstwa maszyn czyta identyfikatory obrazów i nazwę sieci z warstwy bazowej, zamiast mieć je przepisane. Zmiana w jednym miejscu wystarcza.
Sama definicja maszyny jest napisana raz, w module modules/vm-group, i używana przez katalog maszyn. To jest ta sama zasada, co wyciągnięcie powtarzającego się fragmentu do funkcji: opis dysku, konfiguracji startowej i domeny istnieje w jednym egzemplarzu, a katalogi podają mu tylko listę maszyn.
Kolejność ma znaczenie. Warstwę bazową buduje się pierwszą, bo maszyny czytają jej stan. Kasuje się ją natomiast ostatnią, z tego samego powodu. Odwrotna kolejność przy sprzątaniu kończy się maszynami, które odwołują się do nieistniejących już obrazów.
Budowa krok po kroku
Poprzednie sekcje tłumaczyły, dlaczego lab jest zbudowany tak, a nie inaczej. Ta jest do wykonania od góry do dołu. Osiem kroków, z czego pierwsze trzy robi się raz na hoście, a pozostałe pięć powtarza się przy każdym odtworzeniu.
Przy każdym kroku podajemy, co ma z niego wyjść - bo „polecenie się wykonało” to za mało, żeby przejść dalej.
Krok 1. Sprawdź, czy host w ogóle udźwignie wirtualizację
grep -cE 'vmx|svm' /proc/cpuinfo
Wynik większy od zera oznacza, że procesor ma sprzętowe wsparcie wirtualizacji i jest ono włączone. Zero przy nowoczesnym procesorze prawie zawsze znaczy, że funkcja jest wyłączona w ustawieniach firmware komputera - bez niej maszyny albo nie ruszą, albo będą chodzić rozpaczliwie wolno.
Krok 2. Zainstaluj to, czego Terraform nie zainstaluje
To jest pierwszy z warunków wstępnych opisanych dalej: warstwa wirtualizacji musi już istnieć. Nazwy pakietów są z Debiana i Ubuntu:
sudo apt update
sudo apt install -y \
libvirt-daemon-system qemu-system-x86 qemu-utils \
ovmf genisoimage
# Bez tego każde polecenie wymagałoby uprawnień administratora.
sudo usermod -aG libvirt "$USER"
newgrp libvirt # albo po prostu wyloguj się i zaloguj ponownie
Sprawdzenie: virsh list --all ma się wykonać bez sudo i zwrócić pustą listę zamiast błędu uprawnień. Pakiet ovmf dostarcza firmware UEFI, genisoimage jest potrzebny do budowania obrazu z konfiguracją startową, a qemu-utils do konwersji obrazów w kroku 4.
Krok 3. Zainstaluj Terraform albo OpenTofu
Wystarczy jedno z nich. Oba mają własne repozytoria pakietów - HashiCorp pod apt.releases.hashicorp.com, OpenTofu pod packages.opentofu.org. Aktualne polecenia dodania klucza i repozytorium weź z dokumentacji wybranego projektu - instrukcja instalacji Terraform albo instrukcja instalacji OpenTofu, bo adresy kluczy bywają zmieniane.
Sprawdzenie: terraform version albo tofu version zwraca numer. Na naszym hoście stoją oba, zainstalowane właśnie z tych repozytoriów - i to jest w pełni poprawna sytuacja, o ile nie używasz ich naprzemiennie na tym samym stanie.
Krok 4. Utwórz katalogi
mkdir -p ~/lab/{base,testboxes,modules/vm-group}
mkdir -p ~/lab-golden # tu wylądują obrazy bazowe
Katalog na obrazy musi być trwały. Nie /tmp - warstwa bazowa czyta te pliki przy każdym odtwarzaniu obrazu, więc po wyczyszczeniu katalogu tymczasowego odbudowa skończy się błędem o brakującym pliku źródłowym.
Krok 5. Pobierz obrazy i przekonwertuj je do formatu surowego
To jest ten krok, który wynika z pułapki opisanej wyżej - i jednocześnie drugi warunek wstępny, którego nie ma w kodzie. Cztery systemy z tego artykułu:
cd ~/lab-golden
curl -fLO https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2
curl -fLO https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-amd64.qcow2
curl -fLO https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img
curl -fLO https://cloud-images.ubuntu.com/releases/26.04/release/ubuntu-26.04-server-cloudimg-amd64.img
for f in *.qcow2 *.img; do
qemu-img convert -O raw "$f" "${f%.*}.raw"
done
# Kontrola: KAŻDY plik .raw musi zgłaszać format raw.
for f in *.raw; do echo -n "$f: "; qemu-img info "$f" | awk -F': ' '/file format/{print $2}'; done
Sprawdzenie jest tu ważniejsze niż zwykle: jeżeli któryś plik zgłosi qcow2, maszyna z niego zbudowana wstanie do pustego ekranu. Ten jeden warunek decyduje o powodzeniu całej reszty.
Krok 6. Zbuduj warstwę bazową
W katalogu base/ umieść pliki pokazane w sekcji o warstwie bazowej: deklarację wersji, konfigurację połączenia, pulę, obrazy i sieć. W katalogu obrazów wskaż ścieżki do plików surowych z kroku 5, a nie adresy internetowe.
cd ~/lab/base
terraform init # pobiera provider w ZADEKLAROWANEJ wersji
# i zapisuje ją w .terraform.lock.hcl
terraform apply
To jest moment, w którym deklaracja wersji z poprzedniej sekcji zaczyna działać. init czyta blok required_providers, pobiera dokładnie tę wersję providera - nie najnowszą dostępną - i zapisuje ją razem z sumami kontrolnymi w pliku .terraform.lock.hcl. Nagłówek tego pliku mówi to wprost: jest utrzymywany automatycznie przez init, więc nie edytuje się go ręcznie. Od tej chwili każde kolejne uruchomienie w tym katalogu, także u kogoś innego, użyje tego samego providera. Podniesienie wersji wymaga świadomej zmiany w pliku i ponownego init z flagą -upgrade.
Nie myl tego pliku ze stanem. .terraform.lock.hcl zapisuje, jakim narzędziem budujesz - wersje providerów i ich sumy kontrolne - i trafia do repozytorium, żeby wszyscy używali tego samego. Stan, czyli terraform.tfstate, powstaje dopiero przy apply i zapisuje, co zostało utworzone. Stanu do repozytorium się nie wrzuca: bywa duży, zmienia się przy każdym uruchomieniu i potrafi zawierać dane wrażliwe.
init wykonuje się osobno w każdym katalogu głównym. Katalog maszyn z kroku 7 ma własny plik blokady i własną kopię providera, mimo że wersja jest ta sama.
Sprawdzenie - trzy rzeczy naraz:
virsh pool-list # ma być pula z obrazami
virsh vol-list golden # ma być po jednym obrazie na system
virsh net-list # ma być sieć laboratoryjna, aktywna
Na naszym hoście ten krok zajął 70 sekund dla sześciu systemów.
Krok 7. Zbuduj maszyny
W katalogu testboxes/ umieść mapę maszyn z sekcji o czterech maszynach oraz odczyt stanu warstwy bazowej. Pamiętaj o dwóch ustawieniach, bez których wróci pułapka z kroku 5: disk_strategy = "copy" i firmware UEFI.
cd ~/lab/testboxes
terraform init
terraform apply
Sprawdzenie: virsh list --all pokazuje wszystkie maszyny w stanie uruchomionym. Na naszym hoście krok zajął 205 sekund dla sześciu maszyn - to najdłuższy etap, bo każdy dysk jest pełną kopią obrazu.
Krok 8. Poczekaj na konfigurację startową i zaloguj się
Maszyny są już uruchomione, ale przez pierwsze minuty instalują pakiety. Najpierw poczekaj, aż wszystkie dostaną adres:
virsh net-dhcp-leases lab
Potem wygeneruj wpisy SSH i zaloguj się po nazwie maszyny:
make ssh-config # albo ręcznie: virsh domifaddr <maszyna>
ssh deb13
U nas od uruchomienia do możliwości zalogowania mijały około dwóch minut. Jeżeli któraś maszyna zgłosi błąd konfiguracji startowej, zajrzyj do sekcji o Debianie 11 - to niekoniecznie usterka labu, a może być informacja o systemie, który wypadł ze wsparcia.
Łącznie: około sześciu minut na kroki od 6 do 8, przy założeniu, że kroki 1-5 zostały wykonane wcześniej. Te pierwsze pięć robi się raz. Późniejsze odtworzenie labu - także pełne, ze skasowaniem wszystkiego - to już tylko kroki 6, 7 i 8.
Warstwa bazowa: obrazy i sieć
Warstwa bazowa tworzy trzy rzeczy: miejsce na obrazy, same obrazy i sieć, w której będą pracować maszyny. Zaczyna się od zadeklarowania wersji - narzędzia i wtyczki, bo to dwie różne rzeczy:
terraform {
required_version = ">= 1.3"
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.8.3"
}
}
}
provider "libvirt" {
uri = "qemu:///system"
}
Wersja providera jest wpisana dokładnie, a nie jako zakres, i to jest świadoma decyzja. Zapis ~> 0.8 oznacza „wszystko poniżej 1.0”, więc wpuszcza także wydania z serii 0.9 - a te istnieją, od 0.9.0 do 0.9.9. Jedna z nich zepsuła nam walidację konfiguracji, i to bez żadnej zmiany po naszej stronie. Od tamtej pory każdy katalog ma wpisaną konkretną wersję.
Połączenie qemu:///system oznacza systemowy demon libvirt, ten sam, którego używa graficzny menedżer maszyn. Aby nie potrzebować uprawnień administratora do każdej operacji, konto powinno należeć do grupy libvirt.
Dalej powstaje pula, czyli katalog, w którym libvirt trzyma obrazy, oraz same obrazy bazowe:
resource "libvirt_pool" "golden" {
name = "golden"
type = "dir"
target {
path = "/var/lib/libvirt/images/golden"
}
}
resource "libvirt_volume" "base" {
for_each = toset(local.distros_to_build)
name = "${each.key}-base.qcow2"
pool = libvirt_pool.golden.name
source = local.distro_url[each.key]
format = "raw"
}
Konstrukcja for_each tworzy tyle obrazów, ile pozycji ma lista - jeden opis, wiele wyników. Nazwa local.distros_to_build odwołuje się do wartości wyliczonej wcześniej, w tym wypadku listy systemów do zbudowania.
Katalog systemów jest zwykłą mapą. Poniżej cztery pozycje, o które chodzi w tym artykule:
distros = {
"debian-12" = {
pretty = "Debian 12 (bookworm)"
latest_url = "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2"
}
"debian-13" = {
pretty = "Debian 13 (trixie)"
latest_url = "https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-amd64.qcow2"
}
"ubuntu-2404" = {
pretty = "Ubuntu 24.04 LTS (noble)"
latest_url = "https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img"
}
"ubuntu-2604" = {
pretty = "Ubuntu 26.04 LTS (resolute)"
latest_url = "https://cloud-images.ubuntu.com/releases/26.04/release/ubuntu-26.04-server-cloudimg-amd64.img"
}
}
enabled_distros = ["debian-12", "debian-13", "ubuntu-2404", "ubuntu-2604"]
Zwróć uwagę na dwa szczegóły, które łatwo przeoczyć. Debian i Ubuntu mają różny układ adresów: Debian wstawia datę wydania i do nazwy katalogu, i do nazwy pliku, a Ubuntu tylko do nazwy katalogu. Dlatego trzymamy pełne adresy zamiast składać je z samej daty. Drugi szczegół: lista enabled_distros musi pokrywać wszystkie systemy używane przez maszyny, bo inaczej maszyna nie znajdzie swojego obrazu.
Na koniec sieć laboratoryjna, osobna od domyślnej sieci hosta:
resource "libvirt_network" "lab" {
name = "lab"
mode = "nat"
addresses = ["192.168.123.0/24"]
domain = "lab"
dhcp { enabled = true }
dns { enabled = true }
autostart = true
}
Oddzielna sieć dla maszyn testowych ma prostą zaletę: eksperyment z regułami zapory albo z trasowaniem nie dotyka tego, co host ma w sieci domyślnej. Adresy przydziela usługa DHCP (od angielskiego Dynamic Host Configuration Protocol), więc nie przypisujemy ich w kodzie na sztywno - do czego jeszcze wrócimy przy logowaniu.
Cztery maszyny testowe
Cała lista maszyn to jedna mapa. Każdy wiersz opisuje jedną maszynę, a wszystko, czego nie podano, bierze wartość domyślną z modułu:
vms = {
deb12 = { group = "testdeb", profile = "base", distro = "debian-12" }
deb13 = { group = "testdeb", profile = "base", distro = "debian-13" }
ubu2404 = { group = "testubu", profile = "base", distro = "ubuntu-2404" }
ubu2604 = { group = "testubu", profile = "base", distro = "ubuntu-2604" }
}
disk_strategy = "copy"
wait_for_lease = false
Wiersze są krótkie dzięki temu, że w typie modułu pola mają wartości domyślne. W praktyce wygląda to tak:
variable "vms" {
type = map(object({
group = string
profile = string
distro = string
vcpus = optional(number, 2)
memory_mib = optional(number, 2048)
disk_gib = optional(number, 10)
network = optional(string, "lab")
running = optional(bool, true)
autostart = optional(bool, false)
}))
}
Stąd te 2 rdzenie i 2 GiB z poprzedniej sekcji - nie trzeba ich powtarzać przy każdej maszynie. Jeżeli jedna z nich potrzebuje więcej, dopisujesz tylko to jedno pole: deb13 = { ..., memory_mib = 4096 }.
Warto zwrócić uwagę na dwa ustawienia spoza mapy. autostart = false oznacza, że maszyny nie wstają razem z hostem - to celowe, bo lab ma być włączany wtedy, kiedy jest potrzebny. wait_for_lease = false mówi, że tworzenie maszyn nie ma czekać, aż każda z nich dostanie adres z DHCP. Przy czterech maszynach na raz to oszczędza sporo czasu, ale ma konsekwencję: zaraz po utworzeniu narzędzie nie zna jeszcze ich adresów.
Pole group nie jest ozdobnikiem. Pozwala pracować na podzbiorze maszyn bez ruszania reszty:
# tylko maszyny Debiana, Ubuntu zostaje nietknięte
terraform apply -var='only_groups=["testdeb"]'
To jest wygodniejsze i bezpieczniejsze niż wskazywanie pojedynczych zasobów z linii poleceń, bo filtr działa na wejściu - narzędzie w ogóle nie widzi maszyn spoza grupy, zamiast widzieć je i pomijać.
Co dostaje maszyna przy pierwszym starcie
Obraz chmurowy startuje bez instalatora, ale też bez konta, na które można się zalogować. Uzupełnia to cloud-init, czytając przygotowany dla niego plik. W labie plik jest szablonem, w który przy tworzeniu maszyny wstawiane są nazwa hosta, konto i klucz publiczny:
#cloud-config
hostname: ${hostname}
fqdn: ${hostname}.${domain}
users:
- name: ${username}
groups: [sudo]
shell: /bin/bash
sudo: "ALL=(ALL) NOPASSWD:ALL"
ssh_authorized_keys:
- ${ssh_public_key}
package_update: true
package_upgrade: false
packages:
- qemu-guest-agent
- python3
- sudo
- curl
final_message: "cloud-init OK - ${hostname}.${domain} gotowy"
Cztery pakiety z tej listy nie są przypadkowe. python3 jest potrzebny, bo Ansible wykonuje na maszynie docelowej moduły napisane w Pythonie - bez niego playbook nie ruszy. qemu-guest-agent to mały program, przez który host może zapytać system w środku o jego adres; bez niego zostaje odczyt z tablicy DHCP, który bywa nieaktualny. package_upgrade: false jest świadome: maszyna ma wstawać szybko i w stanie zgodnym z obrazem, a aktualizacja pakietów to często właśnie to, co chcemy przetestować, a nie coś, co ma się wydarzyć samo w tle.
Ostatnia linia bywa niedoceniana. final_message pojawia się na konsoli szeregowej maszyny, kiedy cloud-init skończy pracę. Kiedy maszyna „nie wstaje”, jej brak jest pierwszą informacją diagnostyczną: albo konfiguracja startowa nie została w ogóle wczytana, albo przerwała się w połowie.
Pułapka: dlaczego maszyny nie bootowały
To jest fragment, dla którego warto było napisać ten artykuł. Objaw był taki: maszyny powstawały poprawnie, narzędzie kończyło pracę bez błędu, a system się nie uruchamiał. Starszy firmware pokazywał komunikat o dysku, z którego nie da się uruchomić systemu, a UEFI kończyło w swojej powłoce, nie widząc żadnego systemu plików. Przy tym qemu-img i narzędzia do partycji czytały obraz bez zarzutu, a maszyna utworzona ręcznie przez virt-install z tego samego obrazu wstawała normalnie.
Przyczyna okazała się jedna. Provider w wersji 0.8.3, przy podpięciu dysku przez disk { file = ... }, wpisuje do opisu maszyny format surowy, ignorując rzeczywisty format pliku. Widać to w opisie działającej maszyny:
$ virsh dumpxml deb13 | grep -A2 "<disk type"
<disk type='file' device='disk'>
<driver name='qemu' type='raw'/>
<source file='/var/lib/libvirt/images/deb13.img'/>
Obrazy chmurowe są natomiast publikowane w formacie qcow2, czyli takim, który ma własny nagłówek na początku pliku. Jeżeli dysk jest w rzeczywistości qcow2, a maszyna dostaje informację, że jest surowy, to qemu podaje firmware'owi początek pliku tak, jak leży. Firmware czyta nagłówek qcow2 w miejscu, w którym spodziewa się tablicy partycji - i słusznie stwierdza, że żadnej tam nie ma.
Dlaczego inne narzędzia działały? Bo qemu-img i fdisk same rozpoznają format po nagłówku, a virt-install wpisuje do opisu maszyny format prawidłowy.
Rozwiązanie: skoro provider i tak zadeklaruje format surowy, dysk faktycznie ma być surowy. Provider dziedziczy format ze źródła, więc wystarczy, żeby obraz bazowy był surowy - dyski maszyn odziedziczą to po nim. Konwersję robi jedno polecenie i nie wymaga uprawnień administratora.
mkdir -p ~/virt_infra/golden-raw
qemu-img convert -O raw \
debian-13-genericcloud-amd64.qcow2 \
~/virt_infra/golden-raw/debian-13.raw
Następnie w warstwie bazowej wskazujesz te pliki zamiast adresów w Internecie, a maszyny tworzysz z ustawieniem disk_strategy = "copy". Sprawdzone: przy takim układzie UEFI czyta tablicę partycji i partycję rozruchową oryginalnego obrazu chmurowego, maszyna wstaje, cloud-init się wykonuje i logowanie działa.
Dwie rzeczy, które warto zapamiętać przy okazji:
- Trzymaj obrazy surowe w katalogu trwałym, nie w
/tmp. Warstwa bazowa czyta te pliki przy każdym odtwarzaniu obrazu. Po wyczyszczeniu katalogu tymczasowego odtworzenie maszyny kończy się błędem o brakującym pliku źródłowym. - Format surowy kosztuje miejsce. Dysk w formacie qcow2 rośnie w miarę zapisu, surowy zajmuje od razu całość. To dlatego cztery maszyny po 10 GiB zajmują dokładnie 40 GiB, a nie tyle, ile faktycznie zapisano w środku.
Przy okazji rozwiązały się dwa mniejsze kłopoty. Dysk budowany jako warstwa na obrazie bazowym, zamiast pełnej kopii, potykał się o mechanizm kontroli dostępu na hoście, który nie dopuszczał pliku bazowego - pełna kopia nie ma tego problemu. A konfiguracja startowa podpinana jako napęd optyczny nie działała na nowszym typie maszyny wirtualnej, który nie ma odpowiedniego kontrolera; podpięcie jej jako zwykłego dysku rozwiązało sprawę.
Zastrzeżenie: to wszystko dotyczy providera w wersji 0.8.3. W nowszych wydaniach zachowanie mogło się zmienić - dlatego właśnie wersja jest zadeklarowana wprost, a nie zakresem.
Powtarzalność: najnowszy obraz czy z konkretnego dnia
Dystrybucje publikują obrazy chmurowe okresowo, z wbudowanymi poprawkami bezpieczeństwa, w katalogach oznaczonych datą. Obok nich jest katalog wskazujący najnowszy. To stawia przed wyborem, który dobrze pokazuje, na czym polega powtarzalność:
| Wybór | Zaleta | Koszt |
|---|---|---|
| Zawsze najnowszy | świeże poprawki bez żadnej pracy | ten sam kod uruchomiony dziś i za miesiąc zbuduje lab z innego obrazu; różnicy nie widać w repozytorium |
| Konkretny dzień | ten sam kod daje zawsze ten sam lab | obraz się starzeje i trzeba go świadomie podnosić |
To jest dokładnie ta sama decyzja, co przy wersji providera, tylko przeniesiona na obrazy systemów. W labie trzymamy oba adresy i przełącznik między nimi, bo jedno i drugie ma swoje zastosowanie: przy zwykłej pracy chcemy świeżych poprawek, a przy odtwarzaniu błędu sprzed miesiąca - dokładnie tamtego obrazu.
Jest tu jedna pułapka praktyczna, o której łatwo się przekonać w najgorszym momencie: serwery Debiana usuwają stare zrzuty po kilku tygodniach. Adres wskazujący konkretny dzień sprzed pół roku zwróci błąd. Jeżeli budujesz z konkretnej daty i dostajesz odpowiedź o braku pliku, trzeba wziąć aktualne oznaczenie z listy katalogów. Ubuntu trzyma swoje wydania dłużej, ale zasada jest ta sama - nie zakładaj, że adres sprzed roku nadal odpowiada.
Operacje dnia drugiego
Postawienie labu to jedno, a codzienna praca z nim to drugie. Trzy operacje wykonujemy najczęściej.
Odtworzenie jednej maszyny
Najczęstsza sytuacja: test popsuł maszynę i chcemy ją mieć z powrotem w stanie wyjściowym, nie ruszając pozostałych. Wymaga to wymuszenia wymiany trzech rzeczy naraz - dysku, konfiguracji startowej i samej maszyny:
terraform apply \
-replace='module.vms.libvirt_volume.vm_disk["deb13"]' \
-replace='module.vms.libvirt_cloudinit_disk.vm_init["deb13"]' \
-replace='module.vms.libvirt_domain.vm["deb13"]'
Adresy wyglądają rozwlekle, bo zasoby siedzą w module: najpierw jego nazwa, potem typ zasobu, a w nawiasie klucz z mapy maszyn. Właśnie dlatego w labie mamy na to skrót w pliku Makefile - make restore DIR=testboxes VM=deb13 - który składa te trzy adresy sam. Odtworzenie całej grupy działa tak samo, tylko pętla po nazwach maszyn z grupy.
Ważne, żeby wymienić wszystkie trzy rzeczy. Sam dysk bez konfiguracji startowej da maszynę z czystym systemem, ale ze starym kluczem i nazwą hosta z poprzedniego wcielenia.
Powiększenie dysku bez utraty danych
Dysk maszyny da się powiększyć w miejscu, bez odtwarzania. W labie robi to make resize VM=deb13 GB=30, a pod spodem jest zwykła operacja libvirt na woluminie. Tu działa zasada „tylko w górę”: warstwa bazowa i maszyny mają wymuszanie rozmiaru zapisane tak, żeby powiększać, a nigdy nie zmniejszać. Zmniejszenie dysku z danymi to prosta droga do uszkodzenia systemu plików, więc kod po prostu tego nie robi.
Logowanie po nazwie maszyny
Tu wracamy do adresów przydzielanych przez DHCP. Sieć laboratoryjna ma włączony DNS, ale rozwiązywanie nazw gości bywa zawodne - zwłaszcza tuż po starcie maszyny albo gdy dostała inny adres niż poprzednio. Zamiast walczyć z tym po stronie sieci, generujemy zwykły plik konfiguracyjny SSH:
make ssh-config # odpytuje maszyny o adresy i zapisuje ~/.ssh/config-lab
Powstaje plik z wpisem na każdą maszynę, który wystarczy raz podłączyć w ~/.ssh/config linią Include ~/.ssh/config-lab. Od tego momentu ssh deb13 po prostu działa. Adresy odczytywane są najpierw przez agenta w systemie gościa, a dopiero w drugiej kolejności z tablicy DHCP - dlatego ten agent jest na liście pakietów w konfiguracji startowej.
Plik jest nadpisywany w całości przy każdym uruchomieniu, więc własnych wpisów w nim nie trzymamy - od tego jest osobny plik, którego generowanie nie rusza. Maszyny lab są odtwarzane często i przy każdym odtworzeniu zmieniają klucz hosta, dlatego wpisy celowo nie sprawdzają kluczy znanych. To jest ustępstwo uzasadnione w labie i nie należy go przenosić na maszyny produkcyjne.
Sprzątanie
Kasowanie odbywa się w kolejności odwrotnej do budowania. Najpierw maszyny, na końcu warstwa bazowa:
terraform -chdir=testboxes destroy # tylko maszyny testowe
terraform -chdir=base destroy # NA KOŃCU: obrazy i sieć lab
Podział na dwa stany pokazuje tu swoją wartość. Skasowanie maszyn nie zabiera obrazów bazowych, więc postawienie labu z powrotem trwa tyle, co skopiowanie plików - bez ponownego pobierania kilku gigabajtów. W praktyce drugiego polecenia używa się rzadko: warstwa bazowa stoi miesiącami, a maszyny znikają i wracają.
Domyślna sieć hosta nie jest zarządzana przez ten kod, więc sprzątanie jej nie dotyczy. To celowe - nie chcemy, żeby operacja na labie zabrała sieć, z której korzystają inne maszyny.
Ile trwa pełne odtworzenie
Żeby nie zostawiać tego w sferze deklaracji, skasowaliśmy cały lab i zbudowaliśmy go od nowa. Najpierw maszyny, potem warstwa bazowa - czyli w kolejności odwrotnej do budowania - a następnie odbudowa w kolejności właściwej. Zmierzone czasy:
| Krok | Co się stało | Czas |
|---|---|---|
| Kasowanie maszyn | 19 zasobów: dyski, konfiguracje startowe, maszyny | natychmiast |
| Kasowanie warstwy bazowej | 14 zasobów: pula, obrazy bazowe, sieć laboratoryjna | natychmiast |
| Odbudowa warstwy bazowej | pula, obrazy, sieć | 70 s |
| Odbudowa maszyn | dyski, konfiguracje startowe, maszyny wraz z powiększeniem dysków | 205 s |
| Start i konfiguracja startowa | adres z DHCP, konto, klucz, pakiety | około 2 min |
Od pustego miejsca do maszyn, na które można się zalogować: około sześciu minut. Kasowanie jest natychmiastowe, bo sprowadza się do usunięcia plików i definicji. Najdłuższy krok to odbudowa maszyn - i tu widać koszt formatu surowego, bo każdy dysk jest pełną kopią obrazu, a nie warstwą na nim.
Po tej operacji warto od razu odświeżyć plik konfiguracji SSH, bo adresy przydzielone przez DHCP prawie na pewno będą inne niż poprzednio. Sprawdziliśmy całą ścieżkę: po wygenerowaniu pliku logowanie po nazwie maszyny działa bez podawania adresu.
Jedno zastrzeżenie do tych liczb. Był to host, na którym libvirt jest zainstalowany, obrazy w formacie surowym leżą na dysku, a pliki ze zmiennymi są na miejscu - czyli wszystko, o czym mowa w następnej sekcji. Zmierzony jest cykl skasowania i odbudowy, a nie postawienie labu na maszynie, która nigdy go nie widziała.
Lab sam mówi, kiedy system wypada ze wsparcia
Przy tej odbudowie wydarzyła się rzecz, której nie planowaliśmy, a która dobrze pokazuje, po co lab w ogóle istnieje. Pięć z sześciu maszyn zakończyło konfigurację startową czysto. Szósta - Debian 11, którego w tym artykule celowo nie opisujemy jako systemu testowego - zgłosiła błąd:
$ cloud-init status
status: error
$ sudo apt-get update
E: Release file for .../bullseye-security/InRelease is expired
(invalid since 2d 16h 44min 11s)
Plik z podpisem repozytorium bezpieczeństwa wygasł dwa dni wcześniej. Debian celowo nadaje tym plikom datę ważności: kiedy wydanie przestaje być wspierane, nikt ich już nie odświeża, a menedżer pakietów odmawia korzystania z przeterminowanego indeksu, zamiast po cichu instalować ze źródła, którego nikt nie pilnuje.
Maszyna wstała i wpuściła po SSH - konto i klucz powstają wcześniej niż instalacja pakietów - ale krok z aktualizacją pakietów zakończył się niepowodzeniem i cała konfiguracja startowa dostała status błędu. To jest dokładnie ten sygnał, dla którego warto mieć lab: system wypadł ze wsparcia i pierwszą osobą, która się o tym dowiedziała, była maszyna testowa, a nie serwer klienta.
Wniosek praktyczny jest prosty i wart zapamiętania: jeżeli konfiguracja startowa ma włączone odświeżanie listy pakietów, to odtworzenie maszyny jest zarazem testem, czy jej system nadal żyje. Nic nie trzeba w tym celu dopisywać.
Czego ten kod nie robi
„Infrastruktura opisana kodem” brzmi jak obietnica, że jedno polecenie odtworzy całość na dowolnej maszynie. W praktyce prawie każdy taki projekt ma warunki wstępne, których w kodzie nie ma - i lepiej je znać, niż odkryć w trakcie. Poniżej cztery, które dotyczą tego labu.
Po pierwsze, przygotowanie hosta. Terraform zakłada, że zainstalowane są libvirt oraz QEMU - czyli, kolejno, warstwa zarządzania maszynami wirtualnymi i sam emulator, który je uruchamia, że w systemie leży firmware UEFI dla maszyn wirtualnych i że Twoje konto należy do grupy pozwalającej zarządzać maszynami bez uprawnień administratora. Sam się o to nie zatroszczy.
Po drugie, nie każda pula jest zarządzana. Warstwa bazowa tworzy pulę na obrazy systemów, ale dyski maszyn lądują w puli, która musi już istnieć - jej nazwa jest zwykłą zmienną wejściową, opisaną wprost jako „istniejąca”. To celowe: jest to standardowa pula hosta, z której korzystają też maszyny spoza labu, więc nie chcemy, żeby operacja na labie mogła ją usunąć.
Po trzecie, obrazy w formacie surowym powstają poza Terraformem. Konwersję z sekcji o formacie dysku wykonuje osobny skrypt, uruchamiany ręcznie. Nie ma na to ani zasobu w kodzie, ani skrótu w pliku Makefile - warstwa bazowa tylko czyta gotowe pliki z dysku.
Po czwarte - i to jest najbardziej podstępne - pliki, które wybierają działającą ścieżkę, są wyłączone z repozytorium. Reguły ignorowania obejmują wszystkie pliki ze zmiennymi oraz katalog z obrazami surowymi. Sprawdziliśmy, co dostaje ktoś, kto sklonuje repozytorium: nie ma tam ani pliku wskazującego obrazy surowe, ani pliku ustawiającego pełną kopię dysku, ani samych obrazów.
Skutek jest gorszy niż błąd. Lista maszyn ma wartość domyślną, więc po sklonowaniu repozytorium polecenie wykona się bez żadnego ostrzeżenia - tyle że sięgnie po wartości domyślne: obrazy qcow2 prosto z Internetu i dysk budowany jako warstwa. To jest dokładnie ta kombinacja, która daje maszyny niebootujące się z powodów opisanych wyżej. Dostajesz więc lab, który powstał „poprawnie” i nie działa, a przyczyna siedzi w dwóch plikach, których w repozytorium nie ma.
Wnioski są dwa i oba wykraczają poza ten lab. Pierwszy: reguły ignorowania warto przeglądać pod kątem tego, czy nie wypada przez nie konfiguracja, a nie tylko sekrety i stan. Wykluczenie wszystkich plików ze zmiennymi jest wygodne, dopóki nie okaże się, że to właśnie w nich siedzi jedyna działająca kombinacja ustawień. Rozwiązaniem jest tu wersja przykładowa trzymana w repozytorium - u nas jest taka dla zmiennych przypinających obraz do konkretnego dnia, ale dla pozostałych plików jej brakuje.
Drugi: test „czy da się odtworzyć” trzeba wykonać na czystej maszynie, a nie na tej, na której się pracuje. Na hoście, który ma już pule, obrazy i pliki lokalne, wszystko będzie działać niezależnie od tego, co faktycznie znalazło się w repozytorium. Dopiero klon w pustym katalogu pokazuje prawdę - i to zajmuje kwadrans, a nie dzień.
Warto przy tym oddzielić dwie rzeczy, bo łatwo je pomylić. Braki opisane wyżej dotyczą pierwszego uruchomienia - postawienia labu tam, gdzie go nie było. Codzienne odtwarzanie, opisane w sekcji o operacjach dnia drugiego, działa bez zastrzeżeń: skasowanie maszyny i postawienie jej z powrotem w stanie wyjściowym to jedno polecenie i kilka minut.
Ograniczenia tego podejścia
- To lab, nie miniatura produkcji. Jedna maszyna fizyczna, jeden host wirtualizacji, brak nadmiarowości. Nadaje się do sprawdzenia, czy playbook działa na czterech systemach; nie nadaje się do wnioskowania o wydajności ani o zachowaniu przy awarii sprzętu.
- Stan leży w pliku lokalnym. Dla jednej osoby to zaleta - zero infrastruktury dodatkowej. Dla zespołu byłby to problem, bo dwie osoby pracujące równocześnie nadpiszą sobie stan nawzajem. Wtedy trzeba przenieść stan w miejsce wspólne, z blokowaniem.
- Opis dotyczy jednej wersji providera. Zachowanie z formatem dysku, kluczowe dla całej konstrukcji, sprawdziliśmy na
0.8.3. Przy podnoszeniu wersji trzeba to sprawdzić ponownie, zaczynając od jednej maszyny. - Format surowy zajmuje pełne miejsce. Cztery maszyny to równe 40 GiB, niezależnie od tego, ile faktycznie w nich zapisano. Przy większej liczbie maszyn to zaczyna być odczuwalne.
- Konfiguracja startowa działa raz. Wszystko, co ma się wydarzyć przy pierwszym uruchomieniu, dzieje się tylko wtedy. Zmiana w tym pliku nie wpłynie na istniejącą maszynę, dopóki jej nie odtworzysz - i to jest częsta przyczyna zdziwienia.
- Lab nie zastępuje etapu przejściowego. Sprawdzenie zmiany na czystym Debianie 13 nie dowodzi, że zadziała na serwerze z pięcioletnią historią konfiguracji. To dwa różne testy i oba są potrzebne.
Checklista
- Host ma włączoną wirtualizację, zainstalowane libvirt z QEMU, a konto należy do grupy
libvirt. - Wersja narzędzia i wersja providera są zadeklarowane wprost, a plik blokady jest w repozytorium.
- Warstwa bazowa jest budowana przed maszynami, a kasowana po nich.
- Obrazy bazowe są w formacie surowym i leżą w katalogu trwałym, nie tymczasowym.
- Lista systemów w warstwie bazowej pokrywa wszystkie systemy używane przez maszyny.
- Konfiguracja startowa wstawia Twój klucz publiczny, a nie przykładowy z artykułu.
- Maszyny nie startują razem z hostem, chyba że świadomie tego chcesz.
- Wiesz, jak odtworzyć jedną maszynę bez ruszania pozostałych, i sprawdziłeś to raz na spokojnie.
- Wygenerowany plik konfiguracji SSH jest podłączony w
~/.ssh/config, a własne wpisy trzymasz osobno. - Sprawdziłeś na czystym katalogu, co naprawdę zawiera repozytorium - czy reguły ignorowania nie wycięły plików potrzebnych do pierwszego uruchomienia.
- Ustępstwa poczynione dla wygody labu - brak sprawdzania kluczy hostów, konto bez hasła do
sudo- zostają w labie.
Lab zwraca zainwestowany czas w momencie, w którym przestajesz się zastanawiać, czy możesz coś zepsuć. Cała ta konstrukcja - dwie warstwy, wspólny moduł, zadeklarowane wersje - służy jednej rzeczy: żeby odpowiedź na pytanie „a co się stanie, jak…” była na wyciągnięcie jednego polecenia, a jej koszt wynosił kilka minut.
Chcesz testować zmiany, zanim trafią na produkcję?
Budujemy środowiska testowe odwzorowujące Twoją infrastrukturę, opisane kodem i odtwarzalne w kilka minut - żeby aktualizacja systemu przestała być wieczorem pełnym napięcia.
Zobacz też