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 o contenen menys paquets preinstal·lats, la qual cosa redueix el risc que s'exploti programari sense pedaços o innecessari. alpine debian-slim 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 al terminal. docker images ➜ 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 és una 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. capa d'imatge Docker capa de sistema de fitxers immutable Les imatges Docker es construeixen de manera incremental, capa per capa, i cada capa correspon a una instrucció del . Per obtenir les capes de la imatge, podeu executar l'ordre . Dockerfile 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 . A partir de Rails 7.1, el es genera amb la nova aplicació Rails. A continuació es mostra un exemple de com pot semblar. Dockerfile Dockerfile # 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 anterior per fer que la mida final de la imatge sigui eficient. Dockerfile 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 del Rails Dockerfile anterior. Ho faré referència com a versió . el Dockerfile extret 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 . És la imatge que s'utilitza per crear el contenidor. La imatge base és la primera capa del i és l'única capa que no crea el mateix . Dockerfile Dockerfile Dockerfile La imatge base s'especifica amb l'ordre , seguida del nom i l'etiqueta de la imatge. L'etiqueta és opcional i, si no s'especifica, s'utilitza l'etiqueta . La imatge base pot ser qualsevol imatge disponible a Docker Hub o qualsevol altre registre. FROM latest Al about, estem utilitzant la imatge amb l'etiqueta . La imatge és disponible a Docker Hub. L'etiqueta és una versió fina de la imatge Ruby que es basa en la imatge . Tot i que la imatge é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 . Dockerfile ruby 3.4.1-slim ruby la imatge oficial Ruby 3.4.1-slim debian-slim debian-slim 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 i l'anterior és . bookworm bullseye 219 MB en lloc d'1 GB: una gran diferència. Però, què passa si la imatge és encara més petita? La imatge 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 (en comptes de ) i (un conjunt compacte d'utilitats Unix) en comptes dels homòlegs de GNU. Tot i que tècnicament és possible utilitzar la imatge per executar Rails, no ho cobriré en aquest article. alpine alpine musl glibc busybox alpine 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ó , i 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ó . Per il·lustrar aquest punt, mirem l'exemple següent. RUN COPY FROM Dockerfile RUN # 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ó en diverses línies, cosa que, òbviament, les fa més . Però, com afectarà la mida de la imatge? Construïm la imatge i comprovem-la. RUN llegibles pels humans ➜ 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 només triga 19 segons ( ). versió original amb capes minimitzades 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 . Tot i que sembla una petita diferència en aquest exemple, la diferència serà molt més gran si dividiu totes les instruccions en diverses línies. reducció del 6% de la mida RUN Regla núm. 3: instal·leu només el necessari De manera predeterminada, instal·la els paquets recomanats així com els paquets que li vau demanar. L'opció diu que instal·li només els paquets especificats explícitament i no els recomanats. apt-get install --no-install-recommends apt-get ➜ 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 és 70 MB més gran que l' . Això suposa un . --no-install-recommends original augment del 16% de mida la utilitat per veure quins fitxers s'han afegit a la imatge; llegiu-ne més informació al final de l'article. Utilitzeu d'immersió Regla 4: neteja després de les instal·lacions El original inclou l'ordre després de l'ordre . 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 rm -rf /var/lib/apt/lists/* /var/cache/apt/archives apt-get install 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 i creem la imatge. Dockerfile sense cap optimització # 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 . Això mostra clarament com d'important és optimitzar el , les imatges més grans triguen més temps a construir-se i consumeixen més espai en disc. augment del 200% de mida Dockerfile Feu servir sempre .dockerignore El fitxer és similar al fitxer 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. .dockerignore .gitignore Si, per qualsevol motiu, no teniu el fitxer al vostre projecte, podeu crear-lo manualment. Us suggereixo que utilitzeu la de Rails com a punt de partida. A continuació es mostra un exemple de com pot semblar. .dockerignore plantilla oficial de fitxers .dockerignore # 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* fitxer al projecte no només permet excloure fitxers i directoris innecessaris (p. ex., fluxos de treball de GitHub de la carpeta o dependències de JavaScript de ) del context. També ajuda a evitar afegir accidentalment informació sensible a la imatge. Per exemple, el fitxer que conté les variables d'entorn o el fitxer que s'utilitza per desxifrar les credencials. Tenir un .dockerfile .github node_modules .env master.key 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 é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. Dive docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock wagoodman/dive:latest without-optimizations 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 establerta en , la sortida de l'ordre es troba a la captura de pantalla següent. CI true docker run -e CI=true --rm -it -v /var/run/docker.sock:/var/run/docker.sock wagoodman/dive:latest without-optimizations 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 , a més d'això, hi havia eines de tercers com . --squash 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 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. --squash En lloc d'aixafar capes, es recomana utilitzar construccions en diverses etapes. Rails ja utilitza compilacions de diverses etapes, explicaré com funciona al següent article. Dockerfile Conclusions L'optimització de les imatges de Docker, com qualsevol altra optimització, . É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. no es pot fer una vegada i oblidar-se