Kontenery LXC – WstępOkoło 10 minut

Cat w kontenerze ;-)

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
  1. 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 instalacji libvirtd):

    [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.

  1. Stworzyć katalog ~/.config/lxc
  2. 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
    
  3. W tym momencie wypadałoby zrobić reboot, celem przyporządkowania do nowych cgroup.

  4. Po konfiguracji przystępujemy do tworzenia pierwszego kontenera:

    lxc-create -n pierwszykontener -t download
    
  5. 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
  1. 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.

  1. 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ę.

Przymiarki do projektów na Jupyterze w kontenerze LXC
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. 🙂