En este tutorial estará ejecutando una imagen contenerizada de un api escrito en flask, la principal funcionalidad de este microservicio es sumar dos números que llegan por body mediante un POST al endpoint expuesto. Adicionalmente se explicará el uso de variables de entorno y redes en contenedores. El link al repositorio guía puede encontrarlo en el siguiente link

Objetivos

Al finalizar el tutorial el estudiante estará en capacidad de:

Requisitos para desarrollar el tutorial

  1. Para poder desarrollar este tutorial, necesita docker en su máquina local, en caso de que no lo tenga instalado diríjase al tutorial de la semana 1 en el siguiente enlace.

Diríjase a la página al repositorio del curso en el siguiente link. Allí podrá ver una imagen llamada s1-calculadora-numeros. Descargue la imagen a su máquina local con el siguiente comando:

user@192 ~ % docker pull ghcr.io/misw-4301-desarrollo-apps-en-la-nube/s1-calculadora-numeros:vSuma-0.0.1

Luego de ejecutar el comando podrá ver lo siguiente en su terminal:

user@192 ~ % docker pull ghcr.io/misw-4301-desarrollo-apps-en-la-nube/s1-calculadora-numeros:vSuma-0.0.1
vSuma-0.0.1: Pulling from misw-4301-desarrollo-apps-en-la-nube/s1-microservicio-calculadora
9b18e9b68314: Already exists
f46cc1940986: Already exists
c16557d6efc1: Already exists
fec79d09724b: Already exists
ec28093b52f9: Already exists
ad2c97454063: Already exists
4f4fb700ef54: Already exists
d3341da26273: Already exists
b78383c9b4bb: Already exists
Digest: sha256:754433ecdaebf2d0b9613258a9171ea4b99f875ab8cdb8a28a8eeb5ced09b947
Status: Downloaded newer image for ghcr.io/misw-4301-desarrollo-apps-en-la-nube/s1-calculadora-numeros:vSuma-0.0.1

Note que algunas capas de la imagen se reutilizan si ya han descargado imágenes de Python como los del tutorial comandos básicos de docker, esto incrementa la reutilización de componentes reduciendo el uso del disco y acelerando la compilación al permitir que cada paso se almacene en caché. Para validar que la imagen esté descargada con éxito ejecute el siguiente comando en su terminal:

user@192 ~ % docker images

Luego de ejecutarlo podrá observar lo siguiente:

user@192 ~ % docker images
 ghcr.io/misw-4301-desarrollo-apps-en-la-nube/s1-calculadora-numeros   vSuma-0.0.1    cd3584571fe7   5 days ago     57.6MB

Los datos desplegados corresponden al nombre de la imagen , el tag de la imagen descargada, el id asociado a la imagen, la fecha de descarga y el tamaño de la imagen descargada respectivamente.

Si analiza el código usado para crear la imagen en este link, podrá observar que el microservicio se levanta en el puerto 4000, sin embargo al correrlo en la contenedora, este puerto 4000 quedará aislado de su máquina local, al comportarse como un ambiente separado al suyo. Para poder consumir el api desde su máquina local, es necesario enlazar (binding) de un puerto de su máquina anfitriona a un puerto en el contenedor. Para realizar esto ejecute el siguiente comando:

user@192 ~ % docker run -d -p <PUERTO_MAQUINA_LOCAL>:<PUERTO_CONTENEDORA> <IMAGEN>

Para el caso del ejemplo corresponde a :

user@192 ~ % docker run -d -p 4000:4000 ghcr.io/misw-4301-desarrollo-apps-en-la-nube/s1-calculadora-numeros

El argumento -d ejecuta el contenedor en background (detached mode), por lo que no estará atado al terminal y no podrá ver el output de su ejecución. Omita este argumento para ver la salida directamente en el terminal; tener presente que esto bloquea la terminal y deberá usar Ctrl + C o Cmd + C para poder desbloquearla. El argumento -p permite enlazar el puerto de su máquina local con la contenedora.

Luego de correr el comando podrá ver lo siguiente en su terminal:

user@192 ~ % docker run -d -p 4000:4000 ghcr.io/misw-4301-desarrollo-apps-en-la-nube/s1-calculadora-numeros
aed008b5f8b54377e48e8558941b380b15b5e45c9337a6179d6090bdd06e5e1c

El resultado corresponde al id del contenedor corriendo con éxito en el background de su sistema.

Para este punto del tutorial debería tener una imagen del API ejecutándose en una contenedora, para validarlo ejecute el comando de listar los contenedores activos mediante el comando:

user@192 ~ % docker ps

Allí podrá ver lo siguiente:

user@192 ~ % docker ps
CONTAINER ID         IMAGE                                                                     COMMAND              CREATED          STATUS          PORTS                    NAMES
aed008b5f8b5   ghcr.io/misw-4301-desarrollo-apps-en-la-nube/s1-calculadora-numeros   "python view.py"   5 minutes ago   Up 5 minutes   0.0.0.0:4000->4000/tcp   eloquent_bohr

El comando arroja información como :

Luego de verificar que el microservicio está en ejecución, vamos a consumirlo, para esto utilice la plataforma de API con la que se sienta más cómodo (e.i Postman) y ejecute el siguiente curl:

user@192 ~ % curl --location --request POST 'http://localhost:4000/suma' \
--header 'Content-Type: application/json' \
--data-raw '{    
    "num_1" : 2,
    "num_2": 3
}'

Note como estamos consumiendo el servicio /suma ejecutándose en el puerto 4000 (De nuestra máquina local y de la contenedora respectivamente gracias al binding de puertos), para sumar los números 2 y 3. Al ejecutar la consulta anteriormente expuesta podrá ver lo siguiente:

{
    "message": "Estudiante la suma de los dos números es: 5",
    "result": 5
}

Como es esperado se ve el resultado de la suma, con un valor igual a 5, experimente un poco el endpoint y pruebe con otros números de su interés.

Las variables de entorno, son variables dinámicas que pueden afectar al comportamiento de los procesos en ejecución en un microservicio. El API de calculadora está configurado para entregar la respuesta con el mensaje "Estudiante la suma de los dos números es: <RESULTADO>", sin embargo si analiza el código en esta sección, podrá observar que se hace captura de una variable de entorno llamada "user_name" mediante:

user_name = os.getenv("user_name")

Si el usuario no la provee, por defecto tomará el valor de "Estudiante", esta es la razón de porqué se imprime dentro del mensaje de respuesta. Para que imprima el nombre del usuario (En este caso el suyo), haremos uso de las variables de entorno que acepta el microservicio, para esto detenga la contenedora mediante el comando:

user@192 ~ % docker stop <ID_CONTAINER>

Recuerde que el hash corresponde al id del contenedor en ejecución que obtuvo al ejecutar en pasos anteriores mediante el comando docker ps. Luego de ejecutar el comando de frenado de la contenedora, la iniciaremos de nuevo pero pasando una variable de entorno con su nombre, para esto ejecute:

user@192 ~ % docker stop docker run -d -e <VARIABLE>=<VALOR_VARIABLE> -p 4000:4000 

Para el ejemplo el comando queda de la siguiente forma:

user@192 ~ % docker run -d -e user_name='Andrés' -p 4000:4000 ghcr.io/misw-4301-desarrollo-apps-en-la-nube/s1-calculadora-numeros

Note que mediante el flag -e

(Correspondiente a environment) seguido del nombre de la variable de entorno comunicaremos el nombre del usuario que recibe el microservicio. Si vuelve a ejecutar la consulta al microservicio de suma con el curl:

user@192 ~ % curl --location --request POST 'http://localhost:4000/suma' \
--header 'Content-Type: application/json' \
--data-raw '{    
    "numero_1" : 2,
    "numero_2": 3
}'

Podrá ver como resultado el siguiente mensaje:

{
    "message": "Andrés la suma de los dos números es: 5"
}

Note cómo pasamos información desde un ambiente exterior (Máquina local) a un ambiente confinado como lo es la contenedora, esto es posible mientras el código guardado en la imagen está programado para recibir dichas variables de ambiente.

Antes de validar la existencia de las redes, comprobaremos los puertos expuestos por la contenedora, para esto ejecute:

user@192 ~ % docker port <CONTAINER_ID>

En el ejemplo obtendrá lo siguiente:

user@192 ~ % docker port 09dea5d7c4d2
4000/tcp -> 0.0.0.0:4000

Si analiza cuidadosamente podrá ver el puerto que expone su máquina local haciendo el enlace al puerto 4000 de la contenedora mediante el protocolo tcp.

Para ver en qué redes asociadas tiene el contenedor, ejecute el comando docker inspect:

user@192 ~ % docker inspect <CONTAINER_ID>

Allí podrá ver un JSON de respuesta, si navega hasta el final encontrará la sección de "Networks", para el ejemplo se está manejando una red de tipo puente (Bridge), esta utiliza un puente de software que permite que los contenedores conectados a la misma red de puente se comuniquen, mientras proporciona aislamiento de los contenedores que no están conectados a esa red de puente.

"Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "26fb2379715b7cc8ba81d5e806bf9d58f3e6a9c81ee82232426fd321a62f934b",
                    "EndpointID": "3cdd5fa280f5335283b7e1dcd310d4c14c2c6978bd92b5de52f35619296b30cb",
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.2",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:11:00:02",
                    "DriverOpts": null
                }
            }

Si quisiera ver que tipo de redes tiene disponible corriendo en su máquina local asociadas a docker , puede correr el comando:

user@192 ~ % docker network ls

Luego de ejecutarlo podrá ver lo siguiente:

user@192 ~ % docker network ls
26fb2379715b   bridge                           bridge    local
0cce8ab08755   none                             null      local

Este comando enumera todas las redes que conoce el Demon de docker. Esto incluye las redes que se extienden a través de múltiples hosts en un clúster de contenedores.

En este tutorial crearemos una red de tipo "bridge", para crearla ejecute el comando:

user@192 ~ % docker network create -d bridge <NOMBRE_RED>

Para el ejemplo crearemos la red llamada TEST_01, en consola se verá de la siguiente manera:

user@192 ~ % docker network create -d bridge TEST_01
777d2e29061cba0355245dcfc3ee1ff5dbb14bf1acc6cdb5bfa398cf26bb9406

Como resultado obtendrá un hash asociado al id de la red creada. Para validar su correcta creación, ejecute:

user@192 ~ % docker network ls

Obtendrá el siguiente resultado:

user@192 ~ % docker network ls
NETWORK ID     NAME                             DRIVER    SCOPE
777d2e29061c   TEST_01                          bridge    local

Si desea ver más detalles de la red puede ejecutar:

user@192 ~ % docker network inspect <NOMBRE_RED>

Para el ejemplo obtendrá el siguiente resultado:

user@192 ~ % docker network inspect TEST_01
[
    {
        "Name": "TEST_01",
        "Id": "777d2e29061cba0355245dcfc3ee1ff5dbb14bf1acc6cdb5bfa398cf26bb9406",
        "Created": "2022-10-21T22:27:03.477940544Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.20.0.0/16",
                    "Gateway": "172.20.0.1"
                }
            ]
...

Observe cómo este comando arroja propiedades tales como el ID de la red, la fecha de creación, el tipo de red (Correspondiente a bridge), la subnet y el gateway asociado a esta.

Lo siguiente en el ejercicio es asociar la contenedora a la red que acabamos de crear, para esto ejecute el siguiente comando:

user@192 ~ % docker network connect <NOMBRE_RED> <NOMBRE_CONTAINER>

Para el ejemplo se verá de la siguiente forma (Recuerde que el nombre del container es asignado aleatoriamente por docker a menos que usted le especifique uno):

user@192 ~ % docker network connect TEST_01 intelligent_vaughan

Posteriormente comprobaremos que el contenedor quedó asociado correctamente a la red creada, nuevamente mediante el comando:

user@192 ~ % docker inspect <CONTAINER_ID>

Para el caso del ejemplo si busca la sección de red en el JSON de respuesta, obtendrá:

TEST_01": {
                    "IPAMConfig": {},
                    "Links": null,
                    "Aliases": [
                        "09dea5d7c4d2"
                    ],
                    "NetworkID": "777d2e29061cba0355245dcfc3ee1ff5dbb14bf1acc6cdb5bfa398cf26bb9406",
                    "EndpointID": "7f9f3f61c5409ab4b38c7f7c7ca744264aea57db499752b01d17d4230e19f5d8",
                    "Gateway": "172.20.0.1",
                    "IPAddress": "172.20.0.2",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:14:00:02",
                    "DriverOpts": {}
                }
            }
        }
    }

Note como el "NetworkID" coincide con el id obtenido en el comando docker network ls, de igual manera el nombre de la red corresponde al creado por el usuario.

(Si desea leer mas al respecto de docker networks diríjase a este link )

Los volúmenes son el mecanismo preferido para conservar los datos generados y utilizados por los contenedores de Docker. Para crear uno, primero detenga la contenedora corriendo mediante:

user@192 ~ % docker stop <ID_CONTAINER>

El primer paso para crear un volumen es mediante el comando:

user@192 ~ % docker volume create <NOMBRE_VOLUMEN>

Para el ejemplo usaremos uno llamado data:

user@192 ~ % docker volume create data

Luego de ejecutar el comando enunciado anteriormente, si corre el comando:

user@192 ~ % docker volume list
DRIVER    VOLUME NAME
local     data

Podrá ver el volumen que hemos creado. Si desea inspeccionar información adicional asociado al volumen, mediante el comando:

user@192 ~ % docker volume inspect <NOMBRE_VOLUMEN>

Podrá ver información relevante al volumen creado, tal como la fecha de creación, ruta del volumen, drive, entre otros datos relevantes.

En el ejemplo el comando se verá de la siguiente manera:

user@192 ~ % docker volume inspect data
[
    {
        "CreatedAt": "2022-10-21T23:12:22Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/data/_data",
        "Name": "data",
        "Options": {},
        "Scope": "local"
    }
]

Para correr y crear el volumen dentro de la contenedora, ejecute el siguiente comando:

user@192 ~ % docker run -v ~/<DIRECTORIO_MAQUINA_LOCAL>/:<DIRECTORIO_CONTENEDOR> -e <NOMBRE_VARIABLE_ENTORNO>=<VALOR> -d -p <PUERTO_LOCAL>:<PUERTO_CONTENEDORA> <IMAGEN>

El comando para el ejemplo debe verse de la siguiente forma:

user@192 ~ % docker run -v ./data_file/:/app/data_file -e user_name='Andrés' -e write_result='show' -d -p 4000:4000 ghcr.io/misw-4301-desarrollo-apps-en-la-nube/s1-calculadora-numeros

Note cómo corremos la aplicación en detached mode (background) en el puerto 4000 como se ha enunciado en pasos anteriores. El volumen ha sido creado en el directorio local y remoto de la contenedora indicados previamente, para escribir en el volumen creado en el contenedor, necesitaremos una segunda variable de entorno.

Si se fija en el código, en esta sección, la nueva variable corresponde a "write_result", la función de esta es escribir en un archivo txt dentro de la contenedora el resultado de la operación, si el valor de esta no es pasado por el usuario, se asigna el valor de "not_show", es decir no se ejecuta la operación de escritura. Luego de correr el comando entraremos al ambiente creado por docker para validar la creación del volumen. Para entrar a una contenedora ejecute el siguiente comando:

user@192 ~ % docker exec -it <ID_CONTAINER> sh

Al ejecutarlo estaremos entrando al ambiente local de la contenedora, podrá ver como la ruta actual es dentro del proyecto del microservicio llamado "app".

/app #

Si ejecuta en la terminal el comando ls podrá ver el contenido de archivos del proyecto.

/app # ls
Dockerfile        README.md         requirements.txt  view.py

Para validar en la instancia de docker levantada, qué valor tienen las variables puede ejecutar el comando:

/app # env

Esto imprimirá en consola las variables de entorno presentes en la contenedora:

/app # env
HOSTNAME=6db89908909b
PYTHON_PIP_VERSION=22.0.4
SHLVL=1
HOME=/root
OLDPWD=/app
GPG_KEY=E3FF2839C048B25C084DEBE9B26995E310250568
PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/5eaac1050023df1f5c98b173b248c260023f2278/public/get-pip.py
TERM=xterm
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
user_name=Andrés
LANG=C.UTF-8
write_result=show

De igual manera en el directorio /app ejecute el comando ls para listar los directorios presentes en el proyecto, allí podrá ver el volumen creado en pasos anteriores llamado data_file.

/app # ls
Dockerfile        data_file         view.py
README.md         requirements.txt

Luego de validar el estado de las variables de ambiente y del volumen, ejecute nuevamente una llamada al servicio de suma (sin salir de la instancia de docker en la terminal), luego de tener una respuesta exitosa de la operación de suma (desde postman o curl), liste el contenido del volumen.

/app # ls datafile/
resultado.txt     

Como puede ver, se ha ejecutado una operación de escritura en el volumen que hemos montado, creando un file llamado "resultado.txt". Acceda al directorio "data_file", allí ejecute en su terminal cat resultado.txt, podrá ver el contenido que alberga este archivo:

/app # cat resultado.txt
Hola Andrés! Al sumar 2 y 4 se obtiene como resultado 6


Como puede notar, el contendor que ejecuta la imagen soporta operaciones I/O bound como la escritura de archivos, si valida en su máquina local en la ruta que creó el directorio, también estará disponible el archivo resultado.txt con el mismo mensaje que observó dentro de la contenedora. Recuerde que la contenedora que corre la api de calculadora, sigue en funcionamiento en el background, para liberar los recursos debe detenerla mediante:

user@192 ~ % docker stop <ID_CONTAINER>

(Si desea leer mas al respecto de docker volumes diríjase a este link )

Si tiene problemas durante el desarrollo de este tutorial, comuníquese por el canal de slack.

[1] "Docker," Docker Documentation, 29-Sep-2022. [Online]. Available: https://docs.docker.com/engine/reference/commandline/docker/. [Accessed: 30-Sep-2022].

[2] Google docs: Sign-in. [Online]. Available: https://docs.google.com/document/d/1cXfGVZJJHmiD-s3QTlVZrT4zWpDMAaPyNNNEUNylr_c/edit#heading=h.catckqbp3e8n. [Accessed: 30-Sep-2022].