Czy Docker ma sens w 2021 roku?

Czy Docker ma sens w 2021 roku?

Na początku grudnia 2020 gruchnęła informacja, że Kubernetes 1.20 "deprecates Docker". Póki co oznacza to, że Kubernetes będzie wyświetlał ostrzeżenie. Właściwie "deprecates Docker" odnosi się do dockershim co dokładniej wyjaśniam poniżej.

Dopiero w wersji 1.22 wsparcie Docker zostanie usunięte, co jest planowane na drugą połowę 2021 roku. I dlatego właśnie uważam że rok 2021 to początek końca Dockera.

Co to Docker i Kubernetes?

Docker pozwala zapakować naszą aplikację (np. plik JAR ze skompilowanym kodem Java) wraz ze środowiskiem uruchomieniowym (np. OpenJRE JVM) w jeden obraz, z którego są tworzone kontenery. Właściwie wszystkie zależności z systemu operacyjnego są dodane do obrazu Docker. Pozawala to na użycie tego samego obrazu na laptopie programisty, środowisku testowym i produkcji. W teorii.

Kubernetes jest orkiestratorem, co oznacza że zarządza wieloma kontenerami i przydziela im zasoby (CPU, RAM, storage) z wielu maszyn w klastrze. Odpowiada też za cykl życia kontenerów i łączenie kilku w jedną całość (jako Pod). Zatem działa poziom wyżej niż Docker zarządzając wieloma kontenerami na wielu maszynach.

Jeśli kontener Docker to odpowiednik maszyny wirtualnej kiedyś, to Kubernetes w świecie kontenerów jest odpowiednikiem dostawców hostingu czy usług chmurowych kiedyś. Docker (a właściwie Docker Compose) pozwala nam uruchamiać różne procesy i łączyć je w sieć oraz przydzielać storage w obrębie jednego komputera. Kubernetes pozwala na to samo w obrębie klastra, złożonego z wielu komputerów.

Kubernetes sprowadził Dockera do poziomu komponentu który zajmuje się uruchamianiem kontenerów. Dzięki wprowadzeniu standardu CRI (Container Runtime Interface) te komponenty są wymienialne. Obecnie tylko containerd oraz cri-o są zgodne z CRI. Docker wymaga adaptera dockershim którego właśnie programiści utrzymujący Kubernetes chcą się pozbyć.

Dlaczego Docker jest ważny?

Docker jest kamieniem milowym jeśli chodzi o popularyzację konteneryzacji. Gdy usłyszałem o nim pierwszy raz w 2013 w podcaście Coder Radio od założycieli dotCloud (później Docker Inc) zauważyłem potencjał. Ledwie rok później Docker umożliwił mi uruchomienie skomplikowanego systemu legacy na swoim komputerze - wtedy wiedziałem że nastąpił przełom.

Przez kilka ostatnich lat Docker z pobocznego projektu w firmie dotCloud przerodził się w biznes warty miliardy dolarów. Pomimo dofinansowania w wysokości 280 mln USD od funduszy venture capital Docker Inc nie radził sobie dobrze biznesowo i został kupiony przez Mirantis. Kwota akwizycji nie została podana do publicznej wiadomości, co jest ciekawe. Zgaduję że to była okazja ;)

Głównym produktem firmy Mirantis jest Kubernetes-as-a-service, gdzie konkurują z VMWare oraz oczywiście dostawcami chmury. Kubernetes jest dla nich istotny do tego stopnia, że chcieli utrzymywać Docker Swarm tylko przez 2 lata, ale szybko wycofali się z tej deklaracji, zapewne pod naciskiem obecnych klientów. Osobiście znam firmę która posiada dużą instalację Docker Swarm i migracja do innego rozwiązania to niełatwa sprawa.

Co to jest Docker Swarm?

Docker Swarm to orkiestrator wbudowany w dystrybucję Dockera. Można powiedzieć że to taki niby Kubernetes, którego obsługuje się tak prosto jak zwykłego Dockera. Oczywiście dochodzi zarządzanie node-ami, replikami, sieciami - jednak nadal jest to znacznie uproszczony widok klastra w porównaniu do Kubernetes.

Zatem można powiedzieć, że Mirantis kupiło konkurencję dla swojego flagowego produktu? Tak jakby. Obecnie Docker Swarm wyzbył się już chorób wieku dziecięcego (np. bug z przydzielaniem zduplikowanych adresów IP), więc wygląda na stabilny produkt dla małych zespołów. Problem w tym że na małych zespołach i małych klastrach zarabia się mało $$$.

Oprócz tego Docker Swarm jest zbyt prosty, po prostu. W naszym zespole jeden człowiek był w stanie stworzyć i obsługiwać klaster Docker Swarm. Nie licząc wspomnianych błędów nie ma przy tym wiele pracy. Duże aktualizacje przychodzą razem z Docker i niezbyt często, więc kolejne zmartwienie odpada.

Jaki interes ma Mirantis (właściciel Docker Enterprise)?

Pewnie nasuwa wam się pytanie: skoro Mirantis zarabia na Kubernetes-as-a-service dla dużych graczy, a Kubernetes usuwa wsparcie Dockera to jaki tu jest sens? Ano właśnie. Z mojej perspektywy wygląda to tak, że firma która zarabia na Kubernetes nie ma powodu inwestować w Docker od kiedy ten przestanie być wspierany przez Kubernetes.

Zastanówmy się jakie opcje ma Mirantis w temacie Dockera? Ja widzę kilka kierunków rozwoju, ale wszystkie kiepsko wróżą dla Docker:

  1. Postawić na containerd, ale wtedy zdegradują się jako dostawca komponentu blisko Linux kernel. Trudno będzie na tym zarobić, szczególnie jeśli obecne kontrakty są na wsparcie w innych niż Linux systemach operacyjnych.
  2. Rozwinąć Docker Swarm. Problem w tym że Swarm musiałby stać się tak złożony jak Kubernetes - jaka jest wtedy jego przewaga? Póki co Swarm nadaje się do małych projektów, ale to małe pieniądze.
  3. Zmienić Docker Engine w najlepsze narzędzie do rozwoju aplikacji dla Kubernetes. Coś jak skaffold.dev może? Ale wtedy nazwa Docker jak i dług techniczny Dockera (o tym później) będzie ciążyć.

Może sprzedawać Docker jako narzędzie dla programistów?

Ostatnia opcja jest ciekawa i mogłaby uratować Docker takiego jakiego znamy, jako świetne narzędzie dla deweloperów żeby szybko postawić skomplikowany system w kontrolowanym, lokalnym środowisku, które jest bardzo zbliżone do tego produkcyjnego. Niestety, sprzedawanie narzędzi dla programistów to trudny biznes i zazwyczaj mało lukratywny.

Wspomniane wcześniej VMWare nabyło tę lekcję wraz z akwizycją Spring Source. W skrócie, firma Spring Source próbowała sprzedawać Spring Framework programistom J2EE (Java) jako lepszy framework rozwoju aplikacji. To okazało się bardzo trudnym biznesem.

Punkt zwrotny to kiedy Spring zaczął być sprzedawany jako platforma zgodna z J2EE działom wsparcia i IT. To tu są prawdziwe pieniądze w świecie enterprise software ;) Polecam obejrzeć co Rod Johnson (do niedawna Spring Source CEO) mówi na ten temat.

Z drugiej strony pójście w stronę budowy narzędzi dla programistów stawiałoby Mirantis jako konkurenta dla Docker Inc, a raczej tego co z tej firmy zostało. Zakładam że umowy podpisane podczas akwizycji zabraniają firmom wchodzenia na swoje rynki, czyli Docker Inc zostanie przy wsparciu programistów i narzędzi dla nich, a Mirantis będzie pracowało z klientami klasy enterprise sprzedając im usługi wdrożenia i wsparcia.

Mirantis dba o obecnych klientów enterprise

Kilka dni później firma Mirantis wydała oświadczenie, że będzie utrzymywać dockershim (adapter Docker do interfejsu CRI) wraz z firmą Docker Inc. Jako powód podają swoich obecnych klientów którzy mają bardziej złożone instalacje Kubernetes, które są zależne od rzeczy specyficznych dla Docker Engine. Co to zmienia? Sytuacja wygląda bardzo podobnie jak przy Docker Swarm. Mirantis będzie miało jeszcze więcej długu technicznego do utrzymania (o czym niżej).

Muszę podkreślić że powyższe to tylko moje spekulacje. Nie mam wglądu ani w umowy między Docker Inc a Mirantis, ani w ich strategię. Opieram się jedynie na oficjalnych informacjach prasowych i obserwacji rynku. Próba wczucia się w to co może zrobić duża firma, bazując na swoim doświadczeniu, to ciekawe ćwiczenie umysłowe. Pozwala spojrzeć z dystansu na firmy które stoją za technologią używaną przez nas. Polecam.

Jeśli kogoś zainteresowała firma Mirantis to wygląda na to że ma biuro w Poznaniu i szuka ludzi do działów technicznych i sprzedażowych: mirantis.com/careers

Kwestie długu technicznego w Docker

Właściwie sytuacja rynkowa to wystarczający powód żeby nie inwestować więcej w Docker jako narzędzie do rozwiązywania problemów biznesowych. Niestety jest jeszcze dług techniczny którego Docker nabawił się przez lata, pomimo kilku strategicznych refaktoringów (m. in. wydzielenie runc i containerd) w tym czasie.

Problemy z union file system

Docker ma już ponad 7 lat historii i to legacy zaczyna ciążyć. Na początku Docker był interfejsem do funkcjonalności Linux kernel takich jak namespaces, union file systems (union FS) i control groups (cgroups). Z czasem gotowe rozwiązania union file system, jak AUFS, przestały wystarczać. Docker Inc postanowił dodać system plików overlay do kernel. Okazało się że ten system był tak nieudany że bardzo szybko powstał overlay2 i ten wkrótce był oznaczony jako polecany.

Mimo rekomendacji Docker przez lata unikałem overlay i overlay2 jak ognia ze względu na częste frustracje błędami i utratą danych. Oczywiście z czasem błędy zostały naprawione, ale w czasach Ubuntu 14.04 czy 16.04 LTS aktualizacje kernel nie były tak częste. Również dodanie obsługi brtfs (który ma funkcję union FS) nie poprawiło sytuacji, bo nadal pamiętam awarię na jedynej maszynie w klastrze która używała brtfs jako systemu plików.

Ostatnio usłyszałem stwierdzenie że cała konstrukcja sytemu plików w kontenerze i użycie union FS to "elegant hack" i powiem szczerze że to bardzo dobre podsumowanie.

Problemy z kontem root i zależnościami

Inną niefortunną decyzją było uruchomienie demona Docker na użytkowniku root, czyli administratorowi który może wszystko na danej maszynie. To powoduje że atak typu "container breakout" jest dużo bardziej grożny, niż gdyby demon działał z mniejszymi uprawnieniami. Zresztą, samo użycie demona jest też legacy, bo Podman (alternatywa Dockera od Red Hat) nie wymaga żadnego demona do uruchamiania kontenerów.

Domyślnie proces w kontenerze też jest uruchomiony z uprawnieniami root. Przez to łatwiej o atak typu "privilege escalation" i przejęcie kontroli nie tylko nad aplikacją, ale nad całą maszyną na której działa kontener. Obecnie jest to uznawane za złą praktykę i zaleca się tworzenie użytkownika z ograniczonymi uprawnieniami, ale nie wszystkie obrazy używają takiej konfiguracji.

Kolejny problem wynika z powyższych i traktowania kontenera jako "lekkiej maszyny wirtualnej". Chodzi mianowicie że każdy kontener ma pełny userspace danej dystrybucji Linux. Mimo że host serwer działa na CentOS to jeden kontener nakłada na to wszystkie katalogi z Ubuntu, a inny z Debian. Przez taką konstrukcję znacznie zwiększa się pole ataku na dany kontener. Co gorsze często jest to inne pole ataku niż system hosta (CentOS vs Ubuntu).

Innymi słowy: nawet jeśli nasz kontener to prosty, statycznie skompilowany microservice napisany w Go to "ciągnie" za sobą całe Ubuntu (na przykład). Rozwiązaniem nie jest użycie małych obrazów Alpine Linux, no chyba że używamy też Alpine na maszynie hosta. Lepszym rozwiązaniem są distroless images które pozbywają się większości zbędnych bibliotek z Debiana.

Jak to wygląda na innych systemach operacyjnych?

Cały czas mówiłem o Docker na Linux, bo to jest natywny system operacyjny dla Docker. Warto pamiętać że Docker na początku to była prosta nakładka na mechanizmy udostępniane przez Linux kernel takie jak namespaces, cgroups (control groups), union file systems. Obecnie runc zajmuje się tą warstwą, ale to integralna część Docker Engine.

Osobiście nie mam doświadczenia z Docker na systemach innych niż Linux. Z tego co wiem to działanie opiera się, w taki czy inny sposób, na wirtualizacji Linux kernel na tych systemach. We wczesnych latach sam odpalałem TinyCore Linux (zajmuje ledwo kilka MB) na VirtualBox żeby przetestować funkcje których jeszcze nie było w stabilnej wersji kernel. Lata minęły, ale zasada pozostaje taka sama.

Jeśli chodzi o Windows 10 to Microsoft sporo inwestuje w "developer experience". Tak naprawdę WSL i WSL2 polega na wciąganiu niemal całych dystrybucji Linuksa, które stają się integralną częścią systemu, jak inne aplikacje. To powoduje że Docker powinien działać dobrze na Windows, bo Microsoft ma w tym interes żeby przyciągnąć programistów.

Pamiętajmy też o tym że większość systemów w chmurze Microsoft Azure działa w oparciu o Linux. Zatem ma sens żeby takie same narzędzia działały w chmurze i na laptopie programisty. Czy to znaczy też, że Microsoft zainwestuje w natywne kontenery na Windows, żeby efektywnie działały w Azure? Szczerze nie mam pojęcia, ale chętnie dowiem się o produkcyjnych użyciu Docker na Windows.

Co do Apple to nie widzę żeby byli zainteresowani rynkiem programistów, mimo że parę lat temu na konferencjach programistycznych MacBook to był powszechny widok. Póki co słyszałem że Docker na MacOS ssie, głównie ze względu na opóźnienia w synchronizacji plików. W dobie procesorów M1 w architekturze ARM dochodzi jeszcze problem cross-kompilacji na x86 i ARM. Jestem bardzo ciekaw jak ten temat się rozwinie.

Jakie są alternatywy dla Docker?

Jak już wspomniałem Docker jest kamieniem milowym, bo spopularyzował pojęcie konteneryzacji. Nie była jednak an pierwszą technologią (istniały już jails na FreeBSD czy chroot w Linux) ani jedyną. Konkurentów pojawiło się całkiem sporo, część z tych technologii została już zapomniana, inne przejęte i wdrożone jako część większego rozwiązania.

Tak naprawdę konkurencyjne technologie konteneryzacji to całe ekosystemy zarządzane przez firmy technologiczne lub fundacje:

  • rkt z CoreOS (deprecated), przejęty przez Red Hat
  • cri-o od Red Hat, wraz z Podman + buildah to nowa generacja narzędzi
  • containerd "wyciągnięty" z kodu Docker, zarządzany przez CNCF (Cloud Native Computing Foundation)

Warto wspomnieć o kaniko od Google, które pozwala budować obrazy Dockera bez root (podobnie jak buildah od RedHat). Skoro Kubernetes też wywodzi się z Google, wcale nie zdziwię się jak Google wypuści alternatywę do containerd zgodną z CRI.

Tak naprawdę to są osobne ekosystemy. Widać tutaj że duzi gracze jak Red Hat czy Google mają wiele do ugrania z tortu wdrażania Kubernetes. Z kolei Mirantis ma tylko Docker. Po co używać Docker, jak można wymienić na nowsze lżejsze komponenty?

Co to oznacza dla mnie?

IMHO to zależy od roli oraz tego jak głęboko siedzimy w Docker jako takim. Chodzi mi tutaj przede wszystkim o wykorzystywanie Dockera do granic możliwości. Często niezgodnie z dobrymi praktykami, bo takie dopiero się tworzyły jak technologia z jednej strony upowszechniała się, a z drugiej dorastała.

Przez lata Docker Engine rozrósł się i wyewoluował w modułową architekturę. Powstały różne implementacje takich komponentów jak logowanie. Pozwoliło to na standaryzację oraz uproszczenie architektury aplikacji. Wystarczyło że każda aplikacja logowała na linuksowe strumienie stdout oraz stderr a Docker zajmował się zbieraniem i lokalnym storage logów. Udostępniał też interfejs w postaci polecenia docker logs do odczytu tych logów.

To bardzo wygodne dla programistów mieć jedno narzędzie do przeglądania logów z aplikacji napisanych w Java, Node, PHP czy innych językach. Z kolei dla ludzi zajmujących się utrzymaniem systemów (IT Ops) istotne są też inne rzeczy jak: gwarancja czy nie stracimy logów, ich retencja, jak szybko logi zapełnią dysk. To zupełnie inny zestaw problemów, których Docker nie rozwiązuje.

Jedna maszyna kontra klaster

To co świetnie sprawdza się w przypadku jednej maszyny w klastrze już niekoniecznie. Przykład to docker service logs które jest odpowiednikiem przeglądarki logów dla Docker Swarm (orkiestratora klastra wbudowanego w Docker). Niestety w tym wypadku często widzimy logi nie po kolei, co jest prawdopodobnie spowodowane różnicami czasu między poszczególnymi maszynami w klastrze.

Użycie NTP do pewnego stopnia niweluje problem rożnicy czasów, ale nie jest remedium. W przypadku logów i ich kolejności lepiej użyć centralnego agregatora, który może nadać timestamp w momencie odbioru logu. Jednak to rozwiązanie to już zupełnie inny kaliber, choć zazwyczaj konieczny w systemie rozproszonym jak klaster.

Reasumując: świetne jest to że Docker wiele rzeczy upraszcza i standaryzuje. Niestety te uproszczenia sprawdzają się tylko jeśli działamy na jednej maszynie (jak logi). Kiedy wchodzimy na poziom klastra sprawy się mocno komplikują i te same uproszczenia zaczynają uwierać.

Żeby wyjaśnić niedopasowanie rozwiązań Dockera posłużę się nomenklaturą frameworka Cynefin. Mamy problem systemu rozproszonego, który w swojej naturze jest złożony (Complex) i próbujemy aplikować rozwiązania z dla natury skomplikowanych systemów (Complicated).

Innymi słowy: rozwiązania wybrane przez Docker które sprawdzają się na jednej maszynie niekoniecznie są dobre jeśli działamy w kontekście klastra gdzie mamy wiele maszyn.

Programista

Z perspektywy programisty technicznie zmieni się niewiele. Nadal będziemy budować obrazy Dockera, bo są one zgodne ze standardem OCI (Open Container Initiative). To powoduje że każdy zgodny CRI będzie w stanie uruchomić te obrazy, czy to lokalnie czy w klastrze.

Najważniejsze zmiany moim zdaniem są w sposobie myślenia o kontenerach. Pora odejść od analogii kontenera jako "lekkiej maszyny wirtualnej" i wziąć pod uwagę założenia aplikacji Cloud-Native. Najważniejsze rzeczy to jeden kontener to jeden proces oraz to że zasoby, takie jak pliki, są ulotne.

Pora zacząć myśleć o kontenerze jako instancji aplikacji odpalonej gdzieś tam w chmurze. Wziąć pod uwagę że będzie wiele kopii tej aplikacji i nigdy nie wiadomo na jakiej maszynie taki kontener wyląduje. Konsekwencją jest to że nie można polegać na lokalnych plikach, bo nowa instancja kontenera nie będzie miała dostępu do tych które zapisała poprzednia.

Druga kwestia to jeden proces w kontenerze. Zarządzanie czasem życia kontenera, load balancing pomiędzy instancjami trzeba zostawić orkiestratorowi (jak Kubernetes). Byłem świadkiem problemu przy migracji bazy danych część kontenerów nie dostała nowego adresu bazy, bo w kontenerze zamiast bezpośrednio Node był odpalony PM2 (process manager dla Node) i restart kontenera nie miał pożądanego efektu.

Jeśli docelowym środowiskiem deploy jest Kubernetes to polecam też zainteresować się rozwiązaniami które pozwalają w wygodny sposób odpalać aplikacje na lokalnym klastrze Kubernetes. Mam tu na myśli narzędzia jak Skaffold (od Google), Draft (Microsoft), Tilt czy KubeVela.

Docker Compose byłby spoko jeśli docelowym środowiskiem jest Docker Swarm, bo używają tego samego formatu plików YAML. Z kolei Kubernetes też niby używa YAML, ale to zupełnie inna bajka. To jest bardzo dynamicznie rozwijający się rynek, na którym będę poszukiwał czegoś dla siebie.

SRE / IT Ops

Dla SRE, IT Ops (czy jak nazywa się ludzie którzy utrzymują infrastrukturę) sprawa jest bardziej skomplikowana jeśli Docker Engine jest używany w Kubernetes. Być może wystarczy użyć containerd jako implementacji CRI (Container Runtime Interface) i po sprawie. W tym wypadku wiele zależy od tego ile zależności od Docker Engine "przeciekło" do infrastruktury.

Przykładem niech będzie Docker-in-Docker (DinD) wykorzystane do budowania obrazów na serwerze Continuous Integration (CI). Już w 2015 roku DinD na CI było rozpoznane jako zła praktyka, ale zanim ta wiedza zdążyła zostać przyjęta, praktyka pokazała że DinD to było tzw. quick-win w środowisku CI w klastrze.

Oczywiście firma Mirantis bardzo chętnie wysłucha naszych rozterek względem uzależnienie skomplikowanego systemu CI od Docker-in-Docker czy innego legacy. W końcu zobowiązała się utrzymywać dockershim i właśnie na tym zarabia. Jestem tylko ciekaw jak dużo kosztuje takie zdjęcie problemu z głowy.

Akurat jestem tym osobiście zainteresowany, bo pracuję przy złożonym CI który używa DinD do budowy obrazów Dockera. Zdaję sobie sprawę że rok 2021 to czas przygotowania tranzycji, czy to do innego CRI czy do dockershim. Obawiam się że ta druga opcja będzie oznaczała zdanie sią na łaskę firmy Mirantis, co może być ryzykiem na poziomie strategicznym. Zobaczymy.

Konsultant

Dla konsultantów taka tranzycja to świetna wiadomość. Po latach beztroskiego używania Dockera w zespołach deweloperskich nadchodzi czas porządków, nauczania i aplikowania dobrych praktyk. Wszystko po to by ułatwić przejście na bardziej restrykcyjne środowiska jak Kubernetes.

Ja osobiście po 6 latach używania Docker (w tym 3 lata Swarm) uczę się nowych runtime, orkiestratorów i zarządzania klastrem. To są trendy które dopiero zaczynają się pojawiać w orbicie zainteresowań korporacji, nie licząc gigantów technologicznych.

Z drugiej strony firmy takie jak Mirantis czy VMWare mają żywotny interes żeby wdrażać i utrzymywać klastry, pobierając za to sowitą opłatę. Tak samo wszyscy dostawcy chmury: AWS, Azure, GCP oferujący hostowany Kubernetes. Dość powiedzieć że niezależny dostawca Linode od 2019 roku oferuje Linode Kubernetes Engine (LKE).

Podsumowanie

To czy jest się czym przejmować, że Kubernetes odchodzi od Docker? I tak, i nie. Wydaje mi się że biznes chmurowy będzie rósł i coraz więcej firm będzie migrować do chmury. W takim środowisku konteneryzacja aplikacji i skalowanie horyzontalne (na wiele maszyn) jest naturalnym kierunkiem. Być może będziemy musieli żyć z komplikacjami które niesie używanie klastrów.

Jeśli skalowanie horyzontalne jest niezbędne to Kubernetes wiele rzeczy upraszcza, mimo że sam w sobie wydaje się skomplikowany. W istocie jest rozbudowany, bo problem który który rozwiązuje (alokacja zasobów w klastrze) jest złożony w swojej naturze (tzw. essential complexity). W tym wypadku Kubernetes daje nam podstawowe narzędzia i nomenklaturę żeby poradzić sobie z tą złożonością.

To tylko kwestia czasu kiedy pojawią się rozwiązania upraszczające Kubernetes. Wszyscy dostawcy chmury już oferują usługę zarządzanego klastra Kubernetes, co zdejmuje sporo zadań operacyjnych z barków IT Ops. Dzięki temu że konfiguracja Kubernetes jest w postaci deklaratywnych plików YAML możliwe będzie zbudowanie narzędzi które pozwolą "wyklikać" klaster. Pokuszę się o stwierdzenie że Kubernetes YAML będzie dla klastrów tym czym HTML był dla World Wide Web.