paint-brush
Optimitzar les imatges de Docker és més que una cosa fetaper@aleksandrov
Nova Història

Optimitzar les imatges de Docker és més que una cosa feta

per Igor Alexandrov17m2025/01/30
Read on Terminal Reader

Massa Llarg; Per llegir

Aquest article forma part d'una sèrie de publicacions on recorreré totes les línies del Dockerfile predeterminat i explicaré les millors pràctiques i optimitzacions. El primer article només tocarà l'optimització de la reducció de la mida de la imatge.
featured image - Optimitzar les imatges de Docker és més que una cosa feta
Igor Alexandrov HackerNoon profile picture
0-item


Aquest article forma part d'una sèrie de publicacions on recorreré totes les línies del Dockerfile predeterminat de Rails i explicaré les millors pràctiques i optimitzacions.


Les imatges de Docker es poden optimitzar de diferents maneres que inclouen, entre d'altres, la reducció de la mida de la imatge, l'optimització del rendiment de la creació, les millors pràctiques de seguretat i manteniment i optimitzacions específiques de l'aplicació. En el primer article, tocaré només l'optimització de la reducció de la mida de la imatge i explicaré per què són importants.

Per què optimitzar la mida de la imatge?

Com en qualsevol altre procés de desenvolupament de programari, cada desenvolupador enumerarà els seus motius pels quals vol fer que les seves compilacions Docker siguin més ràpides. Enumeré els motius que són més importants per a mi.

Construccions i desplegaments més ràpids

Les imatges més petites són més ràpides de crear perquè cal processar menys fitxers i capes. Això millora la productivitat dels desenvolupadors, especialment durant els cicles de desenvolupament iteratius. Les imatges més petites triguen menys temps a enviar-se a un registre i a extreure'n durant els desplegaments. Això és especialment crític a les canonades CI/CD on els contenidors es construeixen i es despleguen amb freqüència.

Costos d'emmagatzematge reduïts i ús d'ample de banda de xarxa

Les imatges més petites consumeixen menys emmagatzematge en registres de contenidors, màquines de desenvolupament local i servidors de producció. Això redueix els costos d'infraestructura, especialment per a desplegaments a gran escala. Les imatges més petites utilitzen menys amplada de banda quan es transfereixen entre servidors, especialment quan creeu imatges localment o en canalitzacions CI/CD i les introduïu a un registre.


"Vam gastar 3,2 milions de dòlars al núvol el 2022... Estalviem uns 7 milions de dòlars en despeses de servidor durant cinc anys des de la nostra sortida del núvol". David Heinemeier Hansson — HEY World

Rendiment i seguretat millorats

Les imatges més petites requereixen menys recursos (p. ex., CPU, RAM) per carregar-se i executar-se, millorant el rendiment global de les aplicacions en contenidors. Els temps d'inici més ràpids fan que els vostres serveis estiguin preparats més ràpidament, cosa que és crucial per a l'escalat i els sistemes d'alta disponibilitat. Les imatges de base mínimes com alpine o debian-slim contenen menys paquets preinstal·lats, la qual cosa redueix el risc que s'exploti programari sense pedaços o innecessari.


A més de tot el que s'ha esmentat anteriorment, l'eliminació de fitxers i eines innecessàries minimitza les distraccions a l'hora de diagnosticar problemes i comporta una millor conservació i una reducció del deute tècnic.

Inspecció d'imatges de Docker

Per obtenir diferents paràmetres de la imatge, inclosa la mida, podeu mirar l'escriptori Docker o executar l'ordre docker images al terminal.


 ➜ docker images REPOSITORY TAG IMAGE ID CREATED SIZE kamal-dashboard latest 673737b771cd 2 days ago 619MB kamal-proxy latest 5f6cd8983746 6 weeks ago 115MB docs-server latest a810244e3d88 6 weeks ago 1.18GB busybox latest 63cd0d5fb10d 3 months ago 4.04MB postgres latest 6c9aa6ecd71d 3 months ago 456MB postgres 16.4 ced3ad69d60c 3 months ago 453MB


Conèixer la mida de la imatge no us ofereix la imatge completa. No saps què hi ha dins de la imatge, quantes capes té o quina mida té cada capa. Una capa d'imatge Docker és una capa de sistema de fitxers immutable de només lectura que és un component d'una imatge de Docker. Cada capa representa un conjunt de canvis fets al sistema de fitxers de la imatge, com ara afegir fitxers, modificar configuracions o instal·lar programari.


Les imatges Docker es construeixen de manera incremental, capa per capa, i cada capa correspon a una instrucció del Dockerfile . Per obtenir les capes de la imatge, podeu executar l'ordre docker history .


 ➜ docker history kamal-dashboard:latest IMAGE CREATED CREATED BY SIZE COMMENT 673737b771cd 4 days ago CMD ["./bin/thrust" "./bin/rails" "server"] 0B buildkit.dockerfile.v0 <missing> 4 days ago EXPOSE map[80/tcp:{}] 0B buildkit.dockerfile.v0 <missing> 4 days ago ENTRYPOINT ["/rails/bin/docker-entrypoint"] 0B buildkit.dockerfile.v0 <missing> 4 days ago USER 1000:1000 0B buildkit.dockerfile.v0 <missing> 4 days ago RUN /bin/sh -c groupadd --system --gid 1000 … 54MB buildkit.dockerfile.v0 <missing> 4 days ago COPY /rails /rails # buildkit 56.2MB buildkit.dockerfile.v0 <missing> 4 days ago COPY /usr/local/bundle /usr/local/bundle # b… 153MB buildkit.dockerfile.v0 <missing> 4 days ago ENV RAILS_ENV=production BUNDLE_DEPLOYMENT=1… 0B buildkit.dockerfile.v0 <missing> 4 days ago RUN /bin/sh -c apt-get update -qq && apt… 137MB buildkit.dockerfile.v0 <missing> 4 days ago WORKDIR /rails 0B buildkit.dockerfile.v0 <missing> 3 weeks ago CMD ["irb"] 0B buildkit.dockerfile.v0 <missing> 3 weeks ago RUN /bin/sh -c set -eux; mkdir "$GEM_HOME";… 0B buildkit.dockerfile.v0 <missing> 3 weeks ago ENV PATH=/usr/local/bundle/bin:/usr/local/sb… 0B buildkit.dockerfile.v0 <missing> 3 weeks ago ENV BUNDLE_SILENCE_ROOT_WARNING=1 BUNDLE_APP… 0B buildkit.dockerfile.v0 <missing> 3 weeks ago ENV GEM_HOME=/usr/local/bundle 0B buildkit.dockerfile.v0 <missing> 3 weeks ago RUN /bin/sh -c set -eux; savedAptMark="$(a… 78.1MB buildkit.dockerfile.v0 <missing> 3 weeks ago ENV RUBY_DOWNLOAD_SHA256=018d59ffb52be3c0a6d… 0B buildkit.dockerfile.v0 <missing> 3 weeks ago ENV RUBY_DOWNLOAD_URL=https://cache.ruby-lan… 0B buildkit.dockerfile.v0 <missing> 3 weeks ago ENV RUBY_VERSION=3.4.1 0B buildkit.dockerfile.v0 <missing> 3 weeks ago ENV LANG=C.UTF-8 0B buildkit.dockerfile.v0 <missing> 3 weeks ago RUN /bin/sh -c set -eux; mkdir -p /usr/loca… 19B buildkit.dockerfile.v0 <missing> 3 weeks ago RUN /bin/sh -c set -eux; apt-get update; a… 43.9MB buildkit.dockerfile.v0 <missing> 3 weeks ago # debian.sh --arch 'arm64' out/ 'bookworm' '… 97.2MB debuerreotype 0.15


Com que ja he proporcionat teoria sobre imatges i capes, és hora d'explorar el Dockerfile . A partir de Rails 7.1, el Dockerfile es genera amb la nova aplicació Rails. A continuació es mostra un exemple de com pot semblar.


 # syntax=docker/dockerfile:1 # check=error=true # Make sure RUBY_VERSION matches the Ruby version in .ruby-version ARG RUBY_VERSION=3.4.1 FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base # Rails app lives here WORKDIR /rails # Install base packages # Replace libpq-dev with sqlite3 if using SQLite, or libmysqlclient-dev if using MySQL RUN apt-get update -qq && \ apt-get install --no-install-recommends -y curl libjemalloc2 libvips libpq-dev && \ rm -rf /var/lib/apt/lists /var/cache/apt/archives # Set production environment ENV RAILS_ENV="production" \ BUNDLE_DEPLOYMENT="1" \ BUNDLE_PATH="/usr/local/bundle" \ BUNDLE_WITHOUT="development" # Throw-away build stage to reduce size of final image FROM base AS build # Install packages needed to build gems RUN apt-get update -qq && \ apt-get install --no-install-recommends -y build-essential curl git pkg-config libyaml-dev && \ rm -rf /var/lib/apt/lists /var/cache/apt/archives # Install application gems COPY Gemfile Gemfile.lock ./ RUN bundle install && \ rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ bundle exec bootsnap precompile --gemfile # Copy application code COPY . . # Precompile bootsnap code for faster boot times RUN bundle exec bootsnap precompile app/ lib/ # Precompiling assets for production without requiring secret RAILS_MASTER_KEY RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile # Final stage for app image FROM base # Copy built artifacts: gems, application COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" COPY --from=build /rails /rails # Run and own only the runtime files as a non-root user for security RUN groupadd --system --gid 1000 rails && \ useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \ chown -R rails:rails db log storage tmp USER 1000:1000 # Entrypoint prepares the database. ENTRYPOINT ["/rails/bin/docker-entrypoint"] # Start server via Thruster by default, this can be overwritten at runtime EXPOSE 80 CMD ["./bin/thrust", "./bin/rails", "server"]


A continuació, proporcionaré una llista d'enfocaments i regles que s'aplicaven al Dockerfile anterior per fer que la mida final de la imatge sigui eficient.

Optimitzar les instal·lacions de paquets

Estic segur que només conserveu el programari necessari a la vostra màquina de desenvolupament local. El mateix s'hauria d'aplicar a les imatges de Docker. En els exemples següents, empitjoraré constantment el Dockerfile extret del Rails Dockerfile anterior. Ho faré referència com a versió original Dockerfile .

Regla núm. 1: utilitzeu imatges mínimes de base

 FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base


La imatge base és el punt de partida del Dockerfile . És la imatge que s'utilitza per crear el contenidor. La imatge base és la primera capa del Dockerfile i és l'única capa que no crea el mateix Dockerfile .


La imatge base s'especifica amb l'ordre FROM , seguida del nom i l'etiqueta de la imatge. L'etiqueta és opcional i, si no s'especifica, s'utilitza l'etiqueta latest . La imatge base pot ser qualsevol imatge disponible a Docker Hub o qualsevol altre registre.


Al Dockerfile about, estem utilitzant la imatge ruby amb l'etiqueta 3.4.1-slim . La imatge ruby és la imatge oficial Ruby disponible a Docker Hub. L'etiqueta 3.4.1-slim és una versió fina de la imatge Ruby que es basa en la imatge debian-slim . Tot i que la imatge debian-slim és una versió mínima de la imatge de Debian Linux que està optimitzada per a la mida. Mireu la taula següent per fer-vos una idea de com de més petita és la imatge slim .


 ➜ docker images --filter "reference=ruby" REPOSITORY TAG IMAGE ID CREATED SIZE ruby 3.4.1-slim 0bf957e453fd 5 days ago 219MB ruby 3.4.1-alpine cf9b1b8d4a0c 5 days ago 99.1MB ruby 3.4.1-bookworm 1e77081540c0 5 days ago 1.01GB


A partir de gener de 2024, la versió actual de Debian s'anomena bookworm i l'anterior és bullseye .


219 MB en lloc d'1 GB: una gran diferència. Però, què passa si la imatge alpine és encara més petita? La imatge alpine es basa en la distribució Alpine Linux, que és una distribució Linux súper lleugera optimitzada per a la mida i la seguretat. Alpine utilitza la biblioteca musl (en comptes de glibc ) i busybox (un conjunt compacte d'utilitats Unix) en comptes dels homòlegs de GNU. Tot i que tècnicament és possible utilitzar la imatge alpine per executar Rails, no ho cobriré en aquest article.

Regla núm. 2: minimitzar les capes

 RUN apt-get update -qq && \ apt-get install --no-install-recommends -y curl libjemalloc2 libvips libpq-dev && \ rm -rf /var/lib/apt/lists /var/cache/apt/archives


Cada instrucció RUN , COPY i FROM Dockerfile crea una nova capa. Com més capes tinguis, més gran serà la mida de la imatge. És per això que la millor pràctica és combinar diverses ordres en una única instrucció RUN . Per il·lustrar aquest punt, mirem l'exemple següent.


 # syntax=docker/dockerfile:1 # check=error=true # Make sure RUBY_VERSION matches the Ruby version in .ruby-version ARG RUBY_VERSION=3.4.1 FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base RUN apt-get update -qq RUN apt-get install --no-install-recommends -y curl RUN apt-get install --no-install-recommends -y libjemalloc2 RUN apt-get install --no-install-recommends -y libvips RUN apt-get install --no-install-recommends -y libpq-dev RUN rm -rf /var/lib/apt/lists /var/cache/apt/archives CMD ["echo", "Whalecome!"]


He dividit la instrucció RUN en diverses línies, cosa que, òbviament, les fa més llegibles pels humans . Però, com afectarà la mida de la imatge? Construïm la imatge i comprovem-la.


 ➜ time docker build -t no-minimize-layers --no-cache -f no-minimize-layers.dockerfile . 0.31s user 0.28s system 2% cpu 28.577 total


Va trigar 28 segons a crear la imatge, mentre que la creació de la versió original amb capes minimitzades només triga 19 segons ( gairebé un 33% més ràpid ).


 ➜ time docker build -t original --no-cache -f original.dockerfile . 0.25s user 0.28s system 2% cpu 19.909 total


Comprovem la mida de les imatges.


 ➜ docker images --filter "reference=*original*" --filter "reference=*no-minimize*" REPOSITORY TAG IMAGE ID CREATED SIZE original latest f1363df79c8a 8 seconds ago 356MB no-minimize-layers latest ad3945c8a8ee 43 seconds ago 379MB


La imatge amb capes minimitzades és 23 MB més petita que la que no té capes minimitzades. Això suposa una reducció del 6% de la mida . Tot i que sembla una petita diferència en aquest exemple, la diferència serà molt més gran si dividiu totes les instruccions RUN en diverses línies.

Regla núm. 3: instal·leu només el necessari

De manera predeterminada, apt-get install instal·la els paquets recomanats així com els paquets que li vau demanar. L'opció --no-install-recommends diu apt-get que instal·li només els paquets especificats explícitament i no els recomanats.


 ➜ time docker build -t without-no-install-recommends --no-cache -f without-no-install-recommends.dockerfile . 0.33s user 0.30s system 2% cpu 29.786 total ➜ docker images --filter "reference=*original*" --filter "reference=*recommends*" REPOSITORY TAG IMAGE ID CREATED SIZE without-no-install-recommends latest 41e6e37f1e2b 3 minutes ago 426MB minimize-layers latest dff22c85d84c 17 minutes ago 356MB


Com podeu veure, la imatge sense --no-install-recommends és 70 MB més gran que l' original . Això suposa un augment del 16% de mida .


Utilitzeu la utilitat d'immersió per veure quins fitxers s'han afegit a la imatge; llegiu-ne més informació al final de l'article.

Regla 4: neteja després de les instal·lacions

El Dockerfile original inclou l'ordre rm -rf /var/lib/apt/lists/* /var/cache/apt/archives després de l'ordre apt-get install . Aquesta ordre elimina les llistes de paquets i arxius que ja no són necessaris després de la instal·lació. Vegem com afecta la mida de la imatge, per aconseguir-ho, crearé un nou Dockerfile sense l'ordre de neteja .


 RUN apt-get update -qq && \ apt-get install --no-install-recommends -y curl libjemalloc2 libvips libpq-dev


La construcció de les imatges porta gairebé el mateix temps que l'original, la qual cosa té sentit.


 ➜ time docker build -t without-cleaning --no-cache -f without-cleaning.dockerfile . 0.28s user 0.30s system 2% cpu 21.658 total


Comprovem la mida de les imatges.


 ➜ docker images --filter "reference=*original*" --filter "reference=*cleaning*" REPOSITORY TAG IMAGE ID CREATED SIZE without-cleaning latest 52884fe50773 2 minutes ago 375MB original latest f1363df79c8a 16 minutes ago 356MB


La imatge sense neteja és 19 MB més gran que la amb neteja, això suposa un augment del 5% de mida .

El pitjor escenari

Què passa si no s'apliquen les quatre optimitzacions esmentades anteriorment? Creem un nou Dockerfile sense cap optimització i creem la imatge.


 # syntax=docker/dockerfile:1 # check=error=true ARG RUBY_VERSION=3.4.1 FROM docker.io/library/ruby:$RUBY_VERSION AS base RUN apt-get update -qq RUN apt-get install -y curl RUN apt-get install -y libjemalloc2 RUN apt-get install -y libvips RUN apt-get install -y libpq-dev CMD ["echo", "Whalecome!"]


 ➜ time docker build -t without-optimizations --no-cache -f without-optimizations.dockerfile . 0.46s user 0.45s system 1% cpu 1:02.21 total


Vaja, va trigar més d'un minut a construir la imatge.


 ➜ docker images --filter "reference=*original*" --filter "reference=*without-optimizations*" REPOSITORY TAG IMAGE ID CREATED SIZE without-optimizations latest 45671929c8e4 2 minutes ago 1.07GB original latest f1363df79c8a 27 hours ago 356MB


La imatge sense optimitzacions és 714 MB més gran que l'original, això suposa un augment del 200% de mida . Això mostra clarament com d'important és optimitzar el Dockerfile , les imatges més grans triguen més temps a construir-se i consumeixen més espai en disc.

Feu servir sempre .dockerignore

El fitxer .dockerignore és similar al fitxer .gitignore utilitzat per Git. S'utilitza per excloure fitxers i directoris del context de la compilació. El context és el conjunt de fitxers i directoris que s'envien al dimoni Docker quan es construeix una imatge. El context s'envia al dimoni Docker com a tarball, per la qual cosa és important mantenir-lo el més petit possible.


Si, per qualsevol motiu, no teniu el fitxer .dockerignore al vostre projecte, podeu crear-lo manualment. Us suggereixo que utilitzeu la plantilla oficial de fitxers .dockerignore de Rails com a punt de partida. A continuació es mostra un exemple de com pot semblar.


 # See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. # Ignore git directory. /.git/ /.gitignore # Ignore bundler config. /.bundle # Ignore all environment files. /.env* # Ignore all default key files. /config/master.key /config/credentials/*.key # Ignore all logfiles and tempfiles. /log/* /tmp/* !/log/.keep !/tmp/.keep # Ignore pidfiles, but keep the directory. /tmp/pids/* !/tmp/pids/.keep # Ignore storage (uploaded files in development and any SQLite databases). /storage/* !/storage/.keep /tmp/storage/* !/tmp/storage/.keep # Ignore assets. /node_modules/ /app/assets/builds/* !/app/assets/builds/.keep /public/assets # Ignore CI service files. /.github # Ignore development files /.devcontainer # Ignore Docker-related files /.dockerignore /Dockerfile*


Tenir un fitxer .dockerfile al projecte no només permet excloure fitxers i directoris innecessaris (p. ex., fluxos de treball de GitHub de la carpeta .github o dependències de JavaScript de node_modules ) del context. També ajuda a evitar afegir accidentalment informació sensible a la imatge. Per exemple, el fitxer .env que conté les variables d'entorn o el fitxer master.key que s'utilitza per desxifrar les credencials.

Utilitza Dive

Totes les optimitzacions esmentades anteriorment poden semblar òbvies quan s'expliquen. Què fer si ja tens una imatge massiva i no saps per on començar?


La meva eina preferida i més útil és Dive . Dive és una eina TUI per explorar una imatge de Docker, el contingut de capes i descobrir maneres de reduir la mida de la imatge. Dive es pot instal·lar amb el gestor de paquets del vostre sistema, o podeu utilitzar la seva imatge oficial de Docker per executar-lo. Utilitzem la imatge del nostre pitjor escenari.


 docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock wagoodman/dive:latest without-optimizations 


Eina d'inspecció de capes de Docker de busseig

A la captura de pantalla de dalt, podeu veure la inspecció de la nostra imatge no òptima. Dive mostra la mida de cada capa, la mida total de la imatge i els fitxers que s'han modificat (afegit, modificat o suprimit) a cada capa. Per a mi, aquesta és la característica més útil de Dive. En llistar els fitxers al tauler dret, podeu identificar fàcilment els fitxers que no són necessaris i eliminar les ordres que els afegeixen a la imatge.


Una cosa que m'encanta de Dive és que, a més de tenir una interfície d'usuari de terminal, també pot proporcionar una sortida compatible amb CI, que també pot ser eficaç en un desenvolupament local. Per utilitzar-lo, executeu Dive amb la variable d'entorn CI establerta en true , la sortida de l'ordre es troba a la captura de pantalla següent.


 docker run -e CI=true --rm -it -v /var/run/docker.sock:/var/run/docker.sock wagoodman/dive:latest without-optimizations 


Sortida compatible amb CI per immersió

La meva preferència personal és utilitzar Dive de manera programada, per exemple, un cop a la setmana, per assegurar-me que les vostres imatges encara estiguin en bon estat. En els propers articles, tractaré els fluxos de treball automatitzats que faig servir per comprovar el meu Dockerfile, inclosos Dive i Hadolint .

No aixafeu capes

Un enfocament per minimitzar la mida de la imatge que he vist és intentar aixafar les capes. La idea era combinar diverses capes en una sola capa per reduir la mida de la imatge. Docker tenia una opció experimental --squash , a més d'això, hi havia eines de tercers com docker-squash .


Tot i que aquest enfocament funcionava en el passat, actualment està obsolet i no es recomana utilitzar-lo. L'aixafament de capes va destruir la característica fonamental de la memòria cau de capes de Docker. A part d'això, mentre feu servir --squash podeu incloure sense voler fitxers sensibles o temporals de capes anteriors a la imatge final. Aquest és un enfocament de tot o res que no té un control detallat.


En lloc d'aixafar capes, es recomana utilitzar construccions en diverses etapes. Rails Dockerfile ja utilitza compilacions de diverses etapes, explicaré com funciona al següent article.

Conclusions

L'optimització de les imatges de Docker, com qualsevol altra optimització, no es pot fer una vegada i oblidar-se . És un procés continu que requereix comprovacions i millores periòdiques. Vaig intentar cobrir els conceptes bàsics, però és fonamental conèixer-los i entendre-los. En els propers articles, parlaré de tècniques i eines més avançades que poden ajudar a fer que les compilacions de Docker siguin més ràpides i eficients.