(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:
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"
Luego instalamos kubectl:
sudo install -o root -g root -m 0755 kubectl / usr / local / bin / kubectl
Finalmente comprobamos que la instalacion fue exitosa imprimiendo la versión de kubectl en el sistema:
kubectl version --client
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
Luego podemos iniciar nuestro clúster con el comando:
minikube start
Si todo está correcto, nuestro clúster de Kubernetes se habilitará y deberíamos ver algo como esto:
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
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
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
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:
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==
Luego necesitamos ejecutar este archivo con kubectl, para generar nuestro componente:
kubectl apply -f sm-mysql-secret.yaml
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
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
Luego necesitamos ejecutar este archivo con kubectl, para generar nuestros componentes:
kubectl apply -f sm-mysql-pv.yaml
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/
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
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
Luego necesitamos ejecutar este archivo con kubectl, para generar nuestro componente:
kubectl apply -f sm-mysql-deploy.yaml
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
apiVersion: v1
kind: Service
metadata:
name: sm-mysql
spec:
ports:
- protocol: TCP
port: 3306
targetPort: 3306
selector:
app: sm-mysql
Entonces necesitamos ejecutar este archivo con kubectl, para generar nuestro componente:
kubectl apply -f sm-mysql-deploy.yaml
4.4) Test
Podemos ver si nuestros componentes se crearon correctamente y se están ejecutando actualmente:
kubectl get secrets
kubectl get deployments
kubectl get services
kubectl get pods
Podemos entrar a la terminal del Pod:
kubectl exec -it sm-mysql-57fbb49dd-47pvf /bin/bash
Ahora que estamos dentro, podemos ejecutar el comando mysql para ingresar al proceso de la base de datos:
mysql -u root -p root
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;
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
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
Luego necesitamos ejecutar este archivo con kubectl, para generar nuestro componente:
kubectl apply -f sm-phpmyadmin-deploy.yaml
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
apiVersion: v1
kind: Service
metadata:
name: sm-phpmyadmin
spec:
type: NodePort
selector:
app: sm-phpmyadmin
ports:
- protocol: TCP
port: 80
targetPort: 80
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
5.3) Test
Podemos ver si nuestros componentes se crearon correctamente y se están ejecutando actualmente:
kubectl get deployments
kubectl get services
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
A la derecha podemos ver la URL completa para acceder al panel de phpmyadmin. Lo copiamos y pegamos en el navegador:
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
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
Luego necesitamos ejecutar este archivo con kubectl, para generar nuestro componente:
kubectl apply -f sm-nodejs-deploy.yaml
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)
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
apiVersion: v1
kind: Service
metadata:
name: sm-nodejs
spec:
type: NodePort
selector:
app: sm-nodejs
ports:
- protocol: TCP
port: 8080
targetPort: 8080
Luego necesitamos ejecutar este archivo con kubectl, para generar nuestro componente:
kubectl apply -f sm-nodejs-service.yaml
6.3) Test
Podemos ver si nuestros componentes se crearon correctamente y se están ejecutando actualmente:
kubectl get deployments
kubectl get services
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
A la derecha podemos ver la URL completa para acceder a la API de NodeJS. Lo copiamos y pegamos en el navegador:
Está funcionando y está recibiendo correctamente las solicitudes. Podemos probarlo yendo a la ruta de los estudiantes en nuestro navegador:
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
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:
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:
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:
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:
8) Probando nuestra aplicacion
Ahora podemos hacer una peticion GET para sacar a nuestros estudiantes con Postman:
Podemos hacer una peticion POST para registrar un estudiante:
Podemos obtener a nuestros estudiantes con el navegador, que realiza solicitudes GET de forma predeterminada:
Podemos editar un alumno con una peticion PUT:
Podemos obtener un alumno específico:
Finalmente podemos eliminar a un estudiante:
Podemos visualizar en el navegador el estudiante restante tras la eliminacion previa:
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
Ahora verificamos que hemos escalado con éxito nuestra aplicación Node.js:
kubectl get deployments
Podemos ver que la implementación de "sm-nodejs" tiene 3/3 pods en ejecución.
kubectl get 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!
Top comments (0)