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żda RUN
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.