Dockerfile.1
wykonuje wiele RUN
:
FROM busybox
RUN echo This is the A > a
RUN echo This is the B > b
RUN echo This is the C > c
Dockerfile.2
dołącza do nich:
FROM busybox
RUN echo This is the A > a &&\
echo This is the B > b &&\
echo This is the C > c
Każdy RUN
tworzy warstwę, więc zawsze zakładałem, że mniej warstw jest lepszych, a więc Dockerfile.2
lepiej.
Jest to oczywiście prawdą, gdy a RUN
usuwa coś dodanego przez poprzednie RUN
(tj. yum install nano && yum clean all
), Ale w przypadkach, gdy każdy RUN
coś dodaje, jest kilka punktów, które musimy wziąć pod uwagę:
Warstwy mają po prostu dodać różnicę powyżej poprzedniej, więc jeśli późniejsza warstwa nie usuwa czegoś dodanego w poprzedniej, nie powinno być zbyt wiele korzyści w zakresie oszczędzania miejsca na dysku ...
Warstwy są pobierane równolegle z Docker Hub, więc
Dockerfile.1
chociaż prawdopodobnie nieco większe, teoretycznie byłyby pobierane szybciej.Dodanie czwartego zdania (tj.
echo This is the D > d
) I lokalna przebudowaDockerfile.1
spowodowałoby szybszą kompilację dzięki pamięci podręcznej, aleDockerfile.2
musiałoby ponownie uruchomić wszystkie 4 polecenia.
Zatem pytanie: jaki jest lepszy sposób na zrobienie pliku Dockerfile?
źródło
Odpowiedzi:
Jeśli to możliwe, zawsze łączę ze sobą polecenia, które tworzą pliki z poleceniami, które usuwają te same pliki w jeden
RUN
wiersz. Dzieje się tak, ponieważ każdaRUN
linia dodaje warstwę do obrazu, a wynikiem są dosłownie zmiany systemu plików, które można zobaczyćdocker diff
w tymczasowym kontenerze, który tworzy. Jeśli usuniesz plik, który został utworzony w innej warstwie, jedyne, co robi system plików unii, rejestruje zmianę systemu plików w nowej warstwie, plik nadal istnieje w poprzedniej warstwie i jest przesyłany przez sieć i przechowywany na dysku. Jeśli więc pobierzesz kod źródłowy, wyodrębnisz go, skompilujesz do pliku binarnego, a na końcu usuniesz pliki tgz i źródłowe, naprawdę chcesz, aby wszystko to zostało zrobione w jednej warstwie, aby zmniejszyć rozmiar obrazu.Następnie osobiście podzieliłem warstwy na podstawie ich możliwości ponownego wykorzystania w innych obrazach i oczekiwanego użycia pamięci podręcznej. Jeśli mam 4 obrazy, wszystkie z tym samym obrazem podstawowym (np. Debian), mogę pobrać kolekcję typowych narzędzi do większości z tych obrazów do pierwszego polecenia uruchomienia, aby inne obrazy skorzystały z buforowania.
Kolejność w pliku Dockerfile jest ważna przy ponownym wykorzystaniu pamięci podręcznej obrazów. Patrzę na wszystkie komponenty, które aktualizują się bardzo rzadko, prawdopodobnie tylko wtedy, gdy aktualizuje się obraz podstawowy i umieszcza je wysoko w pliku Dockerfile. Pod koniec Dockerfile dołączam wszelkie polecenia, które będą działały szybko i mogą się często zmieniać, np. Dodanie użytkownika z identyfikatorem UID hosta lub utworzenie folderów i zmiana uprawnień. Jeśli kontener zawiera zinterpretowany kod (np. JavaScript), który jest aktywnie opracowywany, jest on dodawany tak późno, jak to możliwe, aby przebudowa wykonywała tylko jedną zmianę.
W każdej z tych grup zmian konsoliduję najlepiej, jak potrafię, aby zminimalizować warstwy. Więc jeśli istnieją 4 różne foldery kodu źródłowego, zostaną one umieszczone w jednym folderze, aby można je było dodać za pomocą jednego polecenia. Wszelkie instalacje pakietów z czegoś takiego jak apt-get są łączone w jedną RUN, jeśli jest to możliwe, aby zminimalizować obciążenie menedżera pakietów (aktualizacja i czyszczenie).
Aktualizacja dla kompilacji wieloetapowych:
O wiele mniej martwię się zmniejszaniem rozmiaru obrazu w nieostatecznych etapach wieloetapowej kompilacji. Gdy te etapy nie są oznaczone i wysyłane do innych węzłów, można zmaksymalizować prawdopodobieństwo ponownego użycia pamięci podręcznej, dzieląc każde polecenie w osobnym
RUN
wierszu.Nie jest to jednak idealne rozwiązanie do zgniatania warstw, ponieważ wszystko, co kopiujesz między etapami, to pliki, a nie reszta metadanych obrazu, takich jak ustawienia zmiennych środowiskowych, punkt wejścia i polecenie. A kiedy instalujesz pakiety w dystrybucji Linuksa, biblioteki i inne zależności mogą być rozproszone po całym systemie plików, utrudniając kopiowanie wszystkich zależności.
Z tego powodu używam kompilacji wieloetapowych jako zamiennika do budowania plików binarnych na serwerze CI / CD, więc mój serwer CI / CD musi mieć tylko narzędzia do uruchomienia
docker build
, a nie mieć jdk, nodejs, go i wszelkie inne zainstalowane narzędzia kompilacji.źródło
Oficjalna odpowiedź wymieniona w ich najlepszych praktykach (oficjalne obrazy MUSZĄ być zgodne z tymi)
Od dokowanym 1.10
COPY
,ADD
aRUN
sprawozdanie dodać nową warstwę do obrazu. Zachowaj ostrożność, używając tych stwierdzeń. Spróbuj połączyć polecenia w jednąRUN
instrukcję. Oddziel to tylko wtedy, gdy jest to wymagane dla czytelności.Więcej informacji: https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#/minimize-the-number-of-layers
Aktualizacja: wieloetapowy w dockerze> 17.05
W przypadku kompilacji wieloetapowych można używać wielu plików
FROM
instrukcji w pliku Dockerfile. KażdaFROM
instrukcja jest sceną i może mieć swój własny obraz bazowy. Na ostatnim etapie używasz minimalnego obrazu podstawowego, takiego jak alpine, kopiujesz artefakty kompilacji z poprzednich etapów i instalujesz wymagania dotyczące środowiska wykonawczego. Efektem końcowym tego etapu jest Twój wizerunek. W tym miejscu martwisz się o warstwy, jak opisano wcześniej.Jak zwykle, docker ma świetne dokumenty na temat kompilacji wieloetapowych. Oto krótki fragment:
Świetny wpis na blogu na ten temat można znaleźć tutaj: https://blog.alexellis.io/mutli-stage-docker-builds/
Aby odpowiedzieć na Twoje punkty:
Tak, warstwy są czymś w rodzaju różnic. Nie sądzę, aby dodano warstwy, jeśli nie ma absolutnie żadnych zmian. Problem polega na tym, że po zainstalowaniu / pobraniu czegoś w warstwie # 2 nie można tego usunąć w warstwie # 3. Kiedy więc coś zostanie zapisane w warstwie, nie można już zmniejszyć rozmiaru obrazu, usuwając to.
Chociaż warstwy można przeciągać równolegle, co czyni je potencjalnie szybszymi, każda warstwa niewątpliwie zwiększa rozmiar obrazu, nawet jeśli usuwają pliki.
Tak, buforowanie jest przydatne, jeśli aktualizujesz plik Dockera. Ale działa w jednym kierunku. Jeśli masz 10 warstw i zmienisz warstwę # 6, nadal będziesz musiał odbudować wszystko z warstwy # 6- # 10. Nie jest więc zbyt często, że przyspieszy to proces tworzenia, ale gwarantuje niepotrzebne zwiększenie rozmiaru obrazu.
Dzięki @Mohan za przypomnienie mi o zaktualizowaniu tej odpowiedzi.
źródło
Wygląda na to, że powyższe odpowiedzi są nieaktualne. Uwaga w dokumentacji:
https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#minimize-the-number-of-layers
i
https://docs.docker.com/engine/userguide/eng-image/multistage-build/
Wydaje się, że najlepsza praktyka zmieniła się i polega na używaniu kompilacji wieloetapowych i zachowaniu
Dockerfile
czytelności.źródło
docker image build --squash
opcja wyjdzie poza eksperymentalną.squash
do eksperymentów. Ma wiele sztuczek i ma sens tylko przed kompilacjami wieloetapowymi. W przypadku kompilacji wieloetapowych wystarczy zoptymalizować ostatni etap, co jest bardzo łatwe.To zależy od tego, co włączysz do warstw obrazu.
Kluczową kwestią jest udostępnianie jak największej liczby warstw:
Zły przykład:
Dockerfile.1
Dockerfile 2
Dobry przykład:
Dockerfile.1
Dockerfile 2
Inną sugestią jest to, że usuwanie nie jest tak przydatne tylko wtedy, gdy dzieje się na tej samej warstwie, co czynność dodawania / instalowania.
źródło
RUN yum install big-package
z pamięci podręcznej?