Guía Practica de Contenedores: Administrando nuestra aplicación con Kubernetes (3/3)

crisemcon

crisemcon

Posted on October 4, 2021

Guía Practica de Contenedores: Administrando nuestra aplicación con Kubernetes (3/3)

(Esta es la ultima 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

En esta guía, migraremos nuestra API multicontenedor de manejo de estudiantes de Docker Compose, que usaba Node.js, MySQL y PhpMyAdmin, a una arquitectura de Kubernetes, la cual es visualizada en el siguiente diagrama:

kubernetes architecture


1) Requisitos

Primero instalaremos las tecnologías necesarias para el desarrollo de esta guia: Kubectl y Minikube.
Si ya posees las tecnologias mencionadas anteriormente y las de la primera y segunda parte de la guia, puedes saltarte a la seccion 2

1.1) Instalando Kubectl

Necesitamos una herramienta de línea de comandos para interactuar y ejecutar comandos en clústeres de Kubernetes. Kubectl se utiliza para implementar aplicaciones, inspeccionar y administrar los recursos del clúster.
Entonces, descargamos la última versión de lanzamiento de kubectl con el comando:

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
Enter fullscreen mode Exit fullscreen mode

Luego instalamos kubectl:

sudo install -o root -g root -m 0755 kubectl / usr / local / bin / kubectl
Enter fullscreen mode Exit fullscreen mode

Finalmente comprobamos que la instalacion fue exitosa imprimiendo la versión de kubectl en el sistema:

kubectl version --client
Enter fullscreen mode Exit fullscreen mode

kubectl version

Mas detalles acerca de este paso en: https://kubernetes.io/es/docs/tasks/tools/install-kubectl/


1.2) Instalando Minikube

Ahora necesitamos un entorno de Kubernetes local. Como requisito debemos tener un administrador de contenedores o máquinas virtuales para este paso.
Actualmente tenemos Docker, que es un administrador de contenedores, por lo que podemos continuar con la instalación de minikube:

curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube_latest_amd64.deb
sudo dpkg -i minikube_latest_amd64.deb
Enter fullscreen mode Exit fullscreen mode

Luego podemos iniciar nuestro clúster con el comando:

minikube start
Enter fullscreen mode Exit fullscreen mode

Si todo está correcto, nuestro clúster de Kubernetes se habilitará y deberíamos ver algo como esto:
minikube start

Mas detalles acerca este paso en: https://minikube.sigs.k8s.io/docs/start/

Es posible que este comando no funcione y arroje un error como este: "Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.39/containers/json: dial unix /var/run/docker.sock: connect: permission denied”. Esto se debe a que necesitamos privilegios sudo para ejecutar el comando Docker. Incluso si ejecutamos sudo minikube start, no lo aceptará porque no es seguro ejecutarlo en modo sudo. Para resolver este problema, debemos agregar nuestra cuenta de usuario al grupo de Docker:

sudo groupadd docker
sudo usermod -aG docker $USER
newgrp docker
Enter fullscreen mode Exit fullscreen mode

Mas detalles acerca de este error en: https://linuxhandbook.com/docker-permission-denied/


2) Descripción general de los componentes de Kubernetes para nuestro sistema

kubernetes components overview

Estos son los componentes que vamos a implementar para nuestro sistema de gestión de estudiantes.
Para comprender este diagrama, debemos aprender los conceptos principales de Kubernetes:

  • Node: un nodo puede ser una máquina virtual o física, según el clúster. Cada nodo contiene los servicios necesarios para ejecutar Pods, administrados por el plano de control.

  • Pod: la unidad básica de trabajo. Los pods son las unidades de computación implementables más pequeñas que se pueden crear y administrar en Kubernetes. Los pods casi nunca se crean por sí mismos, sino que los controladores de pods hacen todo el trabajo real.

  • Service: una forma abstracta de exponer una aplicación que se ejecuta en un conjunto de Pods como un servicio de red.

  • Deployment: forma más común de obtener su aplicación en Kubernetes. Crea un ReplicaSet (que crea un conjunto estable de pods) para cada especificación de implementación.

  • Secret: le permiten almacenar y administrar información confidencial, como contraseñas, tokens OAuth y claves ssh.

  • Volume: es esencialmente un directorio accesible para todos los contenedores que se ejecutan en un pod. A diferencia del sistema de archivos local del contenedor, los datos en volúmenes se conservan durante los reinicios del contenedor.

  • Persistent Volume (PV): es un recurso de todo el clúster que puede usar para almacenar datos de manera que persistan más allá de la vida útil de un pod. El PV no está respaldado por un almacenamiento conectado localmente en un nodo trabajador, sino por un sistema de almacenamiento en red.

  • Persistent Volume Claim (PVC): es la solicitud para suministrar almacenamiento persistente con un tipo y configuración específicos.

Para obtener más información, lea la documentación de Conceptos de Kubernetes: https://kubernetes.io/docs/concepts/


3) Componente Secret

Primero, vamos a construir nuestro componente secreto, que contendrá nuestros datos privados como la contraseña de root de MySQL, el usuario de MySQL, la contraseña del usuario de MySQL y el nombre de la base de datos de MySQL.

touch sm-mysql-secret.yaml
Enter fullscreen mode Exit fullscreen mode

Antes de comenzar a escribir en el archivo, debemos recordar que el componente secreto requiere que los valores proporcionados estén codificados en base64. Entonces, necesitamos codificar nuestros datos privados de la siguiente manera:
secrets base64
Ahora podemos escribir las credenciales en nuestro archivo:

apiVersion: v1
kind: Secret
metadata:
        name: sm-mysql-secret
type: Opaque
data:
        sm-mysql-root-pass: cm9vdA==
        sm-mysql-smuser: c211c2Vy
        sm-mysql-smpass: c21wYXNz
        sm-mysql-db: c3R1ZGVudG1hbmFnZW1lbnRkYg==
Enter fullscreen mode Exit fullscreen mode

Luego necesitamos ejecutar este archivo con kubectl, para generar nuestro componente:

kubectl apply -f sm-mysql-secret.yaml
Enter fullscreen mode Exit fullscreen mode

4) Componentes MySQL

A continuacion implementaremos los componentes de Kubernetes relativos a la base de datos MySQL.

4.1) Persistent Volume and Persistent Volume Claim

Primero, debemos configurar los componentes de reclamo de Volumen persistente y Volumen persistente. Esto se puede hacer en el mismo archivo separando ambos con “---”.

touch sm-mysql-pv.yaml
Enter fullscreen mode Exit fullscreen mode
apiVersion: v1
kind: PersistentVolume
metadata:
 name: sm-mysql-pv-volume
 labels:
        type: local
spec:
 storageClassName: manual
 capacity:
        storage: 1Gi
 accessModes:
        - ReadWriteOnce
 hostPath:
        path: "/mnt/data"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
 name: sm-mysql-pv-claim
spec:
 storageClassName: manual
 accessModes:
        - ReadWriteOnce
 resources:
        requests:
         storage: 1Gi
Enter fullscreen mode Exit fullscreen mode

Luego necesitamos ejecutar este archivo con kubectl, para generar nuestros componentes:

kubectl apply -f sm-mysql-pv.yaml
Enter fullscreen mode Exit fullscreen mode

Hemos configurado el Volumen, que tiene 1 Gb de capacidad de almacenamiento y está guardando los datos localmente en el Clúster de Minikube.

Lo podemos ver en la terminal con los comandos:

minikube ssh
cd ../..
cd mnt/data/
Enter fullscreen mode Exit fullscreen mode

4.2) Deployment

Ahora tenemos que configurar el componente Deployment. Esto creará un pod que ejecutará un contenedor con una imagen de MySQL 5.7.
Vamos a utilizar las credenciales almacenadas en nuestro componente Secreto y tenemos que exponer manualmente el puerto "3306", que por defecto está expuesto por el contenedor, pero el pod tiene que conocer esta información. Además, vamos a vincular nuestro Volumen Persistente y reclamarlo con el PVC.

touch sm-mysql-deploy.yaml
Enter fullscreen mode Exit fullscreen mode
apiVersion: apps/v1
kind: Deployment
metadata:
 name: sm-mysql
spec:
 selector:
        matchLabels:
         app: sm-mysql
 strategy:
        type: Recreate
 template:
        metadata:
         labels:
           app: sm-mysql
        spec:
         containers:
         - image: mysql:5.7
           name: sm-mysql
           env:
           - name: MYSQL_ROOT_PASSWORD
             valueFrom:
               secretKeyRef:
                 name: sm-mysql-secret
                 key: sm-mysql-root-pass
           - name: MYSQL_USER
             valueFrom:
               secretKeyRef:
                 name: sm-mysql-secret
                 key: sm-mysql-smuser
           - name: MYSQL_PASSWORD
             valueFrom:
               secretKeyRef:
                 name: sm-mysql-secret
                 key: sm-mysql-smpass
           - name: MYSQL_DATABASE
             valueFrom:
               secretKeyRef:
                 name: sm-mysql-secret
                 key: sm-mysql-db
           ports:
           - containerPort: 3306
             name: sm-mysql
           volumeMounts:
           - name: sm-mysql-persistent-storage
             mountPath: /var/lib/mysql
         volumes:
         - name: sm-mysql-persistent-storage
           persistentVolumeClaim:
             claimName: sm-mysql-pv-claim
Enter fullscreen mode Exit fullscreen mode

Luego necesitamos ejecutar este archivo con kubectl, para generar nuestro componente:

kubectl apply -f sm-mysql-deploy.yaml
Enter fullscreen mode Exit fullscreen mode

Nota: El uso del componente Deployment para una base de datos no es escalable, porque la implementación es stateless y una base de datos es un componente stateful. Si queremos tener una base de datos escalable, necesitaremos implementar un StatefulSet en lugar de una implementación, lo que requiere un trabajo adicional para replicar con éxito los pods.


4.3) Service

Necesitamos configurar nuestro servicio MySQL interno, que permitirá la posibilidad de interactuar con otros pods dentro de nuestro clúster.

touch sm-mysql-service.yaml
Enter fullscreen mode Exit fullscreen mode
apiVersion: v1
kind: Service
metadata:
 name: sm-mysql
spec:
 ports:
 - protocol: TCP
        port: 3306
        targetPort: 3306
 selector:
        app: sm-mysql
Enter fullscreen mode Exit fullscreen mode

Entonces necesitamos ejecutar este archivo con kubectl, para generar nuestro componente:

kubectl apply -f sm-mysql-deploy.yaml
Enter fullscreen mode Exit fullscreen mode

4.4) Test

Podemos ver si nuestros componentes se crearon correctamente y se están ejecutando actualmente:

kubectl get secrets
Enter fullscreen mode Exit fullscreen mode

kubectl get secrets

kubectl get deployments
Enter fullscreen mode Exit fullscreen mode

kubectl get deployments

kubectl get services
Enter fullscreen mode Exit fullscreen mode

kubectl get services

kubectl get pods
Enter fullscreen mode Exit fullscreen mode

kubectl get pods

Podemos entrar a la terminal del Pod:

kubectl exec -it sm-mysql-57fbb49dd-47pvf /bin/bash
Enter fullscreen mode Exit fullscreen mode

Ahora que estamos dentro, podemos ejecutar el comando mysql para ingresar al proceso de la base de datos:

mysql -u root -p root
Enter fullscreen mode Exit fullscreen mode

Ingresamos la contraseña del root que es: "root" y una vez dentro, podemos obtener las bases de datos con la siguiente consulta SQL:

SHOW DATABASES;
Enter fullscreen mode Exit fullscreen mode

show databases

Con esto corroboramos que nuestra base de datos studentmanagementdb se creó correctamente.


5) Componentes PhpMyAdmin

A continuacion implementaremos los componentes de Kubernetes relativos al administrador de base de datos PhpMyAdmin.

5.1) Deployment

Tenemos que configurar el componente Deployment. Esto creará un pod que ejecutará un contenedor con una imagen phpmyadmin de DockerHub.
Vamos a exponer el puerto “80” y usar las mismas credenciales para autenticarnos en nuestro servicio MySQL. Además, vamos a conectar esta Deployment al servicio MySQL llamado "sm-mysql".

touch sm-phpmyadmin-deploy.yaml
Enter fullscreen mode Exit fullscreen mode
apiVersion: apps/v1
kind: Deployment
metadata:
 name: sm-phpmyadmin
 labels:
        app: sm-phpmyadmin
spec:
 replicas: 1
 selector:
        matchLabels:
         app: sm-phpmyadmin
 template:
        metadata:
         labels:
           app: sm-phpmyadmin
        spec:
         containers:
           - name: sm-phpmyadmin
             image: phpmyadmin/phpmyadmin
             ports:
               - containerPort: 80
             env:
               - name: PMA_HOST
                 value: sm-mysql
               - name: PMA_PORT
                 value: "3306"
               - name: MYSQL_ROOT_PASSWORD
                 valueFrom:
                   secretKeyRef:
                     name: sm-mysql-secret
                     key: sm-mysql-root-pass
               - name: PMA_USER
                 valueFrom:
                   secretKeyRef:
                     name: sm-mysql-secret
                     key: sm-mysql-smuser
               - name: PMA_PASSWORD
                 valueFrom:
                   secretKeyRef:
                     name: sm-mysql-secret
                     key: sm-mysql-smpass
Enter fullscreen mode Exit fullscreen mode

Luego necesitamos ejecutar este archivo con kubectl, para generar nuestro componente:

kubectl apply -f sm-phpmyadmin-deploy.yaml
Enter fullscreen mode Exit fullscreen mode

5.2) Service

Finalmente, necesitamos configurar nuestro servicio PhpMyAdmin externo, que permitirá la obtener solicitudes desde fuera del clúster con el navegador de nuestra maquina local.

touch sm-phpmyadmin-service.yaml
Enter fullscreen mode Exit fullscreen mode
apiVersion: v1
kind: Service
metadata:
 name: sm-phpmyadmin
spec:
 type: NodePort
 selector:
        app: sm-phpmyadmin
 ports:
 - protocol: TCP
        port: 80
        targetPort: 80
Enter fullscreen mode Exit fullscreen mode

Nota: El uso de NodePort no es seguro para un entorno de producción porque estamos exponiendo el nodo directamente al exterior. Esto solo es adecuado para fines de desarrollo porque es fácil y rápido de implementar, pero tiene problemas de rendimiento y seguridad. En un entorno de producción, necesitaríamos usar el componente Kubernetes Ingress o configurar un servicio LoadBalancer, que es proporcionado por Kubernetes y también por muchos proveedores de la nube.

Luego necesitamos ejecutar este archivo con kubectl, para generar nuestro componente:

kubectl apply -f sm-phpmyadmin-service.yaml
Enter fullscreen mode Exit fullscreen mode

5.3) Test

Podemos ver si nuestros componentes se crearon correctamente y se están ejecutando actualmente:

kubectl get deployments
Enter fullscreen mode Exit fullscreen mode

kubectl get deployments

kubectl get services
Enter fullscreen mode Exit fullscreen mode

kubectl get services

kubectl get pods
Enter fullscreen mode Exit fullscreen mode

kubectl get pods

Ahora podemos acceder al panel de PhpMyAdmin con nuestro navegador. Para encontrar la URL correcta, necesitamos obtener el puerto del nodo que está expuesto por el servicio NodePort en nuestro clúster de Minikube:

minikube service list
Enter fullscreen mode Exit fullscreen mode

minikube service list

A la derecha podemos ver la URL completa para acceder al panel de phpmyadmin. Lo copiamos y pegamos en el navegador:
phpmyadmin


6) Componentes Node.js

A continuacion implementaremos los componentes de Kubernetes relativos al servidor Node.js.

6.1) Deployment

Vamos a utilizar nuestra imagen Docker "nodejs-studentmanagement" generada en la guía anterior. Expondremos el puerto 8080 del contenedor y estableceremos las variables de entorno de nuestro componente Secret, que, como podemos recordar, contiene las credenciales para la conexión MySQL.

touch sm-nodejs-deploy.yaml
Enter fullscreen mode Exit fullscreen mode
apiVersion: apps/v1
kind: Deployment
metadata:
 name: sm-nodejs
spec:
 replicas: 1
 selector:
        matchLabels:
         app: sm-nodejs
 template:
        metadata:
         labels:
           app: sm-nodejs
        spec:
         containers:
           - name: sm-nodejs
             image: nodejs-studentmanagement
             imagePullPolicy: Never
             ports:
               - containerPort: 8080
             env:
               - name: MYSQL_HOST
                 value: sm-mysql
               - name: MYSQL_PORT
                 value: "3306"
               - name: MYSQL_USER
                 valueFrom:
                   secretKeyRef:
                     name: sm-mysql-secret
                     key: sm-mysql-smuser
               - name: MYSQL_PASS
                 valueFrom:
                   secretKeyRef:
                     name: sm-mysql-secret
                     key: sm-mysql-smpass
               - name: MYSQL_DB
                 valueFrom:
                   secretKeyRef:
                     name: sm-mysql-secret
                     key: sm-mysql-db
Enter fullscreen mode Exit fullscreen mode

Luego necesitamos ejecutar este archivo con kubectl, para generar nuestro componente:

kubectl apply -f sm-nodejs-deploy.yaml
Enter fullscreen mode Exit fullscreen mode

Por defecto, minikube carga imágenes de nuestro sistema de gestión de contenedores que en nuestro caso es Docker, por lo que intentará obtener las imágenes de DockerHub. La imagen "nodejs-studentmanagement" se almacena localmente y no está en DockerHub. Por lo tanto, necesitamos especificar eso con la línea "imagePullPolicy: Never" en este archivo yaml. Además, en nuestra terminal necesitamos configurar la variable de entorno del shell para reutilizar el Docker Daemon desde minikube con el comando:

eval $(minikube docker-env)
Enter fullscreen mode Exit fullscreen mode

6.2) Service

Finalmente, necesitamos configurar nuestro servicio externo nodejs, que permitirá la posibilidad de obtener solicitudes desde fuera del clúster con herramientas como Postman o navegador web.

touch sm-nodejs-service.yaml
Enter fullscreen mode Exit fullscreen mode
apiVersion: v1
kind: Service
metadata:
 name: sm-nodejs
spec:
 type: NodePort
 selector:
        app: sm-nodejs
 ports:
        - protocol: TCP
         port: 8080
         targetPort: 8080
Enter fullscreen mode Exit fullscreen mode

Luego necesitamos ejecutar este archivo con kubectl, para generar nuestro componente:

kubectl apply -f sm-nodejs-service.yaml
Enter fullscreen mode Exit fullscreen mode

6.3) Test

Podemos ver si nuestros componentes se crearon correctamente y se están ejecutando actualmente:

kubectl get deployments
Enter fullscreen mode Exit fullscreen mode

kubectl get deployments

kubectl get services
Enter fullscreen mode Exit fullscreen mode

kubectl get services

kubectl get pods
Enter fullscreen mode Exit fullscreen mode

kubectl get pods

Ahora podemos acceder a la ruta la API con nuestro navegador o Postman. Para encontrar la URL correcta, necesitamos obtener el puerto del nodo que está expuesto por el servicio NodePort en nuestro clúster de Minikube:

minikube service list
Enter fullscreen mode Exit fullscreen mode

minikube service list

A la derecha podemos ver la URL completa para acceder a la API de NodeJS. Lo copiamos y pegamos en el navegador:
nodejs url

Está funcionando y está recibiendo correctamente las solicitudes. Podemos probarlo yendo a la ruta de los estudiantes en nuestro navegador:
students route

Nos muestra que no puede realizar la peticion GET. Para ver que esta fallando, revisaremos los registros de nuestro pod sm-nodejs con el siguiente comando:

kubectl logs sm-nodejs-7575777b4b-4p2zl
Enter fullscreen mode Exit fullscreen mode

nodejs logs

El error nos dice que no puede encontrar la tabla student en studentmanagementdb. Eso es correcto porque no creamos ni preparamos la tabla.
Para ello, solo tenemos que configurar nuestra base de datos de la misma forma que lo hicimos en la guía anterior.


7) Configurando la base de datos

Ahora usamos nuestra base de datos "studentmanagementdb" y hacemos una consulta SQL para crear nuestra tabla de estudiantes:
use studentmanagementdb

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));
Enter fullscreen mode Exit fullscreen mode

Además, necesitamos nuestro procedimiento almacenado para identificar las solicitudes de agregar/editar:
mysql stored procedure

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 //
Enter fullscreen mode Exit fullscreen mode

Nos aseguramos de que se haya creado correctamente navegando a la pestaña Rutinas:
Routines

Ahora todo debería estar funcionando. Vamos a crear un alumno desde PhpMyAdmin. Para hacer esto, navegamos a nuestra tabla y luego presionamos la pestaña Insertar:
Add student phpmyadmin


8) Probando nuestra aplicacion

Ahora podemos hacer una peticion GET para sacar a nuestros estudiantes con Postman:

get students

Podemos hacer una peticion POST para registrar un estudiante:

post student

Podemos obtener a nuestros estudiantes con el navegador, que realiza solicitudes GET de forma predeterminada:

get students browser

Podemos editar un alumno con una peticion PUT:

put student
get students after put

Podemos obtener un alumno específico:
get specific student 2

Finalmente podemos eliminar a un estudiante:
delete student

Podemos visualizar en el navegador el estudiante restante tras la eliminacion previa:
get students after delete


9) Escalando el Deployment Node.js

Podemos escalar fácilmente el Deployment de Node.js modificando el archivo sm-nodejs-deploy.yaml, cambiando el atributo "réplicas" a la cantidad deseada.
Otra forma es usando nuestro terminal. Vamos a escalar a 3 réplicas:

kubectl scale deployment sm-nodejs --replicas=3
Enter fullscreen mode Exit fullscreen mode

kubectl scale

Ahora verificamos que hemos escalado con éxito nuestra aplicación Node.js:

kubectl get deployments
Enter fullscreen mode Exit fullscreen mode

get scaled deployments

Podemos ver que la implementación de "sm-nodejs" tiene 3/3 pods en ejecución.

kubectl get pods
Enter fullscreen mode Exit fullscreen mode

get scaled pods

Finalmente, podemos ver que tenemos 3 pods sm-nodejs y 2 de ellos fueron instanciados recientemente, lo que demuestra que nuestro escalado fue exitoso.


Conclusion

En esta oportunidad introducimos una tecnología muy poderosa para organizar y orquestrar contenedores.
Primero, instalamos kubectl, que es una interfaz de línea de comandos para ejecutar comandos en clústeres de Kubernetes. Luego configuramos Minikube, que es una utilidad que podemos usar para ejecutar Kubernetes en nuestra máquina local. Despues, revisamos algunos de los conceptos principales de Kubernetes y luego visualizamos los componentes principales que necesitábamos para configurar nuestra arquitectura de Kubernetes. Luego nos ensuciamos las manos escribiendo los archivos yaml para finalmente tener nuestro sistema funcionando correctamente, como muestra el diagrama de arquitectura. Por ultimo probamos nuestra API con algunos ejemplos y escalamos nuestra aplicación Node.js para ejecutar 3 instancias de la misma.

Espero que te haya sido de utilidad esta serie de guias!

💖 💪 🙅 🚩
crisemcon
crisemcon

Posted on October 4, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related