Jak za każdym razem stworzyć najmniejszy działający obraz dokera?

19

Cel: tworzenie za każdym razem najmniejszych działających obrazów dokerów

obecny

REPOSITORY          TAG       IMAGE ID            CREATED             SIZE
a-docker-image      latest    x                   42 minutes ago       1.92 GB

Próba

Dodanie kroku czyszczenia na końcu pliku Docker:

#clean
RUN apt-get purge -y wget
RUN rm -r a-build-dir
RUN apt-get purge -y a-package

nieco zmniejszyłem rozmiar obrazu:

REPOSITORY          TAG       IMAGE ID            CREATED             SIZE
a-docker-image      latest    y                   2 minutes ago       1.86 GB

Dyskusja

Zbudowałem różne obrazy dokerów. Za każdym razem próbuję zmniejszyć rozmiar tworzonego obrazu, ale zawsze uważam, że jest on zbyt duży. Szukam skryptu, który został już utworzony przez kogoś na githubie, który usuwa wszystkie zbędne pakiety z obrazu, więc rozmiar utworzonego obrazu będzie możliwie najmniejszy.

Jak powiedziałem, zawsze staram się zmniejszyć rozmiar obrazu, ale chcę zastosować to spójne, aby każdy obraz, który tworzę odtąd, był jak najmniejszy.

Pytanie

Jak za każdym razem stworzyć najmniejszy działający obraz dokera?

030
źródło

Odpowiedzi:

1

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 ubuntudo 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 rmpolecenia 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, Club Dprzed 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 COPYetap 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.

BMitch
źródło
25

A Dockerfiletworzy nową warstwę dla każdego polecenia w pliku. Ponieważ warstwy są dobrze ułożone warstwowo jeden na drugim - nie można usunąć plików dodanych przez poprzednią warstwę. Dlatego podczas instalowania pakietów, pobierania plików lub tworzenia kompilacji w osobnym poleceniu - są one nadal widoczne na obrazie, nawet jeśli zostały usunięte w przyszłej warstwie.

Więc jeśli tylko to zmienisz:

RUN apt-get update -y
RUN apt-get install -y wget a-package
# ...
RUN apt-get purge -y wget
RUN rm -r a-build-dir
RUN apt-get purge -y a-package

Do tego:

RUN apt-get update -y \
    && apt-get install -y wget a-package \
    && mkdir a-build-dir \
    && wget http://some-site/very-big-source-code.tar.gz \
    && tar xzvf very-big-source-code.tar.gz \
    && do-some-compilation \
    && apt-get purge -y wget \
    && cd .. \
    && rm -rf a-build-dir \
    && apt-get purge -y a-package

Otrzymasz znacznie mniejszy obraz.


Inną opcją jest zgniecenie obrazu po jego zbudowaniu. P: Jak działa nowy docker --squash?


Jeszcze inną opcją jest wybór cienkiego podstawowego obrazu. Na przykład obrazy, które używają Alpine Linux jako bazy zamiast Debiana, zajmują zaledwie 10-15 MB zamiast 180-250 MB. I to przed dodaniem własnej aplikacji i danych. Wiele oficjalnych zdjęć bazowych w Docker Hub ma wersję alpejską.

Jewgienij
źródło
3
2.37vs.1.47 GB
030
4

Prawdopodobnie nie do końca odpowiedź, ale warto podać alternatywy.

Mając to na uwadze, stworzono siedlisko szefa kuchni , tworząc pakiet ze wszystkimi potrzebnymi zależnościami bez zbędnego obciążenia obrazu dystrybucyjnego / podstawowego, którego nie chcesz.

Wyciąg na temat tego, co ma znaczenie, rozmiar kontenera z tego postu na blogu za pomocą prostej aplikacji nodejs:

michael@ricardo-2:plans_pkg_part_2$ docker images
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
mfdii/node-example   latest              36c6568c606b        40 minutes ago      655.9 MB
node                 latest              04c0ca2a8dad        16 hours ago        654.6 MB
mfdii/mytutorialapp  latest              534afd80d74d        2 minutes ago       182.1 MB

mdfii/node-examplejest obrazem dokera z klasycznego pliku dokowania, podczas gdy mfdii/mytutorialappjest obrazem dokera utworzonym z siedliskiem.

Jeśli rozmiar jest Twoim głównym zmartwieniem i jesteś gotowy przyjąć krzywą uczenia się planów siedliskowych, może to być rozwiązanie dla Ciebie.

Tensibai
źródło
0

Można również użyć nurkowania

docker run --rm -it \
    -v /var/run/docker.sock:/var/run/docker.sock \
    wagoodman/dive:latest <dive arguments...>

aby uzyskać raport o tym, jakie odpady można usunąć z obrazu dokera w celu zmniejszenia rozmiaru.

030
źródło
0

Jeśli chcesz mieć warstwy programistyczne wielokrotnego użytku, ale zmniejszyć zużycie dysku do dostarczania, możesz utworzyć scaloną „warstwę dostarczającą” w następujący sposób:

  1. Upewnij się, że masz pojemnik, który używa twojego obrazu (jeśli go nie masz, może użyj czegoś takiego docker run IMAGE echo, jeśli polecenie echo jest dostępne)
  2. Znajdź identyfikator kontenera (być może za pomocą docker container ls -l)
  3. Potok, docker exportaby docker importutworzyć scaloną warstwę (coś podobnego docker export 20f192c6530a | docker import - project:merged)

Pozwoli to utrzymać warstwy programowania, ale zapewni mniejszy, scalony obraz, który możesz dostarczyć.

Bill Burdick
źródło
0

Kompilacje wieloetapowe. Użyj obrazu zawierającego wszystkie komponenty kompilacji, aby zbudować aplikację, oraz jaśniejszego obrazu środowiska wykonawczego. Skopiuj tylko artefakt kompilacji do obrazu środowiska wykonawczego. Nie musisz niczego usuwać.

https://docs.docker.com/develop/develop-images/multistage-build/

frankd
źródło
0

prosty .. doker ps sprawdź bieżące uruchomione obrazy .. dla prostego przykładu pliku poniżej ..

OD Ubuntu16

MAINTAINER sreeni (e-mail / domena)

URUCHOM aktualizację apt-get

URUCHOM apt-get install -y nginx

ENTRYPOINT [„/ usr / sbin / nginx”, „- g”, „daemon off;”]

EKSPOZYCJA 80 (port)

prosty plik dokera ...

użyj poniżej polecenia dokera

doker run -d -p 80:80 - nazwa serwera WWW ubuntu16 (nazwa obrazu), po czym sprawdź localhost lub adres ip: 80 (otwórz przeglądarkę i sprawdź)

sreeniwl
źródło
1
popraw formatowanie swojej odpowiedzi ...
Pierre.Vriens