FreeBSD jails a Docker: różnice w izolacji systemowej
Porównanie architektury FreeBSD jails oraz kontenerów Docker. Analiza różnic w zarządzaniu procesami, sieciami i stanem systemu dla obu modeli izolacji środowisk uruchomieniowych.
FreeBSD jails a kontenery Docker w jednym akapicie
FreeBSD jails oraz kontenery Docker rozwiązują ten sam problem w odmienny sposób. Oba rozwiązania uruchamiają odizolowane środowiska użytkownika na współdzielonym jądrze, więc żadne z nich nie jest maszyną wirtualną. Różnica polega na zawartości. Kontener Docker uruchamia jeden proces z warstwowego obrazu pobranego z rejestru. Jail uruchamia kompletne środowisko użytkownika FreeBSD: własny /etc, własne skrypty startowe rc, własną bazę danych pkg oraz dowolną liczbę procesów. Niemal każda inna różnica opisana na tej stronie wynika z tego podstawowego założenia.
SSD Nodes nie oferuje obrazów FreeBSD. Na tej platformie nie można wynająć serwera z systemem FreeBSD, a poniższa treść nie stanowi instrukcji instalacji dla maszyn dostępnych w tej ofercie. Jest to porównanie dwóch modeli izolacji, przygotowane w celu ułatwienia wyboru odpowiedniego rozwiązania dla danego obciążenia oraz zrozumienia konfiguracji stosowanych przez zespoły FreeBSD bez konieczności zgadywania.
Czym w rzeczywistości jest jail
Mechanizm jails pojawił się w FreeBSD 4.0 w marcu 2000 roku, co czyni go starszym od cgroups i o około dekadę starszym od Docker. Mechanizm ten opiera się na jednym wywołaniu jądra. jail(8) pobiera drzewo katalogów i uruchamia w nim procesy z przypisanym identyfikatorem jail ID, a jądro odmawia wykonania określonego zestawu operacji dla każdego procesu posiadającego ten identyfikator. Proces działający w jail nie widzi procesów poza swoim środowiskiem, nie może montować ani odmontowywać systemów plików, nie może ładować modułów jądra ani wiązać się z adresami sieciowymi, które nie zostały przypisane do danego jaila. Nie ma tu oddzielnych typów przestrzeni nazw do nauki ani wyboru funkcji: ograniczenia są nakładane jako jedna całość, regulowana parametrami w konfiguracji jaila.
Na hoście polecenie jls wyświetla listę uruchomionych jaili, a jexec web sh otwiera powłokę wewnątrz jaila o nazwie web.
Jail tworzy się poprzez umieszczenie środowiska użytkownika FreeBSD w katalogu. System bazowy wykonuje to zadanie automatycznie:
sudo bsdinstall jail /usr/local/jails/containers/webPobiera to zestaw dystrybucyjny dla danej wersji systemu i wykonuje większość standardowych kroków instalacyjnych, dzięki czemu można ustawić hasło root, wybrać usługi i dodać użytkowników tak samo, jak na nowym serwerze. Instalator nigdy nie pyta o strefę czasową: kopiuje on plik /etc/localtime z hosta do jaila, dzięki czemu jail dziedziczy strefę czasową hosta. Wynikiem jest instalacja FreeBSD znajdująca się w folderze. Następnie należy opisać ją w pliku /etc/jail.conf:
web {
host.hostname = "web.example.internal";
path = "/usr/local/jails/containers/web";
ip4.addr = "10.0.0.10";
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
mount.devfs;
}Uruchom jail, a następnie sprawdź jego stan:
sudo service jail start web
jlsjls powinno teraz wyświetlić web wraz z identyfikatorem JID, nazwą hosta i adresem IP. Jeśli jail nie pojawia się na liście, należy uruchomić sudo jail -c web bezpośrednio. Zastosuje to tę samą konfigurację w pierwszym planie i wyświetli parametr, którego nie udało się zaakceptować, zamiast pozostawiać informację o błędzie w logach usługi.
Wierszem wartym dwukrotnego przeczytania jest exec.start = "/bin/sh /etc/rc". Uruchomienie jaila powoduje wykonanie standardowego skryptu startowego FreeBSD w jego wnętrzu, dzięki czemu jail uruchamia każdą usługę włączoną w jego własnym pliku /etc/rc.conf. Kontener Docker nie posiada odpowiednika tego kroku, ponieważ uruchamia on proces wejściowy obrazu i zatrzymuje się, gdy ten proces zakończy działanie.
Metody dostarczania oprogramowania: obrazy i rejestry a środowisko użytkownika tworzone samodzielnie
To jest różnica odczuwalna już pierwszego dnia.
W Dockerze wskazujesz nazwę oprogramowania i je otrzymujesz. docker pull nginx pobiera warstwowy obraz z adresowalną zawartością, który został zbudowany i przetestowany przez kogoś innego, a docker compose up -d uruchamia go z przypisanymi wolumenami i siecią. Rejestr jest tutaj produktem. Większość wartości przepływu pracy w Dockerze wynika z faktu, że tysiące projektów publikuje gotowe obrazy, co sprawia, że uruchomienie Dockera na VPS jest krótkim zadaniem, a nie osobnym projektem.
FreeBSD nie udostępnia domyślnego publicznego rejestru obrazów jail. Tworzysz puste środowisko użytkownika i instalujesz w nim oprogramowanie w taki sam sposób, jak w przypadku zwykłego serwera. Wymaga to więcej pisania. Jest to jednak bardziej przejrzyste, ponieważ w jail działa dokładnie to, co umieściło tam pkg, korzystając z tego samego zestawu pakietów, którego używa host.
Narzędzia skracają ten proces. BastilleBSD to popularny menedżer jail, dostępny jako pakiet:
sudo pkg install bastille
sudo sysrc bastille_enable=YES
sudo bastille setup
sudo bastille bootstrap 15.1-RELEASEbastille setup konfiguruje sieć, pamięć masową i zaporę sieciową. bastille bootstrap pobiera wydanie tylko raz, a każdy kolejny jail korzysta z tego samego źródła. FreeBSD 15.1 to aktualne wydanie produkcyjne, dostępne od czerwca 2026; należy podstawić wersję, która jest aktualnie używana.
Utworzenie jaila to jedna komenda, a jego wypełnienie to kolejna:
sudo bastille create web 15.1-RELEASE 10.17.89.10/24
sudo bastille pkg web install nginx
sudo bastille service web nginx start
sudo bastille console webbastille console web zapewnia powłokę logowania wewnątrz jaila, a bastille list pokazuje, co istnieje na hoście. Aby powtórzyć proces budowania, szablony Bastille przechowują kroki w pliku i stosują je do jaila, co jest najbliższym odpowiednikiem Dockerfile w tym środowisku. Szablon jest odtwarzany dla każdego jaila. Nic nie jest dostarczane w formie gotowej do uruchomienia.
Podsumowanie jest zatem krótkie. Docker dostarcza gotowe kompilacje innych osób. Jails wymagają własnych instalacji. Jeśli oprogramowanie z Twojej listy jest dostępne wyłącznie jako obraz kontenera, kwestia wyboru zostaje rozstrzygnięta, zanim pozostałe argumenty zostaną wzięte pod uwagę.
Stan i aktualizacje: rola ZFS
Docker celowo rozdziela stan aplikacji. System plików kontenera jest tymczasowy, dane znajdują się w nazwanym wolumenie lub montowaniu typu bind, a aktualizacja polega na docker compose pull, a następnie docker compose up -d. Kontener zostaje zastąpiony, a wszystko, co nie zostało umieszczone w wolumenie, przepada. Jest to zaleta, jeśli przestrzega się zasad, i przyczyna utraty danych, jeśli się o nich zapomni. Dlatego wybór między montowaniem typu bind a nazwanymi wolumenami ma tak duże znaczenie w stosie Compose.
Jail nie rozdziela stanu, a ZFS jest powodem, dla którego to rozwiązanie działa. Cały jail stanowi jeden dataset:
sudo zfs snapshot zroot/jails/containers/web@pre-upgrade
sudo pkg -j web upgrade
sudo zfs rollback zroot/jails/containers/web@pre-upgradePrzed uruchomieniem polecenia sprawdź rzeczywistą nazwę datasetu za pomocą zfs list; powyższa ścieżka odpowiada układowi opisanemu w podręczniku. Snapshot zajmuje około sekundy i prawie nie zużywa miejsca, dopóki zawartość jaila nie ulegnie zmianie. Jeśli aktualizacja spowoduje awarię usługi, wycofanie zmian (rollback) przywraca cały userland do poprzedniego stanu, włącznie z bazą danych pakietów i plikami konfiguracyjnymi edytowanymi ręcznie w nocy. Docker nie posiada wbudowanego odpowiednika, ponieważ jego model zakłada, że użytkownik nigdy go nie potrzebuje.
zfs clone stanowi drugą połowę tego mechanizmu. Klon snapshotu to nowy, zapisywalny jail, który współdzieli niezmienione bloki z rodzicem. Dzięki temu kopia testowa 3 GB jaila prawie nie zajmuje miejsca na dysku, dopóki nie zaczniesz wprowadzać w niej zmian. W ten sposób administrator FreeBSD tworzy jail "identyczny z produkcyjnym", aby przećwiczyć aktualizację.
Aktualizacja systemu bazowego jest oddzielona od aktualizacji pakietów. W przypadku jaila posiadającego własną kopię userlandu:
sudo freebsd-update -b /usr/local/jails/containers/web fetch installCienkie jaile (thin jails) pozwalają uniknąć powtarzania tej pracy. Montują one jeden współdzielony, tylko do odczytu system bazowy poprzez nullfs i udostępniają każdemu jailowi małą, własną warstwę zapisywalną. Dzięki temu system bazowy aktualizuje się raz, a każdy jail widzi wynik tej operacji. Bastille domyślnie tworzy cienkie jaile.
Sieć: publikowanie portów a decyzja o adresacji
Docker automatycznie zarządza siecią i wymaga jawnego publikowania wyjątków. Kontenery trafiają do mostka, komunikują się ze sobą za pomocą nazw usług w sieciach zdefiniowanych przez użytkownika, a -p 8080:80 udostępnia jeden z nich na hoście. Docker automatycznie tworzy reguły filtrowania pakietów, co wyjaśnia, dlaczego opublikowany port kontenera omija ufw.
Jail wymaga wyboru modelu na etapie konfiguracji; dostępne są dwa warianty.
Shared IP. ip4.addr = "10.0.0.10" ogranicza jail do wskazanego adresu. Sam w sobie nie ingeruje w interfejsy hosta, więc adres musi być już na nich skonfigurowany. Aby jail(8) dodał adres jako alias podczas tworzenia jaila, należy wskazać interfejs: ustaw interface = "em0"; w bloku jaila lub poprzedź adres zapisem ip4.addr = "em0|10.0.0.10";. Jail nie posiada własnego stosu sieciowego, więc nie może uruchomić własnego firewalla. Nie może również w pełni nasłuchiwać na wszystkich adresach: gniazdo wewnątrz jaila żądające 0.0.0.0 jest nadpisywane przez jądro na adres przypisany do jaila. Dwa jaile nie mogą nasłuchiwać na porcie 80 tego samego adresu, dlatego należy przypisać każdemu z nich osobny adres lub umieścić przed nimi reverse proxy.
VNET. Dodanie vnet; do jaila zapewnia mu pełny stos sieciowy: własne interfejsy, tablicę routingu i reguły firewalla. Połączenie z hostem realizowane jest przez epair, czyli wirtualny kabel z końcami po obu stronach, gdzie koniec po stronie hosta jest wpięty do mostka. Jest to rozwiązanie najbardziej zbliżone do modelu Dockera i stanowi podstawę dla typów jaili -V oraz -B w Bastille.
Przekierowanie portu hosta do jaila to reguła przekierowania pf. Bastille automatyzuje ten proces:
sudo bastille rdr web tcp 80 80Nie istnieje tu EXPOSE ani mechanizm automatycznego publikowania. Żaden ruch nie dotrze do jaila, dopóki nie zezwoli na to jego adres lub reguła przekierowania. Wymaga to dłuższego przygotowania, ale zapewnia znacznie bardziej przejrzysty firewall.
Limity zasobów: cgroups a rctl
Docker ogranicza kontener za pomocą cgroups, a limity są definiowane w miejscu konfiguracji kontenera: --memory=1g --cpus=1.5 w wierszu poleceń lub za pomocą odpowiednich kluczy w pliku Compose. Jeśli stos usług jest przechowywany w pliku Docker Compose na VPS, limit znajduje się obok usługi, której dotyczy, i jest wersjonowany w git.
FreeBSD wykorzystuje rctl, czyli podsystem, który należy aktywować. Jądro GENERIC zawiera obsługę rozliczania zasobów, ale jest ona domyślnie wyłączona poprzez opcję jądra RACCT_DEFAULT_TO_DISABLED. Należy dodać odpowiedni parametr do /boot/loader.conf i zrestartować system:
kern.racct.enable=1Następnie należy ustawić regułę i monitorować jej działanie:
sudo rctl -a jail:web:vmemoryuse:deny=1g
rctl -hu jail:webrctl -hu jail:web wyświetla bieżące zużycie zasobów przez jail w czytelnych jednostkach, co pozwala ocenić margines bezpieczeństwa przed wystąpieniem awarii. Akcja deny powoduje, że alokacja przekraczająca limit kończy się niepowodzeniem wewnątrz jaila, dzięki czemu widoczny jest błąd alokacji aplikacji, a nie komunikat o przerwaniu procesu na hoście.
Reguły dodane za pomocą rctl -a znikają po restarcie systemu. Usługa rctl we FreeBSD wczytuje je ponownie z pliku /etc/rctl.conf, dlatego należy zapisać regułę w tym pliku i włączyć usługę:
sudo sysrc rctl_enable=YESJest to obszar, w którym Docker jest wyraźnie wygodniejszy. Limit w pliku Compose jest weryfikowany wraz z usługą, którą ogranicza. Reguła rctl to linia w oddzielnym pliku, wskazująca na jail zdefiniowany w innej lokalizacji.
Gdy rozwiązaniem jest maszyna wirtualna: bhyve
Jail współdzieli jądro systemu gospodarza, więc niektóre funkcje są trwale niedostępne. Nie można uruchomić innej wersji jądra, załadować modułu jądra ani uruchamiać plików binarnych Linux w sposób, w jaki robią to kontenery Linux. FreeBSD posiada warstwę kompatybilności z systemem Linux, zwaną linuxulator, jednak implementuje ona jedynie podzbiór wywołań systemowych Linux i nie stanowi uniwersalnego rozwiązania dla dowolnych obrazów Linux.
bhyve to hiperwizor systemu FreeBSD i jest właściwym narzędziem, gdy wymagana jest rzeczywista granica maszyny: inny system operacyjny, inne jądro lub najemca, z którym nie chcemy współdzielić jądra. Kosztem jest pamięć, która zostaje zarezerwowana zamiast współdzielonej, oraz konieczność aktualizacji drugiego jądra. Jest to ta sama decyzja, którą podejmuje się w systemie Linux wybierając między kontenerami a pełnymi maszynami wirtualnymi, i to ona decyduje o tym, czy potrzebny jest VPS obsługujący wirtualizację zagnieżdżoną.
Ekosystem, czyli uczciwy powód, dla którego większość zespołów korzysta z Docker
Wszystko powyżej dotyczy modelu. O wyborze większości zespołów decyduje skala świata otaczającego dane rozwiązanie.
Docker oferuje Docker Hub oraz GHCR, docker compose, Kubernetes w sytuacji, gdy jeden serwer przestaje wystarczać, CI runners z wbudowaną obsługą kontenerów oraz polecenie typu quickstart w pliku README niemal każdego projektu. Jails oferują drzewo FreeBSD ports, które jest obszerne i starannie utrzymywane, oraz znacznie mniejszy zestaw gotowych do uruchomienia pakietów aplikacji. Gdy projekt publikuje wyłącznie obraz kontenera, w przypadku FreeBSD konieczne jest zapoznanie się z dokumentacją i samodzielne złożenie komponentów.
Jails zyskują na znaczeniu w ramach tego kompromisu. Są pożądane, gdy korzystasz już z ZFS i cenisz możliwość tworzenia snapshotów oraz przywracania całych usług, gdy usługi są natywne dla FreeBSD, gdy wymagasz pełnego środowiska userland dla każdego najemcy zamiast pojedynczego procesu, lub gdy oczekujesz, że jądro, filtr pakietów, system plików i dokumentacja będą utrzymywane jako jeden spójny system. To ostatnie jest tym, co określa się mianem spójności FreeBSD, co zostało szerzej omówione w szerszym porównaniu Linux i FreeBSD jako platform serwerowych oraz w tym, co FreeBSD 15 zmieniło w zastosowaniach serwerowych.
Podsumowanie. Jeśli zespół zna już Docker, koszt migracji jest realny, a zysk musi być konkretny. Nie należy zmieniać rozwiązania ze względu na jakość izolacji; oba modele są na tyle zbliżone, że ważniejsza jest poprawna konfiguracja. Zmiana jest uzasadniona, jeśli wymagane jest przywracanie całych usług oparte na ZFS lub gdy środowiskiem docelowym jest już FreeBSD.
FAQ
Czy mogę uruchamiać obrazy Docker na FreeBSD?
Nie obrazy Linux i nie w ramach wspieranej ścieżki. FreeBSD posiada wsparcie dla kontenerów OCI: sudo pkg install -y podman-suite instaluje Podman, który uruchamia kontenery poprzez ocijail, środowisko uruchomieniowe tworzące pod spodem rzeczywiste jails. Wymaga ono fdescfs zamontowanego w /dev/fd dla monitora kontenerów oraz pf dla NAT (network address translation) kontenerów. Obrazy OCI natywne dla FreeBSD działają najlepiej. Obrazy Linux wymagają dodatkowo warstwy kompatybilności z Linuxem, a według stanu na październik 2026 komunikat instalacyjny portu Podman dla FreeBSD nadal określa to rozwiązanie jako eksperymentalne, przeznaczone do ewaluacji i testów. Jeśli wdrożenie opiera się na stosie obrazów Linux, należy uruchomić je na systemie Linux. W rodzinie RHEL oznacza to instalację Docker na Rocky Linux lub AlmaLinux, gdzie Podman pojawia się ponownie jako pakiet, który przejmuje komendę docker jeszcze przed instalacją jakiegokolwiek oprogramowania.
Czy FreeBSD jails są bezpieczniejsze niż kontenery Docker?
Oba rozwiązania współdzielą jądro hosta, więc błąd w jądrze stanowi ryzyko dla obu, i żadne z nich nie stanowi granicy, którą należałoby wybrać dla kodu całkowicie niezaufanego. Różnica tkwi w punkcie wyjścia. Jail zaczyna od szerokiego zestawu zabronionych operacji, które włącza się ponownie pojedynczo. Kontener Docker zaczyna jako root wewnątrz zestawu przestrzeni nazw (namespaces) z ograniczonymi uprawnieniami (capabilities), a dalsze utwardzanie jest opcjonalne. W praktyce o bezpieczeństwie decyduje konfiguracja, a nie model: jail uruchomiony z włączonymi allow.mount i allow.raw_sockets nie jest bezpieczniejszy niż starannie skonfigurowany kontener.
Jak wykonać kopię zapasową jail?
Należy utworzyć snapshot zbioru danych (dataset) i wysłać go. sudo zfs snapshot zroot/jails/containers/web@backup, a następnie zfs send ten snapshot do innej puli lub do pliku, który zostanie skopiowany poza serwer. Ponieważ jail przechowuje całą przestrzeń użytkownika w jednym zbiorze danych, snapshot rejestruje zainstalowane pakiety oraz dane w jednym spójnym punkcie czasowym, wraz z każdym plikiem konfiguracyjnym edytowanym ręcznie. Jest to przeciwieństwo praktyki Docker, gdzie tworzy się kopie zapasowe nazwanych wolumenów oraz pliku Compose, a resztę odtwarza z obrazu.
Czy potrzebuję BastilleBSD, czy wystarczy system bazowy?
System bazowy jest wystarczający i stanowi lepszy punkt wyjścia. jail.conf, jls, jexec oraz service jail start obejmują cały model, a po ich opanowaniu można zarządzać dowolnym hostem FreeBSD bez konieczności uprzedniego poznawania narzędzi specyficznych dla danego hosta. Bastille to warstwa ułatwiająca pracę: automatyzuje pobieranie wydań, tworzy cienkie (thin) jails, stosuje szablony i zapisuje reguły przekierowań pf. Należy najpierw poznać podstawowe komendy, a Bastille dodać dopiero wtedy, gdy liczba jails sprawi, że ręczne wpisywanie poleceń stanie się uciążliwe.