Zastanów się dobrze, zanim użyjesz Docker-in-Docker dla CI lub środowiska testowego

Zastanów się dobrze, zanim użyjesz Docker-in-Docker dla CI lub środowiska testowego

Docker-in-Docker to zwirtualizowane środowisko demona Docker działające w samym kontenerze w celu tworzenia 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 potem pojawiają się problemy, których można uniknąć, instalując Docker w kontenerze CI Jenkins. W tym artykule dowiesz się, jak to zrobić. Jeśli interesuje Cię ostateczne rozwiązanie bez szczegółów, po prostu przeczytaj ostatnią sekcję artykułu „Rozwiązanie problemu”.

Zastanów się dobrze, zanim użyjesz Docker-in-Docker dla CI lub środowiska testowego

Docker-in-Docker: „Dobrze”

Ponad dwa lata temu umieściłem w Dockerze flaga – uprzywilejowany i napisał pierwsza wersja dinda. Celem było pomóc głównemu zespołowi w szybszym rozwijaniu Dockera. Przed Docker-in-Docker typowy cykl programistyczny wyglądał następująco:

  • hackowanie;
  • zbudować;
  • zatrzymanie działającego demona Dockera;
  • uruchomienie nowego demona Dockera;
  • testowanie;
  • powtórzyć cykl.

Jeśli chciałeś zrobić piękny, powtarzalny montaż (to znaczy w pojemniku), stało się to bardziej skomplikowane:

  • hackowanie;
  • upewnij się, że działa działająca wersja Dockera;
  • zbuduj nowy Docker ze starym Dockerem;
  • zatrzymaj demona Dockera;
  • uruchom nowego demona Dockera;
  • test;
  • zatrzymaj nowego demona Dockera;
  • powtarzać.

Wraz z pojawieniem się Docker-in-Docker proces stał się prostszy:

  • hackowanie;
  • montaż + uruchomienie w jednym etapie;
  • powtórzyć cykl.

Czy tak nie jest dużo lepiej?

Zastanów się dobrze, zanim użyjesz Docker-in-Docker dla CI lub środowiska testowego

Docker-in-Docker: „Zły”

Jednak wbrew powszechnemu przekonaniu Docker-in-Docker to nie 100% gwiazd, kucyków i jednorożców. Mam na myśli to, że istnieje kilka kwestii, o których programista musi wiedzieć.

Jeden z nich dotyczy LSM (modułów bezpieczeństwa Linuksa), takich jak AppArmor i SELinux: podczas uruchamiania kontenera „wewnętrzny Docker” może próbować zastosować profile bezpieczeństwa, które będą powodować konflikt lub dezorientować „zewnętrzny Docker”. Jest to najtrudniejszy problem do rozwiązania podczas próby połączenia oryginalnej implementacji flagi –privileged. Moje zmiany zadziałały i wszystkie testy przeszły na moją maszynę Debian i testowe maszyny wirtualne Ubuntu, ale uległy awarii i spaliły się na maszynie Michaela Crosby'ego (o ile pamiętam, miał Fedorę). Nie pamiętam dokładnej przyczyny problemu, ale mogło to być spowodowane tym, że Mike jest mądrym facetem, który pracuje z SELINUX=enforce (użyłem AppArmor) i moje zmiany nie uwzględniały profili SELinux.

Docker-in-Docker: „Zło”

Drugi problem dotyczy sterowników pamięci masowej Docker. Kiedy uruchamiasz Docker-in-Docker, zewnętrzny Docker działa na zwykłym systemie plików (EXT4, BTRFS lub cokolwiek innego), a wewnętrzny Docker działa na systemie kopiowania przy zapisie (AUFS, BTRFS, Device Mapper itp.). , w zależności od konfiguracji do korzystania z zewnętrznego Dockera). Tworzy to wiele kombinacji, które nie będą działać. Na przykład nie będzie można uruchomić AUFS na AUFS.

Jeśli uruchomisz BTRFS na BTRFS, powinno to na początku działać, ale gdy istnieją zagnieżdżone podwoluminy, usunięcie nadrzędnego wolumenu zakończy się niepowodzeniem. Moduł Device Mapper nie ma przestrzeni nazw, więc jeśli na tej samej maszynie działa wiele instancji Dockera, wszystkie one będą mogły widzieć obrazy (i wpływać) na siebie nawzajem oraz na urządzenia do tworzenia kopii zapasowych kontenerów. To jest złe.

Istnieją rozwiązania pozwalające rozwiązać wiele 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 wolumin i wszystko będzie dobrze. Docker dodał kilka podstawowych przestrzeni nazw do nazw docelowych Device Mapper, więc jeśli na tej samej maszynie uruchomionych jest wiele wywołań Dockera, nie będą one na siebie oddziaływać.

Jednak taka konfiguracja nie jest wcale prosta, jak widać z powyższych artykuły w repozytorium dind na GitHubie.

Docker-in-Docker: Jest coraz gorzej

A co z pamięcią podręczną kompilacji? To również może być dość trudne. Ludzie często pytają mnie: „Jeśli korzystam z Docker-in-Docker, jak mogę używać obrazów hostowanych na moim hoście, zamiast ściągać wszystko z powrotem do mojego wewnętrznego Dockera”?

Niektórzy przedsiębiorczy ludzie próbowali powiązać /var/lib/docker z hosta z kontenerem Docker-in-Docker. Czasami dzielą plik /var/lib/docker z wieloma kontenerami.

Zastanów się dobrze, zanim użyjesz Docker-in-Docker dla CI lub środowiska testowego
Czy chcesz uszkodzić swoje dane? Ponieważ właśnie to spowoduje uszkodzenie Twoich danych!

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

Dlaczego tak jest? Ponieważ jest to wynik jednej z najtrudniejszych lekcji, jakie wyciągnęliśmy podczas tworzenia dotCloud. Silnik kontenera dotCloud działał w oparciu o równoczesny dostęp wielu procesów do pliku /var/lib/dotcloud. Przebiegłe sztuczki, takie jak atomowa wymiana plików (zamiast edycji na miejscu), dodawanie do kodu blokad doradczych i obowiązkowych oraz inne eksperymenty z bezpiecznymi systemami, takimi jak SQLite i BDB, nie zawsze działały. Kiedy przeprojektowaliśmy nasz silnik kontenerowy, który ostatecznie stał się Dockerem, jedną z najważniejszych decyzji projektowych była konsolidacja wszystkich operacji kontenerowych w ramach jednego demona, aby wyeliminować cały nonsens związany ze współbieżnością.

Nie zrozumcie mnie źle: całkowicie możliwe jest stworzenie czegoś dobrego, niezawodnego i szybkiego, co wymaga wielu procesów i nowoczesnego sterowania równoległego. Uważamy jednak, że pisanie i utrzymywanie kodu przy użyciu Dockera jako jedynego odtwarzacza jest prostsze i łatwiejsze.

Oznacza to, że jeśli udostępnisz katalog /var/lib/docker pomiędzy wieloma instancjami Dockera, będziesz mieć problemy. Oczywiście może to zadziałać, szczególnie na wczesnych etapach testów. „Słuchaj, mamo, mogę uruchomić Ubuntu jako okno dokowane!” Ale spróbuj czegoś bardziej złożonego, na przykład wyciągnięcia tego samego obrazu z dwóch różnych instancji, a zobaczysz, jak świat płonie.

Oznacza to, że jeśli Twój system CI wykonuje kompilacje i przebudowy, za każdym razem, gdy ponownie uruchomisz kontener Docker-in-Docker, ryzykujesz upuszczeniem broni nuklearnej do jego pamięci podręcznej. To wcale nie jest fajne!

Roztwór

Cofnijmy się o krok. Czy naprawdę potrzebujesz Docker-in-Docker, czy po prostu chcesz mieć możliwość uruchamiania Dockera oraz budowania i uruchamiania kontenerów i obrazów z systemu CI, gdy sam system CI znajduje się w kontenerze?

Założę się, że większość ludzi chce tej drugiej opcji, co oznacza, że ​​chcą, aby system CI taki jak Jenkins mógł obsługiwać kontenery. Najłatwiej to zrobić, po prostu wkładając gniazdo Docker do kontenera CI i powiązując je z flagą -v.

Mówiąc najprościej, kiedy uruchamiasz kontener CI (Jenkins lub inny), zamiast hakować coś za pomocą Docker-in-Docker, zacznij go od linii:

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

Kontener ten będzie teraz miał dostęp do gniazda Docker i dlatego będzie mógł uruchamiać kontenery. Z tą różnicą, że zamiast uruchamiać kontenery „podrzędne”, uruchomi kontenery „rodzeństwo”.

Spróbuj tego, używając oficjalnego obrazu dokera (który zawiera plik binarny Dockera):

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

Wygląda i działa jak Docker-in-Docker, ale nie jest to Docker-in-Docker: kiedy ten kontener tworzy dodatkowe kontenery, zostaną one utworzone w Dockerze najwyższego poziomu. Nie doświadczysz skutków ubocznych zagnieżdżania, a pamięć podręczna zestawów będzie współdzielona przez wiele wywołań.

Uwaga: w poprzednich wersjach tego artykułu zalecano połączenie pliku binarnego platformy Docker z hosta do kontenera. Stało się to teraz zawodne, ponieważ silnik Dockera nie obsługuje już bibliotek statycznych lub prawie statycznych.

Jeśli więc chcesz używać Dockera z Jenkins CI, masz 2 opcje:
instalacja Docker CLI przy użyciu podstawowego systemu pakowania obrazów (tzn. jeśli Twój obraz jest oparty na Debianie, użyj pakietów .deb), przy użyciu Docker API.

Kilka reklam 🙂

Dziękujemy za pobyt z nami. Podobają Ci się nasze artykuły? Chcesz zobaczyć więcej ciekawych treści? Wesprzyj nas składając zamówienie lub polecając znajomym, VPS w chmurze dla programistów od 4.99 USD, unikalny odpowiednik serwerów klasy podstawowej, który został przez nas wymyślony dla Ciebie: Cała prawda o VPS (KVM) E5-2697 v3 (6 rdzeni) 10GB DDR4 480GB SSD 1Gbps od 19$ czyli jak udostępnić serwer? (dostępne z RAID1 i RAID10, do 24 rdzeni i do 40 GB DDR4).

Dell R730xd 2 razy taniej w centrum danych Equinix Tier IV w Amsterdamie? Tylko tutaj 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6 GHz 14C 64 GB DDR4 4x960 GB SSD 1 Gb/s 100 Telewizor od 199 USD w Holandii! Dell R420 — 2x E5-2430 2.2 GHz 6C 128 GB DDR3 2x960 GB SSD 1 Gb/s 100 TB — od 99 USD! Czytać o Jak zbudować firmę infrastrukturalną klasy z wykorzystaniem serwerów Dell R730xd E5-2650 v4 o wartości 9000 euro za grosz?

Źródło: www.habr.com

Dodaj komentarz