W grę wchodzi wiele technik, bez jednego rozwiązania. Prawdopodobnie będziesz chciał wykonać następujące czynności:
Po pierwsze, zoptymalizuj warstwy obrazu do ponownego użycia. Później często zmieniaj kroki w pliku Docker, aby zwiększyć prawdopodobieństwo buforowania wczesnych warstw z poprzednich kompilacji. Ponownie użyta warstwa pojawi się jako więcej miejsca na dysku docker image ls
, ale jeśli przyjrzysz się systemowi plików, tylko jedna kopia każdej warstwy zostanie zapisana na dysku. Oznacza to, że 3 obrazy po 2 GB każdy, ale które różnią się tylko 50 MB w kilku ostatnich warstwach kompilacji, zajmą tylko 2,1 GB miejsca na dysku, mimo że na liście wygląda na to, że używają 6 GB, ponieważ jesteś podwójne liczenie każdej z ponownie użytych warstw.
Ponowne użycie warstw powoduje, że obrazy z nierzadko zmieniającymi się zależnościami kompilacji instalują je najpierw przed skopiowaniem do kodu. Zobacz dowolny przykład python, który ma wzorzec taki jak:
FROM python
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
# note how the code is copied only after the pip install
# since code changes but requirements.txt doesn't
COPY . .
CMD ["gunicorn", "app:app"]
Wybierz minimalny obraz podstawowy. Dlatego widać ludzie chodzą od ubuntu
do debian:slim
(szczupłe warianty są mniejsze, dostarczane z mniejszą ilością narzędzi), lub nawet alpine
. Zmniejsza to rozmiar punktu początkowego i jest bardzo pomocne, jeśli ciągle wyciągasz nowe wersje obrazu podstawowego. Jeśli jednak obraz podstawowy rzadko się zmienia, ponowne użycie warstwy usuwa wiele zalet minimalnego obrazu podstawowego.
Najmniejszy obraz podstawowy, jaki możesz wybrać scratch
, to nic, żadna powłoka ani biblioteki i jest on użyteczny tylko w przypadku statycznie skompilowanych plików binarnych. W przeciwnym razie wybierz obraz podstawowy zawierający potrzebne narzędzia bez wielu niepotrzebnych narzędzi.
Następnie każdy krok, który zmienia lub usuwa plik, powinien zostać połączony z poprzednimi krokami, które tworzą ten plik. W przeciwnym razie warstwowy system plików, który używa kopiowania przy zapisie nawet w takich przypadkach, jak zmiana uprawnień do pliku, będzie miał oryginalny plik na poprzedniej warstwie, a rozmiar obrazu nie zmniejszy się po usunięciu plików. Dlatego twoje rm
polecenia nie mają wpływu na wynikowe miejsce na dysku. Zamiast tego możesz połączyć polecenia, takie jak:
RUN apt-get update \
&& apt-get install -y \
a-package \
wget \
&& ... \
&& apt-get purge -y wget \
&& rm -r a-build-dir \
&& apt-get purge -y a-package
Zauważ, że nadużywanie łańcucha poleceń może spowolnić twoje kompilacje, ponieważ musisz ponownie zainstalować ten sam zestaw narzędzi za każdym razem, gdy zmieniają się wymagania wstępne (np. Ściąganie kodu za pomocą wget). Zobacz wieloetapową poniżej, aby uzyskać lepszą alternatywę.
Każdy utworzony plik, którego nie potrzebujesz na wynikowym obrazie, powinien zostać usunięty w kroku, który go tworzy. Obejmuje to pamięci podręczne pakietów, dzienniki, strony podręcznika itp. Aby dowiedzieć się, jakie pliki są tworzone w każdej warstwie, możesz użyć narzędzia takiego jak wagoodman / dive (którego osobiście nie sprawdziłem i wyraziłbym ostrożność, ponieważ działa z pełnym dostępem do katalogu głównego) na hoście) lub możesz zbudować obrazy dokera bez przycinania pojemników pośrednich, a następnie wyświetlić różnicę za pomocą:
# first create and leave containers from any RUN step using options on build
docker image build --rm=false --no-cache -t image_name .
# review which layers use an unexpectedly large amount of space
docker image history image_name
# list all containers, particularly the exited ones from above
docker container ps -a
# examine any of those containers
docker container diff ${container_id}
# ... repeat the diff for other build steps
# then cleanup exited containers
docker container prune
Z każdym z tych pośrednich pojemników diff pokaże jakie pliki zostały dodane, zmienione lub usunięte w tym etapie (te są oznaczone A
, C
lub D
przed każdym pliku). To, co pokazuje różnicę, to specyficzny dla kontenera system plików do odczytu / zapisu, którym jest dowolny plik zmieniony przez kontener ze stanu obrazu przy użyciu funkcji kopiowania przy zapisie.
Najlepszym sposobem na zmniejszenie rozmiaru obrazu jest wyeliminowanie niepotrzebnych komponentów, takich jak kompilatory, z dostarczanego obrazu. W tym celu kompilacje wielostopniowe umożliwiają kompilację w jednym etapie, a następnie kopiowanie tylko powstałych artefaktów ze etapu kompilacji do obrazu środowiska wykonawczego, który ma tylko minimum niezbędne do uruchomienia aplikacji. Pozwala to uniknąć konieczności optymalizacji któregokolwiek z etapów kompilacji, ponieważ nie są one dostarczane z otrzymanym obrazem.
FROM debian:9 as build
# still chain update with install to prevent stale cache issues
RUN apt-get update \
&& apt-get install -y \
a-package \
wget \
RUN ... # perform any download/compile steps
FROM debian:9-slim as release
COPY --from=build /usr/local/bin/app /usr/local/bin/app
CMD [ "/usr/local/bin/app" ]
Wielostopniowy jest idealny z kompilowanymi statycznie plikami binarnymi, które można uruchamiać od podstaw jako obraz podstawowy lub przechodząc ze środowiska kompilacji takiego jak JDK do środowiska wykonawczego takiego jak JRE. Jest to najprostszy sposób na radykalne zmniejszenie rozmiaru obrazu przy jednoczesnym szybkim budowaniu. Nadal możesz wykonywać łańcuchy kroków na etapie wydania, jeśli masz kroki, które zmieniają lub usuwają pliki utworzone w poprzednich krokach, ale w przeważającej części COPY
etap z innego etapu izoluje etap wydania od jakiegokolwiek rozdęcia warstwy występującego we wcześniejszych etapach kompilacji.
Uwaga: nie polecam zgniatania obrazów, ponieważ zmniejsza to rozmiar jednego obrazu kosztem eliminacji ponownego użycia warstwy. Oznacza to, że przyszłe wersje tego samego obrazu będą wymagały większego ruchu na dysku i ruchu w sieci, aby wysyłać aktualizacje. Wracając do pierwszego przykładu, squashing może zmniejszyć twój obraz z 2 GB do 1 GB, ale nie 3 zdjęcia mogą zająć 3 GB zamiast 2,1 GB.
2.37
vs.1.47 GB