Konfiguracja kontenerów LXC

Spis treści

Kontenery LXC mają za zadanie odizolować poszczególne usługi od pozostałej części systemu. LXC jest podobny nieco do maszyn wirtualnych, np. tych tworzonych przez VirtualBox. Niemniej jednak, oba mechanizmy różnią się trochę. Zasadnicza różnica między nimi polega na tym, że LXC wykorzystuje środowisko chroot , w którym współdzielone jest jądro operacyjne. Nie trzeba także z góry określać zasobów pod działanie takiego kontenera, tak jak to ma w przypadku maszyn wirtualnych. Rzućmy zatem okiem jak wygląda konfiguracja takich kontenerów na linux'ie.

Konfiguracja systemu pod LXC

Kontener LXC działa na zasadzie zwykłego środowiska chroot ale posiada kilka znaczących różnic. W środowisku chroot nie ma możliwości określenia z jakich urządzeń będziemy korzystać, wobec czego dostępny jest cały katalog /dev/ . W przypadku LXC możemy określić, którym urządzeniom zezwalamy na działanie. Kontener LXC niekoniecznie musi korzystać z ustawień sieci hosta. Można na nim ustawić NAT i za pomocą iptables przepuszczać lub blokować połączenia. Jest też spora różnica w procesach, bo w środowisku chroot , po zamontowaniu katalogu /proc/ , mamy wgląd we wszystkie procesy dostępne w całym systemie. LXC z kolei pozwala zobaczyć jedynie tylko te procesy, które działają w jego obrębie. Naturalnie, host dalej ma możliwość wglądu w procesy wewnątrz kontenera LXC i może dowolnie nimi zarządzać. Dodatkowo LXC jest kontrolowany przez cgroups i można w ten sposób przydzielać zasoby pod kontenery. Można nawet zawiesić (zamrozić) taki kontener, by nie pobierał żadnych zasobów, aż do czasu odmrożenia.

By korzystać z kontenerów LXC, musimy doinstalować w systemie odpowiednie oprogramowanie, tj. pakiet lxc . We wcześniejszych wersjach debiana była cała masa problemów z kontenerami LXC oraz mechanizmem cgroups. Po tym jak systemd stał się domyślnym init'em, to praktycznie wszystkie problemy znikły i nie trzeba przeprowadzać żadnych dodatkowych akcji konfiguracyjnych, by wszystko działało jak należy. W każdym razie, jeśli mamy poprawnie skonfigurowany system pod kontener LXC, to po wpisaniu w terminalu lxc-checkconfig wszystkie kontrolki powinny być zapalone na zielono.

Przygotowanie kontenera LXC

Przy pomocy lxc-create budujemy kontener, w którym to zostanie zainstalowany minimalny system operacyjny. Możemy określić czy będzie to debian oraz ewentualnie jego wydanie, np. jessie. Odpalamy zatem terminal i wpisujemy w nim to poniższe polecenie:

# lxc-create -n kontener -t debian -P /media/Kabi/lxc_machines/ -- -r jessie -a amd64

Parametry w powyższej linijce oznaczają:

  • -n -- nazwa kontenera
  • -t -- szablon użyty do zbudowania systemu
  • -P -- ścieżka do zapisu plików kontenera
  • -r -- release tego co zostało użyte w opcji -t
  • -a -- architektura budowanego systemu

Gdy budujemy kontener po raz pierwszy, zostanie zainicjowany debootstrap , który pobierze, zainstaluje i skonfiguruje minimalny system. W przypadku budowania kolejnego kontenera (ta sama architektura, ta sama gałąź), nie będzie trzeba pobierać minimalnego systemu przez sieć jeszcze raz.

Przechodzimy teraz do edycji pliku konfiguracyjnego kontenera. Znajduje się on w /media/Kabi/lxc_machines/kontener/config . To w nim określamy urządzenia, punkty montowania oraz konfigurację sieci dla kontenera.

Urządzenia

W zależności od przeznaczenia kontenera LXC, będziemy musieli zdecydować, do których urządzeń taki kontener ma mieć dostęp. Praktycznie każde urządzenie z katalogu /dev/ możemy udostępnić z osobna w pliku konfiguracyjnym przez dopisanie w nim pozycji lxc.cgroup.devices.allow , przykładowo:

lxc.cgroup.devices.allow = c 195:0 rwm

Wyżej został użyty dziwny zapis: c 195:0 rwm . Co on oznacza? Dostęp do urządzeń w kontenerach LXC jest przyznawany za pomocą mechanizmu cgroups. Pierwszy znak ( c ) to urządzenie znakowe (char), jest także do wyboru b odpowiadające za urządzenie blokowe. Dalej mamy dwa numerki 195:0 i są to liczby zwracane przy listowaniu urządzeń w katalogu /dev/ . Ostatnia pozycja odpowiada za uprawnienia jakie zostaną przyznane kontenerowi w przypadku tego urządzenia: r , to odczyt, w, to zapis, a m odpowiada za tworzenie plików urządzeń przy pomocy mknod .

Problemy z udev'em

Udev nie działa z kontenerami LXC i wszystkie pliki urządzeń musimy sobie stworzyć ręcznie. Nie ma się co jednak przejmować, bo stworzenie takich urządzeń nie jest specjalnie trudne. Przede wszystkim, musimy określić jakie urządzenia z katalogu /dev/ nas interesują. Załóżmy, że chcemy by kontener miał dostęp do urządzenia /dev/nvidia0 , czyli do karty graficznej. Zaglądamy zatem do katalogu /dev/ i dla przykładu, to urządzenie nvidia0 wygląda tak:

$ ls -al /dev/nvidia0
crw-rw-rw-+ 1 root root 195, 0 Mar 26 12:01 /dev/nvidia0

Kluczowa sprawa, to te dwie liczby: 195 oraz 0 . Urządzenia tworzymy przy pomocy mknod i by stworzyć to urządzenie w kontenerze, wpisujemy w nim tę poniższą linijkę:

root@kontener:/dev# mknod -m 666 /dev/nvidia0 c 195 0

Podobnie postępujemy dla wszystkich urządzeń, do których kontener ma mieć dostęp.

Punkty montowania

W pliku konfiguracyjnym kontenera LXC możemy także określać punkty montowania zasobów, które mogą być udostępniane wewnątrz kontenera. Służy do tego parametr lxc.mount.entry :

lxc.mount.entry=/media/lxc-test/ /media/Kabi/lxc_machines/kontener/rootfs/lxc-test/ none bind 0 0

Pierwsza ścieżka odpowiada za zasób w systemie hosta. Druga za to gdzie go zamontować w kontenerze. Dalej już mamy opcje co programu mount . Katalog docelowy musi istnieć wewnątrz kontenera, w przeciwnym razie kontener się nam nie odpali i zostanie zwrócony poniższy błąd:

lxc-start: No such file or directory - failed to mount '/media/lxc-test/' on '/media/Kabi/lxc_machines/kontener/rootfs/lxc-test/'
lxc-start: failed to setup the mount entries for 'kontener'
lxc-start: failed to setup the container
lxc-start: invalid sequence number 1. expected 2
lxc-start: failed to spawn 'kontener'

Sieć

W pliku konfiguracyjnym umieszczamy też opcje dotyczące połączenia sieciowego kontenera LXC. W poniższym przykładzie został skonfigurowany NAT, który wykorzystuje wirtualny interfejs mostka br-lxc :

#lxc.network.type = empty
lxc.network.type = veth
lxc.network.name = veth0
lxc.network.flags = up
lxc.network.link = br-lxc
lxc.network.veth.pair = veth0-testing
lxc.network.ipv4 = 192.168.10.10/24
lxc.network.ipv4.gateway = 192.168.10.100

Mostek (bridge)

W tym przypadku wymagane jest także skonfigurować mostka. W przeciwnym razie kontener nie będzie miał dostępu do internetu. By móc stworzyć wirtualny interfejs mostka, potrzebne nam są narzędzia, a te są dostarczane przez pakiet bridge-utils . Instalujemy go i tworzymy trzy pliki w katalogu /etc/systemd/network/ :

Plik 20-br-lxc.netdev :

[NetDev]
Description=Bridge for LXC containers
Name=br-lxc
Kind=bridge

Plik 30-br-lxc-static.network :

[Match]
Name=br-lxc

[Network]
Description=LXC bridge configuration
DHCP=no
LinkLocalAddressing=no
Address=192.168.10.100/24
DNS=
IPForward=true

Plik 35-veth.network :

[Match]
Driver=veth
Name=veth0

[Network]
Description=Virtual interfaces Configuration
Bridge=br-lxc

Restartujemy teraz połączenie sieciowe (ewentualnie uruchamiamy komputer ponownie), by mieć pewność, że wszystko zostało skonfigurowane jak należy:

# systemctl daemon-reload
# systemctl restart systemd-networkd

Forwarding pakietów i reguły dla iptables

Musimy dodać jeszcze kilka reguł do naszego skryptu firewall'a:

iptables -t nat -A POSTROUTING -o bond0 -s 192.168.10.0/24 -j MASQUERADE

iptables -t filter -A icmp_in -p icmp -i br-lxc -s 192.168.10.0/24 -j ACCEPT
iptables -t filter -A udp -p udp -i br-lxc -s 192.168.10.0/24 -j ACCEPT
iptables -t filter -A tcp -p tcp -i br-lxc -s 192.168.10.0/24 -j ACCEPT

iptables -t filter -A fw-interfaces -i br-lxc -o br-lxc -s 192.168.10.0/24 -d 192.168.10.0/24 -j ACCEPT
iptables -t filter -A fw-interfaces -i br-lxc -o bond0 -s 192.168.10.0/24 -j ACCEPT

Włączamy także forwarding dla pakietów sieciowych w kernelu przez dopisanie do pliku /etc/sysctl.conf poniższych linijek:

net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1

Resolver

Połączenie kontenera ze światem jak i maszyną hosta powinno już zostać ustanowione. Mogą jednak nie działać domeny. W takim przypadku dobrze jest dopisać te poniższe linijki do pliku /etc/resolv.conf w kontenerze:

nameserver 208.67.222.222
nameserver 208.67.220.220

Plik hosts

Dobrze jest także skonfigurować sobie plik /etc/hosts zarówno na hoście jak i w kontenerze, zwłaszcza jeśli chcemy używać jakichś usług, np. apache2 .

Plik /etc/hosts :

192.168.10.10 lxc.mhouse lxc

Plik /media/Kabi/lxc_machines/kontener/rootfs/etc/hosts :

192.168.1.150 morfikownia.mhouse morfikownia

Plik sources.list

Jeśli zamierzamy instalować sporo rzeczy, powinniśmy rozważyć edycję pliku sources.list w kontenerze. Domyślne wpisy wskazują na mirror, który jest dość wolny. Zmieńmy je zatem:

deb     http://ftp.pl.debian.org/debian/ sid main non-free contrib
#     deb-src http://ftp.pl.debian.org/debian/ sid main non-free contrib

Chroot

W przypadku napotakania większych problemów z konfiguracją kontenera LXC, pamiętajmy, że zawsze możemy skorzystać ze zwykłego środowiska chroot, w którym to możemy doinstalować pakiet iputils-ping zawierający min. narzędzie ping .

Podgląd konfiguracji

Jeśli teraz podejrzymy interfejsy sieciowe (przez ifconfig albo ip addr show ), powinniśmy ujrzeć br-lxc :

br-lxc: flags=4163  mtu 1500
        inet 192.168.10.100  netmask 255.255.255.0  broadcast 192.168.10.255
        ether 5e:c5:78:f0:4a:b0  txqueuelen 0  (Ethernet)
        RX packets 9789  bytes 16299493 (15.5 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 10513  bytes 4859846 (4.6 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Przy włączonym kontenerze, powinien być widoczny także jego interfejs sieciowy:

veth0-testing: flags=4163  mtu 1500
        inet6 fe80::fcbd:d0ff:fe6b:219  prefixlen 64  scopeid 0x20
        ether fe:bd:d0:6b:02:19  txqueuelen 1000  (Ethernet)
        RX packets 7  bytes 578 (578.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 17  bytes 1372 (1.3 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Powinna być także widoczna dodatkowa trasa w tablicy routingu:

# ip route show
...
192.168.10.0/24 dev br-lxc  proto kernel  scope link  src 192.168.10.100

Jeśli zaś chodzi o samą konfigurację mostka, to wygląda ona tak:

# brctl show
bridge name     bridge id               STP enabled     interfaces
br-lxc          8000.5ec578f04ab0       no              veth0-testing

Operowanie kontenerem LXC

By wystartować kontener, wklepujemy do terminala taką oto linijkę:

# lxc-start -n kontener -P /media/Kabi/lxc_machines/

Logujemy się za pomocą użytkownika root oraz hasła, które zostało zwrócone po zakończeniu procesu instalacyjnego i sprawdzamy czy działa sieć przy pomocy apt-get update . Jeśli lista repozytoriów zostanie pobrana, znaczy, żę wszystko działa jak należy.

Zatrzymać kontener możemy albo przez zalogowanie się do niego i wydanie zwykłego polecenia zamykającego system, np. shutdown z odpowiednimi opcjami, czy też poweroff . Możemy także wyłączyć kontener z maszyny hosta przy pomocy:

# lxc-stop -n kontener -P /media/Kabi/lxc_machines/
Mikhail Morfikov avatar
Mikhail Morfikov
Po ponad 10 latach spędzonych z różnej maści linux'ami (Debian/Ubuntu, OpenWRT, Android) mogę śmiało powiedzieć, że nie ma rzeczy niemożliwych i problemów, których nie da się rozwiązać. Jedną umiejętność, którą ludzki umysł musi posiąść, by wybrnąć nawet z tej najbardziej nieprzyjemniej sytuacji, to zdolność logicznego rozumowania.