Nowy semestr akademicki w początkach, wizja tony projektów przede mną, z tego powodu tematem dzisiejszego postu będą kontenery LXC. Omówię pokrótce historię, działanie oraz podstawowe użycie. Uzasadnię także, dlaczego w mojej opinii stanowią lepszą alternatywę powszechnych maszyn wirtualnych i warto się z nimi zapoznać. :)
Rys historyczny
Ogrom programów instalowanych z różnych (czasem nie do końca zaufanych) źródeł, działających w obrębie jednego systemu spowodował konieczność ograniczania dostępu pojedyczego procesu do systemu. Chodziło pierwotnie o to, aby działający proces miał dostęp wyłącznie do danego katalogu i jego podkatalogów i nie był w stanie wyjść poza udostępnioną przestrzeń dyskową. Zmniejszało to rozmiar potencjalnych szkód w systemie plików, jakich mógł dokonać proces, który wymknął się spod kontroli. Take działanie od początku lat 80. gwarantuje chroot jail. Sposób jest prosty: tworzymy katalog (przykładowo /opt/jail
), przygotowujemy w nim niezbędne minimum potrzebne do funkcjonowania procesu (binarkę basha lub innego shella, zestaw podstawowych bibliotek i narzędzi wraz z „więzionym” programem) i wprowadzamy komendę: chroot /opt/jail
. Podany katalog staje się wówczas katalogiem /
(rootem), który, jak wiadomo, w systemach uniksowych jest katalogiem nadrzędnym. Rozwiązanie jak na te czasy było niezłe, aczkolwiek nie gwarantowało stuprocentowego bezpieczeństwa. Pozostała jeszcze kwestia roota, który mógł opuścić chroot jail w dowolnym momencie. Nie było także żadnych ograniczeń dotyczących montowania urządzeń w jailu oraz operowania na nich. Z tego powodu szukano lepszego mechanizmu. Odnaleziono go ponad 30 lat później w kontenerach LXC.
Szybki rzut oka pod maskę
Kontenery, prócz modyfikacji poczciwego chroot, bazują głównie na dwóch stosunkowo nowych funkcjach kernela – cgroups oraz namespaces. Funkcjonalności te dostarczają nieznanych dotychczas mechanizmów izolacji procesów oraz ich zasobów (interfejsów sieciowych, pamięci, CPU, urządzeń). Namespaces (przestrzenie nazw) pozwalają na określenie, które z siedmiu obszarów: montowania, PID, sieci, IPC (komunikacji międzyprocesowej), UTS, UID, cgroup będzie współdzielony dla danego procesu, bądź ich grupy. Procesy w tym samym namespace mogą współdzielić na przykład punkty montowania (jeśli są w tym samym mount namespace), co będzie skutkowało tym, że mount i umnount systemu plików nie będzie miał wpływu na punkty montowania znajdujące się poza tym namespace. Z kolei przeniesienie procesu do nowego network namespace pozwoli nam na tworzenie oddzielnych reguł firewalla, tablic routingu, będziemy mieć do dyspozycji także niezależne stosy IP. Cgroups, z kolei odpowiadają za określanie dostępu do zasobów (CPU, pamięci, I/O), ich kontrolę, limitowanie oraz priorytetyzację. Ta wiedza powinna wystarczyć do zrozumienia idei i wyczucia różnicy między historycznymi rozwiązaniami. Po więcej szczegółów, w postaci porządnych stron manuala z poleceniami obsługującymi cgroups oraz namespaces zapraszam na sam dół notki, bo to obszerny temat, a miało być praktycznie… :)
Praktyka
Kontenery LXC występują w dwóch wersjach: unprivileged oraz privileged. Różnica między jednymi a drugimi jest kwestią mapowania uid/guid użytkowników systemowych na te w kontenerach. Tworzy to dodatkową warstwę ochrony, ponieważ w wypadku „wyskoczenia” z takiego kontenera root, który w kontenerze ma uid 0, ma poza nim uid 100000 (patrz: pliki sub(u|g)id
, czyli jest pozbawiony specjalnych uprawnień. Z tego także powodu jesteśmy jednak także pozbawieni możliwości montowania nośnikow w kontenerze. Zakładam tu jednak, że kontener nie będzie do tego służył, więc możemy zgodzić się na takie ustępstwo. :) Zanim uruchomimy pierwszy kontener musimy: 1. Zainstalować pakiety lxc
, lxc-templates
, lxc-libs
, lxc-docs
(jeśli chcemy mieć dostęp do dokumentacji). 2. Sprawdzić, czy istnieją pliki /etc/subuid
i /etc/subgid
(odpowiadające za mapowanie uid/guid użytkowników), w postaci:
[user]:100000:65536
- Stworzyć plik
/etc/lxc/lxc-usenet
:
-
którego zawartość wygląda tak, w przypadku Debiana/Ubuntu:
[user] veth lxcbr0 100
-
a tak w wypadku chociażby Fedory (interfejs lxcbr0 nie działa poprawnie,
virbr0
wymaga dodatkowo instalacjilibvirtd
):[user] veth virbr0 100
-
Liczba 100 w konfiguracji jest maksymalną liczbą połączeń możliwą nawiązania przez danego użytkownika w sesji przez interfejs lxcbr0/virbr0.
- Stworzyć katalog
~/.config/lxc
-
skopiować
/etc/lxc/default.conf
do nowoutworzonego~/.config/lxc
, pamiętając wcześniej o ewenualnej zmianie interfejsu z punktu 3. i dodać linijki:lxc.id_map = u 0 100000 65536 lxc.id_map = g 0 100000 65536
-
W tym momencie wypadałoby zrobić reboot, celem przyporządkowania do nowych cgroup.
-
Po konfiguracji przystępujemy do tworzenia pierwszego kontenera:
lxc-create -n pierwszykontener -t download
-
Następnie uruchamiamy go poleceniem:
lxc-start -n pierwszykontener
I tu jest ciekawa sprawa, gdyż powinno to działać od razu. Niestety, Fedora, CentOS i inne Nie-Debianopochodne mają notoryczny problem z chmodami dla cgrops, który objawia się takim logiem:
xc-start 20180306224206.694 ERROR lxc_cgfs - cgroups/cgfs.c:lxc_cgroupfs_create:909 - Could not set clone_children to 1 for cpuset hierarchy in parent cgroup.
lxc-start 20180306224206.694 ERROR lxc_cgfs - cgroups/cgfs.c:cgroup_rmdir:209 - Permission denied - Failed to delete /sys/fs/cgroup/devices/user.slice
lxc-start 20180306224206.694 ERROR lxc_cgfs - cgroups/cgfs.c:cgroup_rmdir:209 - Read-only file system - Failed to delete /sys/fs/cgroup/perf_event/
lxc-start 20180306224206.694 ERROR lxc_cgfs - cgroups/cgfs.c:cgroup_rmdir:209 - Permission denied - Failed to delete /sys/fs/cgroup/memory/user.slice
lxc-start 20180306224206.694 ERROR lxc_cgfs - cgroups/cgfs.c:cgroup_rmdir:209 - Read-only file system - Failed to delete /sys/fs/cgroup/hugetlb/
lxc-start 20180306224206.694 ERROR lxc_cgfs - cgroups/cgfs.c:cgroup_rmdir:209 - Permission denied - Failed to delete /sys/fs/cgroup/pids/user.slice/user-1000.slice/session-2.scope
lxc-start 20180306224206.694 ERROR lxc_cgfs - cgroups/cgfs.c:cgroup_rmdir:209 - Read-only file system - Failed to delete /sys/fs/cgroup/cpuset/
lxc-start 20180306224206.695 ERROR lxc_cgfs - cgroups/cgfs.c:cgroup_rmdir:209 - Read-only file system - Failed to delete /sys/fs/cgroup/net_cls,net_prio/
lxc-start 20180306224206.695 ERROR lxc_cgfs - cgroups/cgfs.c:cgroup_rmdir:209 - Permission denied - Failed to delete /sys/fs/cgroup/blkio/user.slice
lxc-start 20180306224206.695 ERROR lxc_cgfs - cgroups/cgfs.c:cgroup_rmdir:209 - Permission denied - Failed to delete /sys/fs/cgroup/cpu,cpuacct/user.slice
lxc-start 20180306224206.695 ERROR lxc_cgfs - cgroups/cgfs.c:cgroup_rmdir:209 - Permission denied - Failed to delete /sys/fs/cgroup/freezer//snap.spotify
lxc-start 20180306224206.695 ERROR lxc_cgfs - cgroups/cgfs.c:cgroup_rmdir:209 - Read-only file system - Failed to delete /sys/fs/cgroup/freezer/
lxc-start 20180306224206.695 ERROR lxc_cgfs - cgroups/cgfs.c:cgroup_rmdir:209 - Permission denied - Failed to delete /sys/fs/cgroup/systemd/user.slice/user-1000.slice/session-2.scope
lxc-start 20180306224206.695 ERROR lxc_start - start.c:lxc_spawn:1286 - Failed creating cgroups.
Istnieje problem który w Debianpochodnych rozwiązuje się automatycznie (prawdopodobnie to wina braku komponentu wchodzącego w skład pakietu deb lxcfs
, który chownuje i odtwarza poprawną strukturę za nas). Jednak nic straconego – jesteśmy w stanie obejść to następującym skryptem z issues, odpalanym przed serią zabaw z kontenerami:
#!/usr/bin/bash
printf '\n\033[42mCreating cgroup hierarchy\033[m\n\n' &&
for d in /sys/fs/cgroup/*; do
f=$(basename $d)
echo "looking at $f"
if [ "$f" = "cpuset" ]; then
echo 1 | sudo tee -a $d/cgroup.clone_children;
elif [ "$f" = "memory" ]; then
echo 1 | sudo tee -a $d/memory.use_hierarchy;
fi
sudo mkdir -p $d/$USER
sudo chown -R $USER $d/$USER
# add current process to cgroup
echo $PPID > $d/$USER/tasks
done
-
Do nowego kontenera dostajemy się po raz pierwszy przez:
lxc-attach -n pierwszykontener
Później możemy zmienić hasło root’a oraz zainstalować serwer ssh, każdy kontener posiada dynamicznie przydzielany adres IP, dlatego jest to znacznie wygodniejszy sposób.
- Jeszcze lista podstawowych poleceń:
-
Wyświetlanie top’a dla kontenerów:
lxc-top
-
Wylistowanie wszystkich kontenerów:
lxc-ls -f
-
Utworzenie checkpointu:
lxc-checkpoint -n [kontener]
-
Zatrzymanie pracy kontenera:
lxc-stop -n [kontener]
-
Zniszczenie kontenera:
lxc-destroy -n [kontener]
Czy to jest dla mnie?
Jak widać wyżej, temat kontenerów LXC pod wzgledem konfiguracji do najprostszych nie należy, ale uważam, że warto jest go poznać. Główną przewagą w stosounku do maszyn wirtualnych jest fakt, że są one bardzo lekkie, ponieważ działają na kernelu posiadanego Linuksa. Powinny przydać się webdeveloperom do testowania stron i aplikacji webowych, a także przy tworzeniu mikroserwisów. Będą także bardzo przydatne w sytuacji, kiedy tymczasowo uruchamiamy sobie jakąś usługę na portach i nie chcemy, żeby otwarte porty oraz pozostałości po pakietach zaśmiecały nam system. Używam ich obecnie jako środowisko Pelicana – statycznego generatora stron w Pythonie, mam także drugi kontener, który służy mi jako serwer Jupyter Notebooka, w którym testuję oraz przygotowuję projekty na uczelnię. W wypadku kiedy coś pójdzie nie tak, albo projekt zostanie oddany, będę mogła użyć lxc-destroy
i nie będzie problemu z zaśmiecaniem systemu.
Podsumowując
Ze swojej strony gorąco polecam zarówno używanie kontenerów, jak i zagłębianie się w ich tajniki. :)
Dodaj komentarz