Docker jest wspaniałą technologią. Przy pomocy Dockera w prosty sposób możemy rozwiązać jeden problem, a na jego miejsce stworzyć dwa inne, nowe, bardziej skomplikowane... Co jest powodem takiego stanu rzeczy? Czy przyczyną jest architektura Dockera? Brak zrozumienia? A może coś więcej?
2. Najczęstsze mity na temat Dockera
• "Docker jest dobry tylko do developmentu. Trzeba być nieodpowiedzialnym,
aby używać Dockera na produkcji"
• "Docker to takie lekkie maszyny wirtualne"
• "Nie ma różnicy w wydajności. Kontener dockerowy to przecież zwykły
proces linuksowy"
• "Nie powinno się uruchamiać baz danych w Dockerze, ponieważ bardzo
łatwo można stracić dane. Docker jest dobry, ale tylko do aplikacji stateless"
• "W kontenerze dockerowym powinien być uruchomiony tylko jeden proces"
3. Co jest tego powodem?
• Docker wystartował będąc bardzo młodym projektem, dużo eksperymentowano
• Ludzie nie czytają dokumentacji
• Ludzie nie śledzą zmian
• Ludzie używają starszych wersji oprogramowania (np. docker z repozytorium
Ubuntu/Debian, starsze wersje kernela)
• Sporo artykułów, blogów, prezentacji opisuje rozwiązania, które są już
deprecated lub też są lepsze metody na rozwiązanie danego problemu
• ... a czasem CORE Team Dockera rzeczywiście coś spierniczył ;)
5. Docker to takie lekkie maszyny wirtualne
• RootFS to nie jest to samo co system operacyjny czy też dystrybucja Linuksa
(kernel, init process, message logger itp.)
• Idea "lekkiej maszyny wirtualnej" to generator "pomysło-problemów":
zainstalujmy SSH serwer, systemd, rsyslog, cron
6. Docker to nie maszyna wirtualna
• Linux namespaces
• pid
• net
• mnt
• user
• ipc
• cgroups
• Warstwowy system plików (overlay2, aufs etc.)
• VETH - Virtual Ethernet Device
21. Wydajność: Native VS Container
• Wydajność to nie tylko CPU, ale również
• Sieć (veth, netfilter, NAT)
• Dysk (overlay2)
22. Docker Network Performance
1. iperf serwer oraz klient uruchomione natywnie na hoście
$ iperf -s
$ iperf -c 127.0.0.1 # 49.5 Gbits/sec xx
23. Docker Network Performance
2. iperf serwer uruchomiony w kontenerze. Mapowanie portu 5001/TCP
pomiędzy hostem a kontenerem
$ export IPERF_INSTALL="apt-get update && apt-get install
iperf"
$ docker run --rm -ti -p 5001:5001 ubuntu:16.04 /bin/bash
-lic "${IPERF_INSTALL} && iperf -s"
$ iperf -c 127.0.0.1 # 19.5 Gbits/secx
x
x
24. Docker Network Performance
3. Tak samo jak w punkcie (2) natomiast zamiast loopback (127.0.0.1)
użyjemy adresu IP przypisanego do eth0 hosta
$ export IPERF_INSTALL="apt-get update && apt-get install
iperf"
$ docker run --rm -ti -p 5001:5001 ubuntu:16.04 /bin/bash
-lic "${IPERF_INSTALL} && iperf -s"
$ iperf -c 192.168.0.100 # 40.5 Gbits/secx
x
x
25. Docker Network Performance
4. iperf server uruchomiony w kontenerze, kontener korzysta z network
namespace hosta
$ export IPERF_INSTALL="apt-get update && apt-get install
iperf"
$ docker run --rm -ti --network host ubuntu:16.04 /bin/
bash -lic "${IPERF_INSTALL} && iperf -s"
$ iperf -c 127.0.0.1 # 49.3 Gbits/sec
$ iperf -c 192.168.0.100 # 49.1 Gbits/sec
x
x
x
x x
26. Docker Network Performance
1
iperf serwer uruchomiony natywnie na hoście
iperf klient uruchomiony natywnie na hoście
49,5 Gbits/sec
2
iperf serwer w kontenerze
iperf client łączy się przez loopback (127.0.0.1)
19,5 Gbits/sec
3
iperf serwer w kontenerze
iperf client łączy się używając adresu IP hosta
40,5 Gbits/sec
4
iperf server w kontenerze, kontener używa --network=host
iperf client łączy się przez loopback (127.0.0.1) oraz adres IP hosta
~49,2 Gbits/
sec
27. Docker Network Performance
• Docker używa netfilter do zarządzania ruchem pomiędzy kontenerem i
hostem (DNAT, MASQUERADE)
$ iptables -L -n
$ iptables -L -n -t nat
• Kernel nie pozwala na filtrowanie ruchu na interface loopback (127.0.0.1),
więc docker dodatkowo uruchamia proces docker-proxy
• Docker-proxy działa w user-space, dlatego widać zauważalną różnice
(49 Gbits/sec VS 19 Gbits/sec)
28. Docker Storage Performance
• Kontenery dockerowe działają w oparciu o warstwowy system plików
• Mamy do dyspozycji wiele rodzajów storage-u:
• DeviceMapper (niska wydajność bez konfiguracji)
• BTRFS/ZFS (dobra wydajność, zaawansowane ficzery, wymaga zarządzania i
setupu)
• VFS (nieska wydajność, w ostateczności do developmentu)s
• AUFS (Dockera 18.06 i starsze, Ubutnu 14.04, Kernel 3.13 bez Overlay2)
• Overlay2 (rekomendowany)
32. Problemy / Limity OverlayFS
• open(2) - overlay2 obsługuję tylko podzbiór standardu POSIX
• fd1=open("foo", O_RDONLY)
• fd2=open("foo", O_RDWR)
• po operacji "copy_up" (otwarcie pliku w celu zapisu) fd1 oraz fd2 wskazują na dwa różne pliki
• fd1 wskazuje na plik z "lowerdir"
• fd2 wskazuje na plik z "upperdir"
• Obejściem problemu jest zrobienie touch przez co plik zostanie skopiowany do "upperdir"
• rename(2) również nie jest w pełni dobrze obsługiwane: trzeba obsłużyć failure i zrobić ręczni
"copy and unlink"
33. Przykład problemu z open(2)
$ cat Dockerfile
FROM debian:wheezy
RUN apt-get update &&
apt-get install -y mysql-server
34. Przykład problemu z open(2)
$ cat Dockerfile
FROM debian:wheezy
RUN apt-get update &&
apt-get install -y mysql-server
$ docker build -t tmp/mysql .
...
35. Przykład problemu z open(2)
$ cat Dockerfile
FROM debian:wheezy
RUN apt-get update &&
apt-get install -y mysql-server
$ docker build -t tmp/mysql .
...
$ docker run -ti --name foo tmp/mysql /bin/bash
root@f778488d22bd:/# service mysql start
[FAIL] Starting MySQL database server: mysqld ... failed!
36. Przykład problemu z open(2)
$ docker diff foo
C /var
C /var/lib
C /var/lib/mysql
C /var/lib/mysql/ib_logfile0
37. Rozwiązanie: Docker Volumes
$ cat Dockerfile
FROM debian:wheezy
VOLUME /var/lib/mysql
RUN apt-get update &&
apt-get install -y mysql-server
39. Rozwiązanie: Docker Volumes
$ cat Dockerfile
FROM debian:wheezy
VOLUME /var/lib/mysql
RUN apt-get update &&
apt-get install -y mysql-server
$ docker build -t tmp/mysql .
...
$ docker run -ti --name foo tmp/mysql /bin/bash
root@f778488d22bd:/# service mysql start
[ ok ] Starting MySQL database server: mysqld ..
[info] Checking for tables which need an upgrade, are corrupt or
were not closed cleanly..
41. Czy VOLUME jest szybsze od Overlay2?
$ time docker build
--no-cache
--file Dockerfile01 . > /dev/null
real 0m37.643s
user 0m0.185s
sys 0m0.052s
x
x
42. Czy VOLUME jest szybsze od Overlay2?
$ time docker build
--no-cache
--file Dockerfile01 . > /dev/null
real 0m37.643s
user 0m0.185s
sys 0m0.052s
$ time docker build
--no-cache
--file Dockerfile02 . > /dev/null
real 0m27.955s
user 0m0.163s
sys 0m0.070s
x
x
x
x
43. Jeden kontener = jeden proces
• ... ale dlaczego? Best practice!
• Best practice? Dlaczego postgres i nginx uruchamia wiele procesów?
• hmm ... no dobra. Czepiasz się. Jeden kontener = jedna aplikacja
• itd. itd.
• https://docs.docker.com/config/containers/multi-service_container/
• ... w takim razie o co chodzi?
44. Jeden kontener = jeden proces
• Chodzi tu o zarządzanie procesami wewnątrz kontenera
• PID Namespace jest przypisywany do procesu, pierwszego który go stworzył
• Jeśli proces się kończy to stworzony PID Namespace przestaje istnieć
• ... nawet jeśli inny kontener go używa
(--pid container:some-container)
• Musimy zatem posiadać jeden główny proces (PID 1, process "init"):
• obsługa sygnałów (TERM, INT)
• zarządzanie procesami, które stworzył
45. Obsługa sygnałów
• docker kill wysyła sygnał SIGKILL ("kill -9 [process]")
• Nie ma możliwości obsłużenia tego sygnału
• Rodzaj sygnału można zmienić: docker kill -s TERM
• docker stop wysyła sygnał SIGTERM ("kill [process]")
• Jeśli process nie zostanie zakończony po upływie określonego czasu
(parametr -t|--time, domyślnie 10 sekund) to zostanie zabity (SIGKILL)
46. CMD: EXEC vs SHELL
• W Dockerfile przy pomocy instrukcji CMD możemy wskazać na proces, który zostanie uruchomiony
w kontenerze
• ... lub jeśli zdefiniowany ENTRYPOINT to wartość CMD zostanie przekazana do ENTRYPOINT jako
argument
• ENTRYPOINT ["/usr/bin/my-script"]
CMD ["start"]
Wynik: /usr/bin/my-script start
• CMD posiada dwie formy EXEC oraz SHELL
• EXEC - proces zostanie uruchomiony jako PID 1
• SHELL - najpierw zostanie uruchomiony shell (domyślnie /bin/sh -c ), a następnie nasza
aplikacja
47. Obsługa sygnałów: aplikacja
$ cat script.sh
#!/bin/sh
trap "echo Got signal! && exit" TERM INT
echo "pid is $$"
while :
do
sleep 1
done