Spanish (Español) translation by Ruben Solis (you can also view the original English article)
Los contenedores Docker están creciendo como una buena práctica para la implementación y administración de sistemas distribuidos nativos de la nube. Los contenedores son instancias de imágenes Docker. Resulta que hay mucho que saber y comprender acerca de las imágenes.
En este tutorial de dos partes, abordaré las imágenes de Docker en profundidad. En la primera parte traté los principios básicos, consideraciones de diseño y la inspección de las interioridades de una imagen. En esta parte, cubro la construcción de tus propias imágenes, la solución de problemas y el trabajo con los repositorios de imágenes.
Cuando finalices el tutorial, tendrás una sólida comprensión de lo que son exactamente las imágenes de Docker y cómo utilizarlas eficazmente en tus propias aplicaciones y sistemas.
Construyendo Imágenes
Hay dos formas de crear imágenes. Puedes modificar un contenedor existente y luego confirmarlo ('commit') como una nueva imagen, o puedes escribir un Dockerfile y construirlo como imagen. Trataremos ambos casos y explicaremos los pros y los contras.
Construcciones Manuales
Con las construcciones manuales, tratas tu contenedor como un ordenador normal. Instalas paquetes, escribes archivos y, cuando todo esta completado, lo confirmas ('commit') y acabas obteniendo una nueva imagen que usas como plantilla para crear muchos más contenedores idénticos o incluso como base para otras imágenes.
Comencemos con la imagen alpine, que es una imagen muy pequeña y espartana basada en Alpine Linux. Podemos ejecutarla en modo interactivo para entrar en un shell. Nuestro objetivo es agregar un archivo llamado "yeah" (sí) que contenga el texto "it works!" (¡funciona!) en el directorio raíz y luego crear una nueva imagen llamada "yeah-alpine".
Aquí vamos. Bien, ya estamos en el directorio raíz. Veamos qué hay ahí.
> docker run -it alpine /bin/sh / # ls bin dev etc home lib linuxrc media mnt proc root run sbin srv sys tmp usr var
¿Qué editor está disponible? ¿Sin vim, sin nano?
/ # vim /bin/sh: vim: not found / # nano /bin/sh: nano: not found
Oh bien. Solo queremos crear un archivo:
/ # echo "it works!" > yeah / # cat yeah it works!
Salí del shell interactivo y puedo ver el contenedor llamado "vibrant_spence" con docker ps --all
. La marca --all
es importante porque el contenedor ya no se está ejecutando.
> docker ps --all CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES c8faeb05de5f alpine "/bin/sh" 6 minutes ago Exited vibrant_spence
Aquí, creo una nueva imagen del contenedor "vibrant_spence". Agregué el mensaje de commit "mine, mine, mine" por si acaso.
> docker commit -m "mine, mine, mine" vibrant_spence yeah-alpine sha256:e3c98cd21f4d85a1428...e220da99995fd8bf6b49aa
Vamos a ver. Sí, hay una nueva imagen, y en su historial se puede ver una nueva capa con el comentario "mine, mine, mine".
> docker images REPOSITORY TAG IMAGE ID SIZE yeah-alpine latest e3c98cd21f4d 4.8 MB python latest 775dae9b960e 687 MB d4w/nsenter latest 9e4f13a0901e 83.8 kB ubuntu-with-ssh latest 87391dca396d 221 MB ubuntu latest bd3d4369aebc 127 MB hello-world latest c54a2cc56cbb 1.85 kB alpine latest 4e38e38c8ce0 4.8 MB nsqio/nsq latest 2a82c70fe5e3 70.7 MB > docker history yeah-alpine IMAGE CREATED SIZE COMMENT e3c98cd21f4d 40 seconds ago 66 B mine, mine, mine 4e38e38c8ce0 7 months ago 4.8 MB
Ahora para la prueba real. Eliminemos el contenedor y creemos un nuevo contenedor a partir de la imagen. El resultado esperado es que el archivo "yeah" estará presente en el nuevo contenedor.
> docker rm vibrant_spence vibrant_spence > docker run -it yeah-alpine /bin/sh / # cat yeah it works! / #
¿Qué puedo decir? ¡Sí, funciona!
Usando un Dockerfile
Crear imágenes a partir de contenedores modificados es genial, pero no hay una rendición de cuentas. Es difícil hacer un seguimiento de los cambios y saber cuáles fueron las modificaciones específicas. La manera ordenada de crear imágenes es hacerlo usando un Dockerfile.
El Dockerfile es un archivo de texto similar a un script de shell, pero admite varios comandos. Cada comando que modifica el sistema de archivos crea una nueva capa. En la primera parte discutimos la importancia de dividir correctamente tu imagen en capas. El Dockerfile es un gran tema en sí mismo.
Aquí, solo demostraré un par de comandos para crear otra imagen, "oh-yeah-alpine", basada en un Dockerfile. Además de crear el infame archivo "yeah", también instalaremos vim. La distribución de Alpine Linux utiliza un sistema de gestión de paquetes llamado "apk". Aquí está el Dockerfile:
FROM alpine # Copy the "yeah" file from the host COPY yeah /yeah # Update and install vim using apk RUN apk update && apk add vim CMD cat /yeah
La imagen base es alpine. Se copia el archivo "yeah" del mismo directorio de host donde está el Dockerfile (la ruta del build context). Luego, se ejecuta apk update
y se instala vim. Finalmente, se establece el comando que se ejecuta cuando se ejecuta el contenedor. En este caso, imprimirá en la pantalla el contenido del archivo "yeah".
Ok. Ahora que sabemos en lo que nos estamos metiendo, construyamos nuestra imagen. La opción "-t" establece el repositorio. No especifiqué una etiqueta, por lo que será la predeterminada "latest".
> docker build -t oh-yeah-alpine . Sending build context to Docker daemon 3.072 kB Step 1/4 : FROM alpine ---> 4e38e38c8ce0 Step 2/4 : COPY yeah /yeah ---> 1b2a228cc2a5 Removing intermediate container a6221f725845 Step 3/4 : RUN apk update && apk add vim ---> Running in e2c0524bd792 fetch https://dl-cdn.alpinelinux.org/.../APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org.../x86_64/APKINDEX.tar.gz v3.4.6-60-gc61f5bf [http://dl-cdn.alpinelinux.org/alpine/v3.4/main] v3.4.6-33-g38ef2d2 [http://dl-cdn.alpinelinux.org/.../v3.4/community] OK: 5977 distinct packages available (1/5) Installing lua5.2-libs (5.2.4-r2) (2/5) Installing ncurses-terminfo-base (6.0-r7) (3/5) Installing ncurses-terminfo (6.0-r7) (4/5) Installing ncurses-libs (6.0-r7) (5/5) Installing vim (7.4.1831-r2) Executing busybox-1.24.2-r9.trigger OK: 37 MiB in 16 packages ---> 7fa4cba6d14f Removing intermediate container e2c0524bd792 Step 4/4 : CMD cat /yeah ---> Running in 351b4f1c1eb1 ---> e124405f28f4 Removing intermediate container 351b4f1c1eb1 Successfully built e124405f28f4
Se ve bien. Verifiquemos que se creó la imagen:
> docker images | grep oh-yeah oh-yeah-alpine latest e124405f28f4 About a minute ago 30.5 MB
¡Ten en cuenta cómo la instalación de vim y sus dependencias aumentó el tamaño del contenedor de los 4,8 MB de la imagen base alpine a los enormes 30,5 MB!
Todo está muy bien. ¿Pero funciona?
> docker run oh-yeah-alpine it works!
¡Oh, sí, funciona!
En caso de que todavía tengas sospechas, vayamos al contenedor y examinemos el archivo "yeah" con nuestro vim recién instalado.
> docker run -it oh-yeah-alpine /bin/sh / # vim yeah it works! ~ ~ . . . ~ "yeah" 1L, 10C
El Build Context y el archivo .dockerignore
No te lo dije, pero originalmente, cuando traté de construir la imagen oh-yeah-alpine, se quedó colgada durante varios minutos. El problema fue que simplemente puse el Dockerfile en mi directorio de inicio. Cuando Docker crea una imagen, primero empaqueta todo el directorio donde está el Dockerfile (incluidos los subdirectorios) y lo pone a disposición para los comandos COPY en el Dockerfile.
Docker no está tratando de ser inteligente y analizar tus comandos COPY. Simplemente empaqueta todo. Ten en cuenta que el contenido del build no terminará en tu imagen, pero ralentizará tu comando de build si tu build context es innecesariamente grande.
En este caso, simplemente copié el Dockerfile y el "yeah" en un subdirectorio y ejecuté el comando docker build en ese subdirectorio. Pero a veces tienes un árbol de directorios complicado del que desea copiar archivos y subdirectorios específicos e ignorar otros. Ingresa al archivo .dockerignore.
Este archivo le permite controlar exactamente lo que entra en el build context. Mi truco favorito es primero excluir todo y luego comenzar a incluir las partes que necesito. Por ejemplo, en este caso podría crear el siguiente archivo .dockerignore y mantener el archivo Docker y el "yeah" en mi directorio de inicio:
# Exclude EVERYTHING first * # Now selectively include stuff !yeah
No es necesario incluir el "Dockerfile" en sí mismo o el archivo ".dockerignore" en el build context
Copia frente a montaje
A veces, lo que necesitas es copiar archivos en la imagen, pero en otros casos es posible que desees que tus contenedores sean más dinámicos y funcionen con archivos en el host. Aquí es donde entran en juego los volúmenes y los montajes.
Montar directorios de host es un juego diferente. Los datos son propiedad del host y no del contenedor. Los datos se pueden modificar cuando se detiene el contenedor. El mismo contenedor se puede iniciar con diferentes directorios de host montados.
Etiquetado de imágenes
Etiquetar imágenes es muy importante si desarrollas un sistema basado en microservicios y generas muchas imágenes que a veces deben asociarse entre sí. Puedes agregar tantas etiquetas como desees a una imagen.
Ya has visto la etiqueta predeterminada "latest". A veces, tiene sentido agregar otras etiquetas, como "tested", "release-1.4" o el git commit que corresponde a la imagen.
Puedes etiquetar una imagen durante un build o más tarde. A continuación, se explica cómo agregar una etiqueta a una imagen existente. Ten en cuenta que mientras una etiqueta es llamada, también puedes asignar un nuevo repositorio.
> docker tag oh-yeah-alpine oh-yeah-alpine:cool-tag > docker tag oh-yeah-alpine oh-yeah-alpine-2 > docker images | grep oh-yeah oh-yeah-alpine-2 latest e124405f28f4 30.5 MB oh-yeah-alpine cool-tag e124405f28f4 30.5 MB oh-yeah-alpine latest e124405f28f4 30.5 MB
También puedes quitar la etiqueta eliminando una imagen por su nombre de etiqueta. Esto da un poco de miedo porque si quitas la última etiqueta por accidente, pierdes la imagen. Pero si creas imágenes a partir de un Dockerfile, puedes simplemente reconstruir la imagen.
> docker rmi oh-yeah-alpine-2 Untagged: oh-yeah-alpine-2:latest > docker rmi oh-yeah-alpine:cool-tag Untagged: oh-yeah-alpine:cool-tag
Si trato de eliminar la última imagen etiquetada restante, aparece un error porque es utilizada por un contenedor.
> docker rmi oh-yeah-alpine Error response from daemon: conflict: unable to remove repository reference "oh-yeah-alpine" (must force) - container a1443a7ca9d2 is using its referenced image e124405f28f4
Pero si quito el contenedor ...
> docker rmi oh-yeah-alpine Untagged: oh-yeah-alpine:latest Deleted: sha256:e124405f28f48e...441d774d9413139e22386c4820df Deleted: sha256:7fa4cba6d14fdf...d8940e6c50d30a157483de06fc59 Deleted: sha256:283d461dadfa6c...dbff864c6557af23bc5aff9d66de Deleted: sha256:1b2a228cc2a5b4...23c80a41a41da4ff92fcac95101e Deleted: sha256:fe5fe2290c63a0...8af394bb4bf15841661f71c71e9a > docker images | grep oh-yeah
Sí. Se fue. Pero no te preocupes. Podemos reconstruirlo:
> docker build -t oh-yeah-alpine . > docker images | grep oh-yeah oh-yeah-alpine latest 1e831ce8afe1 1 minutes ago 30.5 MB
Sí, está de vuelta. ¡Dockerfile para la victoria!
Trabajar con registros de imágenes
Las imágenes son muy similares en algunos aspectos a los repositorios de git. También se crean a partir de un conjunto ordenado de commits. Puedes pensar en dos imágenes que usen las mismas imágenes base como ramas (aunque no hay merge ni rebase en Docker). Un registro de imágenes es el equivalente a un servicio de alojamiento central de git como GitHub. ¿Adivina cuál es el nombre del registro de imágenes oficial de Docker? Así es, Docker Hub.
Haciendo pull de imágenes
Cuando ejecutas una imagen, si no existe, Docker intentará extraerla de uno de sus registros de imágenes configurados. De forma predeterminada, va a Docker Hub, pero puedes controlarlo en tu archivo "~/.docker/config.json". Si usas un registro diferente, puedes seguir sus instrucciones, que generalmente implican iniciar sesión con sus credenciales.
Eliminemos la imagen "hello-world" y hagamos pull de ella nuevamente usando el comando docker pull
.
> dockere images | grep hello-world hello-world latest c54a2cc56cbb 7 months ago 1.85 kB > docker rmi hello-world hello-world
Se ha ido. Hagamos pull ahora.
> docker pull hello-world Using default tag: latest latest: Pulling from library/hello-world 78445dd45222: Pull complete Digest: sha256:c5515758d4c5e1e...07e6f927b07d05f6d12a1ac8d7 Status: Downloaded newer image for hello-world:latest > dockere images | grep hello-world hello-world latest 48b5124b2768 2 weeks ago 1.84 kB
El último hello-world fue reemplazado por una versión más nueva.
Haciendo push de imágenes
Hacer push de imágenes es un poco más complicado. Primero debes crear una cuenta en Docker Hub (u otro registro). A continuación, inicia sesión. Luego, debes etiquetar la imagen que deseas enviar de acuerdo con el nombre de tu cuenta ("g1g1" en mi caso).
> docker login -u g1g1 -p <password> Login Succeeded > docker tag hello-world g1g1/hello-world > docker images | grep hello g1g1/hello-world latest 48b5124b2768 2 weeks ago 1.84 kB hello-world latest 48b5124b2768 2 weeks ago 1.84 kB
Ahora, puedo enviar la imagen etiquetada g1g1/hello-world.
> docker push g1g1/hello-world The push refers to a repository [docker.io/g1g1/hello-world] 98c944e98de8: Mounted from library/hello-world latest: digest: sha256:c5515758d4c5e...f6d12a1ac8d7 size: 524
Conclusión
Las imágenes de Docker son las plantillas de sus contenedores. Están diseñadas para ser eficientes y ofrecer la máxima reutilización mediante el uso de un controlador de almacenamiento del sistema de archivos en capas.
Docker proporciona muchas herramientas para enumerar, inspeccionar, crear y etiquetar imágenes. Puedes extraer y enviar imágenes a registros de imágenes como Docker Hub para administrar y compartir fácilmente tus imágenes.