Guía Practica de Contenedores: Contenerizando nuestra API con Docker y Docker Compose (2/3)
Tags: docker, dockercompose
Published: 2021-10-04 22:15:47

*(Esta es la segunda parte de una guía practica sobre contenedores que abarca el desarrollo de un API CRUD, el uso de Docker para la contenerizacion de la aplicación, la incorporación de Docker Compose para la sincronización de múltiples contenedores, y finalmente Kubernetes para la orquestación de los contenedores que conforman la aplicación que desarrollaremos.) [Codigo Fuente](https://github.com/crisemcon/Guia-Practica-Contenedores)*

En esta oportunidad utilizaremos Docker para construir una imagen de Node.js que incluya nuestra API CRUD desarrollada en la guía anterior. Luego ejecutaremos el contenedor y descubriremos la necesidad de incorporar mas contenedores. Debido a esto, utilizaremos Docker Compose para ejecutar en simultaneo 3 contenedores: Uno ejecutando nuestro servidor Node.js, otro ejecutando MySQL y el ultimo ejecutando PhpMyAdmin. Esto se puede visualizar en el siguiente diagrama:

![docker compose diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bzptxt0dokks9u0lu8ye.png)

------------------------------------------

##1) Requisitos

Primero instalaremos las tecnologías necesarias para el desarrollo de esta guía: Docker y Docker-Compose.
*Si ya posees las tecnologías mencionadas anteriormente y las de la primera parte de la guía, puedes saltarte a la sección 2*


###1.1) Instalando Docker

Existen varios métodos para instalar Docker, pero en esta guía instalaremos Docker usando el repositorio. 
Primero, configuramos el repositorio con los siguientes comandos de terminal: 
```
sudo apt-get update
sudo apt-get install \
   apt-transport-https \
   ca-certificates \
   curl \
   gnupg \
   lsb-release
```
Luego agregamos la clave GPG oficial de Docker con:
```
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
```
El siguiente paso es configurar el repositorio estable: 
```
echo \
  "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
```
Finalmente instalamos la última versión de Docker Engine y Containerd: 
```
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io
```
Podemos verificar que Docker Engine está instalado correctamente ejecutando la imagen hello-world: 
```
sudo docker run hello-world
```
*Mas detalles acerca este paso en: https://docs.docker.com/engine/install/ubuntu/*

------------------------------------------

##1.2) Instalando Docker Compose

Primero, descargamos la versión estable actual de Docker Compose: 
```
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
```
Luego aplicamos permisos ejecutables al binario: 
```
sudo chmod +x /usr/local/bin/docker-compose
```
Finalmente verificamos la versión de Docker Compose para comprobar que la instalacion fue exitosa: 
```
docker-compose --version
```

*Mas detalles acerca este paso en: https://docs.docker.com/compose/install/*

------------------------------------------

##2) Contenerizando nuestra aplicación Node.js

Primero necesitamos crear un Dockerfile (documento de texto que contiene todos los comandos que un usuario puede llamar en la línea de comandos para ensamblar una imagen) en nuestro directorio de proyecto:
```
touch Dockerfile
```

```Dockerfile
FROM node:14

WORKDIR /app

COPY package*.json ./

RUN npm install
RUN npm install -g nodemon

COPY script.js ./

CMD ["nodemon","script.js"]
```
La primera sentencia indica que vamos a utilizar Node.js v14 como nuestra imagen base. 
Luego configuramos nuestro directorio de trabajo, que en nuestro caso será una carpeta llamada “app” y contendrá nuestros archivos. 
A continuación copiamos tanto package.json como package-lock.json en nuestro directorio de trabajo. Este archivo contiene los nombres de los paquetes necesarios para ejecutar nuestra aplicación. 
Luego queremos que el contenedor instale las dependencias con el comando RUN. Además, copiamos nuestro archivo principal “script.js”.
Finalmente le decimos al contenedor que ejecute el comando “nodemon script.js” para iniciar el servidor cuando el contenedor comience a ejecutarse. 

Ahora construimos nuestra imagen con el siguiente comando:
```
sudo docker build -t node-restapi .
```

Deberíamos obtener una respuesta como la siguiente:
![docker build](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ldff5n7akizbv7ehfhel.png)

Como recordamos, configuramos nuestro servidor para que se ejecute en el puerto 8080. Sin embargo, ahora ese será el puerto dentro del contenedor. 
Entonces, cuando queremos ejecutar nuestro contenedor, tenemos que adjuntar un puerto de nuestra máquina local al puerto del contenedor. 
Eso se logra con el parámetro “-p 7000: 8080”. En ese caso, el puerto 7000 se conectará al puerto 8080 del contenedor.
Además, queremos ejecutar el contenedor con una terminal interactiva para imprimir el estado del servidor. Para eso, agregamos el parámetro “-it”. El comando completo es el siguiente:

```
sudo docker run -it -p 7000:8080 node-restapi
```
![docker run -it -p](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ruh9v5ar9xk14e330skr.png)

*En el log mostrado en la terminal podemos apreciar que nuestro contenedor no se pudo conectar correctamente a la base de datos. Esto sucede porque el contenedor no tiene nuestra base de datos, pues está en nuestra máquina local. Esto sera resuelto en la siguiente sección mediante Docker Compose.*

Ahora nuestro contenedor se está ejecutando. Abrimos otra terminal para ver todos los contenedores en ejecución:
```
sudo docker ps
```
![docker ps](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/40w115cbovae13epz583.png)

Si intentamos realizar una petición GET a la ruta http://localhost:7000/students, vemos el siguiente error en la terminal del contenedor: 
![get error docker](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ebldjaya7e6ioibmz506.png)

Esa es una buena señal. Nuestro servidor intenta manejar la petición, pero registra un error porque no puede conectarse a la base de datos para consultar la solicitud. Esto es lo que vamos a resolver en el siguiente apartado.

------------------------------------------


##3) Contenerizando nuestra base de datos y sincronizando nuestra aplicación basada en contenedores con Docker Compose

En esta sección vamos a ejecutar 3 contenedores simultáneamente: 
* Servidor Node.js
* Base de datos MySql
* Administrador de base de datos PhpMyAdmin.

Primero, necesitamos hacer un par de cambios en algunos archivos. Editemos nuestro archivo script.js, específicamente el objeto mysqlConnection: 

Antes:
```javascript
//MySQL details
var mysqlConnection = mysql.createConnection({
 host: "localhost",
 user: "root",
 password: "password",
 database: "studentmanagement",
 multipleStatements: true,
});
```

Despues:
```javascript
var mysqlHost = process.env.MYSQL_HOST || 'localhost';
var mysqlPort = process.env.MYSQL_PORT || '3306';
var mysqlUser = process.env.MYSQL_USER || 'root';
var mysqlPass = process.env.MYSQL_PASS || 'password';
var mysqlDB = process.env.MYSQL_DB || 'studentmanagement';

//MySQL details
var mysqlConnection = mysql.createConnection({
 host: mysqlHost,
 port: mysqlPort,
 user: mysqlUser,
 password: mysqlPass,
 database: mysqlDB,
 multipleStatements: true,
});
```
Este cambio es para usar la variable de entorno del contenedor para configurar el usuario, la contraseña, el puerto, el host y la base de datos de MySQL.

Anteriormente, expusimos el puerto 8080 del contenedor manualmente en la terminal, pero puede ser configurado en el Dockerfile: 

Antes:
```Dockerfile
FROM node:14

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY script.js ./

CMD ["node","script.js"]
```

Despues:
```Dockerfile
FROM node:14

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY script.js ./

EXPOSE 8080

CMD ["node","script.js"]
```

Ahora construimos nuestra nueva imagen con el siguiente comando: 
```
sudo docker build . -t nodejs-studentmanagement
```

Luego vamos a crear el archivo docker-compose.yml, que describe los servicios, redes, variables de entorno y comunicación entre los contenedores: 
```
touch docker-compose.yml
```

Como mencionamos, necesitamos tres servicios: nodejs, mysql y phpmyadmin. Además, vamos a tener dos redes: frontend y backend. La red de backend contendrá los tres servicios y la red de frontend solo el servicio Node.js. 

Además, conectaremos el puerto 30001 al 8080 de nuestro contenedor Node.js y el puerto 30002 al puerto 80 del servicio PhpMyAdmin. El archivo docker-compose.yml debe contener lo siguiente:

*La imagen de MySQL 5.7 habilita por defecto el puerto 3306 para conexiones entrantes, mientras que PhpMyAdmin habilita por defecto el puerto 80 para conexiones entrantes.*

```yml
version: "3.2"
services:
        nodejs:
           build:
               context: .
           image: nodejs-studentmanagement
           depends_on:
               - mysql
           networks:
               - frontend
               - backend
           environment:
               - MYSQL_HOST=mysql-studentmanagement
               - MYSQL_USER=smuser
               - MYSQL_PASS=smpass
               - MYSQL_DB=studentmanagementdb
           volumes:
               - ./www/:/var/www/html
           ports:
               - "30001:8080"
           container_name: nodejs-studentmanagement
        mysql:
           image: mysql:5.7
           networks:
               - backend
           environment:
               - MYSQL_ROOT_PASSWORD=root
               - MYSQL_USER=smuser
               - MYSQL_PASSWORD=smpass
               - MYSQL_DATABASE=studentmanagementdb
           container_name: mysql-studentmanagement
        phpmyadmin:
           image: phpmyadmin/phpmyadmin:4.7
           depends_on:
               - mysql
           networks:
               - backend
           ports:
               - "30002:80"
           environment:
               - PMA_HOST=mysql-studentmanagement
               - PMA_PORT= 3306
           volumes:
               - /sessions
           container_name: phpmyadmin-studentmanagement
networks:
        frontend:
        backend:
```

Finalmente usamos docker-compose para construir y ejecutar los tres contenedores, manteniendo las dependencias entre ellos: 

```
sudo docker-compose up
```

Ahora los tres contenedores están funcionando. 
A continuacion necesitamos configurar la base de datos. Para ello, utilizaremos PhpMyAdmin, que está adjunto al puerto 30002. 
Abrimos nuestro navegador y navegamos a http: //localhost:30002 e iniciamos sesión con las credenciales descritas en el docker-compose.yml:

* User: smuser
* Password: smpass

Ahora usamos nuestra base de datos "studentmanagementdb" y hacemos una consulta SQL para crear nuestra tabla de estudiantes: 
![use studentmanagementdb](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2awofqb6z5qax30crrjc.png)
```sql
CREATE DATABASE studentmanagement;
USE studentmanagement;
CREATE TABLE student (student_id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,student_name VARCHAR(50),student_email VARCHAR(100), student_phone VARCHAR(15));
```
Además, necesitamos nuestro procedimiento almacenado para identificar las solicitudes de agregar/editar: 
![mysql stored procedure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/di68625p7343ail1lqsg.png)
```sql
DELIMITER //

CREATE PROCEDURE `studentAddOrEdit`(
IN _student_id INT,
IN _student_name VARCHAR(50),
IN _student_email VARCHAR(100),
IN _student_phone VARCHAR(15)
)
BEGIN
IF _student_id = 0 THEN
INSERT INTO student(student_name,student_email,student_phone)
VALUES (_student_name,_student_email,_student_phone);
ELSE
UPDATE student
SET
student_name = _student_name,
student_email = _student_email,
student_phone = _student_phone
WHERE student_id = _student_id;
END IF;
SELECT _student_id AS 'student_id';
END //
```

Nos aseguramos de que se haya creado correctamente navegando a la pestaña Rutinas:
![Routines](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5f1twxth48m7fqs9cr9w.png)

------------------------------------------

##4) Probando nuestra API con Postman

Podemos hacer una petición POST para registrar un estudiante con Postman:

![post student](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ux5vk70e56zbyexdt15c.png)

Podemos hacer una petición POST para registrar otro estudiante: 
![post student postman](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b7oghpbenq92njgv1hu9.png)

Podemos realizar una petición GET para obtener los estudiantes con el navegador, que realiza peticiones HTTP GET por defecto al navegar a una dirección:
![get students browser](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/acq021gx7phx1209r787.png)

Podemos editar un alumno con una petición PUT: 
![put student postman](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kc7ca1jgsn5a3l952tg5.png)

Podemos obtener un alumno específico con una petición GET: 
![get student postman](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6wvfbwjrxo0k05bealkl.png)
![get student result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9np9kmp8k4owqsfr4emy.png)

Podemos eliminar a un estudiante con una petición DELETE:
![delete student postman](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u1o8nrn9taz4tc290b11.png)

Finalmente podemos visualizar los estudiantes desde el navegador para verificar el resultado final:
![final phpmyadmin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nr8l7vgn85ocyvm56h5a.png)

------------------------------------------

##Conclusion

En esta segunda parte de la guia utilizamos Docker para construir una imagen de Node.js con nuestro programa y ejecutamos nuestro propio contenedor. Tras esto surgio la necesidad de conectar el contenedor a una base de datos, por lo que utilizamos Docker Compose para ejecutar en simultaneo 3 contenedores: el servidor Node.js, la base de datos MySql y el administrador de base de datos PhpMyAdmin.
En la siguiente guia migraremos nuestra aplicacion multicontenedor de Docker Compose a una arquitectura de Kubernetes. 

Espero que te haya sido de utilidad esta guia y nos vemos en la siguiente!