Docker
Contenedores
Hello world
Ejecutar el primer contenedor tipo Hello world con el comando docker run hello-world
!docker run hello-world
Como no tenemos el contenedor guardado en local, docker lo descarga de docker hub. Si ahora volvemos a ejecutar el contenedor, ya no aparecerá el primer mensaje, en el que indica que se está descargando
!docker run hello-world!docker run hello-world
Unable to find image 'hello-world:latest' locallyHello from Docker!This message shows that your installation appears to be working correctly.To generate this message, Docker took the following steps:1. The Docker client contacted the Docker daemon.2. The Docker daemon pulled the "hello-world" image from the Docker Hub.(amd64)3. The Docker daemon created a new container from that image which runs theexecutable that produces the output you are currently reading.4. The Docker daemon streamed that output to the Docker client, which sent itto your terminal.To try something more ambitious, you can run an Ubuntu container with:$ docker run -it ubuntu bashShare images, automate workflows, and more with a free Docker ID:https://hub.docker.com/For more examples and ideas, visit:https://docs.docker.com/get-started/
Para ver los contenedores que están corriendo ejecutar docker ps
!docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Como vemos no hay ningún contenedor abierto. Pero sin embargo, si ejecutamos docker ps -a
(all
) vemos que si aparecen
!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES1efb51bbbf38 hello-world "/hello" 10 seconds ago Exited (0) 9 seconds ago strange_thompson5f5705e7603e hello-world "/hello" 15 seconds ago Exited (0) 14 seconds ago laughing_jang
Vemos que aparecen dos contenedores llamados hello-world
que son los dos que hemos ejecutado antes. Por tanto cada vez que ejecutamos el comando run
, docker crea un nuevo contenedor, no ejecuta uno que ya exista
Si queremos tener más información de uno de los dos contenedores podemos ejecutar docker inspect <id>
, donde <id>
corresponde a la ID del docker que se ha mostrado en la lista anterior
!docker inspect 1efb51bbbf38
[{"Id": "1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e","Created": "2023-09-04T03:59:17.795499354Z","Path": "/hello","Args": [],"State": {"Status": "exited","Running": false,"Paused": false,"Restarting": false,"OOMKilled": false,"Dead": false,"Pid": 0,"ExitCode": 0,"Error": "","StartedAt": "2023-09-04T03:59:18.406663026Z","FinishedAt": "2023-09-04T03:59:18.406181184Z"},"Image": "sha256:9c7a54a9a43cca047013b82af109fe963fde787f63f9e016fdc3384500c2823d","ResolvConfPath": "/var/lib/docker/containers/1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e/resolv.conf","HostnamePath": "/var/lib/docker/containers/1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e/hostname","HostsPath": "/var/lib/docker/containers/1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e/hosts","LogPath": "/var/lib/docker/containers/1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e/1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e-json.log","Name": "/strange_thompson",...}}}]
Como acordarnos de IDs es complicado para nosotros, docker asigna nombres a los contenedores para facilitarnos la vida. Así en la lista anterior, la última columna corresponde al nombre que ha asignado docker a cada contenedor, de modo que si ahora ejecutamos docker inspect <name>
obtendremos la misma información que con la ID
Vuelvo a ejecutar docker ps -a
para volver a ver la lista
!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES1efb51bbbf38 hello-world "/hello" 2 minutes ago Exited (0) 2 minutes ago strange_thompson5f5705e7603e hello-world "/hello" 2 minutes ago Exited (0) 2 minutes ago laughing_jang
Y ahora ejecuto docker inspect <name>
para ver la información del contenedor
!docker inspect strange_thompson
[{"Id": "1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e","Created": "2023-09-04T03:59:17.795499354Z","Path": "/hello","Args": [],"State": {"Status": "exited","Running": false,"Paused": false,"Restarting": false,"OOMKilled": false,"Dead": false,"Pid": 0,"ExitCode": 0,"Error": "","StartedAt": "2023-09-04T03:59:18.406663026Z","FinishedAt": "2023-09-04T03:59:18.406181184Z"},"Image": "sha256:9c7a54a9a43cca047013b82af109fe963fde787f63f9e016fdc3384500c2823d","ResolvConfPath": "/var/lib/docker/containers/1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e/resolv.conf","HostnamePath": "/var/lib/docker/containers/1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e/hostname","HostsPath": "/var/lib/docker/containers/1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e/hosts","LogPath": "/var/lib/docker/containers/1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e/1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e-json.log","Name": "/strange_thompson",...}}}]
Pero por qué con docker ps
no vemos ningún contenedor y con docker ps -a
sí. Esto es porque docker ps
solo muestra los contenedores que están corriendo, mientras que docker ps -a
muestra todos los contenedores, los que están corriendo y los que están apagados
Podemos crear un contenedor asignándole un nombre nosotros mediante el comando docker run --name <name> hello-world
!docker run --name hello_world hello-world
Hello from Docker!This message shows that your installation appears to be working correctly.To generate this message, Docker took the following steps:1. The Docker client contacted the Docker daemon.2. The Docker daemon pulled the "hello-world" image from the Docker Hub.(amd64)3. The Docker daemon created a new container from that image which runs theexecutable that produces the output you are currently reading.4. The Docker daemon streamed that output to the Docker client, which sent itto your terminal.To try something more ambitious, you can run an Ubuntu container with:$ docker run -it ubuntu bashShare images, automate workflows, and more with a free Docker ID:https://hub.docker.com/For more examples and ideas, visit:https://docs.docker.com/get-started/
Esto será más cómodo para nosotros, ya que podremos controlar nosotros los nombres de los contenedores
Si ahora queremos crear otro contenedor con el mismo nombre no podremos, porque docker no permite que se dupliquen los nombres de los contenedores. De modo que si queremos renombrar el contenedor podemos usar el comando docker rename <old name> <new name>
!docker rename hello_world hello_world2
Tenemos ahora un montón de contenedores iguales. Así que si queremos borrar alguno tenemos que usar el comando docker rm <id>
ó docker rm <name>
!docker rename hello_world hello_world2!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESf432c9c2ca21 hello-world "/hello" 9 seconds ago Exited (0) 8 seconds ago hello_world21efb51bbbf38 hello-world "/hello" 4 minutes ago Exited (0) 4 minutes ago strange_thompson5f5705e7603e hello-world "/hello" 4 minutes ago Exited (0) 4 minutes ago laughing_jang
!docker rm hello_world2
hello_world2
Si volvemos a ver la lista de contenedores, el contenedor hello_world2
ya no estará
!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES1efb51bbbf38 hello-world "/hello" 5 minutes ago Exited (0) 5 minutes ago strange_thompson5f5705e7603e hello-world "/hello" 5 minutes ago Exited (0) 5 minutes ago laughing_jang
Si queremos borrar todos los contenedores, podemos hacerlo uno a uno, pero como es muy pesado, podemos borrar todos mediante el comando docker container prune
. Este comando elimina solo los contenedores que estén parados
!docker container prune
WARNING! This will remove all stopped containers.Are you sure you want to continue? [y/N] y
Docker pregunta si estás seguro, y si le dices que sí, borra todos. Si ahora listo todos los contenedores no aparece ninguno
!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
El modo iteractivo
Vamos a ejecutar un ubuntu mediante el comando docker run ubuntu
!docker run ubuntu
Unable to find image 'ubuntu:latest' locallylatest: Pulling from library/ubuntuDigest: sha256:20fa2d7bb4de7723f542be5923b06c4d704370f0390e4ae9e1c833c8785644c1[1AStatus: Downloaded newer image for ubuntu:latest
Como vemos ahora ha tardado más en descargar. Si listamos los contenedores mediante el comando docker ps
vemos que no aparece el contenedor que acabamos de crear, es decir, no está corriendo
!docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Listamos ahora todos los contenedores
!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESda16b3a85178 ubuntu "bash" 4 seconds ago Exited (0) 3 seconds ago hardcore_kare
Vemos que el estado del contenedor es Exited (0)
Si nos fijamos en comando del contenedor aparece bash
y junto al estado Exited (0)
nos indica que ha arrancado Ubuntu, ha ejecutado su bash, ha terminado la ejecución y ha devuelto un 0. Esto pasa porque al bash de Ubuntu no se le ha dicho nada que hacer. Para solucionar esto, ahora vamos a ejecutar el contenedor mediante el comando docker run -it ubuntu
, con it
lo que le estamos indicando es que lo queremos ejecutar en modo iterativo
!docker run -it ubuntu
root@5b633e9d838f:/#
Ahora vemos que estamos dentro del bash de ubuntu. Si ejecutamos el comando cat /etc/lsb-release
podemos ver la distribución de Ubuntu
!root@5b633e9d838f:/# cat /etc/lsb-release
DISTRIB_ID=UbuntuDISTRIB_RELEASE=22.04DISTRIB_CODENAME=jammyDISTRIB_DESCRIPTION="Ubuntu 22.04.1 LTS"
Si abrimos otra terminal y vemos la lista de contenedores, ahora si aparecerá el contenedor corriendo Ubuntu
!docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES5b633e9d838f ubuntu "bash" 3 minutes ago Up 3 minutes funny_mirzakhani
Vemos el contenedor con Ubuntu y en su estado podemos ver UP
Si vemos ahora la lista de todos los contenedores, veremos que aparecen los dos contenedores con Ubuntu, el primero apagado y el segundo el que está corriendo
!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES5b633e9d838f ubuntu "bash" 3 minutes ago Up 3 minutes funny_mirzakhanida16b3a85178 ubuntu "bash" 3 minutes ago Exited (0) 3 minutes ago hardcore_kare
Si volvemos a la terminal donde teníamos Ubuntu corriendo dentro de un docker, si escribimos exit
saldremos de Ubuntu.
!root@5b633e9d838f:/# exit
exit
Si ejecutamos docker ps
el contenedor ya no aparece
!docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Pero si ejecuto docker ps -a
sí que aparece. Esto quiere decir que el contenedor se apagó
!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES5b633e9d838f ubuntu "bash" 4 minutes ago Exited (0) 27 seconds ago funny_mirzakhanida16b3a85178 ubuntu "bash" 4 minutes ago Exited (0) 4 minutes ago hardcore_kare
Esto ocurre porque al escribir exit
, en realidad lo estamos escribiendo en la consola del bash de Ubuntu, lo que significa que que estamos terminando el proceso bash de Ubuntu.
Ciclo de vida de un contenedor
En docker, cuando el proceso principal de un contenedor se termina, se apaga el contenedor. Dentro de un contenedor pueden ejecutarse varios procesos, pero solo cuando se termina el proceso principal se apaga el contenedor
Por tanto, si queremos correr un contenedor que no se apague cuando finalice un proceso, debemos hacer que su proceso principal no se termine. En este caso, que no finalice bash
Si queremos ejecutar un contenedor con ubuntu, pero que no finalice cuando termine el proceso de bash lo podemos hacer de la siguiente manera
!docker run --name alwaysup -d ubuntu tail -f /dev/null
ce4d60427dcd4b326d15aa832b816c209761d6b4e067a016bb75bf9366c37054
Lo que hacemos es primero darle el nombre alwaysup
, en segundo lugar pasarle la opción -d
(detach
) para que el contenedor se ejecute en segundo plano y por último le decimos el proceso principal que queremos que se ejecute en el contenedor, que en este caso es tail -f /dev/null
que equivale a un comando nop
Esto nos devolverá la ID del contenedor, pero no estaremos dentro de ubuntu como pasaba antes
Si ahora vemos la lista de contenedores que se están ejecutando aparece el contenedor que acabamos de crear
!docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESce4d60427dcd ubuntu "tail -f /dev/null" 18 seconds ago Up 17 seconds alwaysup
Como ya tenemos un contenedor corriendo siempre, podemos conectarnos al el mediante el comando exec
. Le decimos el nombre o la ID del contenedor y le pasamos el proceso que queremos que se ejecuta. Además pasamos la opción -it
para decirle que sea iteractivo
!docker exec -it alwaysup bash
root@ce4d60427dcd:/#
Ahora volvemos a estar dentro de ubuntu. Si ejecutamos el commando ps -aux
podemos ver una lista de los procesos que se están ejecutando dentro de ubuntu.
!ps -aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMANDroot 1 0.0 0.0 2820 1048 ? Ss 13:04 0:00 tail -f /dev/nullroot 7 0.0 0.0 4628 3796 pts/0 Ss 13:04 0:00 bashroot 15 0.0 0.0 7060 1556 pts/0 R+ 13:05 0:00 ps -aux
Vemos solo tres procesos, el ps -aux
, el bash
y el tail -f /dev/null
Este contenedor va a estar siempre encendido mientras el proceso tail -f /dev/null
siga corriendo
Si salimos del contenedor con el comando exit
y ejecutamos el comando docker ps
vemos que el contenedor sigue encendido
!exit
exit
!docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESce4d60427dcd ubuntu "tail -f /dev/null" 2 minutes ago Up 2 minutes alwaysup
Para poder finalizar el proceso y poder apagar el contenedor debemos usar el comando docker stop <name>
!docker stop alwaysup
alwaysup
Si ahora volvemos a listar los contenedores encendidos ya no aparece el contenedor con Ubuntu
!docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Y si listamos todos los contenedores, aparece el contenedor con Ubuntu, y su estado Exited
!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESce4d60427dcd ubuntu "tail -f /dev/null" 14 minutes ago Exited (137) About a minute ago alwaysup5b633e9d838f ubuntu "bash" 19 minutes ago Exited (0) 15 minutes ago funny_mirzakhanida16b3a85178 ubuntu "bash" 20 minutes ago Exited (0) 20 minutes ago hardcore_kare
Tambien podemos pausar un contenedor mediante el comando docker pause <name>
!docker run --name alwaysup -d ubuntu tail -f /dev/null
8282eaf9dc3604fa94df206b2062287409cc92cbcd203f1a018742b5c171c9e4
Ahora lo pausamos
!docker pause alwaysup
alwaysup
Si volvemos a ver todos los contenedores, vemos que el contenedor con Ubuntu está pausado
!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES8282eaf9dc36 ubuntu "tail -f /dev/null" 41 seconds ago Up 41 seconds (Paused) alwaysup5b633e9d838f ubuntu "bash" 19 minutes ago Exited (0) 15 minutes ago funny_mirzakhanida16b3a85178 ubuntu "bash" 20 minutes ago Exited (0) 20 minutes ago hardcore_kare
Contenedores de un solo uso
Si a la hora de ejecutar un contenedor, ponemos la opción --rm
, ese contenedor se va a borrar cuando termine de ejecutarse.
!docker run --rm --name autoremove ubuntu:latest
Si ahora vemos qué contenedores tenemos
!docker run --rm --name autoremove ubuntu:latest!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Vemos que no está el contenedor que acabamos de crear
Exponer contenedores al mundo exterior
Vamos a crear un nuevo contenedor con un servidor
!docker run -d --name proxy nginx
Unable to find image 'nginx:latest' locallylatest: Pulling from library/nginxf1ad4ce1: Pulling fs layerb079d0f8: Pulling fs layer5fbbebc6: Pulling fs layerffdd25f4: Pulling fs layer32c8fba2: Pulling fs layer24b8ba39: Pull complete 393kB/1.393kBB[5ADigest: sha256:2888a97f7c7d498bbcc47ede1ad0f6ced07d72dfd181071dde051863f1f79d7bStatus: Downloaded newer image for nginx:latest1a530e04f14be082811b72ea8b6ea5a95dad3037301ee8a1351a0108ff8d3b30
Esto crea un servidor, vamos a volver a listar los contenedores que están corriendo
!docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES1a530e04f14b nginx "/docker-entrypoint.…" 1 second ago Up Less than a second 80/tcp proxy
Ahora aparece una nueva columna con el puerto, y nos dice que el servidor que acabamos de crear está en el puerto 80
bajo el protocolo tcp
.
Si abrimos un navegador e intentamos conectarnos al servidor mediante http://localhost:80
no conseguimos conectar. Esto es porque cada contenedor tiene su propia interfaz de red. Es decir, el servidor está escuchando en el puerto 80
del contenedor, pero nosotros estamos intentando conectar al puerto 80
del host
Paramos el contenedor para relanzarlo de otra forma
!docker stop proxy
proxy
Si listamos los contenedores no aparece corriendo
!docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Lo borramos para volver a crearlo
!docker rm proxy
proxy
Si listamos todos los contenedores ya no está
!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESce4d60427dcd ubuntu "tail -f /dev/null" 19 minutes ago Exited (137) 5 minutes ago alwaysup5b633e9d838f ubuntu "bash" 24 minutes ago Exited (0) 20 minutes ago funny_mirzakhanida16b3a85178 ubuntu "bash" 24 minutes ago Exited (0) 24 minutes ago hardcore_kare
Para volver a crear el contenedor con el servidor y poderlo ver desde el host, tenemos que usar la opción -p
(publish
), indicando en primer lugar el puerto en el que queremos verlo en el host y a continuación el puerto del contenedor, es decir, -p <ip host>:<ip conteiner>
!docker run -d --name proxy -p 8080:80 nginx
c199235e42f76a30266f6e1af972e0a59811806eb3d3a9afdd873f6fa1785eae
Listamos los contenedores
!docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESc199235e42f7 nginx "/docker-entrypoint.…" 22 seconds ago Up 21 seconds 0.0.0.0:8080->80/tcp, :::8080->80/tcp proxy
Vemos que el puerto del contenedor es 0.0.0.0:8080->80/tcp
. Si ahora vamos a un navegador e introducimos 0.0.0.0:8080
podremos acceder al servidor del contenedor
Al listar los contenedores, en la columna PORTS
indica 0.0.0.0:8080->80/tcp
, lo que nos ayuda a ver la relación de puertos
Para ver los logs del contenedor, mediante el comando docker logs <name>
puedo ver los logs del contenedor
!docker logs proxy
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d//docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh/docker-entrypoint.sh: Configuration complete; ready for start up2022/09/13 13:24:06 [notice] 1#1: using the "epoll" event method2022/09/13 13:24:06 [notice] 1#1: nginx/1.23.12022/09/13 13:24:06 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)2022/09/13 13:24:06 [notice] 1#1: OS: Linux 5.15.0-46-generic2022/09/13 13:24:06 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:10485762022/09/13 13:24:06 [notice] 1#1: start worker processes2022/09/13 13:24:06 [notice] 1#1: start worker process 312022/09/13 13:24:06 [notice] 1#1: start worker process 322022/09/13 13:24:06 [notice] 1#1: start worker process 332022/09/13 13:24:06 [notice] 1#1: start worker process 342022/09/13 13:24:06 [notice] 1#1: start worker process 352022/09/13 13:24:06 [notice] 1#1: start worker process 362022/09/13 13:24:06 [notice] 1#1: start worker process 372022/09/13 13:24:06 [notice] 1#1: start worker process 382022/09/13 13:24:06 [notice] 1#1: start worker process 392022/09/13 13:24:06 [notice] 1#1: start worker process 402022/09/13 13:24:06 [notice] 1#1: start worker process 41...172.17.0.1 - - [13/Sep/2022:13:24:40 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://0.0.0.0:8080/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" "-"172.17.0.1 - - [13/Sep/2022:13:25:00 +0000] " üâV$Zqi'×ü[ïºåÇè÷&3nSëÉìÂØѾ Ç?áúaÎuã/ØRfOHì+\»±¿Òm°9 úúÀ+À/À,À0̨̩ÀÀ / 5 localhost ÿ " 400 157 "-" "-" "-"172.17.0.1 - - [13/Sep/2022:13:25:00 +0000] " ü)bCÙmñëd"ÏÄE#~LÁµk«lî[0 ÐÒ` Æ Rêq{Pòûâ¨IôtH~Ê1-| êêÀ+À/À,À0̨̩ÀÀ / 5 " 400 157 "-" "-" "-"172.17.0.1 - - [13/Sep/2022:13:26:28 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" "-"
Ahora puedo ver todas las peticiones que se le han hecho al servidor. Pero si quiero ver los logs en tiempo real, mediante docker logs -f <name>
lo puedo hacer
!docker logs -f proxy
Ahora puedo ver los logs en tiempo real. Para salir introducir CTRL+C
Como puede llegar un momento en el que haya muchos logs, si solo quieres los últimos logs, mediante la opción --tail <num>
puedo ver los últimos <num>
logs. Si añado la opción -f
estaremos viendo siempre los últimos <num>
logs
!docker logs --tail 10 proxy
2022/09/13 13:24:06 [notice] 1#1: start worker process 412022/09/13 13:24:06 [notice] 1#1: start worker process 42172.17.0.1 - - [13/Sep/2022:13:24:16 +0000] " üE¶EgóÉÊì§y#3ÜQïê$¿# ÷-,s!rê|®ß¡LZª4y³t«ÀÎ_¸çÿ'Ï êêÀ+À/À,À0̨̩ÀÀ / 5 localhost ÿ " 400 157 "-" "-" "-"172.17.0.1 - - [13/Sep/2022:13:24:16 +0000] " ü}©Dr{;z¼zÂxßæl?§àDoK'g»µ %»ýÙ?Û³TöcJ÷åÂÒ¼¢£ë½=R¼ ÊÊÀ+À/À,À0̨̩ÀÀ / 5 localhost ÿ " 400 157 "-" "-" "-"172.17.0.1 - - [13/Sep/2022:13:24:39 +0000] "GET / HTTP/1.1" 200 615 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" "-"2022/09/13 13:24:40 [error] 34#34: *3 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.17.0.1, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "0.0.0.0:8080", referrer: "http://0.0.0.0:8080/"172.17.0.1 - - [13/Sep/2022:13:24:40 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://0.0.0.0:8080/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" "-"172.17.0.1 - - [13/Sep/2022:13:25:00 +0000] " üâV$Zqi'×ü[ïºåÇè÷&3nSëÉìÂØѾ Ç?áúaÎuã/ØRfOHì+\»±¿Òm°9 úúÀ+À/À,À0̨̩ÀÀ / 5 localhost ÿ " 400 157 "-" "-" "-"172.17.0.1 - - [13/Sep/2022:13:25:00 +0000] " ü)bCÙmñëd"ÏÄE#~LÁµk«lî[0 ÐÒ` Æ Rêq{Pòûâ¨IôtH~Ê1-| êêÀ+À/À,À0̨̩ÀÀ / 5 " 400 157 "-" "-" "-"172.17.0.1 - - [13/Sep/2022:13:26:28 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" "-"
Si además añadimos la opción -t
podemos ver la fecha y hora de cada log, de esta manera si hemos tenido un problema, podemos saber en qué momento ha ocurrido
!docker logs --tail -t 10 proxy
2022-09-13T13:24:06.573362728Z 2022/09/13 13:24:06 [notice] 1#1: start worker process 412022-09-13T13:24:06.651127107Z 2022/09/13 13:24:06 [notice] 1#1: start worker process 422022-09-13T13:24:16.651160189Z 172.17.0.1 - - [13/Sep/2022:13:24:16 +0000] " üE¶EgóÉÊì§y#3ÜQïê$¿# ÷-,s!rê|®ß¡LZª4y³t«ÀÎ_¸çÿ'Ï êêÀ+À/À,À0̨̩ÀÀ / 5 localhost ÿ " 400 157 "-" "-" "-"2022-09-13T13:24:16.116817914Z 172.17.0.1 - - [13/Sep/2022:13:24:16 +0000] " ü}©Dr{;z¼zÂxßæl?§àDoK'g»µ %»ýÙ?Û³TöcJ÷åÂÒ¼¢£ë½=R¼ ÊÊÀ+À/À,À0̨̩ÀÀ / 5 localhost ÿ " 400 157 "-" "-" "-"2022-09-13T13:24:39.117398081Z 172.17.0.1 - - [13/Sep/2022:13:24:39 +0000] "GET / HTTP/1.1" 200 615 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" "-"2022-09-13T13:24:39.117412408Z 2022/09/13 13:24:40 [error] 34#34: *3 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.17.0.1, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "0.0.0.0:8080", referrer: "http://0.0.0.0:8080/"2022-09-13T13:24:40.117419389Z 172.17.0.1 - - [13/Sep/2022:13:24:40 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://0.0.0.0:8080/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" "-"2022-09-13T13:25:00.117434249Z 172.17.0.1 - - [13/Sep/2022:13:25:00 +0000] " üâV$Zqi'×ü[ïºåÇè÷&3nSëÉìÂØѾ Ç?áúaÎuã/ØRfOHì+\»±¿Òm°9 úúÀ+À/À,À0̨̩ÀÀ / 5 localhost ÿ " 400 157 "-" "-" "-"2022-09-13T13:25:00.223560881Z 172.17.0.1 - - [13/Sep/2022:13:25:00 +0000] " ü)bCÙmñëd"ÏÄE#~LÁµk«lî[0 ÐÒ` Æ Rêq{Pòûâ¨IôtH~Ê1-| êêÀ+À/À,À0̨̩ÀÀ / 5 " 400 157 "-" "-" "-"2022-09-13T13:26:25.223596738Z 172.17.0.1 - - [13/Sep/2022:13:26:28 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" "-"
Paramos y borramos el contenedor
!docker rm -f proxy
proxy
!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESce4d60427dcd ubuntu "tail -f /dev/null" 26 minutes ago Exited (137) 13 minutes ago alwaysup5b633e9d838f ubuntu "bash" 31 minutes ago Exited (0) 27 minutes ago funny_mirzakhanida16b3a85178 ubuntu "bash" 32 minutes ago Exited (0) 32 minutes ago hardcore_kare
Datos en Docker
Bind mounts
Vamos a ver los contenedores que tenemos parados
!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESce4d60427dcd ubuntu "tail -f /dev/null" 26 minutes ago Exited (137) 13 minutes ago alwaysup5b633e9d838f ubuntu "bash" 31 minutes ago Exited (0) 28 minutes ago funny_mirzakhanida16b3a85178 ubuntu "bash" 32 minutes ago Exited (0) 32 minutes ago hardcore_kare
Vamos a borrar los dos de ubuntu en los que su comando principal es la bash y vamos a dejar el que dejamos una no operación
!docker rm funny_mirzakhani
funny_mirzakhani
!docker rm hardcore_kare
hardcore_kare
!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESce4d60427dcd ubuntu "tail -f /dev/null" 27 minutes ago Exited (137) 14 minutes ago alwaysup
Vamos a volver a ejecutar el contenedor de ubuntu que hemos dejado, esto se hace mediante el comando start
!docker start alwaysup
alwaysup
Nos metemos otra vez dentro de el
!docker exec -it alwaysup bash
root@ce4d60427dcd:/#
En el contenedor, puedo crear una nueva carpeta que se llame dockerfolder
!mkdir dockerfolder
Si listamos los archivos aparecerá la nueva carpeta
!ls
bin boot dev dockerfolder etc home lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var
Si salimos del contenedor
!exit
exit
Y lo borramos
!docker rm -f alwaysup
alwaysup
Si listamos todos los contenedores ya no aparece el último que hemos creado
!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Vamos a volver a hacer todo, pero primero vamos a crear una carpeta en el host en la que compartiremos los datos con el contenedor
!mkdir dockerHostFolder
Vemos que dentro de la carpeta no hay nada
!mkdir dockerHostFolder!ls dockerHostFolder
Ahora obtenemos nuestra ruta absoluta
!mkdir dockerHostFolder!ls dockerHostFolder!pwd
/home/wallabot/Documentos/web/portafolio/posts
Volvemos a crear el contenedor pero añadiendo la opción -v
(bind mount
). A continuación se añade la ruta absoluta de la carpeta del host y la ruta absoluta de la carpeta en el contenedor, -v <host path>:<container path>
!docker run -d --name alwaysup -v ~/Documentos/web/portafolio/posts/dockerHostFolder:/dockerContainerFolder ubuntu tail -f /dev/null
4ede4512c293bdcc155e9c8e874dfb4a28e5163f4d5c7ddda24ad2863f28921b
Entramos al contenedor, listamos los archivos y ya aparece la carpeta que habíamos creado
!docker exec -it alwaysup bash
root@4ede4512c293:/#
root@4ede4512c293:/# ls
bin dev etc lib lib64 media opt root sbin sys usrboot dockerContainerFolder home lib32 libx32 mnt proc run srv tmp var
Vamos al directorio del contenedor que hemos compartido, creamos un archivo y salimos del contenedor
root@4ede4512c293:/# cd dockerContainerFolder
root@4ede4512c293:/dockerContainerFolder# touch bindFile.txt
root@4ede4512c293:/dockerContainerFolder# exit
exit
Vemos qué hay dentro de la carpeta compartida
!ls dockerHostFolder
bindFile.txt
Pero es más, si borramos el contenedor, el archivo sigue ahí
!docker rm -f alwaysup
alwaysup
!ls dockerHostFolder
bindFile.txt
Si vuelvo a crear un contenedor compartiendo las carpetas, todos los archivos estarán en el contenedor
!docker run -d --name alwaysup -v ~/Documentos/web/portafolio/posts/dockerHostFolder:/dockerContainerFolder ubuntu tail -f /dev/null
6c021d37ea29d8b23fe5cd4968baa446085ae1756682f65340288b4c851c362d
!docker exec -it alwaysup bash
root@6c021d37ea29:/#
!root@6c021d37ea29:/# ls dockerContainerFolder/
bindFile.txt:/#
Eliminamos el contenedor
!docker rm -f alwaysup
alwaysup
!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Volúmenes
Los volúmenes se crearon como una evolución de los bind mounts
para dar más seguridad. Podemos listar todos los volúmenes de docker mediante docker volume ls
!docker volume ls
DRIVER VOLUME NAME
Vamos a crear un nuevo volumen para el contenedor de ubuntu, para ello usamos el comando docker volume create <volume name>
!docker volume create ubuntuVolume
ubuntuVolume
Si volvemos a listar los volúmenes aparecerá el que acabamos de crear
!docker volume ls
DRIVER VOLUME NAMElocal ubuntuVolume
Sin embargo no aparece como una carpeta en el sistema de archivos del host. Con ls -d */
listamos todas las carpetas
!ls -d */
dockerHostFolder/ __pycache__/
Vamos a volver a crear un contenedor, pero ahora lo creamos con el volumen que acabamos de crear con la opción --mount
, indicando el volumen fuente mediante src=<volume name>
(si el volumen no existiese, docker lo crearía), a continuación el destino separado por una ,
, dst=<container path>
, es decir --mount src=<volume name>,dst=<container path>
!docker run -d --name alwaysup --mount src=ubuntuVolume,dst=/dockerVolumeFolder ubuntu tail -f /dev/null
42cdcddf4e46dc298a87b0570115e0b2fc900cb4c6db5eea22a61409b8cb271d
Una vez creado podemos ver los volúmenes del contenedor mediante el comando inspect
y filtrando por '{{.Mounts}}'
$ docker inspect --format '{{.Mounts}}' alwaysup
[
{
volume ubuntuVolume /var/lib/docker/volumes/ubuntuVolume/_data /dockerVolumeFolder local z true
}
]
Vemos que el volumen se llama ubuntuVolume
y ademas podemos ver la ruta dónde está guardado, en este caso en /var/lib/docker/volumes/ubuntuVolume/_data
. Hacemos lo mismo que antes, nos metemos en el contenedor, creamos un archivo en la ruta del volumen, salimos y vemos en el host si se ha creado
$ docker exec -it alwaysup bash
root@42cdcddf4e46:/# touch dockerVolumeFolder/volumeFile.txt
root@42cdcddf4e46:/# exit
$ sudo ls /var/lib/docker/volumes/ubuntuVolume/_data
volumeFile.txt
Está el archivo creado
Insertar y extraer archivos de un contenedor
Primero vamos a crear un archivo que queremos copiar dentro de un contenedor
!touch dockerHostFolder/text.txt
Entramos en el contenedor
$ docker exec -it alwaysup bash
root@42cdcddf4e46:/#
Creamos una nueva carpeta donde vamos a copiar el archivo y salimos
root@42cdcddf4e46:/# mkdir folderToCopy
root@42cdcddf4e46:/# ls
bin boot dev dockerVolumeFolder etc folderToCopy home lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var
root@42cdcddf4e46:/# exit
exit
Copiamos dentro del contenedor el archivo mediante el comando cp
, indicando el archivo que quiero copiar, el contenedor donde lo queremos copiar y la ruta dentro del contenedor, docker cp <file> <container>:<container path>
!touch dockerHostFolder/text.txt!docker cp dockerHostFolder/text.txt alwaysup:/folderToCopy
Volvemos a entrar al contenedor y comprobamos que está el archivo
$ docker exec -it alwaysup bash
root@42cdcddf4e46:/# ls folderToCopy/
text.txt
Salimos del contenedor
/# exit
exit
Ahora vamos a extraer el archivo del contenedor y lo vamos a guardar en el host con otro nombre, para ello usamos el comando otra vez el comando cp
, pero indicando ahora el contenedor, la ruta del archivo en el contenedor y la ruta y nombre del que queremos que tenga el archivo en el host, docker cp <container>:<docker file path> <host file path>
!touch dockerHostFolder/text.txt!docker cp dockerHostFolder/text.txt alwaysup:/folderToCopy!docker cp alwaysup:/folderToCopy/text.txt dockerHostFolder/fileExtract.txt
Vemos que está en el host
!touch dockerHostFolder/text.txt!docker cp dockerHostFolder/text.txt alwaysup:/folderToCopy!docker cp alwaysup:/folderToCopy/text.txt dockerHostFolder/fileExtract.txt!ls dockerHostFolder
bindFile.txt fileExtract.txt text.txt
Aunque el contenedor esté parado también se pueden copiar archivos
Por último borramos el contenedor
!docker rm -f alwaysup
alwaysup
Imágenes
Conceptos fundamentales
Las imágenes son los archivos ("plantillas") con toda la configuración para crear un contenedor. Cada vez que creamos un contenedor se crea a partir de una imagen. Cuando creábamos contenedores nuevos, la primera vez salía un mensaje diciendo que no teníamos la imagen y que iba a descargarla. En docker hub existen multitud de imágenes con todo tipo de máquinas, pero para un entorno de desarrollo muy específico podemos crear nuestra propia plantilla para pasársela a alguien y trabaje en un contenedor con la misma configuración que el nuestro
Podemos ver todas las imágenes que tenemos guardadas en nuestro ordenador mediante el comand docker image ls
!docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZEnginx latest 2d389e545974 8 hours ago 142MBubuntu latest 2dc39ba059dc 11 days ago 77.8MBhello-world latest feb5d9fea6a5 11 months ago 13.3kB
Podemos ver los tamaños, y podemos ver como la de nginx
ocupa mucho y por eso tardó más en descargarse que el resto
Otra columna que podemos ver es la de TAG
, esto indica la versión de la imagen. En todas pone latest
, esto quiere decir que es la última. Es decir, en el momento de descargárnosla nos hemos descargado la última versión que hay en docker hub. Esto en un entorno de desarrollo no es óptimo, porque nosotros podemos descargarnos una imagen de ubuntu, y si no especificamos versión se baja la última, por ejemplo la 20.04. Pero después de un tiempo alguien puede querer desarrollar contigo y descargarse esa imagen, pero al no especificar la versión se descargará otra vez la última, que en su caso puede ser la 22.04. Esto puede dar lugar a problemas y a que cosas que a una de las personas le funcione y a la otra no
Podemos ver todas las imágenes que hay en docker hub llendo a https://hub.docker.com/
. Ahí podrás buscar la imagen que mejor se adapte al proyecto que quieras hacer. Si navegamos a la image de Ubuntu, por ejemplo, podemos ver las versiones (tags
) de las imágenes.
Vamos a descargarnos, pero no ejecutar una imagen. Para ello usamos el comando docker pull <hub> <image name>:<tag>
. Si no indicamos el hub, lo descargará de docker hub por defecto, pero podemos indicar otro, por ejemplo uno privado de nuestra organización. También, si no indicamos el tag, por defecto bajará la última versión
!docker pull ubuntu:20.04
20.04: Pulling from library/ubuntuDigest: sha256:35ab2bf57814e9ff49e365efd5a5935b6915eede5c7f8581e9e1b85e0eecbe16[1AStatus: Downloaded newer image for ubuntu:20.04docker.io/library/ubuntu:20.04
Si volvemos a listar las imágenes, vemos que ahora tenemos dos imágenes de ubuntu, una con el tag 20.04
y otra con el tag latest
!docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZEnginx latest 2d389e545974 8 hours ago 142MBubuntu latest 2dc39ba059dc 11 days ago 77.8MBubuntu 20.04 a0ce5a295b63 11 days ago 72.8MBhello-world latest feb5d9fea6a5 11 months ago 13.3kB
Crear imágenes mediante Dockerfile
Creamos un directorio en el host llamado dockerImages
para trabajar en el
!mkdir dockerImages
Creamos un archivo Dockerfile
con el que crearemos una imagen
!mkdir dockerImages!touch dockerImages/Dockerfile
Abrirmos el archivo creado con nuestro editor preferido y escribimos los siguiente:
FROM ubuntu:latest
Esto le dice a docker que cree la imagen a raiz de la imagen latest
de ubuntu
A continuación escribimos un comando que se va a ejecutar en tiempo de compilación
RUN touch /test.txt
Esto quiere decir que cuando se compile el Dockefile
se ejecutará ese comando, pero no cuando se corra el contenedor de la imagen
Al final el Dockerfile
queda así:
FROM ubuntu:latest
RUN touch /test.txt
Compilamos el Dockerfile
mediante el comando build
, con la opción -t
podemos darle un tag
. Por último hay que indicarle la ruta del contexto de build
, más adelante explicaremos esto
!mkdir dockerImages!touch dockerImages/Dockerfile!docker build -t ubuntu:test ./dockerImages
Sending build context to Docker daemon 2.048kBStep 1/2 : FROM ubuntu:latest---> 2dc39ba059dcStep 2/2 : RUN touch /test.txt---> Using cache---> a78cf3ea16d8Successfully built a78cf3ea16d8Successfully tagged ubuntu:testUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
Como vemos se compila en 2 pasos, cada uno tiene una id
, cada uno de esos id
s son capas de la imagen, esto también lo veremos más adelante
Volvemos a ver las imágenes que tenemos guardadas en nuestro ordenador y aparece la que acabamos de crear
!docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZEubuntu test a78cf3ea16d8 8 minutes ago 77.8MBnginx latest 2d389e545974 8 hours ago 142MBubuntu latest 2dc39ba059dc 11 days ago 77.8MBubuntu 20.04 a0ce5a295b63 11 days ago 72.8MBhello-world latest feb5d9fea6a5 11 months ago 13.3kB
Corremos el contenedor a partir de la imagen que acabamos de crear
$ docker run -it ubuntu:test
root@b57b9d4eedeb:/#
Entramos en el bash del contenedor. Como dijimos, el comando RUN se ejecutaba en tiempo de compilación de la imagen, por lo que el archivo que hemos pedido que se creara debería estar en nuestro contenedor
root@b57b9d4eedeb:/# ls
bin boot dev etc home lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys test.txt tmp usr var
Es importante entender que ese archivo se creó cuando se construyó la imagen, es decir, la imagen del contenedor ya tiene ese archivo. No se crea cuando se lanza el contenedor
Salimos del contenedor
root@b57b9d4eedeb:/# exit
exit
Como ya tenemos una imagen la podríamos subir al hub de docker, pero vamos a volver a listar las imágenes antes de eso
!docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZEubuntu test a78cf3ea16d8 20 minutes ago 77.8MBnginx latest 2d389e545974 8 hours ago 142MBubuntu latest 2dc39ba059dc 11 days ago 77.8MBubuntu 20.04 a0ce5a295b63 11 days ago 72.8MBhello-world latest feb5d9fea6a5 11 months ago 13.3kB
Si vemos nos está diciendo que la imagen que acabamos de crear pertenece al repositorio de ubuntu, pero nosotros no tenemos acceso al repositorio de ubuntu, por lo que en docker hub nos tenemos que hacer una cuenta para poder subir la imagen a nuestro repositorio. En mi caso, mi repositorio se llama maximofn
, por lo que cambio el repositorio de la imagen mediante el comando tag
, indicándole la imagen a la que queremos cambiar de repositorio y el nuevo repositorio. En el nuevo repositorio se suele indicar el nombre del repositorio seguido del tipo de imagen y el tag, en mi caso maximofn/ubuntu:test
!docker tag ubuntu:test maximofn/ubuntu:test
Si ahora volvemos a listar las imágenes
!docker tag ubuntu:test maximofn/ubuntu:test!docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZEubuntu test a78cf3ea16d8 24 minutes ago 77.8MBmaximofn/ubuntu test a78cf3ea16d8 24 minutes ago 77.8MBnginx latest 2d389e545974 8 hours ago 142MBubuntu latest 2dc39ba059dc 11 days ago 77.8MBubuntu 20.04 a0ce5a295b63 11 days ago 72.8MBhello-world latest feb5d9fea6a5 11 months ago 13.3kB
Ahora debemos loggearnos dentro de docker hub para poder subir la imagen, para ello usamos el comando login
$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you do not have a Docker ID, head over to https://hub.docker.com to create one.
Username: maximofn
Password:
Login Succeeded
Ahora podemos subir la imagen mediante el comando push
!docker push maximofn/ubuntu:test
The push refers to repository [docker.io/maximofn/ubuntu]06994357: Preparing06994357: Pushed from library/ubuntu test: digest: sha256:318d83fc3c35ff930d695b0dc1c5ad1b0ea54e1ec6e3478b8ca85c05fd793c4e size: 735
Ha subido solo la primera capa, la segunda, como la use a raiz de la imagen de ubuntu, lo que hace es colocar un puntero a esa imagen para no tener capas subidas más de una vez
Hay que tener en cuenta que este repositorio es público, por lo que no debes subir imágenes con datos sensibles. Además, si una imagen no tiene uso en 6 meses será borrada
El sistema de capas
Mediante el comando history
podemos ver las capas de una imagen. Si vemos las capas de la imagen que acabamos de crear usamos docker history ubuntu:test
!docker history ubuntu:test
IMAGE CREATED CREATED BY SIZE COMMENTa78cf3ea16d8 3 minutes ago /bin/sh -c touch /test.txt 0B2dc39ba059dc 12 days ago /bin/sh -c #(nop) CMD ["bash"] 0B<missing> 12 days ago /bin/sh -c #(nop) ADD file:a7268f82a86219801… 77.8MB
Vemos que la primera capa tiene el comando que hemos introducido en el Dockerfile
, además dice que ha sido creada hace 3 minutos. Sin embargo, el resto de capas fueron creadas hace 12 días, y son las capas de la imagen de ubuntu de la que nos hemos basado
Al Dockerfile
que hemos creado antes le añadimos la línea
RUN rm /test.txt
Al final el Dockerfile
queda así:
FROM ubuntu:latest
RUN touch /test.txt
RUN rm /test.txt
Si volvemos a compilar, vemos qué pasa
!docker build -t ubuntu:test ./dockerImages
Sending build context to Docker daemon 2.048kBStep 1/3 : FROM ubuntu:latest---> 2dc39ba059dcStep 2/3 : RUN touch /test.txt---> Using cache---> a78cf3ea16d8Step 3/3 : RUN rm /test.txt---> Running in c2e6887f2025Removing intermediate container c2e6887f2025---> 313243a9b573Successfully built 313243a9b573Successfully tagged ubuntu:testUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
Como vemos hay una capa más con la nueva línea que hemos agregado. Si volvemos a ver las capas de la imagen con history
!docker history ubuntu:test
IMAGE CREATED CREATED BY SIZE COMMENT313243a9b573 About a minute ago /bin/sh -c rm /test.txt 0Ba78cf3ea16d8 3 minutes ago /bin/sh -c touch /test.txt 0B2dc39ba059dc 12 days ago /bin/sh -c #(nop) CMD ["bash"] 0B<missing> 12 days ago /bin/sh -c #(nop) ADD file:a7268f82a86219801… 77.8MB
Vemos que las primeras capas son iguales que antes y ha añadido una nueva capa con el nuevo comando
Búsqueda en docker hub
No hace flata meterse en la página de docker hub para buscar imágenes, se puede hacer desde la terminal. Para ello usamos el comando docker search <image name>
!docker search ubuntu
NAME DESCRIPTION STARS OFFICIAL AUTOMATEDubuntu Ubuntu is a Debian-based Linux operating sys… 16425 [OK]websphere-liberty WebSphere Liberty multi-architecture images … 297 [OK]open-liberty Open Liberty multi-architecture images based… 62 [OK]neurodebian NeuroDebian provides neuroscience research s… 104 [OK]ubuntu-debootstrap DEPRECATED; use "ubuntu" instead 52 [OK]ubuntu-upstart DEPRECATED, as is Upstart (find other proces… 115 [OK]ubuntu/nginx Nginx, a high-performance reverse proxy & we… 98ubuntu/squid Squid is a caching proxy for the Web. Long-t… 66ubuntu/cortex Cortex provides storage for Prometheus. Long… 4ubuntu/apache2 Apache, a secure & extensible open-source HT… 60ubuntu/kafka Apache Kafka, a distributed event streaming … 35ubuntu/mysql MySQL open source fast, stable, multi-thread… 53ubuntu/bind9 BIND 9 is a very flexible, full-featured DNS… 62ubuntu/prometheus Prometheus is a systems and service monitori… 51ubuntu/zookeeper ZooKeeper maintains configuration informatio… 12ubuntu/postgres PostgreSQL is an open source object-relation… 31ubuntu/redis Redis, an open source key-value store. Long-… 19ubuntu/grafana Grafana, a feature rich metrics dashboard & … 9ubuntu/memcached Memcached, in-memory keyvalue store for smal… 5ubuntu/dotnet-aspnet Chiselled Ubuntu runtime image for ASP.NET a… 11ubuntu/dotnet-deps Chiselled Ubuntu for self-contained .NET & A… 11ubuntu/prometheus-alertmanager Alertmanager handles client alerts from Prom… 9ubuntu/dotnet-runtime Chiselled Ubuntu runtime image for .NET apps… 10ubuntu/cassandra Cassandra, an open source NoSQL distributed … 2ubuntu/telegraf Telegraf collects, processes, aggregates & w… 4
Uso de docker para crear aplicaciones
Exposición de puertos
Antes vimos como podíamos vincular un puerto de un contenedor a un puerto del ordenador (-p 8080:80
). Pero para que eso sea posible, a la hora de crear la imagen hay que exponer el puerto, esto se hace añadiendo al Dockerfile la linea EXPOSE <port>
, en el caso de antes
EXPOSE 80
O usar imágenes como base que ya tengan puertos expuestos
Reuso del caché de capas al compilar
Cuando compilamos una imagen, si alguna de las capas que hemos definido ya han sido compiladas antes, docker lo detecta y las usa, no las vuelve a compilar. Si volvemos a compilar la imagen que hemos definido en el Dockerfile
ahora tardará muy poco, porque todas las capas ya están compiladas y docker no las vuelve a compilar
!docker build -t ubuntu:test ./dockerImages
Sending build context to Docker daemon 2.048kBStep 1/3 : FROM ubuntu:latest---> 2dc39ba059dcStep 2/3 : RUN touch /test.txt---> Using cache---> a78cf3ea16d8Step 3/3 : RUN rm /test.txt---> Using cache---> 313243a9b573Successfully built 313243a9b573Successfully tagged ubuntu:testUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
En la segunda y tercera capa aparece el texto Using cache
Como esto es un cuaderno Jupyter a la hora de ejecutar las celdas te da la información del tiempo que tardan en ejecutarse, la vez anterior que compilé la imagen tardó 1,4 segundos, mientras que ahora ha tardado 0,5 segundos
Pero si ahora cambio el Dockerfile, y en la primera línea, donde decía que nos basábamos en la última versión de ubuntu y cambiamos a la versión 20.04
FROM ubuntu:20.04
Al final el Dockerfile
queda así:
FROM ubuntu:20.04
RUN touch /test.txt
RUN rm /test.txt
Si volvemos a compilar tardará mucho más
!docker build -t ubuntu:test ./dockerImages
Sending build context to Docker daemon 2.048kBStep 1/3 : FROM ubuntu:20.04---> a0ce5a295b63Step 2/3 : RUN touch /test.txt---> Running in a40fe8df2c0dRemoving intermediate container a40fe8df2c0d---> 0bb9b452c11fStep 3/3 : RUN rm /test.txt---> Running in 2e14919f3685Removing intermediate container 2e14919f3685---> fdc248fa833bSuccessfully built fdc248fa833bSuccessfully tagged ubuntu:testUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
Ha tardado 1,9 segundos y ya no aparece el texto Using cache
Al cambiar la primera capa, docker vuelve a compilar todas las capas. Esto puede ser un problema porque a la hora de desarrollar código se puede dar el siguiente caso
- Desarrollamos el código en nuestro ordenador
- Al construir la imagen copiamos todo el código de nuestro ordenador al contenedor
- Luego le pedimos a la imagen que instale las librerías necesarias
Esto puede hacer que al cambiar cualquier parte del código, al tener que volver a compilar la imagen, la capa en la que se instalan las librerías se tenga que volver a compilar, ya que ha cambiado una capa anterior
Para solucionar esto la idea sería que a la hora de crear la imagen, primero pidamos que se instalen las librerías y luego que se copie el código de nuestro ordenador al contenedor. Así cada vez que cambiemos el código y volvamos a compilar la imagen, solo se recompilará la capa en la que se copia el código, por lo que la compilación será más rápida
Podrás pensar, que es mejor compartir una carpeta entre el host y el contenedor (bind mount
) donde tendremos el código y así no hace falta volver a compilar la imagen cada vez que cambiemo el código. Y la respuesta es que es verdad, solo he puesto este ejemplo porque es muy fácil de entender, pero es para escenificar que a la hora de crear imágenes hay que pensar bien de manera que si hace falta volver a compilarla, recompile el mínimo número de capas
Escribir correctamente un Dockerfile
Como hemos visto Docker no vuelve a compilar capas de un Dockerfile si ya las ha compilado antes, por lo que las carga de cache. Vamos a ver cómo tiene que ser la forma correcta de escribir un Dockerfile para aprovecharnos de esto
Vamos a partir de este Dockerfile para ir comentando posibles correcciones
FROM ubuntu
COPY ./sourceCode /sourceCode
RUN apt-get update
RUN apt-get install -y python3 ssh
CMD ["python3", "/sourceCode/sourceApp/app.py"]
Como se puede ver se parte de una imagen de ubuntu, se copia la carpeta con el código, se actualizan los repositorios, se instala python, se instala atmbién ssh y se corre la aplicación
Copiar el código antes de la ejecución
Como hemos dicho antes, si primero copiamos el código y luego instalamos python, cada vez que hagamos un cambio en el código y compilemos la imagen la compilará entera, pero si copiamos el código después de instalar python, cada vez que cambiemos el código y compilemos la imagen, solo compilará desde la copia del código y no volverá a instalar python, por lo que el Dockerfile debería pasar a ser así
FROM ubuntu
RUN apt-get update
RUN apt-get install -y python3 ssh
COPY ./sourceCode /sourceCode
CMD ["python3", "/sourceCode/sourceApp/app.py"]
Copiar solo el código nesario
Estamos copiando la carpeta con todo el código, pero a lo mejor dentro tenemos código que no necesitamos, por lo que hay que copiar solo el código que deverdad necesitemos para aplicación, de esta manera la imagen ocupará menos memoria. De modo que el Dockerfile quedaría así
FROM ubuntu
RUN apt-get update
RUN apt-get install -y python3 ssh
COPY ./sourceCode/sourceApp /sourceCode/sourceApp
CMD ["python3", "/sourceCode/sourceApp/app.py"]
Actualizar repositorios e instalar python en la misma línea
Estamos actualizando los repositorios en una líea y en otra instalando python3. Pero puede darse el caso que en la linea en la que instalamos python3 además añadamos pip3, sin embargo, como la línea anterior, en la que se actualizan los repositorios, como no cambia, no se volverá a compilar, al estar cacheada. Por lo que puede pasar que en los repositorios que habíamos actualizado no estén los necesarios para pip3. Por lo que hay que poner las dos acciones an una sola línea
FROM ubuntu
RUN apt-get update && apt-get install -y python3 ssh
COPY ./sourceCode/sourceApp /sourceCode/sourceApp
CMD ["python3", "/sourceCode/sourceApp/app.py"]
No instalar ssh
Habíamos instalado ssh en la imagen para poder debuggear en caso de necesitarlo, pero eso hace que la imagen ocupe más memoria. En caso de necesitar debuggear, deberíamos entrar en el contenedor, instalar ssh y a continuación debuggear. Por lo que quitamos las instalación de ssh
FROM ubuntu
RUN apt-get update && apt-get install -y python3
COPY ./sourceCode/sourceApp /sourceCode/sourceApp
CMD ["python3", "/sourceCode/sourceApp/app.py"]
Usar --no-install-recommends
Cuando instalamos algo en Ubuntu te instala paquetes recomendados, pero que no necesitamos, por lo que la imagen ocupa más espacio. Así que para evitarlo añadimos a la instalación --no-install-recommends
FROM ubuntu
RUN apt-get update && apt-get install -y python3 --no-install-recommends
COPY ./sourceCode/sourceApp /sourceCode/sourceApp
CMD ["python3", "/sourceCode/sourceApp/app.py"]
Borrar lista de repositorios actualizados
Hemos actualizado la lista de repositorios y hemos instalado python, pero una vez hecho ya no necesitamos la lista de repositorios actualizados, porque lo único que harán será que la imagen ocupe más, de modo que los eliminamos después de instaar python y en su misma línea
FROM ubuntu
RUN apt-get update && apt-get install -y python3 --no-install-recommends && rm -rf /var/lib/apt/lists/*
COPY ./sourceCode/sourceApp /sourceCode/sourceApp
CMD ["python3", "/sourceCode/sourceApp/app.py"]
Usar una imagen de Python
Todo lo que hemos hecho de actualizar la lista de paquetes e instalar python no es necesario, ya que ya existen imágenes de python sobre Ubuntu, que seguramente también han seguido buenas prácticas, que incluso lo hayan hecho mejor que nosotros y que ha sido escaneada en busca de vulnerabilidades por Docker Hub. Por lo que quitamos todo eso y partimos de una imagen de Python
FROM python
COPY ./sourceCode/sourceApp /sourceCode/sourceApp
CMD ["python3", "/sourceCode/sourceApp/app.py"]
Especificar la imagen de Python
Al no especificar la imagen de python se está bajando la última, pero en función de cuando construyas el contenedor se puede bajar una u otra, por lo que hay que añadir el tag con la versión de Python que se quiere
FROM python:3.9.18
COPY ./sourceCode/sourceApp /sourceCode/sourceApp
CMD ["python3", "/sourceCode/sourceApp/app.py"]
Elegir un tag pequeño
Hemos elegido el tag 3.9.18
, pero esa versión de python tiene un montón de librerías que posiblemente no las necesitemos, por lo que podemos usar las versiones 3.9.18-slim
que tiene muchas menos librerías instaladas, o la verisón 3.9.18-alphine
que es una versión de python sobre alpine y no sobre Ubuntu. Alpine es una distribución de Linux muy ligera que tiene muy pocos paquetes instalados y que se suele usar mucho en contenedores Docker para que ocupen muy poco espacio
La imagen de python 3.9.18
ocupa 997 MB, la 3.9.18-slim
ocupa 126 MB y la 3.9.18-alpine
ocupa 47.8 MB
FROM python:3.9.18-alpine
COPY ./sourceCode/sourceApp /sourceCode/sourceApp
CMD ["python3", "/sourceCode/sourceApp/app.py"]
Indicar el workspace
En vez de indicar la ruta de la imagen /sourceCode/sourceApp
establecemos que dicha ruta sea el workspace de la imagen. Así cuando copiemos el código o ejecutemos la aplicación no hace falta indicar la ruta
FROM python:3.9.18-alpine
WORKDIR /sourceCode/sourceApp
COPY ./sourceCode/sourceApp .
CMD ["python3", "app.py"]
Indicar el workspace
En vez de indicar la ruta de la imagen /sourceCode/sourceApp
establecemos que dicha ruta sea el workspace de la imagen. Así cuando copiemos el código o ejecutemos la aplicación no hace falta indicar la ruta
FROM python:3.9.18-alpine
WORKDIR /sourceCode/sourceApp
COPY ./sourceCode/sourceApp .
CMD ["python3", "app.py"]
Código compartido en una carpeta bind mount
Habíamos creado una carpeta llamada dockerHostFolder
en la que habíamos compartido archivos entre el host y un contenedor. Dentro además debería haber tres archivos
!ls dockerHostFolder
bindFile.txt fileExtract.txt text.txt
Vamos a aprovechar el archivo text.txt
para ver eso. Vamos a ver qué hay dentro de text.txt
!cat dockerHostFolder/text.txt
No hay salida, el archivo está vacío. Vamos a crear otra vez un contenedor de ubuntu compartiendo la carpeta dockerHostFolder
!cat dockerHostFolder/text.txt!docker run --name alwaysup -d -v ~/Documentos/web/portafolio/posts/dockerHostFolder:/dockerContainerFolder ubuntu tail -f /dev/null
24adbded61f507cdf7f192eb5e246e43ee3ffafc9944b7c57918eb2d547dff19
Vemos que el contenedor está corriendo
!docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES24adbded61f5 ubuntu "tail -f /dev/null" 16 seconds ago Up 15 seconds alwaysup
Entramos en el contenedor, vemos que está text.txt
y que está vacío
$ docker exec -it alwaysup bash
root@24adbded61f5:/# ls dockerContainerFolder/
bindFile.txt fileExtract.txt text.txt
root@24adbded61f5:/# cat dockerContainerFolder/text.txt
root@24adbded61f5:/#
Ahora abrimos en el host el archivo text.txt
con el editor de textos que queramos, escribimos Hola mundo
y guardamos. Si ahora vemos qué hay dentro del archivo en el contenedor veremos el mismo texto
root@24adbded61f5:/# cat dockerContainerFolder/text.txt
Hola mundo
Ahora editamos el archivo en el contenedor y salimos del contenedor
root@24adbded61f5:/# echo hola contenedor > dockerContainerFolder/text.txt
root@24adbded61f5:/# cat dockerContainerFolder/text.txt
hola contenedor
root@24adbded61f5:/# exit
exit
Si miramos el archivo en el host veremos el texto que escribimos en el contenedor
!cat dockerHostFolder/text.txt
hola contenedor
Borramos el contenedor
!docker rm -f alwaysup
alwaysup
Conectar contenedores por red
En caso de que queramos tener varios contenedores corriendo y queremos que se comuniquen, podemos hacer que se comuniquen por red. Docker nos da la posibilidad de hacer eso mediante sus redes virtuales
Vamos a ver qué redes tiene docker mediante el comando docker network ls
!docker network ls
NETWORK ID NAME DRIVER SCOPEde6e8b7b737e bridge bridge localda1f5f6fccc0 host host locald3b0d93993c0 none null local
Vemos que por defecto docker tiene tres redes
- bridge: Está por retrocompatibilidad con versiones anteriores pero no deberíamos usarla ya
- host: Es la red del host
- none: Esta es la que debemos usar si queremos que un contenedor no tenga acceso a internet
Podemos crear redes nuevas que otros contenedores se puedan conectar a ella, para ello usamos el comando docker network create <name>
, para que otros contenedores se puedan conectar además debemos añadir la opción --attachable
!docker network create --attachable myNetwork
2f6f3ddbfa8642e9f6819aa0965c16339e9e910be7bcf56ebb718fcac324cc27
Podemos inspeccionarla mediante el comando docker network inspect <name>
!docker network inspect myNetwork
[{"Name": "myNetwork","Id": "2f6f3ddbfa8642e9f6819aa0965c16339e9e910be7bcf56ebb718fcac324cc27","Created": "2022-09-14T15:20:08.539830161+02:00","Scope": "local","Driver": "bridge","EnableIPv6": false,"IPAM": {"Driver": "default","Options": {},"Config": [{"Subnet": "172.18.0.0/16","Gateway": "172.18.0.1"}]},"Internal": false,"Attachable": true,"Ingress": false,"ConfigFrom": {"Network": ""},"ConfigOnly": false,"Containers": {},"Options": {},"Labels": {}}]
Ahora tenemos que crear dos contenedores para que se puedan comunicar.
Vamos a crear un nuevo contenedor, que llamaremos container1
, con una carpeta compartida y que en su interior se va a llamar folder1
!docker run --name container1 -d -v ~/Documentos/web/portafolio/posts/dockerHostFolder:/folder1 ubuntu tail -f /dev/null
a5fca8ba1e4ff0a67002f8f1b8cc3cd43185373c2a7e295546f774059ad8dd1a
Ahora creamos otro contenedor, llamado container2
, con otra carpeta compartida, pero que se llame folder2
!docker run --name container2 -d -v ~/Documentos/web/portafolio/posts/dockerHostFolder:/folder2 ubuntu tail -f /dev/null
6c8dc18315488ef686f7548516c19b3d716728dd8a173cdb889ec0dd082232f9
Vemos los contenedores corriendo y vemos que están los dos
!docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES6c8dc1831548 ubuntu "tail -f /dev/null" 3 seconds ago Up 2 seconds container2a5fca8ba1e4f ubuntu "tail -f /dev/null" 4 seconds ago Up 3 seconds container1
Ahora tenemos que conectar los contenedores a la red, para ello usamos el comando docker network connect <network name> <container name>
!docker network connect myNetwork container1
!docker network connect myNetwork container1!docker network connect myNetwork container2
Para comprobar que se han conectado bien podemos inspeccionar la red, pero filtrando por los contenedores conectados
$ docker network inspect --format '{{.Containers}}' myNetwork
map
[
6c8dc18315488ef686f7548516c19b3d716728dd8a173cdb889ec0dd082232f9:
{
container2
f828d211e894f7a5a992ce41a2a0def8e2424e9737fb4e1485fc09cc2d607b69
02:42:ac:12:00:03
172.18.0.3/16
}
a5fca8ba1e4ff0a67002f8f1b8cc3cd43185373c2a7e295546f774059ad8dd1a:
{
container1
cff762e6286ebc169804b2a675bbff904102de796751d367c18d4b490c994c45
02:42:ac:12:00:02
172.18.0.2/16
}
]
Como podemos ver el contenedor container1
tiene la IP 172.18.0.2
y el contenedor container2
tiene la IP 172.18.0.3
Nos metemos dentro del contenedor container1
e instalamos ping
$ docker exec -it container1 bash
root@a5fca8ba1e4f:/# apt update
...
root@a5fca8ba1e4f:/# apt install iputils-ping
...
root@a5fca8ba1e4f:/#
Nos metemos dentro del contenedor container2
e instalamos ping
$ docker exec -it container2 bash
root@a5fca8ba1e4f:/# apt update
...
root@a5fca8ba1e4f:/# apt install iputils-ping
...
root@a5fca8ba1e4f:/#
Ahora desde el contenedor container1
hacemos un ping a la IP 172.18.0.3
, que pertenece al contenedor container2
root@a5fca8ba1e4f:/# ping 172.18.0.3
PING 172.18.0.3 (172.18.0.3) 56(84) bytes of data.
64 bytes from 172.18.0.3: icmp_seq=1 ttl=64 time=0.115 ms
64 bytes from 172.18.0.3: icmp_seq=2 ttl=64 time=0.049 ms
64 bytes from 172.18.0.3: icmp_seq=3 ttl=64 time=0.056 ms
64 bytes from 172.18.0.3: icmp_seq=4 ttl=64 time=0.060 ms
^C
--- 172.18.0.3 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3068ms
rtt min/avg/max/mdev = 0.049/0.070/0.115/0.026 ms
Y desde el contenedor container2
hacemos un ping a la IP 172.18.0.2
, que pertenece al contenedor container1
root@6c8dc1831548:/# ping 172.18.0.2
PING 172.18.0.2 (172.18.0.2) 56(84) bytes of data.
64 bytes from 172.18.0.2: icmp_seq=1 ttl=64 time=0.076 ms
64 bytes from 172.18.0.2: icmp_seq=2 ttl=64 time=0.045 ms
64 bytes from 172.18.0.2: icmp_seq=3 ttl=64 time=0.049 ms
64 bytes from 172.18.0.2: icmp_seq=4 ttl=64 time=0.051 ms
^C
--- 172.18.0.2 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3074ms
rtt min/avg/max/mdev = 0.045/0.055/0.076/0.012 ms
Pero hay una cosa mejor que nos permite hacer docker, si no me sé la IP del contenedor al que me quiero conectar, en vez de escribir su IP puedo escribir su nombre
Ahora desde el contenedor container1
hacemos un ping a la IP container2
root@a5fca8ba1e4f:/# ping container2
PING container2 (172.18.0.3) 56(84) bytes of data.
64 bytes from container2.myNetwork (172.18.0.3): icmp_seq=1 ttl=64 time=0.048 ms
64 bytes from container2.myNetwork (172.18.0.3): icmp_seq=2 ttl=64 time=0.050 ms
64 bytes from container2.myNetwork (172.18.0.3): icmp_seq=3 ttl=64 time=0.052 ms
64 bytes from container2.myNetwork (172.18.0.3): icmp_seq=4 ttl=64 time=0.053 ms
^C
--- container2 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3071ms
rtt min/avg/max/mdev = 0.048/0.050/0.053/0.002 ms
Como vemos docker sabe que la IP del contenedor conteiner2
es la 172.18.0.3
Y desde el contenedor container2
hacemos un ping a la IP container1
root@6c8dc1831548:/# ping container1
PING container1 (172.18.0.2) 56(84) bytes of data.
64 bytes from container1.myNetwork (172.18.0.2): icmp_seq=1 ttl=64 time=0.051 ms
64 bytes from container1.myNetwork (172.18.0.2): icmp_seq=2 ttl=64 time=0.058 ms
64 bytes from container1.myNetwork (172.18.0.2): icmp_seq=3 ttl=64 time=0.052 ms
64 bytes from container1.myNetwork (172.18.0.2): icmp_seq=4 ttl=64 time=0.056 ms
^C
--- container1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3057ms
rtt min/avg/max/mdev = 0.051/0.054/0.058/0.003 ms
Como vemos docker sabe que la IP del contenedor conteiner1
es la 172.18.0.2
Salimos de los contenedores y los borramos
!docker network connect myNetwork container1!docker network connect myNetwork container2!docker rm -f container1 container2
container1container2
Borramos también la red que hemos creado
!docker network rm myNetwork
myNetwork
Uso de GPUs
Para poder usar las GPUs del host dentro de los contenedores docker es necesario realizar los pasos descritos en la página de instalación de Nvidia container toolkit
Configurar el repositorio y la llave GPG
Tenemos que configurar el repositorio de nvidia container toolkit
y la llave GPG, para ello ejecutamos el siguiente comando en la consola
distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \
&& curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
&& curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
Instalación de nvidia container toolkit
Una vez hemos actualizado el repositorio y la llave actualizamos los repositorios mediante el comando
sudo apt update
E instalamos nvidia container toolkit
sudo apt install -y nvidia-docker2
Reinicio de docker
Una vez hemos terminado tenemos que reiniciar el demonio de docker mediante
sudo systemctl restart docker
Uso de GPUs
Ahora que hemos configurado docker para poder usar las GPUs del host dentro de los contenedores lo podemos probar mediante la opción --gpus all
. Si se tiene más de una GPU y solo se quiere usar 1 habría que especificarlo, pero de momento aquí solo explicamos como usar todas
Creamos un contenedor que no se va a ejecutar en segundo plano, sino que lo que va a hecer es ejecutar el comando nvidia-smi
para que podamos ver si tiene acceso a las GPUs
!docker run --name container_gpus --gpus all ubuntu nvidia-smi
Unable to find image 'ubuntu:latest' locallylatest: Pulling from library/ubuntu6a12be2b: Pull complete .54MB/29.54MBBDigest: sha256:aabed3296a3d45cede1dc866a24476c4d7e093aa806263c27ddaadbdce3c1054Status: Downloaded newer image for ubuntu:latestMon Sep 4 07:10:36 2023+-----------------------------------------------------------------------------+| NVIDIA-SMI 510.39.01 Driver Version: 510.39.01 CUDA Version: 11.6 ||-------------------------------+----------------------+----------------------+| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC || Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. || | | MIG M. ||===============================+======================+======================|| 0 Quadro T1000 On | 00000000:01:00.0 Off | N/A || N/A 44C P0 15W / N/A | 9MiB / 4096MiB | 0% Default || | | N/A |+-------------------------------+----------------------+----------------------++-----------------------------------------------------------------------------+| Processes: || GPU GI CI PID Type Process name GPU Memory || ID ID Usage ||=============================================================================|| 0 N/A N/A 2545 G 4MiB || 0 N/A N/A 3421 G 4MiB |+-----------------------------------------------------------------------------+
Borramos el contenedor
!doker rm container_gpus
Docker compose
Docker compose vs docker-compose
docker-compose
fue una herramienta que se creó para ayudar al mantenimiento de imágenes y contenedores y había que instalarla a parte de docker. Sin embargo docker lo incorporó en sus últimas versiones y ya no es necesario instalarla, sin embargo, para usarla, en vez de usar el comando docker-compose
hay que usar el comando docker compose
. En muchos sitios encontrarás información con docker-compose
, pero al instalar docker ya te vendrá instalado docker compose
por lo que todo lo que se podía hacer con docker-compose
es compatible con docker compose
Docker compose
Docker compose es una herramienta de docker que hace todo lo que hemos visto hasta ahora, pero ahorrándonos tiempo y esfuerzo. Editando un archivo .yml
podemos decirle a docker compose que cree todos los contenedores que queramos.
Para usarlo una vez no habrá mucha diferencia de escribir todos los comandos que vimos antes o escribir el archivo .yml
pero cuando quires volver a tener la misma configuración de contenedores trabajando, solo con llamar al archivo .yml
volverá a crear toda la configuración
Vamos a crear una carpeta donde guardaremos los archivos de docker compose
!doker rm container_gpus!mkdir dockerComposeFiles
Creamos dentro el archivo .yml
!doker rm container_gpus!mkdir dockerComposeFiles!touch dockerComposeFiles/docker-compose.yml
Un archivo docker compose tiene que empezar por la versión
version: "<v.v>"
En el momento de escribir esto, la última versión es la 3.8
así que escribimos esa
docker-compose.yml:
version: "3.8"
A continuación se indican los servicios, que son los contenedores, en cada servicio hay que especificar la imagen y además se pueden añadir otros parámetros como puertos, variables de entorno, etc
services:
container1:
image: ubuntu
container2:
image: ubuntu
El docker-compose.yml
quedaría así:
version: "3.8"
services:
container1:
image: ubuntu
container2:
image: ubuntu
Una vez que hemos creado el archivo, en su path, podemos ejecutar todo mediante el comando docker compose up
, pero además añadiendo la opción -d
haremos que corra en segundo plano
!doker rm container_gpus!mkdir dockerComposeFiles!touch dockerComposeFiles/docker-compose.yml!cd dockerComposeFiles && docker compose up -d
[+] Running 1/0⠿ Network dockercomposefiles_default Created 0.1s⠋ Container dockercomposefiles-container2-1 Creating 0.0s⠋ Container dockercomposefiles-container1-1 Creating 0.0s[+] Running 1/3⠿ Network dockercomposefiles_default Created 0.1s⠙ Container dockercomposefiles-container2-1 Creating 0.1s⠙ Container dockercomposefiles-container1-1 Creating 0.1s[+] Running 1/3⠿ Network dockercomposefiles_default Created 0.1s⠿ Container dockercomposefiles-container2-1 Starting 0.2s⠿ Container dockercomposefiles-container1-1 Starting 0.2s[+] Running 1/3⠿ Network dockercomposefiles_default Created 0.1s⠿ Container dockercomposefiles-container2-1 Starting 0.3s⠿ Container dockercomposefiles-container1-1 Starting 0.3s[+] Running 1/3⠿ Network dockercomposefiles_default Created 0.1s⠿ Container dockercomposefiles-container2-1 Starting 0.4s⠿ Container dockercomposefiles-container1-1 Starting 0.4s[+] Running 1/3⠿ Network dockercomposefiles_default Created 0.1s⠿ Container dockercomposefiles-container2-1 Starting 0.5s⠿ Container dockercomposefiles-container1-1 Starting 0.5s[+] Running 2/3⠿ Network dockercomposefiles_default Created 0.1s⠿ Container dockercomposefiles-container2-1 Started 0.5s⠿ Container dockercomposefiles-container1-1 Starting 0.6s[+] Running 3/3⠿ Network dockercomposefiles_default Created 0.1s⠿ Container dockercomposefiles-container2-1 Started 0.5s⠿ Container dockercomposefiles-container1-1 Started 0.7s
Si nos fijamos ha creado dos contenedores dockercomposefiles-container1-1
y dockercomposefiles-container2-1
y la red que los une dockercomposefiles_default
Vamos a borrar los dos contenedores
!docker rm -f dockercomposefiles-container1-1 dockercomposefiles-container2-1
dockercomposefiles-container1-1dockercomposefiles-container2-1
Y borramos la red que ha creado
!docker network rm dockercomposefiles_default
dockercomposefiles_default
Vamos a intentar hacer lo que hicimos antes con lo que sabemos hasta ahora. Creamos una nueva imagen que venga con ping
instalado
Dockerfile:
FROM ubuntu:20.04
RUN apt update
RUN apt install iputils-ping -y
Y la compilamos
!docker build -t ubuntu:ping ./dockerImages
Sending build context to Docker daemon 2.048kBStep 1/3 : FROM ubuntu:20.04---> a0ce5a295b63Step 2/3 : RUN apt update---> Running in 3bd5278d39b4WARNING: apt does not have a stable CLI interface. Use with caution in scripts.Get:1 http://security.ubuntu.com/ubuntu focal-security InRelease [114 kB]Get:2 http://archive.ubuntu.com/ubuntu focal InRelease [265 kB]Get:3 http://security.ubuntu.com/ubuntu focal-security/universe amd64 Packages [898 kB]Get:4 http://archive.ubuntu.com/ubuntu focal-updates InRelease [114 kB]Get:5 http://archive.ubuntu.com/ubuntu focal-backports InRelease [108 kB]Get:6 http://archive.ubuntu.com/ubuntu focal/universe amd64 Packages [11.3 MB]Get:7 http://security.ubuntu.com/ubuntu focal-security/main amd64 Packages [2133 kB]Get:8 http://security.ubuntu.com/ubuntu focal-security/multiverse amd64 Packages [27.5 kB]Get:9 http://security.ubuntu.com/ubuntu focal-security/restricted amd64 Packages [1501 kB]Get:10 http://archive.ubuntu.com/ubuntu focal/main amd64 Packages [1275 kB]Get:11 http://archive.ubuntu.com/ubuntu focal/restricted amd64 Packages [33.4 kB]Get:12 http://archive.ubuntu.com/ubuntu focal/multiverse amd64 Packages [177 kB]Get:13 http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages [2594 kB]Get:14 http://archive.ubuntu.com/ubuntu focal-updates/restricted amd64 Packages [1613 kB]Get:15 http://archive.ubuntu.com/ubuntu focal-updates/multiverse amd64 Packages [30.2 kB]Get:16 http://archive.ubuntu.com/ubuntu focal-updates/universe amd64 Packages [1200 kB]Get:17 http://archive.ubuntu.com/ubuntu focal-backports/universe amd64 Packages [27.4 kB]...Successfully built c3d32aa9de02Successfully tagged ubuntu:pingUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
Comprobamos que se ha creado
!docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZEubuntu ping c3d32aa9de02 About a minute ago 112MBmaximofn/ubuntu test a78cf3ea16d8 25 hours ago 77.8MBnginx latest 2d389e545974 33 hours ago 142MBubuntu latest 2dc39ba059dc 12 days ago 77.8MBubuntu 20.04 a0ce5a295b63 12 days ago 72.8MBhello-world latest feb5d9fea6a5 11 months ago 13.3kB
Le cambiamos el tag
!docker tag ubuntu:ping maximofn/ubuntu:ping
!docker tag ubuntu:ping maximofn/ubuntu:ping!docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZEubuntu ping c3d32aa9de02 About a minute ago 112MBmaximofn/ubuntu ping c3d32aa9de02 About a minute ago 112MBmaximofn/ubuntu test c3d32aa9de02 About a minute ago 112MBnginx latest 2d389e545974 33 hours ago 142MBubuntu latest 2dc39ba059dc 12 days ago 77.8MBubuntu 20.04 a0ce5a295b63 12 days ago 72.8MBhello-world latest feb5d9fea6a5 11 months ago 13.3kB
Editamos el archivo docker compose para que coja las imágenes que acabamos de crear
docker-compose.yml:
version: "3.8"
services:
container1:
image: maximofn/ubuntu:ping
container2:
image: maximofn/ubuntu:ping
Y además le decimos que ejecute una no operación
El docker-compose.yml
quedaría así:
version: "3.8"
services:
container1:
image: ubuntu
command: tail -f /dev/null
container2:
image: ubuntu
command: tail -f /dev/null
Lo levantamos
!cd dockerComposeFiles && docker compose up -d
[+] Running 0/0⠋ Container dockercomposefiles-container1-1 Recreate 0.1s⠋ Container dockercomposefiles-container2-1 Recreate 0.1s[+] Running 1/2⠿ Container dockercomposefiles-container1-1 Recreated 0.1s⠙ Container dockercomposefiles-container2-1 Recreate 0.2s[+] Running 1/2⠿ Container dockercomposefiles-container1-1 Recreated 0.1s⠹ Container dockercomposefiles-container2-1 Recreate 0.3s[+] Running 1/2⠿ Container dockercomposefiles-container1-1 Recreated 0.1s⠸ Container dockercomposefiles-container2-1 Recreate 0.4s[+] Running 1/2⠿ Container dockercomposefiles-container1-1 Recreated 0.1s⠼ Container dockercomposefiles-container2-1 Recreate 0.5s[+] Running 1/2⠿ Container dockercomposefiles-container1-1 Recreated 0.1s⠴ Container dockercomposefiles-container2-1 Recreate 0.6s[+] Running 1/2⠿ Container dockercomposefiles-container1-1 Recreated 0.1s⠦ Container dockercomposefiles-container2-1 Recreate 0.7s[+] Running 1/2⠿ Container dockercomposefiles-container1-1 Recreated 0.1s⠧ Container dockercomposefiles-container2-1 Recreate 0.8s[+] Running 1/2⠿ Container dockercomposefiles-container1-1 Recreated 0.1s⠇ Container dockercomposefiles-container2-1 Recreate 0.9s[+] Running 1/2⠿ Container dockercomposefiles-container1-1 Recreated 0.1s⠏ Container dockercomposefiles-container2-1 Recreate 1.0s[+] Running 1/2⠿ Container dockercomposefiles-container1-1 Recreated 0.1s⠋ Container dockercomposefiles-container2-1 Recreate 1.1s[+] Running 1/2⠿ Container dockercomposefiles-container1-1 Recreated 0.1s⠙ Container dockercomposefiles-container2-1 Recreate 1.2s[+] Running 1/2⠿ Container dockercomposefiles-container1-1 Recreated 0.1s⠹ Container dockercomposefiles-container2-1 Recreate 1.3s[+] Running 1/2⠿ Container dockercomposefiles-container1-1 Recreated 0.1s⠸ Container dockercomposefiles-container2-1 Recreate 1.4s[+] Running 1/2⠿ Container dockercomposefiles-container1-1 Recreated 0.1s...[+] Running 2/2⠿ Container dockercomposefiles-container1-1 Started 10.8s⠿ Container dockercomposefiles-container2-1 Started 10.9s[+] Running 2/2⠿ Container dockercomposefiles-container1-1 Started 10.8s⠿ Container dockercomposefiles-container2-1 Started 10.9s
Vemos los contenedores que están corriendo
!docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES935939e5a75d maximofn/ubuntu:ping "tail -f /dev/null" 15 seconds ago Up 13 seconds dockercomposefiles-container2-1f9138d7064dd maximofn/ubuntu:ping "tail -f /dev/null" 25 seconds ago Up 13 seconds dockercomposefiles-container1-1
Están los dos contenedores corriendo, ahora nos metemos en uno e intentamos hacer ping
al otro
$ docker exec -it dockercomposefiles-container1-1 bash
root@f9138d7064dd:/# ping dockercomposefiles-container2-1
PING dockercomposefiles-container2-1 (172.21.0.3) 56(84) bytes of data.
64 bytes from dockercomposefiles-container2-1.dockercomposefiles_default (172.21.0.3): icmp_seq=1 ttl=64 time=0.110 ms
64 bytes from dockercomposefiles-container2-1.dockercomposefiles_default (172.21.0.3): icmp_seq=2 ttl=64 time=0.049 ms
64 bytes from dockercomposefiles-container2-1.dockercomposefiles_default (172.21.0.3): icmp_seq=3 ttl=64 time=0.049 ms
64 bytes from dockercomposefiles-container2-1.dockercomposefiles_default (172.21.0.3): icmp_seq=4 ttl=64 time=0.075 ms
^C
--- dockercomposefiles-container2-1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3068ms
rtt min/avg/max/mdev = 0.049/0.070/0.110/0.025 ms
Como vemos podemos hacer ping
, hemos creado bien la imagen con ping
instalado. Además en el docker compose hemos hecho que se ejecute una no operación para que los contenedores estén corriendo
Borramos los dos contenedores y la red que ha creado
!docker rm -f dockercomposefiles-container1-1 dockercomposefiles-container2-1
dockercomposefiles-container1-1dockercomposefiles-container2-1
!docker network rm dockercomposefiles_default
dockercomposefiles_default
Como nombra docker compose los contenedores
Si nos fijamos los contenedores que creo docker se llamaban dockercomposefiles-container1-1
y dockercomposefiles-container1-1
. Esto es porque la carpeta en la que está el archivo de docker compose está en una carpeta llamada dockerComposeFiles
, por eso la primera parte del nombre de los contenedores es dockercomposefiles
, a continuación aparece el nombre del servicio que le hemos dado en el archivo docker compose (container1
y container2
) y por último un numero para poder crear más si es necesario
Similar ocurre con el nombre de la red que ha creado dockercomposefiles_default
Logs en docker compose
Vamos ahora a cambiar el archivo docker compose, en las líneas en las que teníamos command: tail -f /dev/null
, vamos a poner command: ping 0.0.0.0
Y además le decimos que ejecute una no operación
El docker-compose.yml
quedaría así:
version: "3.8"
services:
container1:
image: ubuntu
command: ping 0.0.0.0
container2:
image: ubuntu
command: ping 0.0.0.0
Esto lo hacemos para que cada contenedor esté escupiendo el ping constantemente, así simulamos unos logs
Si ejecutamos otra vez el docker compose
!cd dockerComposeFiles && docker compose up -d
[+] Running 0/0⠋ Container dockercomposefiles-container1-1 Recreate 0.1s⠋ Container dockercomposefiles-container2-1 Recreate 0.1s[+] Running 0/2⠙ Container dockercomposefiles-container1-1 Recreate 0.2s⠙ Container dockercomposefiles-container2-1 Recreate 0.2s[+] Running 0/2⠹ Container dockercomposefiles-container1-1 Recreate 0.3s⠹ Container dockercomposefiles-container2-1 Recreate 0.3s[+] Running 0/2⠸ Container dockercomposefiles-container1-1 Recreate 0.4s⠸ Container dockercomposefiles-container2-1 Recreate 0.4s[+] Running 0/2⠼ Container dockercomposefiles-container1-1 Recreate 0.5s⠼ Container dockercomposefiles-container2-1 Recreate 0.5s[+] Running 0/2⠴ Container dockercomposefiles-container1-1 Recreate 0.6s⠴ Container dockercomposefiles-container2-1 Recreate 0.6s[+] Running 0/2⠦ Container dockercomposefiles-container1-1 Recreate 0.7s⠦ Container dockercomposefiles-container2-1 Recreate 0.7s[+] Running 0/2⠧ Container dockercomposefiles-container1-1 Recreate 0.8s⠧ Container dockercomposefiles-container2-1 Recreate 0.8s[+] Running 0/2...⠿ Container dockercomposefiles-container1-1 Starting 11.0s⠿ Container dockercomposefiles-container2-1 Started 11.0s[+] Running 2/2⠿ Container dockercomposefiles-container1-1 Started 11.1s⠿ Container dockercomposefiles-container2-1 Started 11.0s
Ahora podemos ver los logs de los dos contenedores mediante el comando docker compose logs
!cd dockerComposeFiles && docker compose logs
dockercomposefiles-container2-1 | PING 0.0.0.0 (127.0.0.1) 56(84) bytes of data.dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.042 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.025 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.022 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.030 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.021 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=6 ttl=64 time=0.021 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=7 ttl=64 time=0.030 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=8 ttl=64 time=0.028 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=9 ttl=64 time=0.028 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=10 ttl=64 time=0.026 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=11 ttl=64 time=0.028 msdockercomposefiles-container1-1 | PING 0.0.0.0 (127.0.0.1) 56(84) bytes of data.dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=12 ttl=64 time=0.027 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=13 ttl=64 time=0.039 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=14 ttl=64 time=0.035 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=15 ttl=64 time=0.034 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=16 ttl=64 time=0.036 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=17 ttl=64 time=0.034 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=18 ttl=64 time=0.036 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=19 ttl=64 time=0.032 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=20 ttl=64 time=0.032 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=21 ttl=64 time=0.033 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=22 ttl=64 time=0.034 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.037 ms...dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=214 ttl=64 time=0.015 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=215 ttl=64 time=0.021 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=216 ttl=64 time=0.020 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=217 ttl=64 time=0.049 ms
Como vemos podemos ver los logs de los dos contenedores, pero en el caso de querer ver solo los de uno, podemos especificar el nombre del servicio
!cd dockerComposeFiles && docker compose logs container1
dockercomposefiles-container1-1 | PING 0.0.0.0 (127.0.0.1) 56(84) bytes of data.dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.037 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.025 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.023 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.031 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.034 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=6 ttl=64 time=0.033 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=7 ttl=64 time=0.034 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=8 ttl=64 time=0.022 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=9 ttl=64 time=0.032 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=10 ttl=64 time=0.029 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=11 ttl=64 time=0.031 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=12 ttl=64 time=0.024 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=13 ttl=64 time=0.029 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=14 ttl=64 time=0.032 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=15 ttl=64 time=0.033 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=16 ttl=64 time=0.034 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=17 ttl=64 time=0.028 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=18 ttl=64 time=0.034 ms...dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=332 ttl=64 time=0.027 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=333 ttl=64 time=0.030 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=334 ttl=64 time=0.033 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=335 ttl=64 time=0.036 ms
!cd dockerComposeFiles && docker compose logs container2
dockercomposefiles-container2-1 | PING 0.0.0.0 (127.0.0.1) 56(84) bytes of data.dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.042 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.025 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.022 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.030 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.021 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=6 ttl=64 time=0.021 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=7 ttl=64 time=0.030 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=8 ttl=64 time=0.028 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=9 ttl=64 time=0.028 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=10 ttl=64 time=0.026 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=11 ttl=64 time=0.028 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=12 ttl=64 time=0.027 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=13 ttl=64 time=0.039 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=14 ttl=64 time=0.035 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=15 ttl=64 time=0.034 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=16 ttl=64 time=0.036 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=17 ttl=64 time=0.034 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=18 ttl=64 time=0.036 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=19 ttl=64 time=0.032 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=20 ttl=64 time=0.032 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=21 ttl=64 time=0.033 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=22 ttl=64 time=0.034 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=23 ttl=64 time=0.035 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=24 ttl=64 time=0.037 ms...dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=340 ttl=64 time=0.034 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=341 ttl=64 time=0.033 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=342 ttl=64 time=0.034 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=343 ttl=64 time=0.036 ms
Si queremos ver los logs continuamente podemos añadir la opción -f
, docker compose logs - <service name>
Si he hecho un docker compose con más de dos servición, cuando se quiera ver los logs de varios servicios solo hay ue añadir más nombres al comando, docker compose logs <name service 1> <name service 2> ...
Exec servicios
Como hemos visto, mediante el comando exec
podemos entrar a un contenedor indicando el nombre del contenedor, el comando que se quiere ejecutar y la opción -it
. Con docker compose esto es más sencillo, ya que solamente es necesario el nombre del servicio y el comando, pero no es necesaria la opción -it
ya que docker compose lo da por supuesto
$ docker compose exec container1 bash
root@a7cf282fe66c:/#
Parando docker compose
Cuando hemos terminado de trabajar, con un solo comando (stop
), docker compose para todo, no hace falta ir parando uno a uno cada contenedor
!cd dockerComposeFiles && docker compose stop
[+] Running 0/0⠋ Container dockercomposefiles-container2-1 Stopping 0.1s⠋ Container dockercomposefiles-container1-1 Stopping 0.1s[+] Running 0/2⠙ Container dockercomposefiles-container2-1 Stopping 0.2s⠙ Container dockercomposefiles-container1-1 Stopping 0.2s[+] Running 0/2⠹ Container dockercomposefiles-container2-1 Stopping 0.3s⠹ Container dockercomposefiles-container1-1 Stopping 0.3s[+] Running 0/2⠸ Container dockercomposefiles-container2-1 Stopping 0.4s⠸ Container dockercomposefiles-container1-1 Stopping 0.4s[+] Running 0/2⠼ Container dockercomposefiles-container2-1 Stopping 0.5s⠼ Container dockercomposefiles-container1-1 Stopping 0.5s[+] Running 0/2⠴ Container dockercomposefiles-container2-1 Stopping 0.6s⠴ Container dockercomposefiles-container1-1 Stopping 0.6s[+] Running 0/2⠦ Container dockercomposefiles-container2-1 Stopping 0.7s⠦ Container dockercomposefiles-container1-1 Stopping 0.7s[+] Running 0/2⠧ Container dockercomposefiles-container2-1 Stopping 0.8s⠧ Container dockercomposefiles-container1-1 Stopping 0.8s...[+] Running 1/2⠿ Container dockercomposefiles-container2-1 Stopped 10.4s⠸ Container dockercomposefiles-container1-1 Stopping 10.4s[+] Running 2/2⠿ Container dockercomposefiles-container2-1 Stopped 10.4s⠿ Container dockercomposefiles-container1-1 Stopped 10.4s
Como se puede ver docker compose ha parado los dos contenedores, pero no los ha borrado, ni ha borrado la red
!docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES1e6c1dd9adb2 maximofn/ubuntu:ping "ping 0.0.0.0" 16 minutes ago Exited (137) 25 seconds ago dockercomposefiles-container2-1a7cf282fe66c maximofn/ubuntu:ping "ping 0.0.0.0" 16 minutes ago Exited (137) 25 seconds ago dockercomposefiles-container1-1
!docker network ls
NETWORK ID NAME DRIVER SCOPE13cc632147f3 bridge bridge locald4a2f718cd83 dockercomposefiles_default bridge localda1f5f6fccc0 host host locald3b0d93993c0 none null local
Docker compose como herramienta de desarrollo
Al igual que vimos antes, para poder desarrollar, lo ideal sería compartir la carpeta que tiene el código con el servicio. Esto con docker compose se hace añadiendo la etiqueta volumes
al archivo docker compose. Primero tenemos que añadirle la ruta de la carpeta donde está el codigo en el host y luego la ruta en el contenedor.
docker-compose.yml:
version: "3.8"
services:
container1:
image: ubuntu
command: ping 0.0.0.0
volumes:
- ../dockerHostFolder/:/dockerContainerFolder
container2:
image: ubuntu
command: ping 0.0.0.0
Como se puede ver, la ruta de la carpeta del host la he puesto relativa
Si levantamos el docker compose
!cd dockerComposeFiles && docker compose up -d
[+] Running 1/0⠋ Container dockercomposefiles-container1-1 Recreate 0.1s⠿ Container dockercomposefiles-container2-1 Created 0.0s[+] Running 0/2⠿ Container dockercomposefiles-container1-1 Starting 0.2s⠿ Container dockercomposefiles-container2-1 Starting 0.2s[+] Running 0/2⠿ Container dockercomposefiles-container1-1 Starting 0.3s⠿ Container dockercomposefiles-container2-1 Starting 0.3s[+] Running 0/2⠿ Container dockercomposefiles-container1-1 Starting 0.4s⠿ Container dockercomposefiles-container2-1 Starting 0.4s[+] Running 1/2⠿ Container dockercomposefiles-container1-1 Started 0.5s⠿ Container dockercomposefiles-container2-1 Starting 0.5s[+] Running 2/2⠿ Container dockercomposefiles-container1-1 Started 0.5s⠿ Container dockercomposefiles-container2-1 Started 0.6s
Si entramos dentro del contenedor, podemos ver qué hay dentro del archivo text.txt
$ docker compose exec container1 bash
root@c8aae9d619d3:/# ls dockerContainerFolder/
bindFile.txt fileExtract.txt text.txt
root@c8aae9d619d3:/# cat dockerContainerFolder/text.txt
hola contenedor
Si ahora lo abrimos en el host, escribimos hola host
y volvemos a ver en el contenedor
root@c8aae9d619d3:/# cat dockerContainerFolder/text.txt
hola host
Y ahora al revés, si lo modificamos en el contenedor
root@c8aae9d619d3:/# echo hola compose > dockerContainerFolder/text.txt
root@c8aae9d619d3:/# exit
exit
Si lo vemos desde el host debemos obtener hola compose
!cat dockerHostFolder/text.txt
hola compose
Exposición de puertos en docker compose
También podemos configurar los puertos en el archivo de docker compose, mediante la etiqueta ports
, indicando el puerto del host y a continuación la ip del servicio
ports:
- <host port>:<service port>
Docker compose en equipo - docker override
Si somos un grupo de personas desarrollando sobre docker con docker compose, es probable que muchas personas anden cambiando el archivo docker compose, lo cual puede hacer que no se sincronicen bien y hayan conflictos.
Para solucionar esto docker ofrece una herramienta llamada docker override. De esta manera puede haber un archivo docker compose base y que cada uno lo modifique mediante docker override.
Para hacer esto, ahora tenemos que crear un archivo llamado docker-compose.override.yml
que será el que podremos editar
!touch dockerComposeFiles/docker-compose.override.yml
Si ahora intentamos levantar el docker compose vamos a recibir un error
!touch dockerComposeFiles/docker-compose.override.yml!cd dockerComposeFiles && docker compose up -d
Top-level object must be a mapping
Y esto es porque docker compose ha detectado que hay un archivo llamado docker-compose.override.yml
y que está vacío, por lo que vamos a editarlo. El archivo docker-compose.override.yml
lo que hace es editar el archivo docker-compose.yml
, por lo que si por ejemplo queremos hacer un cambio en el servicio container2
para añadirle un volumen escribiríamos así el archivo docker-compose.override.yml
docker-compose.override.yml:
version: "3.8"
services:
container2:
volumes:
- ../dockerHostFolder/:/dockerOverrideFolder
Date cuenta que la carpeta compartida en el servicio la he llamado dockerOverrideFolder
, por lo que vamos a levantar el docker compose y ver si vemos esa carpeta en el contenedor container2
!cd dockerComposeFiles && docker compose up -d
[+] Running 1/0⠋ Container dockercomposefiles-container2-1 Recreate 0.1s⠿ Container dockercomposefiles-container1-1 Running 0.0s[+] Running 1/2⠙ Container dockercomposefiles-container2-1 Recreate 0.2s⠿ Container dockercomposefiles-container1-1 Running 0.0s[+] Running 1/2⠹ Container dockercomposefiles-container2-1 Recreate 0.3s⠿ Container dockercomposefiles-container1-1 Running 0.0s[+] Running 1/2⠸ Container dockercomposefiles-container2-1 Recreate 0.4s⠿ Container dockercomposefiles-container1-1 Running 0.0s[+] Running 1/2⠼ Container dockercomposefiles-container2-1 Recreate 0.5s⠿ Container dockercomposefiles-container1-1 Running 0.0s[+] Running 1/2⠴ Container dockercomposefiles-container2-1 Recreate 0.6s⠿ Container dockercomposefiles-container1-1 Running 0.0s[+] Running 1/2⠦ Container dockercomposefiles-container2-1 Recreate 0.7s⠿ Container dockercomposefiles-container1-1 Running 0.0s[+] Running 1/2⠧ Container dockercomposefiles-container2-1 Recreate 0.8s⠿ Container dockercomposefiles-container1-1 Running 0.0s...[+] Running 1/2⠿ Container dockercomposefiles-container2-1 Starting 10.8s⠿ Container dockercomposefiles-container1-1 Running 0.0s[+] Running 2/2⠿ Container dockercomposefiles-container2-1 Started 10.8s⠿ Container dockercomposefiles-container1-1 Running 0.0s
Vemos que ha tardado 10 segundos en montar el servicio container2
, eso es porque ha estado aplicando los cambios
$ docker compose exec container2 bash
root@d8777a4e611a:/# ls dockerOverrideFolder/
bindFile.txt fileExtract.txt text.txt
root@d8777a4e611a:/# cat dockerOverrideFolder/text.txt
hola compose
root@d8777a4e611a:/# exit
exit
Bajamos el compose y borramos los contenedores y la red creada
!cd dockerComposeFiles && docker compose down
[+] Running 0/0⠋ Container dockercomposefiles-container2-1 Stopping 0.1s⠋ Container dockercomposefiles-container1-1 Stopping 0.1s[+] Running 0/2⠙ Container dockercomposefiles-container2-1 Stopping 0.2s⠙ Container dockercomposefiles-container1-1 Stopping 0.2s[+] Running 0/2⠹ Container dockercomposefiles-container2-1 Stopping 0.3s⠹ Container dockercomposefiles-container1-1 Stopping 0.3s[+] Running 0/2⠸ Container dockercomposefiles-container2-1 Stopping 0.4s⠸ Container dockercomposefiles-container1-1 Stopping 0.4s[+] Running 0/2⠼ Container dockercomposefiles-container2-1 Stopping 0.5s⠼ Container dockercomposefiles-container1-1 Stopping 0.5s[+] Running 0/2⠴ Container dockercomposefiles-container2-1 Stopping 0.6s⠴ Container dockercomposefiles-container1-1 Stopping 0.6s[+] Running 0/2⠦ Container dockercomposefiles-container2-1 Stopping 0.7s⠦ Container dockercomposefiles-container1-1 Stopping 0.7s[+] Running 0/2⠧ Container dockercomposefiles-container2-1 Stopping 0.8s⠧ Container dockercomposefiles-container1-1 Stopping 0.8s...⠸ Container dockercomposefiles-container2-1 Stopping 10.4s⠸ Container dockercomposefiles-container1-1 Stopping 10.4s[+] Running 1/2⠿ Container dockercomposefiles-container2-1 Removed 10.4s⠿ Container dockercomposefiles-container1-1 Removing 10.5s[+] Running 2/2⠿ Container dockercomposefiles-container2-1 Removed 10.4s⠿ Container dockercomposefiles-container1-1 Removed 10.5s⠋ Network dockercomposefiles_default Removing 0.1s[+] Running 3/3⠿ Container dockercomposefiles-container2-1 Removed 10.4s⠿ Container dockercomposefiles-container1-1 Removed 10.5s⠿ Network dockercomposefiles_default Removed 0.2s
En este caso, solo con down
docker compose ha parado y borrado todo, ya que, como vemos en los contenedores y en la red pone Removed
Docker compose restart
A la hora de escribir un docker compose, podemos añadir la etiqueta restart
para que si el contenedor se cae, se reinicie automáticamente
restart: always
De esta manera, si el contenedor se cae, se reiniciará automáticamente. Si queremos que se reinicie solo un número de veces, podemos añadirle la opción on-failure
restart: on-failure:<number>
Ahora el contenedor se reiniciará un número de veces, pero si se cae más veces, no se reiniciará. Si queremos que se reinicie siempre, podemos añadirle la opción unless-stopped
restart: unless-stopped
Ahora el contenedor se reiniciará siempre, a no ser que se pare manualmente
Docker avanzado
Administrar ambiente de trabajo
Borrado de contenedores apagados
Después de estar un tiempo desarrollando, podemos tener varios contenedores apagados, pero guardados en el ordenador. Esto al final ocupa memoria, así que con docker contanier prune
podemos eliminar todos los que están parados
!docker run ubuntu
!docker run ubuntu!docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESeffcee24f54a ubuntu "bash" 37 seconds ago Exited (0) 36 seconds ago musing_rosalind
$ docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
effcee24f54aab22e34fdea2465b3b7af132d8c627e5432ba3e915a370876977
Total reclaimed space: 0B
En este caso hemos ahorrado 0 bytes, pero en el caso de dejar contenedores apagados después de mucho desarrollo, seguro que el ahorro de memoria será mayor
Borrado de todos los contenedores
En caso de tener contenedores corriendo, podemos matar todos los contenedores mediante otro comando
El comando docker ps -q
nos devuelve la id de todos los contenedores, por lo que con el comando docker rm -f $(docker ps -aq)
pararemos y borraremos todos
!docker run -d ubuntu tail -f /dev/null
c22516186ef7e3561fb1ad0d508a914857dbc61274a218f297c4d80b1fc33863
!docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESc22516186ef7 ubuntu "tail -f /dev/null" About a minute ago Up About a minute agitated_knuth
!docker rm -f $(docker ps -aq)
c22516186ef7
!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Borrado de todo
Como hemos visto docker también crea redes, imágenes, volúmenes, etc, así que con el comando docker system prune
podemos borrar todos los contenedores parados, todas las redes que no estén usadas por al menos un contenedor, las imágenes repetidas, y lo que haya repetido en la caché de compilación
$ docker system prune
WARNING! This will remove:
- all stopped containers
- all networks not used by at least one container
- all dangling images
- all dangling build cache
Are you sure you want to continue? [y/N] y
Total reclaimed space: 0B
Al igual que antes, no se ha ahorrado mucho espacio, pero después de mucho tiempo desarrollando, el ahorro será considerable
Uso de recursos del host por parte de contenedores
Por ejemplo a la hora de crear un contenedor, podemos limitar la RAM del host que puede usar mediante la opción --memory
!docker run -d --memory 1g ubuntu tail -f /dev/null
d84888eafe531831ef8915d2270422365adec02678122bf59580e2da782e6972
Pero con docker ps
no tenemos acceso a los recursos que está consumiendo el contenedor
!docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESd84888eafe53 ubuntu "tail -f /dev/null" 35 seconds ago Up 34 seconds musing_ritchie
Para ello tenemos el comando docker stats
$ docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
d84888eafe53 musing_ritchie 0.00% 540KiB / 1GiB 0.05% 5.62kB / 0B 0B / 0B 1
Esto es muy util si queremos simular un entorno con un límite de RAM
Deteniendo contenedores correctamente: SHELL vs EXEC
Como hemos explicado, cuando asignamos un proceso a un contenedor, cuando ese proceso termina, el contenedor se para, pero a veces podemos encontrarnos con problemas con esto. Vamos a crear una nueva carpeta llamada Dockerfile_loop
!mkdir Dockerfile_loop
Ahora vamos a crear un archivo llamado loop.sh
dentro de Dockerfile_loop
!mkdir Dockerfile_loop!touch Dockerfile_loop/loop.sh
Y vamos a escribir lo siguiente dentro de loop.sh
#!/usr/bin/env bash
trap "exit 0" SIGTERM
while true; do :; done
Si yo ejecuto este script en el host se ejcuta hasta que introduzca CTRL+C
./loop
^C
Ahora vamos a crear un archivo Dockerfile
dentro de Dockerfile_loop
!mkdir Dockerfile_loop!touch Dockerfile_loop/loop.sh!touch Dockerfile_loop/Dockerfile
Dockerfile:
FROM ubuntu:trusty
COPY ["loop.sh", "/"]
CMD /loop.sh
Vamos a crear una imagen basada en ubuntu que copia el script dentro y lo ejecuta, y el script se ejecuta hasta que recibe la señal SIGTERM
del sistema operativo. Compilamos la imagen
!mkdir Dockerfile_loop!touch Dockerfile_loop/loop.sh!touch Dockerfile_loop/Dockerfile!docker build -t ubuntu:loop ./Dockerfile_loop
Sending build context to Docker daemon 3.072kBStep 1/3 : FROM ubuntu:trusty---> 13b66b487594Step 2/3 : COPY ["loop.sh", "/"]---> 89f2bbd25a88Step 3/3 : CMD /loop.sh---> Running in ff52569c35fdRemoving intermediate container ff52569c35fd---> feb091e4efa3Successfully built feb091e4efa3Successfully tagged ubuntu:loopUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
Ejecutamos el contenedor
docker run -d --name looper ubuntu:loop bash
!docker run -d --name looper ubuntu:loop
8a28f8cc9892213c4e0603dfdde320edf52c091b82c60510083549a391cd6645
Comprobamos y vemos que el contenedor está corriendo
!docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES8a28f8cc9892 ubuntu:loop "/bin/sh -c /loop.sh" 4 seconds ago Up 3 seconds looper
Intentamos parar el contenedor con docker stop looper
. Docker stop trata de parar el contenedor enviándole la señal SIGTERM
.
%%time!docker stop looper
looperCPU times: user 89.2 ms, sys: 21.7 ms, total: 111 msWall time: 10.6 s
Esto ha tardado unos 10 segundos en detenerse, cuando tendría que ser inmediato. Esto es porque stop
ha mandado la orden SIGTERM
para que se parara el contenedor, pero como no paraba, al rato le ha mandado un SIGKILL
para forzar que se detenga. Vemos aver que pasa, si listamos los contenedores
!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES8a28f8cc9892 ubuntu:loop "/bin/sh -c /loop.sh" 23 seconds ago Exited (137) 2 seconds ago looper
Podemos ver que la señal de Exited
es 137
, eso quivale a SIGKILL, es decir, docker tuvo que forzar el apagado.
Vamos a borrar el contenedor y a volverlo a correr
!docker rm looper
looper
!docker run -d --name looper ubuntu:loop
84bc37f944d270be5f84a952968db2b8cf5372c61146d29383468198ceed18fd
Si ahora intentamos parar el contenedor con docker kill looper
%%time!docker kill looper
looperCPU times: user 9.1 ms, sys: 857 µs, total: 9.96 msWall time: 545 ms
Vemos que el tiempo son unos 500 ms, es decir, docker lo ha parado en un momento enviándole la orde SIGKILL
. Porque kill
no manda SIGTERM
y si en un tiempo no se ha parado el contenedor manda SIGKILL
, lo que hace es mandar SIGKILL
desde el inicio.
Si vemos los contenedores, vemos que la señal de salida es la misma, 137
!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES84bc37f944d2 ubuntu:loop "/bin/sh -c /loop.sh" 6 seconds ago Exited (137) 2 seconds ago looper
Esta no es la manera correcta de apagar un contenedor, porque cuando queramos apagar el contenedor habría que hacerlo mediante la señal SIGTERM
, para que este termine de procesar lo que estuviese haciendo y luego se apague
Si borramos el contenedor y lo volvemos a correr
!docker rm looper
looper
!docker run -d --name looper ubuntu:loop
b9d9f370cc0de7569eb09d0a85cd67e8ea6babc0754a517ccba5c5057f5cc50e
Si ahora vemos los procesos que se están ejecutando dentro del contenedor
!docker exec looper ps -ef
UID PID PPID C STIME TTY TIME CMDroot 1 0 0 14:05 ? 00:00:00 /bin/sh -c /loop.shroot 7 1 93 14:05 ? 00:00:02 bash /loop.shroot 8 0 0 14:05 ? 00:00:00 ps -ef
En realidad el proceso principal, el 1, no es /loop.sh
sino que es /bin/sh -c /loop.sh
, es decir, es un proceso hijo del shell
. Por lo que cuando llegaba la señal SIGTERM
le llegaba a shell
, pero este no se lo manda a sus procesos hijos, por eso no le llegaba a loop.sh
Para que no pase esto hay que cambiar el Dockerfile
a lo siguiente
Dockerfile:
FROM ubuntu:trusty
COPY ["loop.sh", "/"]
CMD ["/loop.sh"] # antes era CMD /loop.sh
Esta forma se llama exec form
, mientras que la anterior se llama shell form
, de manera que de la anterior forma corre el proceso como un hijo del shell
, mientras que de la forma exec form
ejecuta el proceso que le digamos. Así que borramos el contenedor, volvemos a compilar y volvemos a hacer correr el contenedor con la imagen
!docker rm -f looper
looper
!docker build -t ubuntu:loop ./Dockerfile_loop
Sending build context to Docker daemon 3.072kBStep 1/3 : FROM ubuntu:trusty---> 13b66b487594Step 2/3 : COPY ["loop.sh", "/"]---> Using cache---> 89f2bbd25a88Step 3/3 : CMD ["/loop.sh"]---> Running in 6b8d92fcd57cRemoving intermediate container 6b8d92fcd57c---> 35a7bb2b1892Successfully built 35a7bb2b1892Successfully tagged ubuntu:loopUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
!docker run -d --name looper ubuntu:loop
850ae70c071426850b28428ac60dcbf875c6d35d9b7cc66c17cf391a23392965
Si ahora veo los procesos dentro del contenedor
!docker exec looper ps -ef
UID PID PPID C STIME TTY TIME CMDroot 1 0 88 14:14 ? 00:00:02 bash /loop.shroot 7 0 0 14:14 ? 00:00:00 ps -ef
Ahora sí veo que el proceso principal, el 1, es /loop.sh
Si ahora pruebo a parar el contenedor
%%time!docker stop looper
looperCPU times: user 989 µs, sys: 7.55 ms, total: 8.54 msWall time: 529 ms
Vemos que tarda ms. Vémos el código con el que paró
!docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES850ae70c0714 ubuntu:loop "/loop.sh" About a minute ago Exited (0) 33 seconds ago looper
Contenedores ejecutables
Si queremos un binario que corra como un ejecutable, en el dockerfile
hay que especificar el comando en ENTRYPOINT
y los parámetros del comando en CMD
, vamos a verlo
Vamos a crear una nueva carpeta donde guardaremos el Dockerfile
!mkdir dockerfile_ping
Ahora creamos un Dockerfile dentro
!mkdir dockerfile_ping!touch dockerfile_ping/Dockerfile
Escribimos dentro del Dockerfile lo siguiente
FROM ubuntu:trusty
ENTRYPOINT [ "/bin/ping", "-c", "3" ]
CMD [ "localhost" ]
Compilamos la imagen
!mkdir dockerfile_ping!touch dockerfile_ping/Dockerfile!docker build -t ubuntu:ping ./dockerfile_ping
Sending build context to Docker daemon 3.072kBStep 1/3 : FROM ubuntu:trusty---> 13b66b487594Step 2/3 : ENTRYPOINT [ "/bin/ping", "-c", "3" ]---> Using cache---> 1cebcfb542b1Step 3/3 : CMD [ "localhost" ]---> Using cache---> 04ddc3de52a2Successfully built 04ddc3de52a2Successfully tagged ubuntu:pingUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
Si ahora corremos la imagen sin pasarle un parámetro, el contenedor se hará un ping a sí mismo
!docker run --name ping_localhost ubuntu:ping
PING localhost (127.0.0.1) 56(84) bytes of data.64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.041 ms64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.058 ms64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.054 ms--- localhost ping statistics ---3 packets transmitted, 3 received, 0% packet loss, time 2027msrtt min/avg/max/mdev = 0.041/0.051/0.058/0.007 ms
Pero si ahora le pasamos un parámetro, hará ping a la dirección que le digamos
!docker run --name ping_google ubuntu:ping google.com
PING google.com (216.58.209.78) 56(84) bytes of data.64 bytes from waw02s06-in-f14.1e100.net (216.58.209.78): icmp_seq=1 ttl=111 time=3.93 ms64 bytes from waw02s06-in-f14.1e100.net (216.58.209.78): icmp_seq=2 ttl=111 time=6.80 ms64 bytes from waw02s06-in-f14.1e100.net (216.58.209.78): icmp_seq=3 ttl=111 time=6.92 ms--- google.com ping statistics ---3 packets transmitted, 3 received, 0% packet loss, time 2002msrtt min/avg/max/mdev = 3.930/5.886/6.920/1.383 ms
Borramos los contenedores
!docker rm ping_localhost ping_google
ping_localhostping_google
El contexto de build
Vamos a crear una carpeta llamada dockerfile_contexto
!mkdir dokerfile_contexto
Ahora creamos en ella dos archivos, un test.txt
y el Dockerfile
!mkdir dokerfile_contexto!touch dokerfile_contexto/Dockerfile dokerfile_contexto/text.txt
Modificamos el Dockerfile y ponemos lo siguiente
FROM ubuntu:trusty
COPY [".", "/"]
Esto lo que va a hacer es que va a copiar dentro de la imagen, todo lo que tenga en la carpeta en la que se encuentra el Dockerfile
. Compilamos la imagen
!mkdir dokerfile_contexto!touch dokerfile_contexto/Dockerfile dokerfile_contexto/text.txt!docker build -t ubuntu:contexto ./dokerfile_contexto
Sending build context to Docker daemon 2.56kBStep 1/2 : FROM ubuntu:trusty---> 13b66b487594Step 2/2 : COPY [".", "/"]---> 3ab79fdce389Successfully built 3ab79fdce389Successfully tagged ubuntu:contextoUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
Vamos a ver qué hay dentro del contenedor
!docker run --name ls ubuntu:contexto ls
Dockerfilebinbootdevetchomeliblib64mediamntoptprocrootrunsbinsrvsystext.txttmpusrvar
Como vemos está el archivo text.txt
. Pero al puede que dentro de la carpeta que está en el mismo directorio que el Dockerfile
haya archivos o carpetas que no queremos que se copien en la imagen, por la razón que sea, así que al igual que en git tenemos el .gitignore
, en docker tenemos el .dockerignore
, donde metemos los archivos o carpetas que no queremos que a la hora de compilar se tengan en cuenta
Así que creamos un archivo .dockerignore
!touch dokerfile_contexto/.dockerignore
Y dentro añadimos el text.txt
, y de paso el Dockerfile
que no lo necesitamos dentro de la imagen
.dockerignore:
Dockerfile
text.txt
Borramos el contenedor que habíamos creado, volvemos a compilar y vemos qué hay dentro del contenedor
!touch dokerfile_contexto/.dockerignore!docker rm ls
ls
!docker build -t ubuntu:contexto ./dokerfile_contexto
Sending build context to Docker daemon 3.072kBStep 1/2 : FROM ubuntu:trusty---> 13b66b487594Step 2/2 : COPY [".", "/"]---> 7a6689546da4Successfully built 7a6689546da4Successfully tagged ubuntu:contextoUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
!docker run --name ls ubuntu:contexto ls
binbootdevetchomeliblib64mediamntoptprocrootrunsbinsrvsystmpusrvar
Vemos que ahora no están ni Dockerfile
, ni text.txt
. Borramos el contenedor
!docker rm ls
ls
Multi-stage build
Al final de un desarrollo no queremos que todo el código esté en la imagen que se va a mandar a producción.
Podemos dividir el dockerfile
en dos, por ejemplo, el developer.Dockerfile
y el production.Dockerfile
, donde en desarrollo habrá más cosas que en el de producción. A la hora de compilarlos, mediante la opción -f
elegimos el dockerfile
que queremos usar
docker build -t <tag> -f developer.Dockerfile
docker build -t <tag> -f production.Dockerfile
Pero para no tener que crear dos archivos Dockerfile
, docker creó el multi stage buils
. Con un solo Dockerfile
vamos a solucionar el problema
Creamos la carpeta donde vamos a guardar el Dockerfile
!mkdir docker_multi_stage
Y dentro creamos el archivo Dockerfile
!mkdir docker_multi_stage!cd docker_multi_stage && touch Dockerfile
Editamos el archivo metiendo lo siguiente
# Etapa 1: Generar el ejecutable con Python basado en Alpine
FROM python:3.9-alpine as build-stage
WORKDIR /app
# Instalar dependencias para PyInstaller
RUN apk add --no-cache gcc musl-dev libc-dev
# Generar hello.py
RUN echo 'print("Hello from Alpine!")' > hello.py
# Instalar PyInstaller
RUN pip install pyinstaller
# Usar PyInstaller para crear un ejecutable independiente
RUN pyinstaller --onefile hello.py
# Etapa 2: Ejecutar el ejecutable en una imagen de Alpine
FROM alpine:latest
WORKDIR /app
# Copiar el ejecutable desde la etapa de build
COPY --from=build-stage /app/dist/hello .
# Comando por defecto para ejecutar el ejecutable
CMD ["./hello"]
Como se puede ver el Dockerfile
está dividido en dos, por un lado se trabaja sobre la imagen python:3.9-alpine
que se llama como build-stage
. Y por otro lado trabajamos sobre la imagen alpine:latest
, que es una imagen de linux muy ligera y se utiliza mucho en producción
Lo compilamos
!docker build -t maximofn/multistagebuild:latest ./docker_multi_stage
Si ahora hacemos vemos las imágenes que tenemos
!mkdir docker_multi_stage!cd docker_multi_stage && touch Dockerfile!docker build -t maximofn/multistagebuild:latest ./docker_multi_stage!docker image ls
[+] Building 0.0s (0/2) docker:defaultREPOSITORY TAG IMAGE ID CREATED SIZEmaximofn/multistagebuild latest 7fb090d1495d 8 minutes ago 13.6MB
Podemos ver la imagen que acabamos de crear y que solo pesa 13.6 MB. Vamos a bajarnos la imagen de Python para ver cuanto pesa
!docker pull python:3.9-alpine
3.9-alpine: Pulling from library/pythona8db6415: Already existsd5e70e42: Already exists3fe96417: Already existsaa4dddbb: Already exists518be9f7: Already exists Digest: sha256:6e508b43604ff9a81907ec17405c9ad5c13664e45a5affa2206af128818c7486Status: Downloaded newer image for python:3.9-alpinedocker.io/library/python:3.9-alpine
!docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZEmaximofn/multistagebuild latest 7fb090d1495d 9 minutes ago 13.6MBpython 3.9-alpine 6946662f018b 9 days ago 47.8MB
Podemos ver que mientras nuestra imagen pesa solo 13.6 MB, la de python con la que ha construido la aplicación pesa 47.8 MB. Por lo que podemos sacar dos conclusiones, con la primera imagen, la de python, ha construido la aplicación, ha generado el ejecutable y ese ejecutable es el que usamos en la segunda imagen, la de alpine. Además podemos ver que aunque la primera imagen que usa es la de python no se descarga en nuestro sistema, ya que la hemos tenido que descargar nosotros
Pues ya solo queda probarlo
!docker run --rm --name multi_stage_build maximofn/multistagebuild
Hello from Alpine!
Funciona!
Multi arch builds
Supongamos que queremos hacer una imagen que queremos que se pueda ejecutar en un ordenador y en una raspberry, el ordenador probablemente tenga un micro con arquitectura AMD64, mientras que la raspberry tiene un micro con arquitectura ARM. Por lo que no podemos crear la misma imagen para los dos, es decir, cuando creamos una imagen la creamos con un Dockerfile
que suele empezar así
FROM ...
Por lo que el Dockerfile
de la imagen del ordenador podría empezar así
FROM ubuntu:latest
Mientras que el de la raspberry podría empezar así
FROM arm64v8/ubuntu:latest
Tendríamos que crear dos archivos Dockerfile
compilarlos y en el ordenador usar una imagen y en la raspebrry usar otra
Para evitar tener que ver la arquitectura del ordenador y ver qué imagen tenemos que usar Docker creo los manifest
, que como su nombre indica es un manifiesto que indica en función de qué arquitectura de micro tengamos usa una imagen u otra
Así que vamos a ver cómo hacer esto
En primer lugar creamos una carpeta donde vamos a crear nuestros archivos Dockerfile
!mkdir docker_multi_arch
Ahora creamos los dos dockerfiles
!mkdir docker_multi_arch!cd docker_multi_arch && touch Dockerfile_arm64 Dockerfile_amd64
Escribimos del Dockerfile
para AMD64
!mkdir docker_multi_arch!cd docker_multi_arch && touch Dockerfile_arm64 Dockerfile_amd64!cd docker_multi_arch && echo "FROM ubuntu:20.04" >> Dockerfile_amd64 && echo "CMD echo 'Hello from amd64'" >> Dockerfile_amd64
!mkdir docker_multi_arch!cd docker_multi_arch && touch Dockerfile_arm64 Dockerfile_amd64!cd docker_multi_arch && echo "FROM ubuntu:20.04" >> Dockerfile_amd64 && echo "CMD echo 'Hello from amd64'" >> Dockerfile_amd64!cd docker_multi_arch && echo "FROM arm64v8/ubuntu:latest" >> Dockerfile_arm && echo "CMD echo 'Hello from ARM'" >> Dockerfile_arm
Ahora compilamos las dos imágenes
!mkdir docker_multi_arch!cd docker_multi_arch && touch Dockerfile_arm64 Dockerfile_amd64!cd docker_multi_arch && echo "FROM ubuntu:20.04" >> Dockerfile_amd64 && echo "CMD echo 'Hello from amd64'" >> Dockerfile_amd64!cd docker_multi_arch && echo "FROM arm64v8/ubuntu:latest" >> Dockerfile_arm && echo "CMD echo 'Hello from ARM'" >> Dockerfile_arm!cd docker_multi_arch && docker build -t maximofn/multiarch:arm -f Dockerfile_arm .
[+] Building 0.0s (0/1) docker:default[+] Building 0.2s (2/3) docker:default=> [internal] load build definition from Dockerfile_amd64 0.1s=> => transferring dockerfile: 89B 0.0s=> [internal] load .dockerignore 0.1s=> => transferring context: 2B 0.0s=> [internal] load metadata for docker.io/library/ubuntu:20.04 0.1s[+] Building 0.3s (2/3) docker:default=> [internal] load build definition from Dockerfile_amd64 0.1s=> => transferring dockerfile: 89B 0.0s=> [internal] load .dockerignore 0.1s=> => transferring context: 2B 0.0s=> [internal] load metadata for docker.io/library/ubuntu:20.04 0.2s[+] Building 0.5s (2/3) docker:default=> [internal] load build definition from Dockerfile_amd64 0.1s=> => transferring dockerfile: 89B 0.0s=> [internal] load .dockerignore 0.1s=> => transferring context: 2B 0.0s=> [internal] load metadata for docker.io/library/ubuntu:20.04 0.4s[+] Building 0.6s (2/3) docker:default=> [internal] load build definition from Dockerfile_amd64 0.1s=> => transferring dockerfile: 89B 0.0s=> [internal] load .dockerignore 0.1s=> => transferring context: 2B 0.0s=> [internal] load metadata for docker.io/library/ubuntu:20.04 0.5s...=> => transferring context: 2B 0.0s=> [internal] load build definition from Dockerfile_arm 0.0s=> => transferring dockerfile: 94B 0.0s=> [internal] load metadata for docker.io/arm64v8/ubuntu:latest 1.8s=> [auth] arm64v8/ubuntu:pull token for registry-1.docker.io 0.0s=> CACHED [1/1] FROM docker.io/arm64v8/ubuntu:latest@sha256:94d12db896d0 0.0s=> exporting to image 0.0s=> => exporting layers 0.0s=> => writing image sha256:a9732c1988756dc8e836fd96e5c9512e349c97ea5af46 0.0s=> => naming to docker.io/maximofn/multiarch:arm 0.0s
Vamos a ver que tenemos las ods imágenes compiladas
!docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZEmaximofn/multiarch arm a9732c198875 4 weeks ago 69.2MBmaximofn/multiarch amd64 5b612c83025f 6 weeks ago 72.8MB
Vemos que hemos compilado las dos imágenes. Para poder crear un manifest, primero tenemos que subir las imágenes a docker hub, así que las subimos
!docker push maximofn/multiarch:amd64
The push refers to repository [docker.io/maximofn/multiarch]82bdeb5f: Mounted from library/ubuntu amd64: digest: sha256:30e820f2a11a24ad4d8fb624ae485f7c1bcc299e8cfc72c88adce1acd0447e1d size: 529
!docker push maximofn/multiarch:arm
Si me voy a mi docker hub puedo ver que mi imagen maximofn/multiarch
tiene los tags amd64
y arm
Ahora vamos a crear el manifest
basado en estas dos imágenes
!docker push maximofn/multiarch:arm!docker manifest create maximofn/multiarch:latest maximofn/multiarch:amd64 maximofn/multiarch:arm
The push refers to repository [docker.io/maximofn/multiarch]Created manifest list docker.io/maximofn/multiarch:latest
Una vez creado, tenemos que indicar las arquitecturas de las CPUs a las que corresponde cada uno
!docker manifest annotate maximofn/multiarch:latest maximofn/multiarch:amd64 --os linux --arch amd64
!docker manifest annotate maximofn/multiarch:latest maximofn/multiarch:amd64 --os linux --arch amd64!docker manifest annotate maximofn/multiarch:latest maximofn/multiarch:arm64 --os linux --arch arm64
manifest for image maximofn/multiarch:arm64 does not exist in maximofn/multiarch:latest
Una vez creado y anotado podemos subir el manifest
a docker hub
!docker manifest push maximofn/multiarch:latest
sha256:1ea28e9a04867fe0e0d8b0efa455ce8e4e29e7d9fd4531412b75dbd0325e9304
Si ahora vuelvo a mirar los tags que tiene mi imagen maximofn/multiarch
veo también la de latest
Ahora, tanto si quiero usar mi imagen desde una máquina con CPU AMD64 o CPU ARM con hacer FROM maximofn/multiarch:latest
, docker comprobará la arquitectura de la CPU y bajará el tag amd64
o el tag arm
. Vamos a verlo, si desde mi ordenador ejecuto la imagen obtengo
!docker run maximofn/multiarch:latest
Como no la tiene se la baja y luego aparece el texto Hello from amd64
, ya que la CPU de mi ordenador tiene una arquitectura AMD64
Si ahora me conecto por ssh a una raspberry pi y pruebo lo mismo obtengo
raspiberry@raspberrypi:~ $ docker run maximofn/multiarch:latest
Unable to find image 'maximofn/multiarch:latest' locally
latest: Pulling from maximofn/multiarch
Digest: sha256:1ea28e9a04867fe0e0d8b0efa455ce8e4e29e7d9fd4531412b75dbd0325e9304
Status: Downloaded newer image for maximofn/multiarch:latest
Hello from ARM
Aparece Hello from ARM
ya que la raspberry tiene un micro con arquitectura ARM
Como se puede ver cada máquina se ha bajado la imagen que necesitaba
Escritura correcta de Dockerfiles avanzado
Ya vimos la manera de escribir correctamente dockerfiles, pero hay una cosa más que podemos hacer ahora que conocemos el multi-stage build y es crear un contenedor para crear el ejecutable y otro más pequeño para ejecutarlo
Llegamos a la conclusión de que un buen dockerfile podía ser este
FROM python:3.9.18-alpine
WORKDIR /sourceCode/sourceApp
COPY ./sourceCode/sourceApp .
CMD ["python3", "app.py"]
Vamos a crear ahora un ejecutable en un contenedor builder y en otro más pequeño lo ejecutamos
FROM python:3.9.18-alpine as builder
WORKDIR /sourceCode/sourceApp
RUN apk add --no-cache gcc musl-dev libc-dev && pip install pyinstaller
COPY ./sourceCode/sourceApp .
RUN pyinstaller --onefile app.py
FROM alpine:3.18.3
WORKDIR /sourceCode/sourceApp
COPY --from=builder /sourceCode/sourceApp/dist/app .
CMD ["./app"]
Creamos el código de python en la ruta necesaria
!docker run maximofn/multiarch:latest!mkdir multistagebuild/sourceCode!mkdir multistagebuild/sourceCode/sourceApp!touch multistagebuild/sourceCode/sourceApp/app.py!echo 'print("Hello from Alpine!")' > multistagebuild/sourceCode/sourceApp/app.py
Ahora compilamos la imagen
!docker run maximofn/multiarch:latest!mkdir multistagebuild/sourceCode!mkdir multistagebuild/sourceCode/sourceApp!touch multistagebuild/sourceCode/sourceApp/app.py!echo 'print("Hello from Alpine!")' > multistagebuild/sourceCode/sourceApp/app.py!docker build -t maximofn/multistagebuild:alpine-3.18.3 ./multistagebuild
Unable to find image 'maximofn/multiarch:latest' locally[+] Building 0.0s (0/0) docker:default[+] Building 0.0s (0/1) docker:default[+] Building 0.2s (3/5) docker:default=> [internal] load build definition from Dockerfile 0.1s=> => transferring dockerfile: 357B 0.0s=> [internal] load .dockerignore 0.1s=> => transferring context: 2B 0.0s=> [internal] load metadata for docker.io/library/alpine:3.18.3 0.1s=> [internal] load metadata for docker.io/library/python:3.9.18-alpine 0.1s=> [auth] library/alpine:pull token for registry-1.docker.io 0.0s[+] Building 0.3s (3/5) docker:default=> [internal] load build definition from Dockerfile 0.1s=> => transferring dockerfile: 357B 0.0s