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”.
Docker-in-Docker: „Dobrze”
Ponad dwa lata temu umieściłem w Dockerze
- 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?
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
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.
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,
Dell R730xd 2 razy taniej w centrum danych Equinix Tier IV w Amsterdamie? Tylko tutaj
Źródło: www.habr.com