Con el lanzamiento de acceso anticipado de Docker Scout , Docker Hub finalmente está comenzando a visualizar las imágenes internas. ¡Esto es genial! ¿Por qué Docker Hub no hizo esto hace años? Por su modelo de negocio.
Antes de continuar, repasemos brevemente de qué están hechas las imágenes de Docker.
Cuando descarga una imagen de Docker ejecutando docker pull
en la línea de comando, la CLI de Docker muestra el progreso de descarga de cada capa a medida que extrae la imagen. Si alguna vez descargaste una imagen de Docker, probablemente la hayas visto:
Si ha trabajado con Docker antes, probablemente haya definido algunas de estas capas para sus propias imágenes. Los desarrolladores definen implícitamente estas capas cuando escriben Dockerfiles. Por ejemplo, cada línea de este Dockerfile produce una capa:
FROM ubuntu:22.04 # install apt dependencies RUN apt-get update RUN apt-get install -y iputils-ping python3 python3-pip # install python dependencies RUN pip3 install numpy # cleanup apt lists RUN rm -rf /var/lib/apt/lists/* CMD ["/bin/bash"]
Las capas son directorios archivados de Linux: son archivos comprimidos del sistema. Docker descarga todas las capas de imágenes y descomprime cada una de ellas en un directorio separado. Cuando inicia un contenedor desde una imagen de Docker usando el comando docker run
, el demonio de Docker combina las capas de la imagen para formar un contenedor.
Gran parte del valor agregado de Docker como producto es que abstrae estos detalles, permitiendo a los usuarios aprovechar los beneficios de los contenedores en capas sin pensar en cómo funcionan. Pero todas las abstracciones se filtran, y Docker no es una excepción: a veces, es necesario correr el telón.
Las capas de imágenes de Docker contienen la historia de origen de cada binario presente en el sistema de archivos del contenedor. La primera línea de una imagen de Docker es "la línea FROM". Define la imagen de Docker (y, por lo tanto, las capas de imagen) sobre la que se construye el Dockerfile.
Al inspeccionar las capas del Dockerfile actual y las capas de su cadena de imágenes principal, los desarrolladores pueden determinar de dónde proviene cada archivo en el sistema de archivos raíz de un contenedor. Esta es una información muy valiosa. Ayuda a los desarrolladores:
Imagine hacer clic en las capas de una visualización para rastrear los cambios de archivos en las versiones de imágenes de Docker. Cuando un escaneo de seguridad automatizado identifica una vulnerabilidad en una de sus imágenes, imagine usar una herramienta de inspección de capas para identificar cómo se introdujo la vulnerabilidad.
Los tamaños de imagen excesivos pueden costar mucho dinero a las empresas. Muchas canalizaciones de CI/CD extraen imágenes de Docker para cada solicitud de extracción, por lo que las descargas prolongadas de imágenes de Docker pueden ralentizar las canalizaciones, lo que hace que los desarrolladores sean menos eficientes y pierdan tiempo de CPU. Dado que las organizaciones suelen pagar los costos de infraestructura por horas, cada hora desperdiciada es un gasto innecesario.
Más allá de los recursos informáticos desperdiciados, las imágenes de Docker infladas pueden generar costos excesivos de transferencia de red. Si una organización descarga imágenes de Docker en regiones de AWS, o desde AWS a la Internet abierta, el exceso de imágenes de Docker se traduce directamente en un gasto innecesario en infraestructura. Esto se suma rápidamente.
Es fácil introducir accidentalmente hinchazón en las capas de imágenes de Docker. El Dockerfile mostrado anteriormente contiene un ejemplo clásico: la capa de la línea 4 guarda 40 MB de archivos en el disco, que la capa de la línea 11 elimina más adelante. Debido a cómo funcionan las imágenes de Docker, esos datos siguen siendo parte de la imagen, lo que agrega 40 MB de tamaño de imagen innecesario.
Este es un ejemplo simple; de hecho, proviene directamente de la documentación de Docker. En Dockerfiles más complejos, este error puede ser mucho más difícil de detectar.
Puede ser difícil interactuar con las capas de imágenes de Docker usando la línea de comando, pero antes de que se lanzara Docker Scout recientemente, la línea de comando era donde se encontraba lo último en tecnología. Aquí están los dos enfoques básicos.
Este es el método Unix sencillo y que puede hacer usted mismo. Todo lo que necesita es un host que ejecute un demonio Docker. Es un enfoque simple:
docker create
para iniciar un contenedor a partir de la imagen.docker inspect
para buscar los directorios de capas del nuevo contenedor.cd
en esos directorios en la línea de comando y razona sobre las capas en tu cabeza.
Esto es una molestia. Digamos que estamos tratando de rastrear una imagen hinchada en una imagen de Docker que hemos estado usando durante algunos meses pero que recientemente ha crecido significativamente en tamaño.
Primero, creamos un contenedor a partir de la imagen:
where-the@roadmap-ends ~ $ docker create --name example where-the-roadmap-ends 339b8905b681a1d4f7c56f564f6b1f5e63bb6602b62ba5a15d368ed785f44f55
Luego, docker inspect
nos dice dónde terminaron los directorios de capas de la imagen descargada en nuestro sistema de archivos:
where-the@roadmap-ends ~ $ docker inspect example | grep GraphDriver -A7 "GraphDriver": { "Data": { "LowerDir": "/var/lib/docker/overlay2/1c18bd289d9c3f9f0850e301bf86955395c312de3a64a70e0d0e6a5bed337d47-init/diff:/var/lib/docker/overlay2/wbugwbg23oefsf678r7anbn4f/diff:/var/lib/docker/overlay2/j0dekt7y8xgix11n0lturmf8t/diff:/var/lib/docker/overlay2/zd57mz6l4zrsjk9snc2crphfq/diff:/var/lib/docker/overlay2/83za1pmv9xri44tddzyju0usm/diff:/var/lib/docker/overlay2/8c639b22627e0ad91944a70822b442e5bff848968263a37715a293a15483c170/diff", "MergedDir": "/var/lib/docker/overlay2/1c18bd289d9c3f9f0850e301bf86955395c312de3a64a70e0d0e6a5bed337d47/merged", "UpperDir": "/var/lib/docker/overlay2/1c18bd289d9c3f9f0850e301bf86955395c312de3a64a70e0d0e6a5bed337d47/diff", "WorkDir": "/var/lib/docker/overlay2/1c18bd289d9c3f9f0850e301bf86955395c312de3a64a70e0d0e6a5bed337d47/work" }, "Name": "overlay2"
Las capas que queremos ver para esta investigación son la lista de directorios "LowerDir". Los otros directorios no forman parte de la imagen de Docker en sí; podemos ignorarlos.
Entonces, analizamos la lista de directorios "LowerDir" en la línea de comando:
where-the@roadmap-ends ~ $ docker inspect example | grep GraphDriver -A7 | grep LowerDir | awk '{print $2}' | sed 's|"||g' | sed 's|,||g' | sed 's|:|\n|g' /var/lib/docker/overlay2/1c18bd289d9c3f9f0850e301bf86955395c312de3a64a70e0d0e6a5bed337d47-init/diff /var/lib/docker/overlay2/wbugwbg23oefsf678r7anbn4f/diff /var/lib/docker/overlay2/j0dekt7y8xgix11n0lturmf8t/diff /var/lib/docker/overlay2/zd57mz6l4zrsjk9snc2crphfq/diff /var/lib/docker/overlay2/83za1pmv9xri44tddzyju0usm/diff /var/lib/docker/overlay2/8c639b22627e0ad91944a70822b442e5bff848968263a37715a293a15483c170/diff
Ésta es la lista de capas de la imagen, en orden, con la capa más baja primero. Ahora necesitamos correlacionar manualmente estos directorios de capas con las líneas de Dockerfile que los produjeron.
Desafortunadamente, Docker no nos brinda una manera de extraer directamente estas líneas de una imagen de Docker; esto es lo mejor que podemos obtener usando docker history
:
where-the@roadmap-ends ~ $ docker history where-the-roadmap-ends IMAGE CREATED CREATED BY SIZE COMMENT 6bbac081b2a7 2 hours ago CMD ["/bin/bash"] 0B buildkit.dockerfile.v0 <missing> 2 hours ago RUN /bin/sh -c rm -rf /var/lib/apt/lists/* #… 0B buildkit.dockerfile.v0 <missing> 2 hours ago RUN /bin/sh -c pip3 install numpy # buildkit 70MB buildkit.dockerfile.v0 <missing> 2 hours ago RUN /bin/sh -c apt-get install -y iputils-pi… 343MB buildkit.dockerfile.v0 <missing> 2 hours ago RUN /bin/sh -c apt-get update # buildkit 40.1MB buildkit.dockerfile.v0 <missing> 8 months ago /bin/sh -c #(nop) CMD ["bash"] 0B <missing> 8 months ago /bin/sh -c #(nop) ADD file:550e7da19f5f7cef5… 69.2MB
Con este resultado, podemos identificar qué capas tienen directorios y qué comando Dockerfile creó la capa (que se muestra en la columna CREADO POR).
El comando docker history
genera las capas en su contenedor en el mismo orden en que docker inspect
enumera los directorios de capas. Sabiendo esto, podemos fusionar manualmente las dos salidas para ver qué capas son más grandes que otras, qué comando Dockerfile las creó y qué directorio contiene cada capa.
Aquí está el contenido de la Capa A, que realiza una apt-get update
:
where-the@roadmap-ends ~ $ du -hs /var/lib/docker/overlay2/83za1pmv9xri44tddzyju0usm/diff/var/lib/apt/lists 38.2M /var/lib/docker/overlay2/83za1pmv9xri44tddzyju0usm/diff/var/lib/apt/lists
En comparación con el contenido de la capa B, eso elimina los archivos sobrantes de la capa A:
where-the@roadmap-ends ~ $ du -hs /var/lib/docker/overlay2/wbugwbg23oefsf678r7anbn4f/diff/var/lib/apt/lists 4.0K /var/lib/docker/overlay2/wbugwbg23oefsf678r7anbn4f/diff/var/lib/apt/lists
El directorio /var/lib/apt/lists
existe en ambas capas, pero en la capa B, el directorio casi no utiliza espacio.
Esto se debe a que el directorio de la capa B contiene "archivos de borrado", que Docker utiliza para marcar archivos para su exclusión del sistema de archivos del contenedor final.
Debido a esto, a pesar de que los archivos se "eliminan" en la Capa B, todavía existen en la Capa A y, por lo tanto, contribuyen al tamaño general de su imagen: 38,2 MB de hinchazón innecesaria.
Ahora bien, ¿no fue tan fácil? 😉
El método manual es tan complejo y difícil de manejar que la comunidad de código abierto creó una herramienta específicamente para esta tarea: se llama dive
. Dive es una herramienta CLI que toma una imagen como entrada, analiza sus sistemas de archivos y presenta una interfaz de usuario interactiva basada en texto en su terminal. Correlaciona las capas de la imagen por usted, lo que le permite inspeccionar los directorios de capas más fácilmente.
Cuando se ejecuta en la imagen de Docker del Dockerfile anterior, se ve así:
┃ ● Layers ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ Aggregated Layer Contents ├─────────────────────────────────────────────────────────────────────────────── Cmp Size Command └── var 69 MB FROM acee8cf20a197c9 └── lib 40 MB RUN /bin/sh -c apt-get update # buildkit └── apt 343 MB RUN /bin/sh -c apt-get install -y iputils-ping python3 python3-pip # buildkit └── lists 70 MB RUN /bin/sh -c pip3 install numpy # buildkit ├── auxfiles 0 B RUN /bin/sh -c rm -rf /var/lib/apt/lists/* # buildkit ├── lock ├── partial │ Layer Details ├─────────────────────────────────────────────────────────────────────────────────────────── ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-backports_InRelease ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-backports_main_binary-arm64_Packages.lz4 Tags: (unavailable) ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-backports_universe_binary-arm64_Packages.lz4 Id: 2bc27a99fd5750414948211814da00079804292360f8e2d7843589b9e7eb5eee ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-security_InRelease Digest: sha256:6e6fb36e04f3abf90c7c87d52629fe154db4ea9aceab539a794d29bbc0919100 ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-security_main_binary-arm64_Packages.lz4 Command: ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-security_multiverse_binary-arm64_Packages.lz4 RUN /bin/sh -c apt-get update # buildkit ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-security_restricted_binary-arm64_Packages.lz4 ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-security_universe_binary-arm64_Packages.lz4 │ Image Details ├─────────────────────────────────────────────────────────────────────────────────────────── ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-updates_InRelease ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-updates_main_binary-arm64_Packages.lz4 Image name: where-the-roadmap-ends ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-updates_multiverse_binary-arm64_Packages.lz4 Total Image size: 522 MB ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-updates_restricted_binary-arm64_Packages.lz4 Potential wasted space: 62 MB ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-updates_universe_binary-arm64_Packages.lz4 Image efficiency score: 90 % ├── ports.ubuntu.com_ubuntu-ports_dists_jammy_InRelease ├── ports.ubuntu.com_ubuntu-ports_dists_jammy_main_binary-arm64_Packages.lz4 Count Total Space Path ├── ports.ubuntu.com_ubuntu-ports_dists_jammy_multiverse_binary-arm64_Packages.lz4 2 28 MB /var/lib/apt/lists/ports.ubuntu.com_ubuntu-ports_dists_jammy_universe_binary-arm64_Pack ├── ports.ubuntu.com_ubuntu-ports_dists_jammy_restricted_binary-arm64_Packages.lz4 2 7.3 MB /usr/bin/perl └── ports.ubuntu.com_ubuntu-ports_dists_jammy_universe_binary-arm64_Packages.lz4 2 4.4 MB /usr/lib/aarch64-linux-gnu/libstdc++.so.6.0.30 2 2.9 MB /var/lib/apt/lists/ports.ubuntu.com_ubuntu-ports_dists_jammy_main_binary-arm64_Packages 2 2.0 MB /var/lib/apt/lists/ports.ubuntu.com_ubuntu-ports_dists_jammy-updates_main_binary-arm64_ 2 1.7 MB /var/lib/apt/lists/ports.ubuntu.com_ubuntu-ports_dists_jammy-updates_universe_binary-ar
El buceo es una herramienta maravillosa y me alegro de que exista, pero a veces se queda corta. Las interfaces basadas en texto no son las más fáciles de usar; a veces, Grafana es mejor que top
.
Además, las imágenes grandes pueden abrumar las capacidades de Dive. Al inspeccionar imágenes grandes, Dive consume mucha memoria; a veces, el kernel finalizará el proceso de Dive antes de que genere datos.
Desde la perspectiva de un desarrollador, siempre ha tenido sentido esperar que existan visualizaciones de capas de imágenes de Docker en Docker Hub. Después de todo, nuestros datos de imágenes de Docker ya se encuentran en el backend de Docker Hub.
A menudo me he imaginado inspeccionando las partes internas de la imagen de Docker en mi navegador, usando una interfaz de usuario que se parecía a esta:
Con Docker Scout, parece que Docker se está moviendo en esa dirección como empresa. Hoy, si navego hasta la última imagen de Postgres en Docker Hub, me aparece esto:
Como desarrollador, esto es apasionante. Esta nueva interfaz de usuario me permite explorar visualmente los detalles de la capa de imagen, resaltando la vulnerabilidad y los problemas de tamaño de la imagen tal como yo quiero.
Cuando se lanzó Docker Hub por primera vez, tenía dos tipos de usuarios:
Y Docker tuvo que abordar estos dos tipos de usuarios de manera diferente.
Después de que Docker debutó en 2013, los desarrolladores inmediatamente quisieron utilizar su producto. Los contenedores Docker estandarizan el empaquetado y la implementación del software de todos. Los contenedores rápidamente se hicieron populares en la comunidad de desarrolladores de software.
Los editores de software, sin embargo, se mostraron más vacilantes. ¿Por qué deberían cargar su software en una plataforma cuya utilidad principal es eliminar la diferenciación entre productos de software?
Docker tuvo que ganárselos para que Docker Hub tuviera éxito. Entonces, en lugar de centrarse en atraer desarrolladores, el diseño y el conjunto de funciones de Docker Hub se dirigieron a los editores de software.
Para los editores, Docker Hub era un sitio de marketing. Les dio un lugar para anunciar sus productos. No permitió a los desarrolladores ver los detalles internos de la imagen de Docker, como tampoco los concesionarios de automóviles permiten desmontar sus bloques de motor. Los detalles de ingeniería interna no se exhibían, sino que se ocultaban, por lo que los compradores se concentraban en los productos en sí, no en cómo se fabricaban.
Docker Hub carecía de funciones orientadas a los desarrolladores para la optimización del tamaño de la imagen y la introspección de la cadena de suministro porque Docker ya se ganó a los desarrolladores sin esas funciones. De hecho, esas características tienden a hacer que los editores de software queden mal, y eran los usuarios que Docker Hub aún necesitaba conquistar para tener éxito.
Esta dinámica de servir tanto a los editores como a los desarrolladores de software convirtió a Docker Hub en una plataforma de dos caras.
Las plataformas bilaterales son empresas que tienen dos categorías diferentes de usuarios y necesitan que ambos participen en la plataforma para que todo funcione. Por lo general, es la motivación para utilizar la plataforma lo que divide a los usuarios en diferentes grupos.
Si bien es posible que los dos grupos de usuarios no realicen transacciones entre sí directamente, las plataformas bilaterales son como mercados: no crean valor a menos que los proveedores se presenten para vender y los compradores se presenten para comprar.
En la industria tecnológica, las plataformas bilaterales son omnipresentes. Cuando tienen éxito, estos modelos de negocio tienden a producir efectos de red que impulsan a las empresas a un crecimiento y rentabilidad sostenidos. Después de cierto punto, el tamaño de la plataforma y la posición establecida en un espacio atraen a nuevos usuarios a la plataforma.
Una vez que sepa lo que está buscando, las plataformas de dos lados son fáciles de detectar. Aquí hay tres ejemplos.
En LinkedIn, hay dos tipos de usuarios: empleados y gerentes de contratación. Ambos grupos participan por diferentes razones: los empleados quieren empleos y los gerentes de contratación quieren contratar gente.
Ningún grupo pudo obtener lo que quería del sitio hasta que el otro grupo comenzó a participar. Una vez que se registró un número suficiente de cada grupo, se convirtió en el lugar predeterminado para los nuevos miembros de cualquiera de los grupos, y el crecimiento del sitio se perpetuó, hasta que Microsoft lo compró por 26 mil millones de dólares.
En YouTube , hay creadores de contenido y hay espectadores. Los espectadores vienen al sitio para ver videos: los creadores de contenido publican en el sitio en busca de buenas vibraciones, fama y fortuna.
Después de que YouTube se ganara la reputación de ser un lugar donde los creadores de contenido podían tener éxito, aparecieron cada vez más creadores de contenido y, a medida que generaban más contenido, más espectadores los visitaban. Una vez que la plataforma creció más allá de cierto tamaño, los creadores de contenido y los espectadores no tuvieron más remedio que seguir usándola: cada grupo necesitaba al otro y solo podían encontrarse a través de YouTube.
Para que Docker Hub fuera relevante, necesitaba que los editores de software cargaran imágenes y los desarrolladores las descargaran. Una vez que suficientes editores enviaran imágenes a Docker Hub, se convertiría en el lugar predeterminado para que los desarrolladores pudieran aprovecharlas. A medida que la plataforma siguiera creciendo, Docker Hub consolidaría su dominio como el único registro que los gobernaría a todos.
Al menos ese era el plan. En realidad, Docker (la empresa) decayó y Docker Hub nunca se convirtió en "el indicado". Al construir una plataforma bilateral, las startups tienen un producto, pero tienen que encontrar el ajuste producto-mercado dos veces. Para Docker, esta carga era demasiado difícil de soportar; al final, lo dividió por la mitad .
Ahora, después de vender su negocio empresarial , Docker se ha reinventado, centrando su servicio de suscripción exclusivamente en los desarrolladores y sus empleadores. Docker Hub ya no es una plataforma de dos caras, es un simple SaaS: los clientes pagan una tarifa mensual a cambio de enviar y extraer sus imágenes.
Esto cambia el cálculo. Ya no existe un perfil de usuario "editor" al que Docker debe complacer: todos están interesados en los desarrolladores. Para Docker Hub, esto allana el camino para la funcionalidad de inspección de capas de imágenes de Docker Scout. Para aquellos de nosotros que miramos desde lejos, demuestra el acoplamiento sutil y fundamental entre el modelo de negocio de una startup y su oferta de productos.
También publicado aquí.