Contenedores e Imágenes
Si está comenzando su jornada con contenedores, es una buena idea comenzar estudiando Docker. Tenemos un material completo disponible aquí en nuestro sitio que aborda ese asunto detalladamente.
Este estudio le ayudará a familiarizarse con la creación de imágenes y a comprender cómo los contenedores funcionan.
El principal objetivo de ese material es enseñar cómo containerizar una aplicación, lo que involucra la creación de una imagen para la misma.
Aunque los contenedores ya existían en Linux mucho antes de Docker, esa herramienta revolucionó la manera como trabajamos con ellos, estableciendo un patrón para la creación de imágenes. Al construir un Dockerfile, estamos en verdad creando una imagen que será ejecutada en un container runtime.
Para mantener esos patrones, fue creada la opencontainers.org. Un Dockerfile es simplemente un archivo que adhiere a esos patrones.
No es función del desarrollador importarse con cual container runtime está instalado en los nodes del cluster que la aplicación ejecuta. Él solo precisa crear imágenes siguiendo la convención y buenas prácticas.
Desde que usemos los patrones del opencontainers.org podemos ejecutar la imagen en varios runtimes diferentes. Puede ser el containerd, o el CRI-O, Podman, Rkt y por ahí va.
A pesar de no ser obligatorio, es recomendable que el archivo que vamos a declarar los pasos para la creación de la imagen sea nombrado Dockerfile. La principal ventaja es que esto elimina la necesidad de especificar el nombre del archivo como un parámetro, ya que ese es el patrón utilizado.
Podemos crear imágenes usando varias herramientas diferentes:
El único requisito es tener un container runtime instalado en la máquina. Esas herramientas simplemente interactúan con el runtime.
La instalación del Docker nos fornece todas esas herramientas en una única instalación, lo que es ideal para ambientes de desarrollo. Sin embargo, para ejecutar los contenedores en un cluster, es mejor evitar instalar todas esas herramientas y optar apenas por lo que es necesario, o sea, un container runtime. Por esa razón, actualmente el containerd es la opción más recomendada.
Entonces para el estudio, haga la instalación del Docker para su sistema operativo.
Imágenes
Si está ejecutando una aplicación en su máquina ¿qué precisó hacer?
1 - Tener un sistema operativo para ejecutar las cosas 2 - Tener el compilador o interpretador del lenguaje. Por ejemplo el java, go, python y por ahí va. 3 - Instalar las dependencias de la aplicación, o sea las bibliotecas, paquetes, etc 4 - Buildar la aplicación para generar los ejecutables 5 - Ejecutar los ejecutables con las variables de ambientes definidas.
Pero al final lo que precisamos realmente es tener un sistema que puedan ejecutar los archivos generados. No precisamos tener el código fuente de la aplicación dentro del contenedor, solo los ejecutables y sus dependencias.
Voy a llamar de Dockerfile el archivo que irá crear la rutina.
1 - Siempre es necesario declarar un FROM
FROM ubuntu
Vamos a buildar ese cara. Cree un archivo Dockerfile con ese contenido.
❯ docker build -t davidpuziol/ubuntu .
DEPRECATED: The legacy builder is deprecated and will be removed in a future release.
Install the buildx component to build images with BuildKit:
https://docs.docker.com/go/buildx/
Sending build context to Docker daemon 317.2MB
Step 1/1 : FROM ubuntu
latest: Pulling from library/ubuntu
fdcaa7e87498: Pull complete
Digest: sha256:562456a05a0dbd62a671c1854868862a4687bf979a96d48ae8e766642cd911e8
Status: Downloaded newer image for ubuntu:latest
---> de52d803b224
Successfully built de52d803b224
Successfully tagged davidpuziol/ubuntu:latest
¿Qué fue hecho? A partir del ubuntu él creó la imagen llamada davidpuziol/ubuntu y nada más. Si ejecutamos esa imagen tenemos el ubuntu allá
❯ docker run -it davidpuziol/ubuntu:latest bash
root@e8e350471d95:/# cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo
# No tenemos el python aquí
root@e8e350471d95:/# whereis python
python:
root@e8e350471d95:/#
la imagen davidpuziol/ubuntu es exactamente la misma imagen del ubuntu. Vea que tiene el mismo tamaño, el mismo IMAGE_ID
❯ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
davidpuziol/ubuntu latest de52d803b224 2 days ago 76.2MB
ubuntu latest de52d803b224 2 days ago 76.2MB
kindest/node <none> 09c50567d34e 2 months ago 956MB
Esa será la imagen inicial que va a usar para comenzar a hacer lo que precisa. Vamos a imaginar que quiero instalar el python en ese ubuntu. Cada comando RUN ejecuta una layer en su contenedor.
Precisamos hacer el update de los repositorios y después instalar el python.
FROM ubuntu:22.04
RUN sudo apt-get update
RUN sudo apt-get install python -y
Buildando esa imagen con el mismo comando arriba tendremos una imagen que hizo update y luego después ya instaló el python. Esa imagen utilizó el ubuntu 22.04. La primera usó el 24.04.
Todo comando no debe tener interacción con el usuario, recuerde eso. Por eso el -y fue pasado en el parámetro install.
Buildando nuevamente, vamos a definir una tag. Cuando no definimos la tag es latest
docker build -t davidpuziol/ubuntu:v1 .
# va a mostrar todo el proceso de instalación
# Ejecutando imagen
❯ docker run -it davidpuziol/ubuntu:v1 bash
root@db6927a8b536:/# whereis python3
python3: /usr/bin/python3 /usr/lib/python3 /etc/python3 /usr/share/python3
root@db6927a8b536:/# python3 --version
Python 3.10.12
Pero precisamos que esta imagen tenga variables de ambiente. Vamos a colocar una variable llamada MYVAR=david
FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install python -y
ENV MYVAR=david
# Vamos a cambiar para la v2
docker build -t davidpuziol/ubuntu:v2 .
DEPRECATED: The legacy builder is deprecated and will be removed in a future release.
Install the buildx component to build images with BuildKit:
https://docs.docker.com/go/buildx/
Sending build context to Docker daemon 317.2MB
Step 1/4 : FROM ubuntu:22.04
---> 437ec753bef3
Step 2/4 : RUN apt-get update
---> Using cache
---> 36ece36a9a1c
Step 3/4 : RUN apt-get install python3 -y
---> Using cache # OBSERVE QUE ÉL UTILIZÓ LA IMAGEN QUE ÉL YA TENÍA ANTES
---> 243f9ff2aa56
# ADICIONÓ SOLAMENTE EL ÚLTIMO PASO
Step 4/4 : ENV MYVAR=david
---> Running in 9200241f31f5
---> Removed intermediate container 9200241f31f5
---> ef48a65d545f
Successfully built ef48a65d545f
Successfully tagged davidpuziol/ubuntu:v2
docker run -it davidpuziol/ubuntu:v2 bash
root@ba9c48260bb4:/# env | grep MYVAR
MYVAR=david
root@ba9c48260bb4:/#
hasta ahora nosotros solamente instalamos herramientas, variables de ambiente, pero todavía no colocamos nada para ejecutar.
Cuando definimos para ejecutar la aplicación con el comando docker run -it davidpuziol/ubuntu:v2 bash nos forzamos que el bash sea el entrypoint de ese contenedor. Si el bash parar el contenedor morirá.
La función del contenedor es morir, lo que está aguantando él vivo es el bash que fue pasado. Si el bash para él morirá. Todo contenedor precisa de un entrypoint. El entrypoint es el proceso que irá aguantar el contenedor ejecutando.
FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install python -y
ENV MYVAR=david
ENTRYPOINT ["bash"]
docker build -t davidpuziol/ubuntu:v3 .
DEPRECATED: The legacy builder is deprecated and will be removed in a future release.
Install the buildx component to build images with BuildKit:
https://docs.docker.com/go/buildx/
Sending build context to Docker daemon 317.2MB
Step 1/5 : FROM ubuntu:22.04
---> 437ec753bef3
Step 2/5 : RUN apt-get update
---> Using cache
---> 36ece36a9a1c
Step 3/5 : RUN apt-get install python3 -y
---> Using cache
---> 243f9ff2aa56
Step 4/5 : ENV MYVAR=david
---> Using cache
---> ef48a65d545f
Step 5/5 : ENTRYPOINT ["bash"]
---> Running in 952493e489a0
---> Removed intermediate container 952493e489a0
---> a2a274001f9a
Successfully built a2a274001f9a
Successfully tagged davidpuziol/ubuntu:v3
# No pasamos el comando que él irá ejecutar. Cuando entramos él fue luego al bash
❯ docker run -it davidpuziol/ubuntu:v2
root@e0ae401dc83b:/#
Vamos a observar como la imagen creció.
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
davidpuziol/ubuntu v3 a2a274001f9a 2 minutes ago 158MB # entrypoint no cambió el tamaño, pero cambió el ID
davidpuziol/ubuntu v2 ef48a65d545f 11 minutes ago 158MB # environment no cambia el tamaño, pero cambió el ID
davidpuziol/ubuntu v1 243f9ff2aa56 18 minutes ago 158MB # S0 + python
davidpuziol/ubuntu latest de52d803b224 2 days ago 76.2MB # solamente el SO
ubuntu latest de52d803b224 2 days ago 76.2MB
ubuntu 22.04 437ec753bef3 8 days ago 77.9MB
kindest/node <none> 09c50567d34e 2 months ago 956MB
Observación:
ENTRYPOINT represente en el contenedor del POD el command:
CMD representa en el contenedor del POD los args:
Si queremos crear una imagen que va a dormir 60 segundos
FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install python -y
ENV MYVAR=david
ENTRYPOINT ["sleep"]
CMD ["60"]
Tenemos tb el COPY que irá copiar el contenido de la máquina que está buildando para dentro de la imagen. Ese es el momento que usted irá copiar la aplicación para dentro del contenedor.
FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install python -y
RUN otros comandos de dependencia que usted precisa del python por ejemplo
ENV MYVAR=david
# ORIGEN EN LA MÁQUINA LOCAL DESTINO EN EL CONTENEDOR
copy . /opt/myapp
# misma cosa que hacer un RUN cd /opt/myapp para cambiar de directorio
WORKDIR /opt/myapp
# la puerto del contenedor que usted quiere exponer.
EXPOSE 8080
ENTRYPOINT ["python3"]
CMD ["meuapp.py"]
Eso es el básico que precisa saber. El puerto será la comunicación con la aplicación. Sin exponer un puerto un service en kubernetes no conseguirá alcanzar el contenedor del pod.
Es claro que su aplicación también estará preparada para hacer alguna cosa en el puerto 8080 por eso estamos exponiendo este puerto.
tiene muchos detalles que involucran la creación de una imagen. Ese contenedor arriba está ejecutando con usuario root que no sería ideal para seguridad. Veremos más detalles a lo largo del camino.
Haga un estudio más profundizado sobre Docker que todo quedará más claro.
Confiera más informaciones sobre entrypoint e runtime
Esté familiarizado con los comandos
docker image
docker inspect
docker run -p porthost:postcontainer image:tab
docker run -p porthost:postcontainer --entrypoint meuentrypoint image:tab arg1 arg2 arg3
docker run -e APP=test
# comandos mapeando volúmenes también son necesarios para entender los contenedores y cosas las cosas funcionan dentro de los pods