V tomto seriali by som chcel popísať, ako Docker vytvára kontainery.

Čo je to kontajner

Dnes existujú rôzne technológie, ktoré sa zaoberajú vytváraním kontajnerov. Dá sa dokonca povedať, že je okolo nich celkom pekný hype. Určite si už videl nejaké cool prezentácie o niečom čo sa volá Docker kde ti bolo jednoducho povedané, že Docker využíva namespaces, cgroups, chroot, atď. na vytváranie kontajnerov. Ale načo je toto všetko potrebné na vytvorenie kontajnera? Prečo to nieje jednoducho systémove volanie a hotovo? Pretože realita je taká, že kontajnery neexistujú - sú vymyslené. Nič také ako “linux container” v kerneli nieje. Kontajner patri do oblasti user space a teda mimo kernelu.

Namespaces

Na začiatok si povieme niečo o Linux namespace aby bolo jasné ako sú použité v rámci Dockeru. A neskor sa pozrieme na to, ako sú namespaces kombinované so cgroups a izolovanými filsystémami na vytvorenie niečoho užitočného.

Najskor by by som mal povedať podstatu namespaces a načo sú užitočné. Namespace je funkcia Linux kernelu rozparticiovať rôzne resourcy do separatneho “priestoru”, kde skupina resourcov (napríklad procesov) v jednom priestore/namespace vidi iba resourcy patriace do toho istého namespacu. Linux kernel pozná niekoľko typov namespacov. Kukneme sa na ne.

NET Namespace

Sieťový namespace poskytuje vlastný pohľad na network stack tvojho sytému. Do toho môže byť zahrnuý aj localhost. V tomto príklade je pohlad z vnútra docker kontajnera bežiaceho s --net host a teda schopného vidieť všetky sieťové rozhrania hostu.

root@virt01:/containers# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
    inet 192.168.160.101/24 brd 192.168.160.255 scope global enp1s0
       valid_lft forever preferred_lft forever
    inet6 fe80::5054:ff:fefd:43c0/64 scope link
       valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ab:3d:e1:a2 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:abff:fe3d:e1a2/64 scope link
       valid_lft forever preferred_lft forever

Namespacy sú primárne ovládané pomocou clone flags. Pri systemovom volaní “clone” na vytvorenie nového sieťového namespace pre subproces je použitá variabla CLONE_NEWNET a tento namespace je neskôr spárovaný s veth takže kontajner dostane vlastnú IP alokovanú na bridge, väčšinou docker0.

MNT Namespace

Mount namespace ti dáva špecifikovaný pohľad na mounty na tvojom filesysteme. Ľudia si často tento namespace zamieňajú s jailingom procesu vnútri chroot a podobne. Lenže to nieje pravda. Mount namespace nieje to isté ako filesystem jail. chroot je fajn ale ten neposkytuje kompletnú izoláciu a jeho efekt je obmedzený iba na root mountpoint. Samostatný mount namespace docoľuje aby každý z izolovaných procesov mal vlastný pohľad na pôvodny mountpoint sysému.

Na vytvorenie nového mount namespace sa používa variable CLONE_NEWNS. Wait… čože? Nie CLONE_NEWMOUNT alebo CLONE_NEWMNT? Nie, pretože mount namespace bol úplne prvý namespace a tato variabla mu jednoducho ostala.

UTS Namespace

Ďalší namespace je UTS namespace. Ten je zodpovedný za identifikáciu systému. To zahŕňa hostname a domainname. Jednoducho umožňuje kontajneru mať svoj vlastný hostname, nezávisle na hostovi a ostatných kontajneroch. Tu je ako flag použitý CLONE_NEWUTS. Štandardne je jeho hodnota zdedená z hostu.

IPC Namespace

IPC namespace slúži na izolovanie komunikácie medzi procesmi, ako SysV alebo message queues. Tu sa používa flag CLONE_NEWIPC

PID Namespace

Tento namespace zabezpečuje s ktorými proces ID možes komunikovať a vidieť ich. Keď vytvoríš novy PID namespace, prvý proces bude PID 1. Ak tento proces existuje, kernel killne všetky ostatné v rámci tohto namespace.

Clone flag pre tento namespace je CLONE_NEWPID. Ak sa teraz pozriem na jednoduchý kontajner s CMD ["bash"] uvidím toto:

root@virt01:/containers# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.1  20236  3108 pts/0    Ss   15:11   0:00 bash
root        11  0.0  0.0  17492  2060 pts/0    R+   16:19   0:00 ps aux

USER Namespace

Aby sa zabezpečila prevencia proti privilege-escalation útokom, je vhodné aplikáciu v kontajnery nakonfigurovať tak, aby používala neprivilegovaného usera. Ak to nieje možné a proces v kontajneri musí bežať ako root použi user namespace. Tento namespace zabezpečí, že môžeš mať užívateľov v rámci jedneho namespace ktorý niesú tí istí užívatelia mimo tohto namespace. To znamená, že vďaka user namespace, môže byť user root vramci kontajnera ale zároveň ako “neroot” na hoste. Toto je docielené vďaka UID a GID mapingu.

Clone flag je použitý CLONE_NEWUSER.

A na záver

Takže pre rekapituláciu prešiel som cez Network, Mount, IPC, UTS, PID a User namespaces. Niekedy nabudúce sa pozriem ako sa jailne proces kontajnera na root filesysteme (docker image) a na používanie cgroups.