Dobrze się zastanów, zanim użyjesz Docker-in-Docker do CI lub środowiska testowego.

Dobrze się zastanów, zanim użyjesz Docker-in-Docker do CI lub środowiska testowego.

Docker-in-Docker to wirtualne środowisko działającego demona Docker uruchamianego w samym kontenerze do budowania obrazów kontenerów. Głównym celem stworzenia Docker-in-Docker była pomoc w rozwoju samego Dockera. Wiele osób używa go do uruchamiania Jenkins CI. Na początku wydaje się to normalne, ale później pojawiają się problemy, których można uniknąć, instalując Dockera w kontenerze Jenkins CI. W tym artykule opisano, jak to zrobić. Jeśli interesuje cię ostateczne rozwiązanie bez szczegółów, wystarczy przeczytać ostatnią sekcję artykułu "Rozwiązanie problemu".

Dobrze się zastanów, zanim użyjesz Docker-in-Docker do CI lub środowiska testowego.

Docker-in-Docker: "Dobry"

Ponad dwa lata temu wstawiłem w Dockera flaga --privileged i napisałem pierwszą wersję dind. Celem było przyspieszenie prac głównego zespołu nad Dockerem. Przed wprowadzeniem Docker-in-Docker typowy cykl rozwoju wyglądał tak:

  • hackity hack;
  • budowanie (build);
  • zatrzymywanie uruchomionego demona Docker;
  • uruchamianie nowego demona Docker;
  • testowanie;
  • powtórzenie cyklu.

Jeśli chciałeś jednak wykonać ładne, powtarzalne budowanie (czyli w kontenerze), to stawało się ono bardziej złożone:

  • hackity hack;
  • upewnienie się, że uruchomiona jest działająca wersja Dockera;
  • zbudowanie nowego Dockera na starym Dockerze;
  • zatrzymanie demona Docker;
  • uruchomienie nowego demona Docker;
  • przetestowanie;
  • zatrzymanie nowego demona Docker;
  • powtórzenie.

Po wprowadzeniu Docker-in-Docker proces uległ uproszczeniu:

  • hackity hack;
  • budowanie + uruchamianie w jednym etapie;
  • powtórzenie cyklu.

Nieprawdaż, to dużo lepsze?

Dobrze się zastanów, zanim użyjesz Docker-in-Docker do CI lub środowiska testowego.

Docker-in-Docker: "Zły"

Jednak wbrew powszechnemu przekonaniu, Docker-in-Docker nie składa się w 100% ze słoneczek, jednorożców i gwiazdek. Chodzi mi o to, że istnieje kilka problemów, o których programista powinien wiedzieć.

Jednym z nich są LSM (moduły zabezpieczeń Linux), takie jak AppArmor i SELinux: podczas uruchamiania kontenera „wewnętrzny Docker” może próbować zastosować profile bezpieczeństwa, które będą kolidować lub mylić „zewnętrzny Docker”. To jest najtrudniejszy problem do rozwiązania podczas próby połączenia pierwotnej implementacji flagi –privileged. Moje zmiany działały, a wszystkie testy również przeszły na moim komputerze Debian i na testowych maszynach wirtualnych Ubuntu, ale upadły i spaliły się na maszynie Michaela Crosby'ego (o ile pamiętam, miał Fedore). Nie mogę przypomnieć sobie dokładnej przyczyny problemu, ale być może wynikało to z tego, że Mike – mądry człowiek, który pracuje z SELINUX=enforce (używałem AppArmor), a moje zmiany nie uwzględniały profilów SELinux.

Docker-in-Docker: „Zły”

Drugi problem dotyczy sterowników pamięci masowej Docker. Kiedy uruchamiasz Docker-in-Docker, zewnętrzny Docker działa na standardowym systemie plików (EXT4, BTRFS lub dowolnym innym, którym dysponujesz), a wewnętrzny Docker działa na systemie kopii zapisu (AUFS, BTRFS, Device Mapper itd., w zależności od tego, co jest skonfigurowane do użycia w zewnętrznym Dockerze). W związku z tym powstaje wiele kombinacji, które nie będą działać. Na przykład, nie możesz uruchomić AUFS na AUFS.

Jeśli uruchamiasz BTRFS na BTRFS, powinno to działać na początku, ale gdy pojawią się zagnieżdżone podobjętości, usunięcie rodzica podobjętości (parent subvolume) będzie niemożliwe. Moduł Device Mapper nie ma przestrzeni nazw, więc jeśli wiele instancji Docker korzysta z niego na jednej maszynie, wszystkie będą mogły widzieć (i wpływać) na obrazy nawzajem i na urządzenia do tworzenia kopii zapasowych kontenerów. To jest złe.

Istnieją obejścia dla wielu z tych problemów. Na przykład, jeśli chcesz używać AUFS w wewnętrznym Dockerze, po prostu zamień folder \/var\/lib\/docker w tom, a wszystko będzie w porządku. Docker dodał pewne podstawowe przestrzenie nazw do docelowych nazw Device Mapper, więc jeśli kilka wywołań Docker jest wykonywanych na jednej maszynie, nie będą one „następowały” na siebie nawzajem.

Jednak taka konfiguracja wcale nie jest prosta, jak można zobaczyć z tych artykułów w repozytorium dind na GitHubie.

Docker-in-Docker: robi się jeszcze gorzej

A co z pamięcią podręczną budowy? To również może być dość skomplikowane. Ludzie często pytają mnie: „jeśli uruchamiam Docker-in-Docker, jak mogę używać obrazów znajdujących się na moim hoście, zamiast ponownie pobierać wszystko w moim wewnętrznym Dockerze”?

Niektórzy pomysłowi ludzie próbowali podłączyć /var/lib/docker z hosta do kontenera Docker-in-Docker. Czasami współdzielą /var/lib/docker z kilkoma kontenerami.

Dobrze się zastanów, zanim użyjesz Docker-in-Docker do CI lub środowiska testowego.
Chcesz uszkodzić dane? Bo to właśnie to uszkodzi Twoje dane!

Demon Docker wyraźnie został zaprojektowany tak, aby mieć wyłączny dostęp do /var/lib/docker. Nic innego nie powinno „dotykać, szturchać ani badać” żadnych plików Dockera znajdujących się w tym folderze.

Dlaczego tak jest? Bo to jest wynikiem jednej z najtrudniejszych lekcji, które nauczyliśmy się przy tworzeniu dotCloud. Silnik kontenerowy dotCloud działał z wieloma procesami, które jednocześnie uzyskiwały dostęp do /var/lib/dotcloud. Sprytne sztuczki, takie jak atomowa wymiana plików (zamiast edytowania na miejscu), „przerywanie” kodu poprzez blokady rekomendacyjne oraz inne eksperymenty z systemami bezpieczeństwa, takimi jak SQLite i BDB, nie zawsze działały. Kiedy przerabialiśmy nasz silnik kontenerowy, który ostatecznie przekształcił się w Dockera, jednym z głównych rozwiązań projektowych było zgrupowanie wszystkich operacji z kontenerami pod jednym demonem, aby zakończyć całą tę absurdalność jednoczesnego dostępu.

Nie zrozum mnie źle: całkiem możliwe jest stworzenie czegoś dobrego, niezawodnego i szybkiego, co będzie obejmowało kilka procesów i nowoczesne zarządzanie równoległe. Ale uważamy, że łatwiej i prościej jest pisać i utrzymywać kod, używając Dockera jako jedynego gracza.

To oznacza, że jeśli dzielisz katalog /var/lib/docker między kilkoma instancjami Dockera, będziesz mieć problemy. Oczywiście, może to zadziałać, szczególnie na wczesnych etapach testowania. „Słuchaj, mamo, mogę uruchomić ubuntu 'dockerem!'” Ale spróbuj zrobić coś bardziej skomplikowanego, na przykład pobrać ten sam obraz z dwóch różnych instancji, a zobaczysz, jak świat wybucha w płomieniach.

Oznacza to, że jeśli váš system CI wykonuje kompilacje i rekonstrukcje, za każdym razem, gdy uruchamiasz kontener Docker-in-Docker, ryzykujesz zresetowanie w jego pamięci podręcznej ''bomby jądrowej''. To wcale nie jest fajne!

Rozwiązanie problemu

Zróbmy krok wstecz. Czy naprawdę potrzebujesz Docker-in-Docker, czy po prostu chcesz mieć możliwość uruchamiania Dockera, a dokładniej kompilowania i uruchamiania kontenerów oraz obrazów z twojego systemu CI, podczas gdy sam ten system CI znajduje się w kontenerze?

Założę się, że większości ludzi potrzebna jest ta druga opcja, czyli chcą, aby system CI, taki jak Jenkins, mógł uruchamiać kontenery. A najprostszym sposobem, aby to zrobić, jest po prostu podłączenie gniazda Dockera do twojego kontenera CI, wykorzystując flagę -v.

Mówiąc prościej, gdy uruchamiasz swój kontener CI (Jenkins lub inny), zamiast majstrować coś z Docker-in-Docker, uruchom go z linii:

docker run -v /var/run/docker.sock:/var/run/docker.sock ...

Teraz ten kontener będzie miał dostęp do gniazda Dockera i w związku z tym będzie mógł uruchamiać kontenery. Z wyjątkiem tego, że zamiast uruchamiać ''podrzędne'' kontenery, będzie uruchamiał ''powiązane'' kontenery.

Spróbuj to zrobić, używając oficjalnego obrazu dockera (który zawiera plik wykonywalny Dockera):

docker run -v /var/run/docker.sock:/var/run/docker.sock 
           -ti docker

To wygląda i działa jak Docker-in-Docker, ale to nie jest Docker-in-Docker: gdy ten kontener będzie tworzył dodatkowe kontenery, będą one tworzone w Dockerze wyższego poziomu. Nie doświadczysz skutków ubocznych zagnieżdżenia, a pamięć podręczna kompilacji będzie współdzielona dla wielu wywołań.

Uwaga: wcześniejsze wersje tego artykułu zalecały podłączenie pliku wykonywalnego Dockera z hosta do kontenera. Teraz stało się to niewiarygodne, ponieważ mechanizm Dockera nie jest już stosowany do statycznych lub prawie statycznych bibliotek.

Tak więc, jeśli chcesz używać Dockera z Jenkins CI, masz 2 opcje:
zainstalować CLI Dockera, korzystając z podstawowego systemu pakowania obrazu (tzn. jeśli twój obraz oparty jest na Debianie, użyj pakietów .deb), korzystając z API Dockera.

Trochę reklamy 🙂

Dziękujemy, że jesteś z nami. Podobają Ci się nasze artykuły? Chcesz zobaczyć więcej interesujących materiałów? Wspieraj nas składając zamówienie lub polecając nas znajomym, chmurowe VPS dla programistów od 4,99 $, unikatowy odpowiednik serwerów entry-level, który został stworzony przez nas dla Ciebie: Cała prawda o VPS (KVM) E5-2697 v3 (6 rdzeni) 10GB DDR4 480GB SSD 1Gbps od 19 $ lub jak prawidłowo podzielić serwer? (dostępne opcje z RAID1 i RAID10, do 24 rdzeni i do 40GB DDR4).

Dell R730xd dwa razy tańszy w centrum danych Equinix Tier IV w Amsterdamie? Tylko u nas 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100TB od 199 dolarów w Holandii! Dell R420 — 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — od 99 dolarów! Czytaj o tym Jak zbudować infrastrukturę klasy korporacyjnej z zastosowaniem serwerów Dell R730xd E5-2650 v4 kosztujących 9000 euro za grosze?

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster