scieee AI-readable full text Open interactive document viewer

Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes

López Rodríguez, Miguel Ángel

Abstract

El objetivo de este trabajo es el análisis y despliegue de tecnologías de contenedores y bases de datos NoSQL, así como la implementación de una aplicación web práctica. Para ello, se estudiará el gestor de contenedores Podman, abordando su compatibilidad con Docker, su arquitectura y comparativa con otros gestores. Se analizará la base de datos NoSQL Cassandra, incluyendo sus características, arquitectura y modelo de datos. Además, se explorará el despliegue de microservicios utilizando Podman Compose y Kubernetes, proporcionando ejemplos prácticos de uso. Finalmente, se desarrollará una aplicación web para monitorizar el tiempo en lugares, detallando la estructura del front-end, back-end y base de datos, así como su despliegue tanto localmente como en la nube. Las funcionalidades y apariencia de la aplicación serán verificadas a través de pruebas, incluyendo su uso en dispositivos móviles.

Full text

Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las Tecnologías de Telecomunicación Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes Dpto. Ingeniería Telemática Escuela Técnica Superior de Ingeniería Universidad de Sevilla Autor: Miguel Ángel López Rodríguez Tutor: Antonio Jesús Sierra Collado Sevilla, 2024 iii Trabajo Fin de Grado Grado en Ingeniería de las Tecnologías de Telecomunicación Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes Autor: Miguel Ángel López Rodríguez Tutor: Antonio Jesús Sierra Collado Dpto. de Ingeniería Telemática Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2024 v Trabajo Fin de Grado: Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes Autor: Miguel Ángel López Rodríguez Tutor: Antonio Jesús Sierra Collado El tribunal nombrado para juzgar el Proyecto arriba indicado, compuesto por los siguientes miembros: Presidente: Vocales: Secretario: Acuerdan otorgarle la calificación de: Sevilla, 2024 El Secretario del Tribunal vii A mi familia A mis maestros ix Agradecimientos En primer lugar, quisiera agradecer a mi tutor Antonio Jesús Sierra, por permitirme realizar este trabajo y por proponerme ideas y mejoras de este. En segundo lugar, quiero expresar mi profunda gratitud a toda mi familia, quienes siempre han estado ahí para ayudarme y motivarme. A mis padres, Manolo y Pepa, por su infinita paciencia; a mi hermana Diana, por su constante confianza en mí; y especialmente a mi hermano Ale, por convencerme de estudiar este grado, hacerme enamorar de las tecnologías y la telemática, y brindarme su apoyo incondicional en todos los temas académicos. No puedo olvidarme de mi tío Juan y mi abuela Juana, quienes siempre me han motivado a estudiar. Además, quisiera agradecer a todos los compañeros que he conocido durante la carrera, por el apoyo mutuo que nos hemos brindado a lo largo de todo este arduo camino desde el principio. También me gustaría agradecer a aquellos compañeros que se han convertido en amigos para toda la vida, por todos los momentos compartidos durante la carrera: Aldana, Bruno, Cristiano, Cachimber, Rafa, Félix, Jesús, Ocaña, Ernesto, Traca, Markito, JM, Andrés y todos los demás. No puedo olvidar a todos esos amigos que me han acompañado a lo largo de mi vida: Juanma, mi compañero de vida; Inma y Natalia, que siempre han estado en las buenas y en las malas; Leo, Gabino y Salvi, por estar ahí sin importar el tiempo que pase sin vernos. También quiero agradecer por todos los buenos momentos vividos este año a personas como: Manolito, Pedro, Josema y a los guerreros de Cádiz. Por último, agradecer a todas esas personas, profesores y amigos que no he nombrado que han hecho que sea mejor persona y dar siempre la mejor versión de mí mismo. Miguel Ángel López Rodríguez Sevilla, 2024 xvii ÍNDICE DE TABLAS Tabla 1: Equipamiento mínimo necesario para la instalación de Podman 7 Tabla 2: Comparativa con otros gestores de contenedores 8 Tabla 3: Listado de comandos de Podman 15 Tabla 4: Versiones de las tecnologías utilizadas en la aplicación web 83 Tabla 5: Dependencias del Back-End 93 Tabla 6: Dependencias del Front-End 101 xix ÍNDICE DE FIGURAS Figura 1: Diferencias entre contenedor y VM 3 Figura 2: Estructura de un pod 4 Figura 3: Ejecución de contenedor (análisis de protocolos) 5 Figura 4: Lista de mensajes de Wireshark generados [1] 5 Figura 5: Lista de mensajes de Wireshark generados [2] 5 Figura 6: Lista de mensajes de Wireshark generados [3] 6 Figura 7: Lista de mensajes de Wireshark generados [4] 6 Figura 8: Interfaces del S.O tras la instalación 7 Figura 9: Interfaces del S.O tras la ejecución de un contenedor 7 Figura 10: Información y versión de Golang 9 Figura 11: Información y versión de conmon 10 Figura 12: Información y versión de crun 10 Figura 13: Información y versión de Podman (código fuente) 11 Figura 14: Información y versión de Podman (repositorios) 11 Figura 15: Lista completa de comandos del manejo de contenedores de Podman 12 Figura 16: Ejemplo 1 de creación de contenedor 13 Figura 17: Ejemplo 2 de creación de contenedor 14 Figura 18: Comprobación del contenedor del Ejemplo 2 14 Figura 19: Lista completa de comandos del manejo de imágenes de Podman 16 Figura 20: Espacio de trabajo del Ejemplo 1 (Dockerfile) 16 Figura 21: Contenido del fichero Dockerfile del Ejemplo 1 (Dockerfile) 16 Figura 22: Build del Ejemplo 1 (Dockerfile) 17 Figura 23: Comprobación de la creación de la imagen del Ejemplo 1 (Dockerfile) 17 Figura 24: Ejecución de un contenedor con la imagen del Ejemplo 1 (Dockerfile) 17 Figura 25: Pull de imagen de Ubuntu 18 Figura 26: Comprobación del pull 18 Figura 27: Creación de un repositorio (Push) 19 Figura 28: Logearse en Podman (Push) 19 Figura 29: Subir al repositorio el Ejemplo 1 (Push) 20 Figura 30: Comprobación de subida del Ejemplo 1 (Push) 20 Figura 31: Pull de la imagen del Ejemplo 1 subida al repositorio (Pull) 21 Figura 32: Comprobación del Pull de la imagen del Ejemplo 1 (Pull) 21 Figura 33: Deslogeo de Podman y Pull fallido al repositorio (Pull) 21 Figura 34: Tipos de NoSQL 23 Figura 35: Logo de Cassandra 24 Figura 36: Estructura de Cassandra 25 Figura 37: Escritura de datos de Cassandra 27 Figura 38: Ejemplo de columna en Cassandra 29 Figura 39: Ejemplo de fila en Cassandra 30 Figura 40: Ejemplo de column family en Cassandra 30 Figura 41: Ejemplo de Keyspace en Cassandra 31 Figura 42: Pull de la imagen de Cassandra 32 Figura 43: Comprobación del pull de la imagen de Cassandra 32 Figura 44: Ejecución de un contenedor con la imagen de Cassandra 32 Figura 45: Comprobación de la ejecución de un contenedor con la imagen de Cassandra 32 Figura 46: Obtención de la ip del contenedor de Cassandra 33 Figura 47: Descarga de los binarios de cqlsh 33 Figura 48: Conexión a Cassandra utilizando cqlsh 33 Figura 49: Comprobación de la creación de las tablas 34 Figura 50: Insercción correcta en tabla ‘alumnos’ 35 Figura 51: Insercción correcta en tabla ‘profesores’ 36 Figura 52: Insercción correcta en tabla ‘clientes’ 36 Figura 53: Inserción correcta en tabla ‘pedidos’ 36 Figura 54: Creación y elección del keyspace ‘prueba1’ 37 Figura 55: Creación de la tabla ‘ejemplo1’ 38 Figura 56: Descripción de la tabla ‘ejemplo1’ 38 Figura 57: Modificación de la tabla ‘ejemplo1’ 39 Figura 58: Eliminación de la tabla ‘ejemplo1’ 39 Figura 59: Obtener todos los registros de la tabla alumnos 40 Figura 60: Obtener un registro específico filtrando por clave primaria 40 Figura 61: Obtener un registro filtrando por columna (no clave primaria) con ALLOW FILTERING 40 Figura 62: Obtener un registro filtrando por columna ( no clave primaria) sin ALLOW FILTERING 40 Figura 63: Comprobación de obtener todos los registros donde una fila tiene un dato 41 Figura 64: Comprobación de obtener todos los registros de la tabla alumnos con multiples condiciones 41 Figura 65: Comprobación de actualizar una fila por clave primaria (double) 41 Figura 66: Comprobación de actualizar una fila por clave primaria (lista) 41 Figura 67: Comprobación de actualizar una fila por clave primaria (añadir a la lista) 42 Figura 68: Actualizar varios campos de una fila 42 Figura 69: Comprobación de insertar una fila en la tabla 42 Figura 70: Comprobación de insertar en la tabla si no existe 43 Figura 71: Comprobación de eliminar una fila de la tabla 43 Figura 72: Logo Podman Compose 46 xxi Figura 73: Instalación de Podman Compose 47 Figura 74: Comprobación de la instalación de Podman Compose 47 Figura 75: docker-compose.yaml del ejemplo 1 de Podman Compose 50 Figura 76: Build de Podman compose 54 Figura 77: Up de Podman Compose 54 Figura 78: Frontend del Ejemplo1 de Podman Compose 55 Figura 79: Alert del Ejemplo1 de Podman Compose 55 Figura 80: Petición del Ejemplo1 de Podman Compose 55 Figura 81: Logo de Kubernetes 56 Figura 82: Estructura de Kubernetes 57 Figura 83: Objetos de Kubernetes 58 Figura 84: Esquema real de LoadBalancer 61 Figura 85: Logo de kind 62 Figura 86: Comprobación de la instalación de kind 62 Figura 87: Comprobación de la instalación de kubectl 62 Figura 88: Creación de clúster con kind 66 Figura 89: Comprobación de la creación del clúster con kind 66 Figura 90: Información de los nodos con kubectl 66 Figura 91: Información de los namespaces 67 Figura 92: Servicios añadidos al clúster 67 Figura 93: Servicios funcionando en el clúster 67 Figura 94: Información de los pods tras la ejecución de cloud provider 68 Figura 95: Frontend del Ejemplo1 de Kubernetes 68 Figura 96: Alert del Ejemplo 1 de Kubernetes 68 Figura 97: Logo de GKE 69 Figura 98: Creación de la cuenta de Google Cloud Platform 69 Figura 99: Creación de un nuevo proyecto en Google Cloud Platform 70 Figura 100: Selección de Kubernetes Engine 70 Figura 101: Comprobación de la activación de la API de Kubernetes en Google Cloud 70 Figura 102: Selección Kubernetes Engine > Clústeres 70 Figura 103: Cambiar a la creación de un clúster éstandar en GKE 71 Figura 104: Esperando la cración del clúster ‘cluster-prueba’ en GKE 71 Figura 105: Comprobación de la creación del clúster ‘cluster-prueba’ en GKE 71 Figura 106: Comprobación de la instalación de gcloud CLI 72 Figura 107: Autorización de gcloud CLI 72 Figura 108: Obtención de parámetros de conexión del clúster ‘cluster-prueba’ 73 Figura 109: Conexión del clúster ‘cluster-prueba’ con kubectl 73 Figura 110: Lista de clúster tras añadir el nuevo clúster ‘cluster -prueba’ 73 Figura 111: Selección del contexto de kubectl para el clúster ‘cluster-prueba’ 74 Figura 112: Cargando los servicios al clúster real ‘cluster-prueba’ 74 Figura 113: Comprobación de los servicios en el clúster real 74 Figura 114: Comprobación de creación de LoadBalancer en Google Cloud 74 Figura 115: Acceso con sistema Windows a la aplicación web 75 Figura 116: Acceso con sistema Linux (VM) a la aplicación web 75 Figura 117: Logo de la aplicación final 77 Figura 118: Logo de React.js 78 Figura 119: Logo de Next.js 79 Figura 120: Logo de Bootstrap 80 Figura 121: Logo de Node.js + Express.js 82 Figura 122: Logo de Cassandra + Podman 83 Figura 123: Logo de Ubuntu 22.04 LTS (Mate) y VirtualBox 84 Figura 124: Logo de Visual Studio Code 84 Figura 125: Logo de Git y GitHub 85 Figura 126: Logo de npm 85 Figura 127: Logo de herramientas para el despliegue de la aplicación 86 Figura 128: Creación del proyecto de next.js 87 Figura 129: Inicio del proyecto de next.js 89 Figura 130: Inicio del proyecto Back-End y prueba de la API 91 Figura 131: Inicio de la base de datos del proyecto 91 Figura 132: Componente Cabecera 101 Figura 133: Componente Pie 102 Figura 134: Componente SearchBox 102 Figura 135: Componente ChartTodas 103 Figura 136: Estructura de comunicación del sistema 104 Figura 137: Apariencia de Index ( Inicio sesión ) 106 Figura 138: Apariencia de Index ( Recuperar contraseña ) 107 Figura 139: Apariencia de Registro 108 Figura 140: Apariencia de Restablecer contraseña 109 Figura 141: Apariencia de Menú Principal 110 Figura 142: Apariencia de Monitorizar 111 Figura 143: Apariencia de Pronóstico 112 Figura 144: Apariencia de Mi Cuenta 113 Figura 145: Inicio de la aplicación con Podman Compose 116 Figura 146: Prueba de la aplicación con Podman Compose 117 Figura 147: Comprobación de subida de imágenes del proyecto 118 Figura 148: Comprobación de la creación correcta del clúster (kind) 122 Figura 149: Comprobación del funcionamiento de la aplicación en el clúster (kind) 123 Figura 150: Comprobación de la instalación de Cert-Manager 126 xxiii Figura 151: Comprobación de la instalación de Traefik 126 Figura 152: Esquema del clúster kubernetes de la aplicación 129 Figura 153: Comprobación de aplicar los objetos kubernetes al clúster 130 Figura 154: Asociación del dominio a dirección estática 130 Figura 155: Acceso a la aplicación por el dominio 131 Figura 156: Acceso a la aplicación por la dirección IP 131 Figura 157: Acceso correcto del usuario 132 Figura 158: Acceso incorrecto del usuario 132 Figura 159: Reestablecer contraseña usuario y email existente 133 Figura 160: Reestablecer contraseña usuario y email no existente 133 Figura 161: Correo de restablecimiento de contraseña 134 Figura 162: Página de reestablecimiento de contraseña 134 Figura 163: Éxito al reestablecer la contraseña 134 Figura 164: Error al reestablecer la contraseña 135 Figura 165: Comprobación de restablecimiento de contraseña 135 Figura 166: Creación de cuenta 136 Figura 167: Correo de confirmación para crear la cuenta 136 Figura 168: Página de confirmación de la creación de la cuenta 136 Figura 169: Error al crear la cuenta 137 Figura 170: Acceso al menú principal 137 Figura 171: Selección de lugar solo con monitorización básica 138 Figura 172: Selección de lugar con monitorización básica + avanzada 139 Figura 173: Diferentes gráficas de la monitorización avanzada 140 Figura 174: Prueba del zoom en una gráfica 140 Figura 175: Prueba de elegir un lugar nuevo en la sección monitorización avanzada 140 Figura 176: Acceso a la sección Pronóstico por defecto 141 Figura 177: Selección de un lugar en la sección Pronóstico 142 Figura 178: Desmonitorización de un lugar específico 143 Figura 179: Desmonitorización de todos los lugares del usuario 143 Figura 180: Cambio correcto de contraseña 144 Figura 181: Comprobación del cambio de contraseña 144 Figura 182: Errores al cambiar la contraseña 144 Figura 183: Mensaje de confirmar borrar cuenta 144 Figura 184: Cuenta borrada 145 Figura 185: Confirmación del borrado de la cuenta de ‘usuarionuevo’ 145 Figura 186: Botón de ayuda de la sección Monitorizar Lugares 146 Figura 187: Botón de ayuda de la sección Pronóstico 146 Figura 188: Apariencia login y registro en dispositivos móviles 147 Figura 189: Apariencia del menú principal y sección Mi Cuenta en dispositivos móviles 147 Figura 190: Apariencia de secciones Monitorizar Lugares y Pronóstico en dispositivos móviles 148 Figura 191: Apariencia de Restablecer contraseña y Verificar cuenta en dispositivos móviles 148 xxv Análisis del gestor de contenedores Podman 6 TRAMO [3] Figura 6: Lista de mensajes de Wireshark generados [3] Observamos que aparte de TCP, ICMPv6 también se hace uso de mDNS (multicast DNS), protocolo que se utiliza para descubrir y resolver nombres de host en una red local sin depender de un servidor DNS centralizado. Los dispositivos en una red IPv6 pueden anunciarse y descubrirse entre sí mediante la difusión de paquetes multicast a la dirección IPv6 especial ff02::fb. Cuando procedemos a cerrar el contenedor de Ubuntu, observamos con Wireshark los mensajes: TRAMO [4] Figura 7: Lista de mensajes de Wireshark generados [4] Como se puede comprobar, los protocolos involucrados en la comunicación de los servicios básicamente son TCP/IP. 7 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes 2.2.2 Interfaces en el S.O Observamos que al instalar Podman, se nos ha creado una nueva interfaz de red (enp0s3) que será la que se comunique con los contenedores. Figura 8: Interfaces del S.O tras la instalación Cuando lanzamos un contenedor también se nos crean unas nuevas interfaces de red (podman0, veth0if2), las cuales corresponden al socket de podman, para comunicar el equipo con el contenedor y la propia interfaz de red del contenedor. Figura 9: Interfaces del S.O tras la ejecución de un contenedor Esta información la podemos obtener con el comando: ip a 2.3 Equipamiento mínimo necesario A continuación, se muestra la tabla del equipamiento mínimo necesario para la instalación y funcionamiento: Hardware necesario ▪ CentOS 7.5 en adelante ▪ Ubuntu 18.04 LTS en adelante ▪ 2 GB Ram, 2 physical CPU cores Infraestructura de red: ▪ Red de localhost / Acceso a Internet ▪ Socket TCP (‘podman.socket’ para permitir la conexión) Elementos Softwares ▪ Lista de dependencias: https://access.redhat.com/documentation/enus/red_hat_codeready_containers/2.0/htmlsingle/getting_started_guide/index#required-software-packages_gsg S.O (usado) ▪ Ubuntu Versión kernel (usado) ▪ 5.15.0-88-generic Versión del S.O (usado) ▪ 22.04.3 LTS Tabla 1: Equipamiento mínimo necesario para la instalación de Podman Análisis del gestor de contenedores Podman 8 2.4 Comparativa con otros gestores de contenedores A continuación, se mostrará una tabla comparándolo con otros gestores de contenedores: Tabla 2: Comparativa con otros gestores de contenedores 2.5 Instalación de Podman Para instalar Podman [2], tenemos dos opciones: • Instalación desde el código fuente • Instalación desde repositorios o paquetes. Los repositorios pueden no estar siempre actualizados con las últimas características o correcciones de errores. Al compilar desde el código fuente tienes acceso inmediato a las últimas actualizaciones y mejoras, lo que puede ser crucial en entornos de desarrollo o producción donde se requiere estar al día con las últimas tecnologías. Sin embargo, la instalación desde el código fuente también requiere más tiempo y conocimientos técnicos, ya que implica compilar el software manualmente y manejar las dependencias adecuadamente. Esto se ve afectado si el proyecto tiene una estructura de código compleja o si tiene dependencias que deben ser configuradas correctamente. Además, la compilación desde el código fuente puede resultar en una configuración única para cada instalación, lo que puede complicar la gestión y la escalabilidad en entornos de producción con múltiples servidores. A continuación, mostraremos las dos opciones de instalación. (Código fuente y repositorios) Nombre del servicio Podman Docker Kubernetes Linux Containers(LXC) Funcionalidades y diferencias No requiere un demonio en segundo plano, compatible con imágenes de Docker. Usa un demonio, plataforma completa con orquestación, interfaz similar a Podman Sistema de orquestación de contenedores para administrar aplicaciones en clústeres. Ofrece contenedores de nivel más bajo, sin orquestación integrada, mayor flexibilidad. S.O. permitidos Linux, Windows, macOS Linux, Windows, macOS Linux, Windows, macOS Linux Equipo de desarrollo Open Source Open Source Open Source Open Source Dependencias software utilizadas Comentado en el punto anterior Contenedor runtime, un servicio de red y un sistema de almacenamiento. Contenedor runtime, Docker Daemon, Docker CLI. Contenedor Runtime, LXC Daemon, LXC CLI. Licencia Apache License 2.0 Apache License 2.0 Apache License 2.0 GPL 2.0 Lenguaje de programación Go, Shell, Python, C, Perl Go Go, Shell, PowerShell C, Shell, Meson Nivel de uso (aprox.) 10-15% 90-85% 90-85% 5% 9 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes 2.5.1 Instalación por código fuente Primero instalaremos las dependencias que encontramos en la guía de instalación de Podman: https://podman.io/docs/installation Antes de instalar las dependencias actualizaremos el repositorio de Ubuntu: sudo apt-get update Para instalarlas utilizaremos el siguiente comando: sudo apt-get install -y make git gcc build-essential pkgconf libtool libsystemddev libprotobuf-c-dev libcap-dev libseccomp-dev libyajl-dev libgcrypt20-dev gomd2man autoconf python3 automake libglib2.0-dev curl libgpgme-dev libdevmapperdev libbtrfs-dev containernetworking-plugins uidmap slirp4netns protobuf-compiler cargo catatonit Con este comando, tendremos instaladas todas las dependencias necesarias para la instalación de Podman. 2.5.1.1 Instalación del compilador (Golang) Para realizar la instalación de Podman, necesitaremos el compilador Golang, el cual lo instalaremos también desde código fuente. Primeros obtendremos su código fuente en la versión 1.22 con el siguiente comando: wget https://go.dev/dl/go1.22.2.linux-amd64.tar.gz Descomprimimos y movemos el contenido a /usr/local con los siguientes comandos: sudo tar -xvf go1.22.2.linux-amd64.tar.gz sudo mv go /usr/local Configuraremos las variables de entorno para el correcto funcionamiento: export GOROOT=/usr/local/go export GOPATH=$HOME export PATH=$GOPATH/bin:$GOROOT/bin:$PATH Creamos un enlace simbólico del ejecutable de go: sudo ln -s /usr/local/go/bin/go /usr/bin/go Después de este proceso, Golang debería haberse instalado correctamente. Figura 10: Información y versión de Golang 2.5.1.2 Instalación de conmon (Monitorización de contenedores) Necesitaremos conmon para la instalación de Podman; lo instalaremos desde código fuente: Primero descargaremos desde el repositorio oficial de git el código fuente: git clone https://github.com/containers/conmon Cambiaremos directorio de trabajo y estableceremos la variable de entorno GOCACHE en un directorio temporal: cd conmon export GOCACHE=“$(mktemp -d)” Análisis del gestor de contenedores Podman 10 Compilamos: make sudo make podman Creamos el enlace simbólico: sudo ln -s /conmon/bin/conmon /usr/bin/conmon Tras esto conmon debería de estar instalado sin problemas. Figura 11: Información y versión de conmon 2.5.1.3 Instalación de crun (runtime de containers) Instalaremos crun desde código fuente. Clonamos el repositorio y cambiamos de directorio a crun: git clone https://github.com/containers/crun cd crun Utilizamos los scripts para realizar la correcta configuración para instalación con los comandos: ./autogen.sh ./configure Compilamos e instalamos: make sudo make install Tras esto, crun está instalado. Figura 12: Información y versión de crun 2.5.1.4 Instalación de Podman Crearemos el directorio /etc/containers y descargamos los archivos ‘registries.conf’ y ‘policy.json’ desde el repositorio de Fedora para establecer configuraciones comunes para contenedores. sudo mkdir -p /etc/containers sudo curl -L -o /etc/containers/registries.conf https://src.fedoraproject.org/rpms/containers-common/raw/main/f/registries.conf sudo curl -L -o /etc/containers/policy.json https://src.fedoraproject.org/rpms/containers-common/raw/main/f/defaultpolicy.json 11 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes Clonamos el repositorio de Podman y cambiamos a ese directorio: git clone https://github.com/containers/podman/ cd podman Compilamos con el BUILDTAGS=“ selinux seccomp”: make BUILDTAGS=“selinux seccomp” PREFIX=/usr Instalamos Podman: sudo make install PREFIX=/usr Activamos el socket de podman: systemctl enable --now podman.socket Tras la instalación de Podman, para el correcto funcionamiento debemos instalar netavark, que permite la creación de la pila de red para los contenedores. Para ello, clonaremos el proyecto y cambiamos a ese directorio: git clone https://github.com/containers/netavark cd netavark Compilamos e instalamos: make make install PREFIX=/usr También necesitaremos passt, que permite crear a usuarios ‘no root’ usar la pila IPv4. git clone https://passt.top/passt cd passt Compilamos e instalamos: make make install PREFIX=/usr Con todo esto Podman debería de haber sido instalado correctamente desde el código fuente. Figura 13: Información y versión de Podman (código fuente) 2.5.2 Instalación desde paquetes o repositorios Para realizar la instalación desde paquetes o repositorios, bastaría con ejecutar los siguientes comandos: sudo apt-get update sudo apt install podman Con este comando se instalarán todas las dependencias necesarias y se instalará Podman. Figura 14: Información y versión de Podman (repositorios) Análisis del gestor de contenedores Podman 12 2.6 Verificación de funcionalidades de Podman Vamos a explorar las características principales de Podman, ya que ofrece una amplia gama de funcionalidades. 2.6.1 Contenedores En este apartado nos centraremos en el manejo y control de los contenedores de Podman. Podemos obtener todos los comandos y funcionalidades relacionados con las imágenes de Podman con el siguiente comando: podman container –-help Figura 15: Lista completa de comandos del manejo de contenedores de Podman Nos centraremos en las funcionalidades más importantes relacionados con el manejo de contenedores. 2.6.1.1 Crear un nuevo contenedor Para crear un nuevo contenedor utilizaremos cualquiera de los siguientes comandos: podman run [opciones] <imagenId> podman container run [opciones] <imagenId> 13 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes Este comando ofrece numerosas opciones, de las cuales se destacarán las más significativas en las siguientes explicaciones. 1. Gestión de hosts y redes: ▪ --add-host: Permite añadir mapeos personalizados de host a IP. ▪ --network: Conecta un contenedor a una red específica. ▪ --ip: Especifica una dirección IPv4 estática para el contenedor. ▪ --dns: Configura servidores DNS personalizados. 2. Control de recursos: ▪ --cpu-period: Limitan el tiempo de CPU para el contenedor. ▪ --memory: Establecen límites de memoria y de intercambio. ▪ --pids-limit: Establece un límite de procesos para el contenedor. 3. Interacción y ambiente: ▪ -it: Mantiene STDIN abierto y asigna un pseudo-TTY. ▪ -e: Configuran variables de entorno en el contenedor. ▪ -w: Especifican el directorio de trabajo dentro del contenedor. 4. Almacenamiento y persistencia: ▪ -v: Montan volúmenes en el contenedor. ▪ --volumes-from: Monta volúmenes desde otro(s) contenedor(es). 5. Seguridad: ▪ --privileged: Concede privilegios extendidos al contenedor. ▪ --security-opt: Permite configurar opciones de seguridad. 6. Administración y control: ▪ -d: Ejecuta el contenedor en segundo plano. ▪ --name: Asigna un nombre al contenedor. ▪ --restart: Especifica la política de reinicio del contenedor. ▪ --rm: Elimina el contenedor después de la salida. Ejemplo 1 de creación de contenedor Crear contenedor con imagen de Ubuntu con nombre ‘prueba ‘en modo iterativo (-it) podman run -it –name prueba ubuntu Figura 16: Ejemplo 1 de creación de contenedor Análisis del gestor de contenedores Podman 14 Ejemplo 2 de creación de contenedor 2 Crear contenedor con imagen de postgresql con nombre ‘prueba_postgres’ (--name prueba_postgres) en segundo plano (-d), que se elimine una vez que pare el contenedor (--rm), mapeando los puertos 5432 (-p 5432:5432), con la ip 10.88.0.3 (--ip 10.88.0.3), configurando la clave de usuario de postgres a ‘clave’ (-e POSTGRES_PASSWORD=clave), cargando el archivo init.sql a la carpeta de inicialización de la base de datos (-v /home/salas/init.sql:/docker-entrypoint-initdb.d/init.sql) podman run -d --rm --name prueba_postgres -p 5432:5432 --ip 10.88.0.3 -e POSTGRES_PASSWORD=clave -v /home/salas/init.sql:/docker-entrypoint- initdb.d/init.sql postgres Figura 17: Ejemplo 2 de creación de contenedor init.sql CREATE DATABASE tfg; \c tfg; CREATE TABLE datos ( id INTEGER PRIMARY KEY, nombre VARCHAR(100), edad INTEGER ); INSERT INTO datos (id,nombre,edad) VALUES(1,'migue',23); Comprobamos si el contenedor funciona correctamente: Figura 18: Comprobación del contenedor del Ejemplo 2 Observamos que nos conectamos con el usuario ‘postgres’ y la clave ‘clave’ a la base de datos ‘tfg’ y podemos obtener los datos de la tabla ‘datos’ correctamente. 15 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes 2.6.1.2 Manejo de contenedores A continuación, mostraremos los comandos más importantes relacionados con el control de los contenedores. Tabla 3: Listado de comandos de Podman ▪ Listar contenedores en ejecución: - podman ps - podman container ps ▪ Listar todos los contenedores: - podman ps -a - podman container ps -a ▪ Borrar un contenedor: - podman rm <id_contenedor o nombre_contenedor> - podman container rm <id_contenedor o nombre_contenedor> ▪ Borrar todos los contenedores (de forma forzada): - podman rm -f –all - podman container rm -f –all ▪ Iniciar contenedor existente: - podman start <id_contenedor o nombre_contenedor> - podman container start <id_contenedor o nombre_contenedor> ▪ Parar contenedor existente: - podman stop <id_contenedor o nombre_contenedor> - podman container stop <id_contenedor o nombre_contenedor> ▪ Reiniciar contenedor existente: - podman restart <id_contenedor o nombre_contenedor> - podman container restart <id_contenedor o nombre_contenedor> ▪ Renombrar contenedor: - podman rename <id_contenedor o nombre_contenedor> <nombre_nuevo> - podman container rename <id_contenedor o nombre_contenedor> <nombre_nuevo> ▪ Mostrar logs (de forma continua) de un contenedor todos los contenedores (de forma forzada): - podman logs -f <id_contenedor o nombre_contenedor> - podman container logs -f <id_contenedor o nombre_contenedor> ▪ Copiar archivos a un contenedor: - podman cp <ruta_archivo_local> <id_contenedor o nombre_contenedor>:<ruta_contenedor> - podman container cp <ruta_archivo_local> <id_contenedor o nombre_contenedor>:<ruta_contenedor> ▪ Ejecutar comando en contenedor: - podman exec <id_contenedor o nombre_contenedor> <comando> - podman container exec <id_contenedor o nombre_contenedor> <comando> Análisis del gestor de contenedores Podman 22 23 3 ESTUDIO DE LA BASE DE DATOS NOSQL CASSANDRA as bases de datos NoSQL [3], o “Not Only SQL”, son sistemas de gestión de bases de datos diseñados para manejar grandes volúmenes de datos de manera flexible y escalable. A diferencia de las bases de datos relacionales tradicionales, que utilizan un modelo de datos tabular y un lenguaje de consulta estructurado (SQL), las bases de datos NoSQL adoptan varios modelos de datos, como el modelo de documentos, el modelo de clave-valor, el modelo de columnas amplias (grandes columnas o column family) y el modelo de grafos. Las bases de datos NoSQL son herramientas especialmente valiosas en entornos donde la estructura de los datos es altamente variable y no se adapta fácilmente a un esquema predefinido. Este escenario es común en aplicaciones web, redes sociales y análisis de big data, entre otros. También ofrecen características clave como alta disponibilidad, escalabilidad horizontal y rendimiento optimizado para cargas de trabajo distribuidas. Las bases de datos NoSQL abarcan una variedad de modelos de datos, cada uno diseñado para manejar diferentes tipos de datos y casos de uso. Algunos de los tipos de bases de datos NoSQL más comunes incluyen: ▪ Bases de datos de documentos: Estas almacenan datos en forma de documentos, como JSON o XML, y son altamente flexibles en términos de esquema. Ejemplos incluyen MongoDB y Couchbase. ▪ Bases de datos de clave-valor: Almacenan datos en forma de pares clave-valor simples y son ideales para aplicaciones que requieren acceso rápido a datos. Ejemplos incluyen Redis y Riak. ▪ Bases de datos de columnas amplias: Almacenan datos en columnas en lugar de filas, lo que permite consultas eficientes sobre grandes conjuntos de datos. Ejemplos incluyen Cassandra y HBase. ▪ Bases de datos de grafos: Están diseñadas para almacenar y consultar relaciones entre datos en forma de grafos, lo que las hace ideales para aplicaciones que requieren análisis de redes y relaciones complejas. Ejemplos incluyen Neo4j y Amazon Neptune. Figura 34: Tipos de NoSQL L La innovación es lo que distingue a un líder de los demás. - Steve Jobs - Estudio de la base de datos NoSQL Cassandra 24 En este trabajo, nos enfocaremos en explorar la base de datos NoSQL Cassandra, ya que esta base de datos se puede adaptar de manera eficiente a la gran cantidad de datos y consultas de la aplicación final que se realizará en apartados posteriores. 3.1 Cassandra Apache Cassandra [4] es una base de datos distribuida NoSQL orientada a columnas, inicialmente desarrollada por Facebook de código abierto en la que confían miles de empresas por su escalabilidad y alta disponibilidad sin comprometer el rendimiento. La escalabilidad lineal y la tolerancia a fallos comprobada en hardware estándar o infraestructura en la nube la convierten en la plataforma perfecta para datos críticos. Está escrita en Java y es altamente escalable. Ofrece una consistencia eventual y está diseñada para ser distribuida, con una estructura de tipo clave-valor. Facebook creó Cassandra con el propósito de integrarla en su motor de búsqueda para mensajes en la bandeja de entrada. Su diseño se inspiró en las bases de datos NoSQL BigTable de Google, que utilizan un modelo de datos orientado a columnas, y en Dynamo de Amazon, que tiene una arquitectura de distribución peer-to-peer. Figura 35: Logo de Cassandra A continuación, se presentarán una serie de características generales de Cassandra: 3.1.1 Características generales Base de datos híbrida: La arquitectura sin maestro y la baja latencia significan que Cassandra resistirá un corte completo del centro de datos sin pérdida de datos, tanto en nubes públicas o privadas como en instalaciones locales. Tolerancia a fallos: El soporte de Cassandra para la replicación en múltiples centros de datos es líder en su clase, proporcionando una latencia más baja para sus usuarios y la tranquilidad de saber que puede sobrevivir a cortes regionales. Los nodos fallidos pueden ser reemplazados sin tiempo de inactividad. Enfoque en calidad: Para garantizar la fiabilidad y la estabilidad, Cassandra se prueba en clústeres de hasta 1,000 nodos y con cientos de casos de uso del mundo real y esquemas probados con repeticiones, pruebas de difuminado, basadas en propiedades, inyección de fallos y pruebas de rendimiento. Rendimiento: Cassandra supera constantemente a las alternativas populares de NoSQL en pruebas y aplicaciones reales, principalmente debido a elecciones arquitectónicas fundamentales. 25 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes Control: Cassandra permite elegir entre replicación síncrona o asíncrona para cada actualización. Las operaciones asíncronas altamente disponibles se optimizan con características como “Hinted Handoff” y “Read Repair”. Seguridad y Observabilidad: La función de registro de auditoría para operadores rastrea la actividad de DML, DDL y DCL con un impacto mínimo en el rendimiento de la carga de trabajo normal, mientras que la herramienta “fqltool” permite la captura y reproducción de cargas de trabajo de producción para su análisis. Base de datos distribuida: Cassandra es adecuado para aplicaciones que no pueden permitirse perder datos, incluso cuando un centro de datos completo se cae. No hay puntos de fallo únicos. No hay cuellos de botella de red. Cada nodo en el clúster es idéntico. Escalabilidad: La capacidad de lectura y escritura aumenta linealmente a medida que se agregan nuevas máquinas, sin tiempo de inactividad o interrupción de las aplicaciones. Cassandra transmite datos entre nodos durante operaciones de escalado como agregar un nuevo nodo o centro de datos durante períodos de tráfico pico. La transmisión sin copias hace que esto sea hasta 5 veces más rápido sin nodos virtuales para una arquitectura más elástica, especialmente en entornos de nube y Kubernetes. 3.1.2 Arquitectura Cassandra opera en una arquitectura distribuida donde múltiples nodos independientes se comunican mediante un protocolo peer-to-peer (P2P). Todos los nodos intercambian información de forma continua entre sí. No existe un nodo principal; los clientes pueden conectarse a cualquier nodo para realizar operaciones de lectura y escritura. Cuando un cliente se conecta a un nodo, este nodo actúa como coordinador entre el cliente y el resto de nodos restantes, los cuales contienen la información de la consulta. El coordinador determina qué nodos deben responder a la consulta y coordina la comunicación entre ellos. Figura 36: Estructura de Cassandra Estudio de la base de datos NoSQL Cassandra 26 3.1.2.1 Componentes principales A continuación, se muestran los componentes principales de Cassandra, donde se destacarán los componentes necesarios para la ejecución y configuración: Ejecución • Clúster: Conjunto de uno o más grupos de nodos interrelacionados denominados datacenter. El clúster en Cassandra representa el conjunto de nodos que trabajan juntos para almacenar y procesar datos. Los nodos dentro de un clúster se comunican entre sí para mantener la coherencia y la disponibilidad de los datos distribuidos. • Centro de Datos (Datacenter): Colección de nodos, que puede ser virtual o física. Un centro de datos en Cassandra es un conjunto lógico de nodos que comparten características similares de rendimiento y disponibilidad y que pueden estar ubicados en la misma ubicación física o en diferentes ubicaciones geográficas. Los centros de datos pueden proporcionar redundancia geográfica para mejorar la tolerancia a fallos y la disponibilidad de los datos • Nodo: Elemento fundamental de la infraestructura de Cassandra, donde se almacenan los datos. Un nodo en Cassandra es una instancia de la base de datos que ejecuta en un servidor físico o virtual. Cada nodo almacena una porción de los datos y participa en la coordinación y distribución de consultas y operaciones entre los demás nodos en el clúster. Los nodos son responsables de mantener la consistencia y la disponibilidad de los datos dentro del sistema distribuido. Están formado por los siguientes elementos: - SSTable: Archivos que almacenan los datos escritos en disco. Cada archivo SSTable es inmutable una vez creado. - MemTable: Estructura de almacenamiento en memoria que contiene datos aún no escritos en un SSTable. - Commit Log: Archivo donde se registra la información sobre los cambios en los datos. Sirve para recuperar datos en caso de fallo en el sistema. Configuración • Protocolo de Comunicación entre Nodos (Gossip): Protocolo peer-to-peer para descubrir y compartir información sobre la ubicación y estado de los nodos en un clúster de Cassandra. Este protocolo permite que los nodos se comuniquen entre sí para intercambiar metadatos, como la dirección IP, el estado del nodo y la información sobre los datos que poseen, facilitando así la coordinación y la detección de fallos en el clúster. • Partitioner: Determina cómo se distribuyen los datos entre los nodos del clúster, tanto para la primera copia como para las copias adicionales. El partitioner se encarga de asignar cada fila de datos a un nodo en función de su clave de partición, lo que asegura una distribución equitativa y eficiente de los datos a través del clúster. • Estrategia de Colocación de Réplicas (Replica Placement Strategy): Define la estrategia para almacenar copias de los mismos datos en diferentes nodos, garantizando accesibilidad y tolerancia a fallos. Esta estrategia determina cómo se distribuyen las réplicas de los datos en el clúster para garantizar la disponibilidad y la consistencia de los datos, incluso en caso de fallos de nodos individuales. • Snitch: Define la topología utilizada por las estrategias de replicación para colocar réplicas y dirigir consultas de manera eficiente. El snitch permite a Cassandra conocer la topología de red del clúster, incluyendo la ubicación física y la conectividad de los nodos, lo que ayuda a determinar la mejor ubicación para almacenar réplicas de datos y dirigir consultas de manera eficiente. 27 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes 3.1.2.2 Escritura de datos El proceso de escritura en Cassandra es rápido y eficiente, dividiéndose en pasos esenciales desde la recepción de la solicitud del cliente hasta la persistencia de los datos en disco. Los pasos que sigue este proceso son los siguientes: 1. Petición de Escritura del Cliente: - El cliente realiza una petición de escritura al sistema. 2. Escritura en el Commit Log: - Se escribe la información de la escritura en el Commit Log para garantizar la durabilidad de los datos en caso de fallo del sistema. 3. Escritura en la MemTable: - La información de la escritura se almacena temporalmente en la MemTable en memoria. 4. Borrado (Flush) de la MemTable: - Cuando la MemTable se llena, se produce un borrado de sus datos liberando memoria. 5. Escritura en la SSTable: - Los datos borrados en la fase de Flush de la MemTable se escriben en disco en archivos llamados SSTable. - Los archivos SSTable son inmutables y no se vuelven a escribir después de volcar los datos de la MemTable, lo que permite una alta eficiencia en la escritura. 6. Compactación (Compaction): - Periódicamente, Cassandra ejecuta la compactación para reducir el número de archivos SSTable eliminando aquellos obsoletos. - El objetivo es optimizar el rendimiento de las lecturas. Figura 37: Escritura de datos de Cassandra Estudio de la base de datos NoSQL Cassandra 28 3.1.2.3 Lectura de datos En Cassandra, se utilizan tres tipos distintos de lectura: • Solicitud Directa de Lectura (Direct Read Request): - El nodo coordinador establece contacto con un nodo que contenga la réplica requerida. • Solicitud de Resumen (Digest Request): - El nodo coordinador se comunica con varios nodos que albergan réplicas, según el nivel de consistencia especificado. - Se verifica la coherencia de los datos devueltos por el nodo contactado en la Solicitud Directa de Lectura. - La consulta se envía a aquellos nodos que estén respondiendo más rápidamente. - En caso de existir inconsistencias, se consideran válidas las réplicas con un timestamp más reciente, las cuales se devuelven al cliente. - Una vez que se ha contactado con los nodos definidos por el nivel de consistencia, se envía una Solicitud de Resumen al resto de las réplicas para verificar su coherencia. • Solicitud de Reparación de Lectura en Segundo Plano (Background Read Repair Request): - En caso de detectarse inconsistencias en las réplicas, las réplicas con un timestamp más antiguo son sobrescritas con los datos provenientes de la réplica más actualizada. Los pasos esenciales que definen el proceso de lectura son: 1. Solicitud de Lectura del Cliente: - El cliente inicia una solicitud de lectura al sistema. 2. Acceso a la MemTable: - En cada nodo que recibe la solicitud, se accede a la MemTable utilizando la clave proporcionada para recuperar los datos necesarios. 3. Acceso a las SSTable: - Si los datos (o parte de ellos) no están en la MemTable, se accede a las SSTable para obtener la información requerida. - Existe una posible limitación de rendimiento si la tarea de compactación no se ha realizado recientemente. En este caso, puede ser necesario leer datos de varias SSTable para recuperar todas las filas necesarias, lo que puede afectar a la velocidad de la operación de lectura. 3.1.2.4 Borrado de datos En Cassandra, la eliminación de datos se lleva a cabo mediante un método conocido como “tombstone”. A continuación, se muestran las características de este método: - Cuando se elimina una fila en Cassandra, en lugar de borrarla del disco (debido a la inmutabilidad de las SSTables), se marca como eliminada mediante un “tombstone” (lápida). - Durante el proceso de compactación, las filas marcadas con “tombstone” son eliminadas definitivamente del disco. • Tiempo de Gracia: Si un nodo que contiene la fila a ser eliminada está caído en el momento de la eliminación, aún tiene la oportunidad de recibir la orden de eliminación si se recupera dentro de un intervalo de tiempo predefinido. Si no se recupera dentro de ese tiempo, la fila no será marcada como eliminada, lo que podría resultar en inconsistencias entre las réplicas. • Límite de Tiempo de Supervivencia (LLTS): Los datos pueden tener una “fecha de caducidad” establecida por Cassandra, después de la cual son marcados como eliminados automáticamente. 29 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes 3.1.3 Modelo de datos El modelo de datos en Cassandra abarca diversos elementos esenciales que posibilitan la organización y el almacenamiento eficientes de la información en un contexto distribuido. En este apartado, examinaremos los componentes fundamentales del modelo de datos: el Clúster, Keyspace, ColumnFamily, Columnas, Filas e Índices. 3.1.3.1 Clúster - En Cassandra, los datos se guardan en un Clúster o Anillo donde se asignan a los nodos dentro de una estructura circular. - Cada nodo en el clúster contiene réplicas de diferentes rangos de datos para garantizar la redundancia y disponibilidad de los datos en caso de fallo de un nodo. - Si un nodo falla, sus réplicas en otros nodos dentro del clúster pueden responder a las consultas para mantener la continuidad del servicio. - La replicación de datos entre los nodos se realiza mediante un protocolo P2P, que asegura que los datos se repliquen de manera adecuada de acuerdo con un factor de replicación definido. 3.1.3.2 Columnas Son la unidad básica de almacenamiento interno en Cassandra, conformadas por una tripleta. Estructura de la columna: 1. Nombre (Name): Identifica la columna. 2. Valor (Value): Contiene la información almacenada en la columna. 3. Timestamp: Indica la fecha y hora en que se escribió la columna. - Estos tres valores, incluido el timestamp, son proporcionados por el cliente de alto nivel (aplicación), lo que resalta la necesidad de sincronizar el reloj del sistema con el reloj del clúster. - El timestamp juega un papel crucial en la resolución de conflictos, especialmente en un entorno de Consistencia Eventual. - Aunque el timestamp puede ser de cualquier tipo, se acostumbra a utilizar el valor en microsegundos desde 1970 como convención estándar. Figura 38: Ejemplo de columna en Cassandra Estudio de la base de datos NoSQL Cassandra 30 3.1.3.3 Filas Las filas representan una agregación de columnas que se agrupan bajo un nombre de referencia, conocido como la clave de fila (row-KEY). - Este concepto es equivalente a una fila en el modelo relacional de bases de datos. Row-KEY o clave primaria de las filas: - La row-KEY funciona como la clave primaria en el modelo relacional. Se compone de dos partes (claves): - Clave de particionado (Partition Key): Las filas con el mismo valor de la clave de particionado se almacenan juntas en la misma partición del disco, lo que permite una recuperación eficiente de datos relacionados. - Clave de agrupamiento (Clustering Key): Define el orden físico en el que se almacenan las filas dentro de una partición, lo que facilita la recuperación ordenada de datos. Figura 39: Ejemplo de fila en Cassandra 3.1.3.4 ColumnFamily (Familia de columnas) Una Familia de Columnas es una agrupación de filas en Cassandra. - Cada ColumnFamily se almacena en un archivo separado y se ordena según la clave primaria (KEY). - Este concepto es equivalente a las tablas en el modelo relacional de bases de datos. - Tipos de columnas: - Standard Columns (Columnas Estándar) - Counter Columns (Columnas de Contador) - Collection Columns (Columnas de Colección): SET, LIST, MAP (Conjunto, Lista, Mapa) - User-defined type (Tipo definido por el usuario) - Tuple-type (Tipo Tupla) - Timestamp type (Tipo de Marca de Tiempo) Figura 40: Ejemplo de column family en Cassandra 31 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes 3.1.3.5 Keyspace Equiparable al esquema en el modelo relacional de bases de datos, generalmente asociado a una aplicación específica. Los atributos básicos de un keyspace son: - Replication factor: Determina cuánto se sacrifica el rendimiento en favor de la consistencia. - Replica placement strategy: Indica cómo se distribuyen las réplicas en el anillo. Existen 2 estrategias: - SimpleStrategy (estrategia simple para un solo centro de datos). - NetworkTopologyStrategy (estrategia de topología de red para varios centros de datos). Cassandra admite el particionado distribuido de datos. Existen 2 formas: - RandomPartitioner: Ofrece un buen equilibrio de carga. - OrderPreservingPartitionioner: Permite consultas de rangos, aunque requiere más trabajo al elegir nodos tokens. Cassandra ofrece consistencia reconfigurable. - Al escribir, el consistency level determina cuántas réplicas deben confirmar la escritura para que la aplicación cliente lo considere exitoso. - Al leer, se especifica cuántas réplicas deben responder para que los datos se retornen a la aplicación cliente Figura 41: Ejemplo de Keyspace en Cassandra 3.1.3.6 Índices Los índices facilitan el acceso a la información almacenada en Cassandra. Existen 2 tipos de índices: - Índices primarios: - Son únicos para cada fila. - Las filas son asignadas a cada nodo según el partitioner y la replica placement strategy, basados en su índice primario. - Cada nodo sabe qué rango de valores del índice primario almacena, permitiendo consultas eficientes al escanear solo los índices de las réplicas de las filas buscadas. - Índices secundarios: - Consisten en índices sobre columnas específicas. - Se implementan como una tabla oculta separada de la tabla que contiene los valores originales. Estudio de la base de datos NoSQL Cassandra 38 El comando será: CREATE TABLE IF NOT EXISTS ejemplo1 ( id uuid, nombre text, edad int, PRIMARY KEY (id, nombre) ); Podemos observar que se ha creado correctamente con el comando: describe tables Figura 55: Creación de la tabla ‘ejemplo1’ Podemos obtener más información de la tabla con el comando: describe table ‘nombre_tabla’ Figura 56: Descripción de la tabla ‘ejemplo1’ Modificar/Suprimir una tabla Podemos añadir, eliminar y modificar campos de la tabla con el comando: ALTER TABLE ‘tabla’ [OPCIONES]; A continuación, se mostrarán los comandos más importantes de modificación de una tabla: • Añadir una nueva columna: ALTER TABLE ‘tabla’ ADD ‘columna’ ‘tipo’; • Elimina una columna: ALTER TABLE ‘tabla’ DROP ‘columna’; • Agregar índice a una columna: CREATE INDEX ‘nombre_indice’ ON ‘tabla’ (‘columna’); • Eliminar índice a una columna: DROP INDEX ‘nombre_indice’; Ejecutaremos estos comandos: alter table ejemplo1 add altura float; alter table ejemplo1 add apellido1 text; alter table ejemplo1 add apellido2 text; 39 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes alter table ejemplo1 add apellido3 int; alter table ejemplo1 drop apellido3; create index indice_1 ON ejemplo1 (nombre); create index indice_2 ON ejemplo1 (apellido1); create index indice_3 ON ejemplo1 (apellido2); drop index indice_3; La tabla quedaría de esta forma tras la ejecución: Figura 57: Modificación de la tabla ‘ejemplo1’ Para suprimir una tabla, bastará con poner el comando: drop table ‘tabla’ Figura 58: Eliminación de la tabla ‘ejemplo1’ 3.1.5.3 Lenguaje de Manipulación de Datos (LMD) Para explicar este apartado ejecutaremos las siguientes sentencias, para crear las tablas en la base de datos: SOURCE ‘/tfg/cassandra/keyspace.cql’; SOURCE ‘/tfg/cassandra/clientes.cql’; SOURCE ‘/tfg/cassandra/pedidos.cql’; SOURCE ‘/tfg/cassandra/alumnos.cql’; SOURCE ‘/tfg/cassandra/profesores.cql’; Tras la creación cargaremos datos, como se ha realizado en apartados anteriores: COPY universidad.alumnos (id,nombre,apellido,curso,email) FROM '/tfg/cassandra/alumnos.csv' WITH HEADER = true; COPY universidad.profesores (id,nombre,apellido,email, asignaturas) FROM '/tfg/cassandra/profesores.csv' WITH HEADER = true; Estudio de la base de datos NoSQL Cassandra 40 COPY tienda.clientes (id,nombre,apellido,direccion) FROM '/tfg/cassandra/clientes.txt' WITH HEADER = true; COPY tienda.pedidos (id,fecha,productos,total) FROM '/tfg/cassandra/pedidos.txt' WITH HEADER = true; Tras cargar los ficheros, nuestras tablas deberán de tener datos. Consultar datos (SELECT) Utilizaremos la tabla universidad.alumnos para realizar estas consultas SELECT [7]. Obtener todos los registros SELECT * FROM universidad.alumnos; Figura 59: Obtener todos los registros de la tabla alumnos Obtener un registro especifico filtrando por clave primaria (id) SELECT * FROM universidad.alumnos WHERE id = 8db43857-a9ea-4601-b1c3-b4a89da9b575; Figura 60: Obtener un registro específico filtrando por clave primaria Obtener un registro especifico filtrando por columna (apellido) SELECT * FROM universidad.alumnos WHERE apellido = 'Gomez' ALLOW FILTERING; Figura 61: Obtener un registro filtrando por columna (no clave primaria) con ALLOW FILTERING Cassandra no permite de forma nativa realizar consultas que no sean por clave primaria o por columnas con índices secundarios sin el uso de ALLOW FILTERING, por temas de rendimiento, es recomendable filtrar a nivel de aplicación. Figura 62: Obtener un registro filtrando por columna ( no clave primaria) sin ALLOW FILTERING 41 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes Obtener registros donde una fila contiene un dato (alumnos inscritos en 4º) SELECT * FROM universidad.alumnos WHERE curso CONTAINS 4 ALLOW FILTERING; Figura 63: Comprobación de obtener todos los registros donde una fila tiene un dato Obtener registros basados en múltiples condiciones (todos los alumnos que tienen el apellido Lopez y están inscritos en el curso 3º) SELECT * FROM universidad.alumnos WHERE apellido = 'Lopez' AND curso CONTAINS 3 ALLOW FILTERING; Figura 64: Comprobación de obtener todos los registros de la tabla alumnos con multiples condiciones Actualizar datos (UPDATE) Utilizaremos la tabla tienda.pedidos para realizar estas sentencias. Actualizar una fila por clave primaria (double) UPDATE tienda.pedidos SET total = 100.0 WHERE id = ccfbc251-d9f5-4f3e-bf72- ffb44c8f3ebb; Figura 65: Comprobación de actualizar una fila por clave primaria (double) Actualizar una fila por clave primaria (lista) UPDATE tienda.pedidos SET productos = {'shampoo', 'acondicionador', 'gel de baño'} WHERE id = ccfbc251-d9f5-4f3e-bf72-ffb44c8f3ebb; Figura 66: Comprobación de actualizar una fila por clave primaria (lista) Estudio de la base de datos NoSQL Cassandra 42 Actualizar una fila por clave primaria (añadir a lista) UPDATE tienda.pedidos SET productos = productos + {'mantequilla'} WHERE id = ccfbc251-d9f5-4f3e-bf72-ffb44c8f3ebb; Figura 67: Comprobación de actualizar una fila por clave primaria (añadir a la lista) Actualizar un pedido por clave primaria (eliminar producto de lista y modificar valor de total) UPDATE tienda.pedidos SET productos = productos - {'gel de baño'} AND total = 5 WHERE id = ccfbc251-d9f5-4f3e-bf72-ffb44c8f3ebb; Figura 68: Actualizar varios campos de una fila Insertar datos Utilizaremos la tabla tienda.clientes para este apartado. Insertar una fila en la tabla INSERT INTO tienda.clientes (id, nombre, apellido, direccion) VALUES (uuid(), 'Carlos', 'Hernandez', 'Calle Luna 123'); Figura 69: Comprobación de insertar una fila en la tabla Utilizaremos la función uuid() de Cassandra que genera automáticamente un uuid, es necesario rellenar todas las columnas 43 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes Insertar una fila en la tabla (si no existe) INSERT INTO tienda.clientes (id, nombre, apellido, direccion) VALUES (b0d50857- 5272-4963-8ab0-5ee5f8638906, 'Carlos', 'Hernandez', 'Calle Luna 123') IF NOT EXISTS ; Figura 70: Comprobación de insertar en la tabla si no existe Como podemos comprobar [applied] = false, ya que esa fila ya existe en la base de datos. Eliminar datos Eliminar fila por clave primaria (id) DELETE FROM universidad.profesores WHERE id = e7782492-508c-499a-bd0b- 3f707c90b026; Figura 71: Comprobación de eliminar una fila de la tabla En Cassandra solo se pueden eliminar las filas filtrando por la clave primaria, es recomendable filtrar a nivel de aplicación para obtener la clave primaria, para después eliminar, por temas de rendimiento 3.1.6 Resumen Este capítulo ha explorado Cassandra, una base de datos NoSQL destacada por su arquitectura distribuida y su alta escalabilidad. Se han discutido sus características generales, tales como la tolerancia a fallos y la alta disponibilidad. La arquitectura de Cassandra, basada en un anillo de nodos equivalentes, y su modelo de datos flexible, han sido explicados en detalle. Además, se han proporcionado directrices para la instalación y manejo de Cassandra, subrayando la importancia de seguir buenas prácticas para garantizar un rendimiento óptimo y una administración efectiva de la base de datos. Finalmente, se ha explicado el uso de Cassandra con ejemplos prácticos para comprender en detalle su funcionamiento. En el próximo capítulo, se explicarán las diversas formas de desplegar microservicios, tanto de manera local como en la nube, entrando en detalle en nuevas tecnologías, como Kubernetes. Estudio de la base de datos NoSQL Cassandra 44 45 4 DESPLIEGUE DE MICROSERVICIOS n los últimos años, el despliegue de microservicios en contenedores ha experimentado un gran crecimiento, transformando fundamentalmente la forma en que las aplicaciones se desarrollan, implementan y escalan. Esta evolución tecnológica ha sido impulsada por la necesidad de crear aplicaciones más ágiles, escalables y fácilmente administrables en entornos de computación en la nube. Los contenedores proporcionan un entorno liviano y portátil que encapsula el código de la aplicación junto con sus dependencias y configuraciones. Esto permite a los desarrolladores empacar, enviar y ejecutar aplicaciones de manera consistente en cualquier entorno, desde el desarrollo local hasta la producción en la nube. El despliegue de microservicios, una arquitectura de software donde una aplicación se descompone en componentes independientes y autónomos, se ha beneficiado enormemente de la tecnología de contenedores. Cada microservicio se puede empaquetar en su propio contenedor, lo que facilita la implementación, el escalado y la gestión individual de cada componente. En términos de despliegue, existen varias formas de implementar microservicios en contenedores: ▪ Orquestadores de contenedores: Herramientas como Kubernetes, Podman-Compose, Docker Swarm y Amazon ECS permiten administrar grandes flotas de contenedores, automatizando tareas como la programación, el escalado y la recuperación de fallos. Estos orquestadores proporcionan una plataforma robusta para desplegar y gestionar microservicios a escala. ▪ Contenedores sin servidor (Serverless Containers): Una evolución de la arquitectura sin servidor, los contenedores sin servidor combinan la portabilidad de los contenedores con la abstracción de la infraestructura proporcionada por las plataformas sin servidor. Servicios como AWS Fargate y Azure Container Instances permiten ejecutar contenedores individuales sin necesidad de administrar clústeres de contenedores. E Lo que conocemos es una gota, lo que no conocemos es un océano. - Isaac Newton - Despliegue de microservicios 46 4.1 Podman Compose Podman Compose [8] es una herramienta que permite a los desarrolladores definir y ejecutar aplicaciones multicontenedor utilizando archivos YAML similares a Docker Compose, pero con la flexibilidad y ventajas de Podman como motor de contenedores. Con Podman Compose, los usuarios pueden describir fácilmente los servicios, redes y volúmenes que componen su aplicación en un formato familiar y luego lanzar y gestionar esos contenedores con un solo comando. Esta herramienta es especialmente útil para aquellos que buscan una alternativa a Docker Compose y desean aprovechar las características de seguridad y rendimiento de Podman en sus aplicaciones contenerizadas. Con Podman Compose, los desarrolladores pueden simplificar el desarrollo y despliegue de aplicaciones en contenedores mientras aprovechan las capacidades avanzadas de Podman para administrar los contenedores de manera eficiente y segura. Figura 72: Logo Podman Compose A continuación, detallaremos las características más importantes, instalación y ejemplo de uso. 4.1.1 Características Podman Compose, como alternativa a Docker Compose, ofrece una serie de propiedades que hacen que el manejo de contenedores sea más flexible y robusto. Aquí tienes algunas de sus características clave: ▪ Compatibilidad con Docker Compose: Podman Compose es compatible con los archivos YAML de Docker Compose, lo que facilita la migración de proyectos existentes de Docker a Podman. Los usuarios pueden aprovechar sus conocimientos y experiencia previa en Docker Compose mientras se benefician de las características adicionales de Podman. ▪ No requiere daemon: A diferencia de Docker, Podman no requiere un daemon en ejecución en segundo plano. Esto hace que Podman sea más seguro y adecuado para entornos multiusuario, ya que los contenedores se ejecutan en el contexto del usuario que los inicia. ▪ Soporte para rootless: Podman Compose es compatible con la ejecución “rootless”, lo que significa que los usuarios pueden ejecutar contenedores sin necesidad de privilegios de root. Esto mejora la seguridad y permite a los usuarios ejecutar contenedores de manera más segura en entornos compartidos. ▪ Integración con Buildah: Podman Compose se integra con Buildah, una herramienta para construir imágenes de contenedor sin necesidad de un daemon de Docker. Esto permite a los usuarios construir imágenes de contenedor de manera eficiente y reproducible directamente desde Podman Compose. ▪ Ejecución local de contenedores: Podman Compose permite levantar y gestionar contenedores a nivel local, lo que facilita el desarrollo y prueba de aplicaciones en el entorno del desarrollador. Esto mejora la productividad al acelerar el ciclo de desarrollo y proporcionar una retroalimentación rápida sobre los cambios realizados en la aplicación. 47 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes 4.1.2 Instalación Para instalar [9] Podman Compose, es necesario tener instalado Podman junto con todas sus dependencias, dado que los contenedores que se despliegan son manejados por Podman. • [ 2.5 Instalación de Podman ] También necesitaremos instalar un complemento (aardvark-dns) que permite a los contenedores mapear nombres de contenedor a direcciones IP (DNS) entre sí si están en la misma red CNI. Para ello realizaremos los utilizaremos los siguientes comandos: git clone https://github.com/containers/aardvark-dns cd aardvark-dns make make install PREFIX=/usr Tras la instalación de aardvark-dns realizaremos la instalación de Podman Compose, para el cual necesitaremos python3. (pip) Si no está instalado en el sistema, utilizaremos el comando: sudo apt install python3-pip Tras la instalación de pip, instalaremos la última versión de Podman Compose con el siguiente comando: pip3 install https://github.com/containers/podman-compose/archive/devel.tar.gz Figura 73: Instalación de Podman Compose Tras este comando podman compose estará instalado correctamente, como se puede observar en la siguiente imagen: Figura 74: Comprobación de la instalación de Podman Compose Despliegue de microservicios 54 4.1.4.2 Ejecución Para realizar la ejecución de este proyecto primero debemos ubicarnos en el directorio raíz del proyecto, para realizar la construcción de las imágenes. Para ello utilizaremos el comando: podman compose build Figura 76: Build de Podman compose A continuación, ejecutaremos el siguiente comando: podman compose up Este se encarga de leer el archivo `docker-compose.yaml` y construye todas las imágenes Docker especificadas en los servicios. Después de construir las imágenes, se utiliza el comando `podman compose up` para montar todo lo indicado en el `docker-compose.yaml`, lo que incluye redes, volúmenes, puertos expuestos, etc. Además, este comando organiza los contenedores en pods donde comparten la conexión de red. Figura 77: Up de Podman Compose Los contenedores (backend y frontend) se han levantado correctamente, y el frontend está disponible en la dirección • http://10.10.10.3:80/ Tras el correcto levantamiento de los contenedores procedemos a ingresar desde el navegador a la dirección donde está alojado el frontend. 55 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes Figura 78: Frontend del Ejemplo1 de Podman Compose Pulsamos el botón y observamos el funcionamiento: Figura 79: Alert del Ejemplo1 de Podman Compose Observamos la petición con el depurador web: Figura 80: Petición del Ejemplo1 de Podman Compose Como podemos observar hemos desplegado de forma local una aplicación web donde se comunica un front-end con un back-end de manera sencilla con Podman compose, montando 2 contenedores que se comunican entre sí, ya que se encuentran en la misma red. Para detener el proyecto, solo bastará con introducir el comando: podman compose down Despliegue de microservicios 56 4.2 Kubernetes Kubernetes [11] es una plataforma de orquestación de contenedores de código abierto diseñada para facilitar el despliegue, la escalabilidad y la gestión eficiente de aplicaciones basadas en microservicios. Permite a los desarrolladores dividir sus aplicaciones en componentes más pequeños y manejables, conocidos como microservicios, y luego desplegar y gestionar estos microservicios de manera independiente en un entorno de contenedores. Una de las características distintivas de Kubernetes es su capacidad para gestionar aplicaciones complejas y distribuidas a gran escala, asegurando que estén siempre disponibles, sean escalables y resistentes a fallos. Comparado con Podman Compose, Kubernetes ofrece una gama más amplia de características y funcionalidades específicamente orientadas hacia entornos de producción a gran escala. Mientras que Podman Compose es ideal para entornos de desarrollo y pruebas locales, Kubernetes brinda capacidades avanzadas de orquestación, como la autoescalabilidad, la tolerancia a fallos y la gestión de recursos, que son fundamentales para entornos de producción. Las formas de desplegar un clúster Kubernetes son: • Localmente: Para desplegar un clúster Kubernetes de forma local, se pueden utilizar herramientas como kind (Kubernetes in Docker), minikube, kubeadm y kubespray • En la nube: Para desplegar un clúster Kubernetes en la nube, se pueden utilizar servicios como Google Kubernetes Engine (GKE) en Google Cloud Platform (GCP), Amazon Elastic Kubernetes Service (EKS) en Amazon Web Services (AWS) o Azure Kubernetes Service (AKS) en Microsoft Azure. Estos servicios te proporcionan un clúster facilitando el montaje, mantenimiento, etc. Figura 81: Logo de Kubernetes En esta sección, exploraremos la estructura de Kubernetes, detallando cada uno de sus componentes, objetos y las diferentes formas de desplegar un clúster Kubernetes. Abordaremos tanto la implementación local, para la cual emplearemos kind (Kubernetes in Docker), como la opción en la nube, utilizando Google Kubernetes Engine (GKE) de Google Cloud Platform. 57 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes 4.2.1 Estructura La estructura de Kubernetes se basa fundamentalmente en el concepto de clúster. Un clúster en Kubernetes es un conjunto de nodos que ejecutan el software de Kubernetes y están coordinados por uno o varios nodo maestro. Este nodo maestro gestiona y supervisa el clúster, mientras que los nodos de trabajo son responsables de ejecutar las aplicaciones y cargar los contenedores. Dentro del clúster encontramos: • Nodo Maestro (Plano de control): - kube-apiserver: Es el punto de entrada para todas las operaciones en el clúster Kubernetes a través de la API RESTful. - etcd: Almacena el estado del clúster Kubernetes, incluidos detalles sobre nodos y trabajos. - kube-scheduler: Se encarga de programar pods en los nodos disponibles, teniendo en cuenta los requisitos de recursos y restricciones de política. - kube-controller-manager: Supervisa el estado del clúster y responde a los cambios, como la creación o eliminación de pods. • Worker Nodes: - kubelet: Es el agente que se ejecuta en cada nodo y se comunica con el kube-apiserver. Se encarga de iniciar, detener y mantener los contenedores. - kube-proxy: Se encarga de manejar el enrutamiento de red en el clúster y de la implementación del balanceo de carga. • Volúmenes: - Proporcionan almacenamiento persistente para los contenedores. Pueden estar respaldados por almacenamiento local del nodo o soluciones de almacenamiento en la nube. • Namespaces: - Espacios virtuales dentro de un clúster Kubernetes que permiten dividir recursos entre varios usuarios o equipos. Dentro de los nodos se encuentran los Pods. Los Pods son la unidad más pequeña desplegable en Kubernetes y pueden contener uno o más contenedores. Comparten el mismo espacio de red y almacenamiento, lo que facilita la comunicación entre ellos. Los servicios abstraen conjuntos de pods como una aplicación coherente, proporcionando una dirección IP única y un nombre DNS (opcional) para acceder a ellos. Figura 82: Estructura de Kubernetes Despliegue de microservicios 58 4.2.2 Objetos Los objetos de Kubernetes [12] son entidades persistentes dentro del sistema de Kubernetes que representan el estado del clúster y gestionan las aplicaciones en contenedores. Estos objetos describen qué aplicaciones se ejecutan en contenedores, los recursos disponibles y las políticas de comportamiento de dichas aplicaciones, como reinicios, actualizaciones y tolerancia a fallos. Cuando creas un objeto en Kubernetes, estás estableciendo un “registro de intención”, indicando al sistema de Kubernetes cómo deseas que sea la carga de trabajo en tu clúster. Estos objetos tienen dos campos principales: el campo spec, que describe el estado deseado del objeto, y el campo status, que describe el estado actual del objeto y se actualiza automáticamente por el sistema de Kubernetes. Para trabajar con los objetos de Kubernetes, necesitas utilizar la API de Kubernetes. Puedes hacerlo directamente mediante llamadas a la API o a través de herramientas como kubectl. Los objetos de Kubernetes se describen en archivos YAML. Figura 83: Objetos de Kubernetes 4.2.2.1 Objeto Deployment El objeto Deployment [13] en Kubernetes es esencial para administrar la implementación de aplicaciones en contenedores de manera escalable, controlada y sin interrupciones. Actúa como un conjunto de réplicas de un Pod y posibilita la definición del estado deseado de las instancias de la aplicación. Permite gestionar actualizaciones y reversión de cambios, además de escalar horizontalmente en respuesta a la demanda. Este recurso proporciona una capa de abstracción sobre los Pods, permitiendo definir la cantidad deseada de instancias de la aplicación que se ejecutarán simultáneamente. También facilita la gestión de la infraestructura subyacente, ya que Kubernetes se encarga de monitorear y mantener el número especificado de Pods en funcionamiento. A continuación, se muestra un ejemplo del objeto Deployment: 59 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes ejemplo-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: ejemplo-deployment spec: replicas: 3 selector: matchLabels: app: servicio-ejemplo template: metadata: labels: app: servicio-ejemplo spec: containers: - name: servicio-ejemplo image: nginx:1.19.6 ports: - containerPort: 80 • apiVersion: Especifica la versión de la API de Kubernetes que se está utilizando. En este caso, es `apps/v1`, lo que indica que estamos utilizando la versión 1 de la API de aplicaciones de Kubernetes. • kind: Indica el tipo de objeto que se está creando. En este caso, es un Deployment, que se utiliza para administrar la implementación y actualización de aplicaciones en Kubernetes. • metadata: Contiene información sobre el objeto, como el nombre del Deployment. En este ejemplo, el nombre del Deployment es “ejemplo-deployment”. • spec: Describe las especificaciones del Deployment, incluyendo el número deseado de réplicas, cómo se seleccionan los Pods y la plantilla para crear los Pods. - replicas: Especifica el número deseado de réplicas de la aplicación que se ejecutarán simultáneamente. En este caso, se están solicitando tres réplicas. - selector: Especifica cómo se seleccionarán los Pods que formarán parte de este Deployment. En este caso, los Pods serán seleccionados si tienen la etiqueta `app: servicio-ejemplo `. - template: Define la plantilla para los Pods que se crearán como parte de este Deployment. ➢ metadata.labels: Establece las etiquetas que se aplicarán a los Pods creados. En este caso, se aplicará la etiqueta `app: servicio-ejemplo `. ➢ spec.containers: Describe los contenedores que se ejecutarán en los Pods. ▪ name: Especifica el nombre del contenedor. En este caso, el contenedor se llama “servicio-ejemplo”. ▪ image: Indica la imagen del contenedor que se utilizará para crear las instancias del mismo. En este ejemplo, se usará la imagen `nginx:1.19.6`. ▪ ports.containerPort: Define el puerto que se expondrá desde el contenedor. En este caso, el contenedor estará escuchando en el puerto 80. Despliegue de microservicios 60 4.2.2.2 Objeto Service El objeto Service [14] en Kubernetes es esencial para exponer aplicaciones en contenedores dentro del clúster o para acceder a ellas desde fuera del clúster. Permite definir una política de acceso a los Pods que forman parte de un Deployment u otro recurso de Kubernetes, proporcionando una dirección IP y un nombre DNS para acceder a la aplicación. A continuación, se muestra un ejemplo del objeto Service: ejemplo-service.yaml apiVersion: v1 kind: Service metadata: name: servicio-ejemplo spec: selector: app: servicio-ejemplo ports: - protocol: TCP port: 80 targetPort: 8080 type: LoadBalancer • apiVersion: Indica la versión de la API de Kubernetes que se está utilizando. En este caso, es `v1`, lo que indica que estamos utilizando la versión 1 de la API de Kubernetes. • kind: Especifica el tipo de objeto que se está creando. En este caso, es un Service, que se utiliza para exponer aplicaciones en contenedores dentro del clúster o para acceder a ellas desde fuera del clúster. • metadata: Contiene información sobre el objeto, como el nombre del Service. En este ejemplo, el nombre del Service es “servicio-ejemplo “. • spec: Describe las especificaciones del Service, incluyendo cómo se seleccionan los Pods y los puertos expuestos. - type: Define el tipo de Service. En este caso es LoadBalancer, lo que significa que el Service será accesible desde fuera del clúster, proporcionando una forma de exponer servicios dentro del clúster Kubernetes a clientes externos. (Existen varios tipos aparte de LoadBalancer: ClusterIP, NodePort) - selector: Especifica qué Pods se incluirán en el Service. En este caso, se seleccionarán los Pods que tienen la etiqueta `app: servicio-ejemplo`. - ports: Define los puertos que serán expuestos por el Service. ➢ protocol: Indica el protocolo de red utilizado. En este caso, es TCP. ➢ port: Especifica el puerto del Service. ➢ targetPort: Indica el puerto en el que los contenedores de los Pods están escuchando. En este ejemplo, los contenedores están escuchando en el puerto 8080. 61 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes LoadBalancer (Balanceador de carga) Un Load Balancer [15], dentro del ámbito de los servicios de red, es un dispositivo o software diseñado para distribuir el tráfico de red entrante entre varios destinos. Su propósito principal es mejorar el rendimiento, la disponibilidad y la fiabilidad de los servicios en línea, logrando una distribución equitativa de la carga de trabajo entre múltiples servidores o recursos. En el contexto de Kubernetes, Load Balancer es un tipo de Service que proporciona una forma de exponer una aplicación fuera del clúster de Kubernetes. Cuando se crea un Service de tipo LoadBalancer en Kubernetes, el proveedor de la nube asigna automáticamente un balanceador de carga externo (si está disponible) y distribuye el tráfico de red entrante a través de él hacia los Pods que respaldan la aplicación. Características del LoadBalancer: • Exposición externa del clúster: Un load balancer puede ser utilizado para exponer servicios dentro del clúster Kubernetes a clientes externos. Por ejemplo, un servicio web alojado en el clúster puede ser accesible desde Internet a través de un balanceador de carga que distribuye el tráfico entrante a los nodos del clúster. • Balanceo de carga entre nodos: En un clúster Kubernetes, los nodos pueden ejecutar múltiples Pods que sirven aplicaciones. Un load balancer puede distribuir la carga entre los nodos del clúster, asegurando que cada nodo no esté sobrecargado y que los recursos estén utilizados de manera eficiente. • Escalabilidad y alta disponibilidad: Al utilizar un load balancer, el clúster Kubernetes puede escalar horizontalmente agregando o eliminando nodos según sea necesario para manejar la carga de trabajo entrante. Además, en caso de que un nodo falle, el load balancer puede redirigir automáticamente el tráfico a los nodos disponibles, garantizando la alta disponibilidad de las aplicaciones. • Gestión del tráfico: El load balancer puede actuar como punto de entrada único para el tráfico hacia el clúster Kubernetes, lo que facilita la gestión y el monitoreo del tráfico entrante y saliente. Esto puede ser útil para aplicar políticas de seguridad, realizar análisis de tráfico y garantizar el cumplimiento de los requisitos de rendimiento. Figura 84: Esquema real de LoadBalancer Despliegue de microservicios 62 4.2.3 Ejemplo de despliegue de un clúster local (kind) A modo de demostración práctica, se va a realizar la prueba de levantar un clúster que ofrezca el microservicio mencionado en el punto 4.1.3 (una pequeña aplicación web en la que el frontend se comunica con el backend) utilizando kind (Kubernetes in Docker) [16]. ”kind” es una herramienta que permite ejecutar clústeres locales de Kubernetes utilizando contenedores Docker como “nodos”. Aunque su principal propósito es probar Kubernetes, también puede ser utilizado para el desarrollo local o la integración continua (CI). Además, permite ejecutar un clúster utilizando Podman, aunque se recomienda utilizar Docker, ya que la compatibilidad con Podman está en fase experimental. Sin embargo, para nuestro ejemplo, lo haremos con Podman. Figura 85: Logo de kind Usaremos la interfaz de línea de comandos de Kubernetes, conocida como kubectl, para interactuar con la API de Kubernetes y el clúster creado por kind. 4.2.3.1 Instalación de kind y kubectl Para realizar la instalación de kind, utilizaremos el siguiente comando: [ $(uname -m) = x86_64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.22.0/kind-$(uname)-amd64 Posteriormente, utilizaremos estos comandos: chmod +x ./kind sudo mv ./kind /usr/local/bin/kind Tras la ejecución de los comandos kind estará instalado en nuestro sistema: Figura 86: Comprobación de la instalación de kind A continuación, instalaremos kubectl, con los siguientes comandos: curl -LO “https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl” sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl Con estos comandos, kubectl se instalará sin problemas: Figura 87: Comprobación de la instalación de kubectl 63 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes 4.2.3.2 Configuración del proyecto Como se ha comentado anteriormente, se va a desplegar con kind el proyecto realizado en el punto 4.1.4. Por lo tanto, las imágenes Docker personalizadas que utilizamos para construir el proyecto de Podman Compose las volveremos a utilizar. Tenemos dos opciones: • Construir las imágenes (necesitaremos el proyecto). • Utilizar las imágenes desde un registro o registry de imágenes. Las imágenes utilizadas en el punto 4.1.4 se han subido al repositorio docker.io/miguecrty/kind siguiendo los pasos del apartado [Pull y push de imágenes]. Esto se ha realizado para poder utilizar las imágenes en cualquier máquina. Imágenes Frontend: docker.io/miguecrty/kind:frontend Backend: docker.io/miguecrty/kind:backend Una vez que tengamos las imágenes localizadas (repositorio o construidas) procederemos a la realización de los objetos deployment y service para cada uno de los microservicios. Realizar la adaptación de docker-compose.yaml a kubernetes, es relativamente sencillo, ya que no necesitaremos configurar networks, porque todos los pods pertenecen al mismo clúster. Configuración del Backend backend-service.yaml apiVersion: v1 kind: Service metadata: name: backend spec: selector: app: backend ports: - protocol: TCP port: 5000 targetPort: 5000 • Este YAML define un objeto de tipo Service en Kubernetes llamado “backend”. El servicio “backend” dirige el tráfico hacia los pods que tengan la etiqueta “app: backend”. Además, expone el puerto 5000 a través del protocolo TCP, redirigiendo ese tráfico al puerto 5000 en los pods seleccionados. Despliegue de microservicios 70 4.2.4.2 Configuración del proyecto (GKE) Para configurar el proyecto, en la parte superior de la página principal aparece un recuadro donde nos permitirá seleccionar el proyecto que queremos, haremos click y crearemos un proyecto nuevo. Figura 99: Creación de un nuevo proyecto en Google Cloud Platform En nuestro caso se llamará ‘proyecto-k8s’, tras la creación lo seleccionamos. La selección de un proyecto se utiliza para organizar de manera correcta los servicios utilizados. Tras la selección del proyecto, habilitaremos la API de Kubernetes, buscando en la barra superior ‘Kubernetes Engine’ Figura 100: Selección de Kubernetes Engine Habilitamos la API y nos saldrá una pantalla parecida a esta: Figura 101: Comprobación de la activación de la API de Kubernetes en Google Cloud En la sección de la izquierda seleccionamos el apartado ‘Kubernetes Engine>Clústeres’ Figura 102: Selección Kubernetes Engine > Clústeres 71 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes En este momento nos encontraremos en la sección de administración de clústeres, donde podremos crear, eliminar y administrar los clústeres de nuestro proyecto. En este momento no existe ningún clúster en nuestro proyecto, por lo tanto, crearemos un clúster de ejemplo para desplegar los servicios mencionados anteriormente. En la opción de clúster de Kubernetes le daremos a la opción de ‘CREAR’, tras elegir esa opción seleccionaremos la opción ‘CAMBIAR A CLÚSTER ÉSTANDAR’ la cual nos permite personalizar nuestro clúster, por defecto, se selecciona la creación de un clúster Autopilot. Figura 103: Cambiar a la creación de un clúster éstandar en GKE La configuración que elegiremos para la creación del clúster será: Aspectos básicos del clúster: - Nombre: cluster-prueba - Tipo de ubicación: Zonal - Zona: europe-west1-b - Canal de versiones: [por defecto] Registro de flotas: - [por defecto – No seleccionado] Grupos de nodos> default-pool: - Nombre: default-pool - Tamaño: Cantidad de nodos: 1 - Resto de campos: [por defecto] [El resto de los campos de todas las opciones se dejarán por defecto] Observamos que el precio aproximado mensual del clúster es 110$ → 0.15$/hora Tras realizar la configuración del clúster le damos al botón inferior ‘CREAR’. Figura 104: Esperando la cración del clúster ‘cluster-prueba’ en GKE El clúster tardará en iniciarse y configurarse varios minutos. Pasado ese tiempo nuestro clúster debe de estar funcionando y esperando a ser administrado. Figura 105: Comprobación de la creación del clúster ‘cluster-prueba’ en GKE Despliegue de microservicios 72 4.2.4.3 Configuración del proyecto (Sistema) Tras la configuración del clúster en GKE, necesitaremos conectar la API de Kubernetes de nuestro sistema con el clúster de GKE. Para ello, necesitaremos instalar la herramienta de administración gcloud CLI [19] en nuestro sistema. Descargaremos los archivos necesarios para la instalación con el comando: curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud- cli-472.0.0-linux-x86_64.tar.gz Extraemos el contenido: tar -xf google-cloud-cli-472.0.0-linux-x86_64.tar.gz Instalamos: ./google-cloud-sdk/install.sh Recargamos CLI para completar la instalación: source ~/.bashrc Observamos que se ha instalado correctamente: Figura 106: Comprobación de la instalación de gcloud CLI Tras la instalación, para poder utilizar la herramienta deberemos autenticar nuestra cuenta con el siguiente comando: gcloud auth login Este comando nos mostrará un link que pegaremos en el navegador y nos redirigirá a un inicio de sesión de nuestra cuenta de Google Cloud Platform, tras esto, nos logeamos y autorizamos a utilizar el servicio de gcloud. Figura 107: Autorización de gcloud CLI Observamos que nos pide que actualicemos algunos componentes de gcloud, lo haremos con el comando: gcloud components update Tras la actualización de los componentes necesitaremos instalar en gcloud un plugin que permite el uso de kubetl llamado gcloud-auth-plugin. Lo instalaremos con el comando: gcloud components install gke-gcloud-auth-plugin Tras la instalación de este plugin podremos configurar el entorno para trabajar con nuestro clúster con kubectl. 73 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes El siguiente paso, como hemos comentado anteriormente, es conectar la API de Kubernetes (kubectl) con nuestro clúster, para ello necesitaremos los parámetros de conexión de nuestro clúster. Estos parámetros se encuentran en la sección principal de la administración del clúster. Figura 108: Obtención de parámetros de conexión del clúster ‘cluster-prueba’ Copiamos y ejecutamos ese comando con gcloud en nuestro sistema. Figura 109: Conexión del clúster ‘cluster-prueba’ con kubectl Observamos que se ha generado una entrada en el archivo de configuración de kubectl, la cual añade las credenciales de conexión del clúster ‘cluster-prueba’ de GKE. Con el siguiente comando obtenemos todos los clústeres configurados en kubectl: kubectl config get-clusters Figura 110: Lista de clúster tras añadir el nuevo clúster ‘cluster -prueba’ Se puede observar que se ha añadido correctamente el clúster ‘cluster-prueba’ Podemos obtener y cambiar el contexto donde trabaja kubectl (clúster donde está funcionando) con los comandos, respectivamente: kubectl config get-contexts kubectl config use-context ‘cluster’ Despliegue de microservicios 74 Figura 111: Selección del contexto de kubectl para el clúster ‘cluster-prueba’ Una vez hayamos cambiado el contexto de kubectl para apuntar al clúster real de GKE llamado 'cluster-prueba', estaremos listos para llevar a cabo cualquier tarea necesaria en el clúster. En nuestro caso, desplegaremos los microservicios que mencionamos anteriormente. Los pasos a seguir serán los mismos que utilizamos para el clúster local, ya que el funcionamiento del clúster real es el mismo. Por lo tanto, nos dirigimos al directorio del proyecto y cargamos los Deployment y Services al clúster, con el comando: kubectl apply -f . Figura 112: Cargando los servicios al clúster real ‘cluster-prueba’ Observamos si los servicios están corriendo correctamente, con los comandos: Kubectl get pods kubectl get svc Figura 113: Comprobación de los servicios en el clúster real Se puede comprobar que la dirección IP externa del frontend se verifica automáticamente sin necesidad de intervención adicional, como hacíamos con kind con el servicio ‘cloud-provider-kind’. En un entorno de clúster real como GKE, el gestor del clúster se encarga de implementar los servicios de manera correcta. Por lo tanto, al detectar el campo 'LoadBalancer' en el archivo frontend-service.yaml, ha configurado automáticamente un servicio de LoadBalancer en Google Cloud Platform. Esto se refleja en la sección Servicios de red > Balanceo de cargas de Google Cloud Platform. Figura 114: Comprobación de creación de LoadBalancer en Google Cloud 75 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes Observamos que el balanceador (LoadBalancer), que es el servicio de frontend, está corriendo en la ruta: • Dirección IP: 34.79.254.246 • Puerto: 80 Al tratarse de un clúster real las direcciones IP que ofrece el LoadBalancer son públicas, por lo tanto, podemos acceder desde cualquier dispositivo ya que nos encontramos en la nube. Figura 115: Acceso con sistema Windows a la aplicación web Figura 116: Acceso con sistema Linux (VM) a la aplicación web Se ha comprobado que levantar un clúster real es sencillo, pero se necesita entender el funcionamiento de un clúster local. Comprender cómo funciona un clúster local es fundamental para familiarizarse con los conceptos y procesos involucrados en la administración de clústeres, lo que facilita luego la transición al despliegue en un entorno de producción real. Además, trabajar con un clúster local proporciona un entorno seguro para realizar pruebas y experimentar con diferentes configuraciones antes de aplicar cambios en un entorno de producción, lo que ayuda a minimizar el riesgo de errores y problemas operativos. Despliegue de microservicios 76 77 5 APLICACIÓN PRÁCTICA n esta sección se explicará cómo se ha implementado una aplicación web utilizando los conceptos teóricos previamente explicados. Para ello, se han empleado diversas tecnologías y frameworks en el desarrollo de la interfaz de usuario. Además, se han utilizado APIs y servicios para gestionar correctamente las solicitudes del usuario. La aplicación es un servicio de monitoreo del tiempo, donde el usuario puede seleccionar un lugar específico para seguir la evolución meteorológica del mismo. Además, la aplicación permite buscar el pronóstico del tiempo para los días siguientes. En los siguientes apartados, se detallarán la estructura, el desarrollo y el despliegue de la aplicación. Figura 117: Logo de la aplicación final 5.1 Estructura En esta sección se propondrá la estructura para nuestra aplicación, detallando las tecnologías utilizadas en cada componente. La arquitectura de la aplicación se dividirá en varias capas para asegurar una separación clara de responsabilidades y mejorar la mantenibilidad y escalabilidad del sistema. A continuación, se describen las principales capas y tecnologías empleadas: 5.1.1 Front-End El Front-End (Capa de presentación) es la interfaz de usuario de la aplicación, donde los usuarios interactúan directamente. Esta capa se desarrollará utilizando tecnologías modernas de front-end para proporcionar una experiencia de usuario fluida y responsiva. En esta sección se introducirán las tecnologías empleadas para el desarrollo del front-end de nuestra aplicación. Estas tecnologías se seleccionaron para asegurar una experiencia de usuario óptima, interfaces dinámicas y una estructura de código mantenible y escalable. E Que algo no haya salido como hayas querido no significa que sea inútil. - Thomas Edison - Aplicación práctica 78 5.1.1.1 React.js React.js [20], comúnmente conocido como React, es una biblioteca de JavaScript de código abierto para la construcción de interfaces de usuario, particularmente para aplicaciones de una sola página. Fue desarrollada por Facebook y se ha convertido en una de las herramientas más populares y ampliamente adoptadas para el desarrollo front-end. A continuación, se mostrarán las principales características y ventajas de React. Figura 118: Logo de React.js Características 1) Componentes  Modularidad: React permite la creación de componentes reutilizables que encapsulan tanto la lógica como la presentación. Esto facilita la gestión y el mantenimiento del código.  Jerarquía: Los componentes pueden anidarse unos dentro de otros, permitiendo la construcción de interfaces complejas a partir de unidades más pequeñas y manejables. 2) JSX (JavaScript XML)  Sintaxis: JSX es una extensión de la sintaxis de JavaScript que se parece mucho a HTML. Permite escribir elementos React de manera declarativa y más legible.  Transpilación: JSX es transformado en llamadas de JavaScript mediante herramientas como Babel, facilitando la integración de lógica y presentación. 3) Virtual DOM  Eficiencia: React mantiene una copia del DOM en memoria (Virtual DOM). Cuando se realizan cambios en el estado de un componente, React actualiza solo los elementos del DOM real que han cambiado, optimizando el rendimiento.  Reconciliación: El proceso de actualización del DOM real basado en el Virtual DOM es llamado reconciliación y es altamente eficiente. 4) Unidirectional Data Flow (Flujo de Datos Unidireccional)  Previsibilidad: Los datos en React fluyen en una sola dirección, desde los componentes padres hacia los componentes hijos. Esto facilita el seguimiento del estado y la gestión de datos, haciendo que el comportamiento de la aplicación sea más predecible y fácil de depurar. 5) Hooks  Funcionalidades Reutilizables: Introducidos en React 16.8, los Hooks permiten usar estado y otras características de React sin escribir una clase. Hooks como `useState` y `useEffect` facilitan la gestión del estado y los efectos secundarios en componentes funcionales. 79 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes Ventajas 1) Reutilización de Componentes: La capacidad de crear componentes modulares y reutilizables acelera el desarrollo y reduce la duplicación de código. 2) Gran Ecosistema: React tiene un ecosistema robusto con numerosas librerías y herramientas que complementan su funcionalidad, como React Router para el enrutamiento y Redux para la gestión del estado. 3) Rendimiento Optimizado: Gracias al Virtual DOM, las aplicaciones React son generalmente rápidas y eficientes en cuanto a la manipulación del DOM. 4) Comunidad Activa: La amplia adopción de React ha generado una gran comunidad de desarrolladores y una abundancia de recursos, tutoriales y soluciones a problemas comunes. 5.1.1.2 Next.js Next.js [21] es un framework de React de código abierto que se utiliza para la construcción de aplicaciones web de manera eficiente y escalable. Desarrollado por Vercel, Next.js agrega funcionalidades clave a React, como la renderización del lado del servidor (SSR) y la generación de sitios estáticos (SSG), lo que permite crear aplicaciones web rápidas y optimizadas para motores de búsqueda. Figura 119: Logo de Next.js A continuación, se mostrarán las principales características y ventajas de Next.js. Características 1) Renderizado del Lado del Servidor (SSR)  Mejora del SEO: Al renderizar las páginas en el servidor, Next.js genera HTML estático para cada ruta, lo que mejora el SEO y la indexación de los motores de búsqueda.  Rendimiento Mejorado: El SSR reduce el tiempo de carga inicial al enviar al cliente una versión prerenderizada de la página, lo que resulta en una experiencia de usuario más rápida. 2) Generación de Sitios Estáticos (SSG)  Eficiencia: Next.js puede generar páginas estáticas durante la compilación, lo que elimina la necesidad de realizar solicitudes de servidor para contenido estático.  Escalabilidad: Las páginas estáticas generadas pueden ser servidas a través de una CDN (Content Delivery Network), lo que garantiza una entrega rápida y escalable del contenido. Aplicación práctica 86 Herramientas de Contenedorización: Se ha seleccionado Podman para realizar el proceso de contenedorización de la aplicación. Podman permitirá desarrollar, probar y desplegar la aplicación en contenedores, como se ha explicado en el siguiente apartado: 2.1 Análisis del gestor de contenedores Podman. Se ha elegido Podman debido a la compatibilidad e integración con la base de datos Cassandra y se utilizará la versión de Podman v5.0.2 Herramientas de Despliegue: Para realizar el despliegue de la aplicación se utilizarán las herramientas nombradas en el apartado: 4.1 Despliegue de microservicios Utilizaremos para levantar la aplicación de forma local la herramienta Podman Compose, donde construiremos todas las imágenes Docker necesarias y levantaremos la aplicación multicontenedor. Tras construir las imágenes y levantar nuestra aplicación multicontenedor utilizaremos la herramienta Kind, la cual creará un clúster kubernetes de forma local, donde realizaremos todas las pruebas pertinentes para que la aplicación funcione correctamente en un clúster local. Después de levantar el clúster local con Kind, realizaremos el despliegue en la nube con GKE, tal y como se realizó en apartados anteriores. Figura 127: Logo de herramientas para el despliegue de la aplicación 5.3 Preparación del proyecto Antes de iniciar la codificación del proyecto, es crucial tener una estructura clara del mismo. La estructura de archivos del proyecto seguirá el siguiente esquema: aplicacionTFG/ ├── frontend/ │ ├── Dockerfile │ └── svc_frontend/ ├── backend/ │ ├── Dockerfile │ └── svc_backend/ ├── bbdd/ │ ├── Dockerfile │ └── cassandra/ └── kubernetes/ Podemos construir está estructura con el siguiente comando: mkdir -p aplicacionTFG/frontend/ aplicacionTFG/backend/ aplicacionTFG/bbdd/cassandra aplicacionTFG/kubernetes • En los directorios svc_frontend/ y svc_backend/ se crearán los proyectos de next.js y node.js respectivamente, como se comentó previamente. • En el directorio cassandra/ se encontrará el archivo .cql que se cargará en la base de datos Cassandra • En el directorio kubernetes/ se encuentran los archivos para levantar el clúster kubernetes. 87 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes 5.3.1 Preparación del Front-End Como se ha comentado en apartados anteriores el frontend, se realizará con React y el framework Next.js, todos los archivos se crearán dentro de la carpeta aplicacionTFG/frontend/ Para iniciar el proyecto debemos tener una versión de node > v20.0.0 y la versión de npm > v9.0.0. Nos ubicaremos en el directorio aplicacionTFG/frontend/ y utilizaremos el siguiente comando para crear el proyecto: cd aplicacionTFG/frontend/ npx create-next-app svc_frontend Figura 128: Creación del proyecto de next.js Elegiremos las siguientes opciones: • TypeScript → No (Realizaremos el proyecto en javascript) • ESLint → No (No utilizaremos esta herramienta de depuración) • Tailwind CSS → No (Utilizaremos bootstrap) • src/ directory → Yes (El directorio raíz será src) • App Router → Yes (Para enroutar la aplicación) • Import alias → No (No necesitaremos modificar el alias) Tras elegir estas opciones nuestro proyecto se creará correctamente y tendrá la siguiente estructura: frontend/svc_frontend/ ├── jsconfig.json (Fichero con opciones para la compilación) ├── next.config.mjs (Fichero de configuración de next.js) ├── node_modules/ (Directorio con todas las dependencias del proyecto) ├── package.json (Fichero con metadatos del proyecto) ├── package-lock.json (Fichero de comprobación de los metadatos) ├── public/ (Directorio que contendrá archivos estáticos) └── src/ (Directorio donde se encuentra el código fuente de la aplicación) A continuación, modificaremos la estructura del proyecto para adaptarla a nuestra aplicación. Entraremos en la carpeta src y borraremos todo su contenido. Aplicación práctica 88 cd aplicacionTFG/frontend/svc_frontend/src rm -rf * Tras borrar todo, crearemos 3 carpetas dentro del directorio src/. mkdir components pages styles • src/components/ → Se colocarán todos los componentes reutilizables de React. • src/pages/ → Contiene las páginas de la aplicación. Cada archivo en esta carpeta representa una ruta en tu aplicación. • src/styles/ → Contiene los archivos de estilos (CSS) globales. Tras la creación de estos directorios accederemos al directorio src/pages/ y crearemos el directorio /api, el cual nos servirá para poder comunicarnos con las apis que creemos en el backend, mediante el servidor de Next.js. mkdir -p src/pages/api En el mismo directorio src/pages crearemos el archivo _app.js, el cual es el componente principal de tu aplicación. Este archivo se utiliza para mantener el estado global, añadir estilos globales y envolver los componentes de página. Crearemos e importaremos el archivo src/pages/styles/globals.css dentro de nuestro fichero _app.js, que por el momento estará vacío y contendrá los estilos globales para toda la aplicación. _app.js import '../styles/globals.css'; function Tfg({ Component, pageProps }) { return ( <Component {...pageProps} /> ); } export default Tfg;Tras la creación de _app.js, crearemos el archivo src/pages/index.js, el cual será nuestra página de inicio, es decir, cuando iniciemos la aplicación y accedemos a ‘/’ se renderiza este archivo. Por el momento, tendrá este contenido. index.js export default function Home() { return ( <div> <p>Hola mundo!!!</p> </div> ); } Por último, modificaremos el fichero svc_frontend/packaje.json, en la sección de scripts cambiaremos lo siguiente: “scripts”: { “dev”: “next dev -p 8080”, “build”: “next build”, “start”: “next start -p 8080”, “lint”: “next lint” } Le añadiremos -p 8080 a dev y start, para que el servidor de next.js arranque en el puerto 8080. 89 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes Tras la modificación del fichero packaje.json, ya podremos iniciar nuestra aplicación. Existen 2 formas de arrancar el proyecto. • Modo desarrollador: En este modo cada cambio que realicemos se renderizará automáticamente sin necesidad de recargar el servidor de Next.js o la página. Se utiliza este modo con el comando: npm run dev • Modo producción: En este modo primero se compila el proyecto y después se lanza el servidor de Next.js, los cambios que realicemos una vez compilados, no se verán afectados tras iniciar el servidor, este modo se utiliza cuando el proyecto esté todo codificado y probado en el modo desarrollador. Se compila con el comando: npm run build Se inicia la aplicación con el comando: npm start Para desarrollar nuestra aplicación utilizaremos el modo desarrollador. Nos ubicamos en el directorio raíz ‘aplicacionTFG/’ y ponemos el siguiente comando: code . Este abrirá una ventana de Visual Code Studio con la carpeta del proyecto abierta. Dentro de Visual Code pulsaremos las teclas [ Control + Shift + ` ] para abrir un terminal interactivo en el editor. Cuando el terminal esté abierto, nos ubicaremos en el directorio svc_frontend/ y pondremos el comando: npm run dev Y accederemos a la dirección http://localhost:8080 Figura 129: Inicio del proyecto de next.js Como podemos ver, el proyecto ha sido iniciado correctamente en el puerto 8080 y todos los directorios han sido creados adecuadamente. Ahora solo queda codificar la aplicación web y llevar a cabo las pruebas correspondientes. Aplicación práctica 90 5.3.2 Preparación del Back-End El Back-End de la aplicación será en Node.js como se ha comentado en apartados anteriores, para ello todos los archivos relevantes al Back-End se encuentran en el directorio aplicacionTFG/backend/ Previamente a la creación del proyecto, crearemos un subdirectorio en la ruta aplicacionTFG/backend/ llamado svc_backend, el cual lo crearemos con el comando: mkdir -p aplicacionTFG/backend/svc_backend Tras la creación del directorio nos movemos a él e iniciaremos el proyecto de Node.js con el comando: npm init -y Este comando nos creará un archivo llamado package.json por defecto en el directorio svc_backend/ Tras la creación de este archivo crearemos un directorio dentro de svc_backend/ llamado src/ donde se encontrará nuestro archivo index.js, el cual contendrá el código fuente de nuestro servidor (Back-End). mkdir src touch src/index.js Esta debe de ser la estructura del proyecto: svc_backend ├── package.json └── src └── index.js Tras la creación de los archivos, deberemos modificar el fichero package.json, para indicarle la nueva ruta de index.js. (Se modificarán las secciones ‘main’ y ‘scripts’, donde crearemos un comando para iniciar de manera sencilla el servidor, con el comando ‘npm start’ ) “main”: “src/index.js”, “scripts”: { “start”: “node src/index.js” } Tras modificar el fichero package.json, instalaremos en el proyecto ‘express.js’ con el siguiente comando: npm install express Modificaremos el archivo src/index.js para iniciar el servidor express y crear una API (GET) de prueba que enviará un mensaje ‘Acceso a la api correcto’. index.js const express = require('express'); const app = express(); app.use(express.json()); const server = app.listen(3000, () => { console.log(`Servidor Express en funcionamiento en el puerto ${3000}`); }); app.get('/prueba', async (req, res) => { console.log(‘Se ha accedido a la api de prueba’); res.status(200).send(“Acceso a la api correcto”); }); 91 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes Tras modificar el fichero, nos moveremos a la ruta aplicacionTFG/backend/svc_backend/ y ejecutaremos el comando para iniciar el servidor: npm start Este comando inicia directamente el servidor, y una vez que está iniciado si hacemos cambios en el código fuente no serán reflejados hasta que reiniciemos el servidor. Tras iniciar el servidor, accederemos desde el navegador a la dirección: http://localhost:3000/prueba Figura 130: Inicio del proyecto Back-End y prueba de la API Podemos observar que la API funciona correctamente y que el navegador, actuando como cliente, recibe la respuesta esperada al realizar la petición a la API. A partir de aquí, podemos crear un servicio de comunicación entre el front-end y el back-end utilizando RESTful. 5.3.3 Preparación de la Base de Datos La base de datos elegida para el proyecto ha sido Cassandra, como se ha comentado en apartados anteriores, procederemos a la instalación utilizando Podman, siguiendo los pasos de puntos anteriores. Respecto a la instalación habrá una diferencia, en el pull de la imagen, la cual no usaremos la imagen oficial de cassandra, sino una de bitnami, la cual permite cargar archivos .cql cuando se levanta la base de datos en el contenedor. Para ello utilizaremos el comando: podman pull bitnami/cassandra:4.0.7 El resto de la instalación es igual que en el apartado: [3.1.4.1. Instalación usando Podman] Iniciamos el contenedor y comprobamos si tenemos conectividad con cqlsh como en el apartado:[3.1.4.2 Acceso mediante el terminal (Test Cluster-cqlsh)], con una diferencia que para acceder necesitaremos un usuario y contraseña de la base de datos, por defecto es USER: ‘cassandra’ PASSWORD: ‘cassandra’ ./cqlsh <ip_contenedor> 9042 -u cassandra Figura 131: Inicio de la base de datos del proyecto Observamos que tenemos la base de datos funcional, tal y como vimos en apartados anteriores, en este caso la base de datos tiene de dirección IP: 10.88.0.2 y puerto: 9042 Aplicación práctica 92 5.4 Codificación del proyecto En este apartado se detallarán las partes más relevantes de la codificación del proyecto, abarcando los servicios, APIs e implementación del sistema. A continuación, se explican cada uno de estos elementos. 5.4.1 Inicialización de la Base de Datos Para estructurar los datos crearemos en la base de datos: • Keyspace: Se creará el keyspace ‘tfg’ con una estrategia de replicación simple y un factor de replicación de 1.  Tabla usuarios: ▪ Columnas: nombre_usu (clave primaria), clave, email. ▪ Propósito: Almacenar datos de usuarios. Por cada usuario se creará una tabla que almacenará los datos de usuario (tabla datos_<nombre_usuario>)  Tabla lugares: ▪ Columnas: lugar (clave primaria), nombre_usu, lat, lon, avanzada (booleano). ▪ Propósito: Almacenar información de lugares asociados a usuarios.  Tabla datos_<nombre_usuario> (Por cada usuario): ▪ Columnas: nombre_lugar, tiempo (mapa), temperatura (mapa), viento (mapa), nubes (mapa), toma (timestamp). ▪ Claves: Clave primaria compuesta por nombre_lugar y toma, con ordenamiento por toma descendente. ▪ Propósito: Almacenar datos específicos de los lugares a lo largo del tiempo.  Inserción de datos: ▪ Se insertará un usuario de prueba, el cual tendrá una tabla de datos. Toda esta información estará contenida en el fichero init_keyspace.cql, el cual será el fichero de inicialización de la base de datos. Este fichero lo ubicaremos en el directorio aplicacionTFG/bbdd/cassandra/ Este fichero lo cargaremos mediante cqlsh. Nos conectaremos a la base de datos mediante cqlsh y utilizaremos la sentencia: SOURCE ‘/ruta/al/fichero/init_keyspace.cql’ Comprobaremos la creación de las tablas con la sentencia: DESCRIBE tables Keyspace tfg ------------ datos_usuario lugares usuarios Observamos que las tablas se han creado correctamente. 93 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes 5.4.2 Codificación del Back-End Previamente a la codificación instalaremos una serie de dependencias, a parte del servidor ‘express’: Tabla 5: Dependencias del Back-End 5.4.2.1 Creación de constantes En este apartado, configuraremos las constantes sensibles que utilizaremos a lo largo del proyecto. Además, utilizaremos Kubernetes (k8s) para gestionar estos valores de configuración y credenciales de forma segura mediante los objetos Secrets y ConfigMap (se explicará en apartados posteriores). El uso del operador lógico || (OR) se utiliza para proporcionar un valor predeterminado en caso de que la variable de entorno no esté definida o tenga un valor “falsy”. Configuración de Email Las constantes relacionadas con el servicio de correo electrónico se configuran de la siguiente manera: const host = process.env.HOST_EMAIL || “smtp-es.securemail.pro”; const emailport = process.env.PORT_EMAIL || 465; const emailuser = process.env.USER_EMAIL || “***************”; const emailpass = process.env.PASS_EMAIL || “***************”; HOST_EMAIL: Dirección del servidor SMTP. PORT_EMAIL: Puerto del servidor SMTP. USER_EMAIL: Usuario del correo electrónico. PASS_EMAIL: Contraseña del correo electrónico. Dependencia Comando de instalación Descripción cassandra-driver npm install cassandra-driver Este es el cliente oficial de Cassandra para Node.js. Proporciona las herramientas necesarias para conectar, consultar y gestionar bases de datos. axios npm install axios Axios es una biblioteca para realizar solicitudes HTTP desde Node.js. Permite hacer peticiones a APIs y servicios externos de manera sencilla y manejar respuestas y errores. nodemailer npm install nodemailer Nodemailer es un módulo para enviar correos electrónicos desde Node.js. Soporta múltiples servicios de correo y protocolos como SMTP. crypto npm install crypto Crypto es un módulo nativo de Node.js para manejar operaciones criptográficas, como cifrado, hashing y generación de tokens seguros pino, pino-pretty npm install pino npm install pinopretty Pino es una librería de registro (logging) extremadamente rápida y ligera para Node.js Aplicación práctica 94 Configuración de Cassandra Las constantes para configurar la conexión a la base de datos Cassandra son las siguientes: const hostBD = process.env.HOST_CASSANDRA || '10.88.0.2'; const datacenterBD = process.env.DATACENTER_CASSANDRA || 'datacenter1'; const portBD = process.env.PORT_CASSANDRA || 9042; const keyspaceBD = process.env.KEYSPACE_CASSANDRA || 'tfg'; const usernameBD = process.env.USERNAME_CASSANDRA || 'cassandra'; const passwordBD = process.env.PASSWORD_CASSANDRA || 'cassandra'; HOST_CASSANDRA: Dirección del host de Cassandra. DATACENTER_CASSANDRA: Centro de datos de Cassandra. PORT_CASSANDRA: Puerto de Cassandra. KEYSPACE_CASSANDRA: Keyspace utilizado en Cassandra. USERNAME_CASSANDRA: Nombre de usuario para Cassandra. PASSWORD_CASSANDRA: Contraseña para Cassandra. Estas constantes se utilizan para configurar el cliente de Cassandra: const client = new cassandra.Client({ contactPoints: [hostBD], localDataCenter: datacenterBD, protocolOptions: { port: portBD }, keyspace: keyspaceBD, credentials: { username: usernameBD, password: passwordBD } }); Configuración de API y Aplicación Utilizaremos la API de OpenWeather: const APIKEY_WEATHER = process.env.APIKEY_WEATHER || '*************************'; const dominio_app = process.env.DOMINIO_APP || 'localhost:8080'; const PORT = process.env.PORT_APP || 3000; APIKEY_WEATHER: Clave API para servicios meteorológicos. DOMINIO_APP: Dominio de la aplicación. PORT_APP: Puerto en el que se ejecutará la aplicación. 95 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes 5.4.2.2 Creación de APIs En este apartado, se describen las APIs creadas para la aplicación. Cada API se implementa como una ruta en el servidor Express y proporciona diferentes funcionalidades esenciales para el funcionamiento de la aplicación. A continuación, se presenta una breve explicación de cada API, especificando su propósito y el método HTTP utilizado. APIs GET • GET /obtenerdatosgraficasavanzados Permite obtener datos climáticos avanzados para un lugar específico asociado a un usuario. Utilizando la base de datos para recuperar información y formatear los datos antes de enviarlos como respuesta en formato JSON.  Recepción de Parámetros Recibe usuario y lugar como parámetros de consulta.  Consulta a la Base de Datos Realiza una consulta en la tabla de datos climáticos del usuario para el lugar especificado.  Procesamiento de Datos Formatea fechas y horas de las tomas. Extrae datos actuales (descripción e icono del tiempo, dirección y velocidad del viento, nubosidad). Extrae datos climáticos (temperaturas, sensación térmica, presión, humedad).  Respuesta al Cliente Devuelve un objeto JSON con los datos climáticos y actuales si se encuentran registros. Si no se encuentran datos, devuelve un error indicando que el usuario no existe en la base de datos Maneja errores de consulta y procesamiento con respuestas de error apropiadas. • GET /obtenerdatosgraficasbasicos Permite obtener datos climáticos básicos para un lugar específico asociado a un usuario. Utiliza la base de datos para recuperar la ubicación y una API externa para obtener los datos climáticos antes de enviarlos como respuesta en formato JSON.  Recepción de Parámetros Recibe usuario y lugar como parámetros de consulta.  Consulta a la Base de Datos Realiza una consulta en la tabla de lugares para obtener las coordenadas (latitud y longitud) del lugar especificado por el usuario.  Consulta a la API Externa Utiliza la latitud y longitud obtenidas para hacer una solicitud a la API de OpenWeatherMap [26] y recuperar datos climáticos históricos de los últimos tres días.  Procesamiento de Datos Formatea las fechas y horas de las tomas. Convierte las temperaturas de Kelvin a Celsius.  Respuesta al Cliente Devuelve un objeto JSON con las temperaturas y las tomas si se encuentran registros. Maneja errores de consulta y procesamiento con respuestas de error apropiadas. Aplicación práctica 102 Pie (pie.js) Este es el componente de pie de página (Pie) para la aplicación, que mostrará un mensaje de derechos de autor y un icono de ayuda opcional, dependiendo de la propiedad ayuda. Usa la propiedad setMostrarAyuda para mostrar información de ayuda cuando se hace clic en el icono. Esta sería su apariencia, dependiendo del valor de la propiedad ayuda: Figura 133: Componente Pie Input búsqueda + Mapa (searchbox.js) Este es el componente de búsqueda de lugares y mapa (SearchBox), que permite seleccionar ubicaciones mediante un cuadro de búsqueda autocompletando con la API de Google Maps [28] y muestra un mapa con un marcador. Dependiendo de las propiedades mostrarMapa y pronostico, mostrará el mapa con un marcador o solo el cuadro de búsqueda. Este componente usará la cookie username para obtener el nombre de usuario y realiza solicitudes a la API para iniciar sondeos cuando se selecciona una ubicación. Esta sería su apariencia, dependiendo del valor de las propiedades: Figura 134: Componente SearchBox 103 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes Gráficas (chartTodas.js) Este es el componente de gráfico (ChartTodas) para la aplicación, que muestra un gráfico interactivo con capacidad de zoom. Usa Chart.js [29] para renderizar el gráfico y el plugin chartjs-plugin-zoom para habilitar el zoom y el desplazamiento. Dependiendo de las propiedades datasets, labels y tipo, el gráfico se configura y muestra los datos correspondientes. Incluye un botón para restablecer el zoom del gráfico. Esta sería su apariencia, por ejemplo, para la gráfica de Temperatura: Figura 135: Componente ChartTodas Verificador login (withAuth.js) Este es el componente de orden superior (withAuth) para la aplicación, que protege rutas mediante verificación de autenticación. Utilizará la cookie username para comprobar si el usuario está autenticado y redirige a la página de inicio si no lo está. Este componente no tiene apariencia gráfica y su función consiste en envolver a las páginas que se crearán en apartados posteriores para comprobar si el usuario está logeado. 5.4.3.2 Codificación de las APIs del servidor de Next.js Todas las APIs creadas en el servidor de Next.js reenviarán las peticiones al Back-End. Como se explicó en apartados anteriores, el backend se encarga de consultar, insertar, actualizar y borrar datos en la base de datos, además de realizar las peticiones a la API de OpenWeather. El servidor de Next.js actuará como intermediario entre el navegador y el Back-End. Al desplegar la aplicación en la nube, el Back-End estará alojado en una máquina real con una IP pública. Por este motivo, no se deben codificar directamente las direcciones IP reales de las máquinas en el código (hardcoding). En su lugar, se utilizarán variables de entorno para gestionar las URLs y las credenciales de acceso, lo que permite una mayor flexibilidad y seguridad en la configuración del entorno. Aplicación práctica 104 Esto tiene varias ventajas: • Seguridad: Las direcciones IP y las credenciales sensibles no estarán expuestas en el código fuente. • Mantenibilidad: Si la dirección del backend cambia, solo será necesario actualizar la variable de entorno correspondiente sin modificar el código fuente. • Portabilidad: El mismo código puede ser desplegado en diferentes entornos (desarrollo, pruebas, producción) con diferentes configuraciones, simplemente cambiando las variables de entorno. Las peticiones seguirán esta estructura: Figura 136: Estructura de comunicación del sistema Todas las APIs del servidor de Next.js se encontrarán en el directorio: aplicacionTFG/frontend/svc_frontend/src/pages/api/ El servidor se encarga de maneja la solicitud HTTP a la ruta /api/[endpoint] en la aplicación Next.js. Cuando se recibe la solicitud, se reenvía como una solicitud [GET,POST,PUT,DELETE] al endpoint /[endpoint] en el servidor Back-End (especificado por la variable server). La respuesta de esta solicitud [GET,POST,PUT,DELETE] se procesa y se devuelve al cliente original con el estado apropiado y los datos relevantes (ya sea éxito o error). Las APIs que se crearán seguirán la misma estructura, por ejemplo, para el login: aplicacionTFG/frontend/svc_frontend/src/pages/api/login.js import { server } from “../_app”; export default async function handler(req, res) { const response = await fetch('http://'+server+'/obtenerlista?usuario='+req.query.usuario, { method: 'GET', headers: { 'Content-Type': 'application/json' }, }); if (response.ok) { const respuesta = await response.json(); res.status(200).json(respuesta); } else{ res.status(500).json({ message: “La lista no se ha obtenido correctamente” }); } } • Se importa la variable server desde el archivo (_app), la cual se usa para construir la URL de la solicitud. 105 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes • Se define una función manejadora asíncrona handler que recibe dos parámetros, req (la solicitud) y res (la respuesta). • Se realiza la solicitud [GET,POST,PUT,DELETE] al Back-End • Se maneja la respuesta y se reenvía al cliente. A continuación, se muestran todas las APIs que se crearán en el servidor de Next.js: [GET] - Obtener datos para las gráficas <Avanzadas y Básicos> (obtenerdatosgraficas.js) [GET] - Obtener lista de lugares (obtenerlista.js) [GET] - Obtener pronóstico de lugar (pronostico.js) [POST] - Iniciar sondeo para un usuario (iniciarsondeo.js) [POST] - Registrar usuario (registrar.js) [POST] - Resetear contraseña (reset-password.js) [DELETE] - Borrar Cuenta (borracuenta.js) [DELETE] - Desmonitorizar usuario (desmonitorizar.js) [PUT] - Cambiar Contraseña (cambiarpassword.js) 5.4.3.3 Codificación de las páginas En esta sección se detallará como se han codificado las páginas y el componente principal de la aplicación, explicando las partes más relevantes del código. Estas páginas se ubicarán en el directorio: aplicacionTFG/frontend/svc_frontend/src/pages/ _app.js Este es el componente principal (`Tfg`) para la aplicación. Aplica estilos globales y Bootstrap a toda la aplicación. import '../styles/global.css'; import 'bootstrap/dist/css/bootstrap.min.css'; Además, exporta constantes `server` y `googleApiKey` que se configuran a partir de variables de entorno o valores predeterminados. Estas constantes serán utilizadas a lo largo de la aplicación, en los componentes y en las APIs del servidor de Next.js. Inicio (index.js) Esta es la página de inicio de sesión donde los usuarios podrán logearse para acceder al menú principal. Utiliza varios estados y efectos para gestionar la autenticación del usuario, la recuperación de contraseña, y la navegación dentro de la aplicación. Al cargar la página, se verifica si el usuario ya está autenticado mediante la cookie username, y si es así, se redirige automáticamente al menú principal. Si el usuario no está autenticado, se muestra un formulario de inicio de sesión. A continuación, se mostrarán las funcionalidades principales: Inicio de sesión: • Permite al usuario ingresar su nombre de usuario y contraseña. • Verifica los datos con el servidor y establece la cookie username si la autenticación es exitosa. • Redirige al menú principal tras un inicio de sesión exitoso. Aplicación práctica 106 Registro: • Incluye un botón que redirige a la página de registro. Recuperación de contraseña: • Permite a los usuarios recuperar su contraseña mediante un formulario que solicita el nombre de usuario y el email. • Muestra mensajes de éxito o error según la respuesta del servidor. Cargando: • Muestra una animación de carga mientras se verifica la autenticación del usuario al cargar la página. Interfaz de usuario: • Utiliza Bootstrap para el estilo de los formularios y botones. • Muestra u oculta la contraseña mediante un botón. • Incluye el pie de página común a todas las páginas (componente Pie). Manejo de errores y mensajes: • Muestra mensajes de error si hay problemas con el inicio de sesión o la recuperación de la contraseña. • Muestra un mensaje de éxito tras un inicio de sesión exitoso. Esta sería la apariencia de la página Index (formulario de iniciar sesión): Figura 137: Apariencia de Index ( Inicio sesión ) 107 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes Esta sería la apariencia de la página Index (formulario de Recuperar contraseña): Figura 138: Apariencia de Index ( Recuperar contraseña ) Registro (registro.js) Esta es la página de registro de usuarios, donde los usuarios podrán crear el usuario para poder utilizar la aplicación. A continuación, se detalla sus funcionalidades: Registro de Usuario: • Permite al usuario ingresar un nombre de usuario, email, contraseña y repetir la contraseña. • Verifica que la contraseña y la repetición coincidan antes de enviar la solicitud al servidor. • Muestra mensajes de éxito o error según la respuesta del servidor al intentar registrar al usuario. Confirmación de Registro: • Si se proporciona un token en la URL (mediante router.query.token), se muestra un mensaje y un botón para confirmar el registro. • Al hacer clic en el botón, se realiza una solicitud al servidor para confirmar el registro del usuario. Interfaz de Usuario: • Utiliza Bootstrap para el estilo de los formularios y botones. • Incluye la cabecera común (Cabecera) con la propiedad [mostrarBotonHome={true}] y el pie de página (Pie) de la aplicación. Manejo de Errores y Mensajes: • Muestra mensajes de error si hay problemas durante el registro o la confirmación. • Muestra mensajes de éxito cuando el registro o la confirmación son exitosos. Visualización de Contraseñas: • Permite al usuario mostrar u ocultar las contraseñas ingresadas utilizando botones correspondientes. Carga Inicial: • Muestra una animación de carga mientras se espera la verificación del token de registro. Aplicación práctica 108 La siguiente imagen muestra la apariencia de la página Registro: Figura 139: Apariencia de Registro Cambiar contraseña (reset-password.js) Esta es la página de restablecimiento de contraseña, la cual será enviada por correo electrónico cuando el usuario solicite cambiar la contraseña. También se mandará el token generado en el Back-End y el nombre de usuario como parámetros de la URL. Las funcionalidades principales son: Restablecimiento de Contraseña: • Permite al usuario ingresar una nueva contraseña. • Verifica que la contraseña tenga más de 4 caracteres antes de enviar la solicitud al servidor. • Muestra mensajes de éxito o error según la respuesta del servidor al intentar restablecer la contraseña. Interfaz de Usuario: • Utiliza Bootstrap para el estilo de los formularios y botones. • Incluye Font Awesome para los íconos de visibilidad de contraseña. Manejo de Errores y Mensajes: • Muestra mensajes de error si hay problemas durante el restablecimiento de la contraseña. • Muestra un mensaje de éxito cuando la contraseña se restablece correctamente. Visualización de Contraseña: • Permite al usuario mostrar u ocultar la contraseña ingresada mediante un botón correspondiente. 109 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes A continuación, se mostrará la apariencia de la página Restablecer Contraseña: Figura 140: Apariencia de Restablecer contraseña Menú principal (menu.js) Esta es la página del menú principal de la aplicación donde se podrá elegir entre las siguientes secciones (‘Monitorizar’, ‘Pronóstico’, ‘Mi cuenta’). Esta página está envuelta por el componente withAuth, por lo tanto, si no está logeado no entrará nunca en la misma. Autenticación: • Utiliza la cookie username para verificar si el usuario está autenticado, mostrando el menú solo cuando está logeado. Cabecera y Pie de página: • Incluye componentes de Cabecera y Pie para mantener consistencia visual y funcional. Interacción de Usuario: • Permite navegar entre diferentes secciones (monitorizar lugares, pronóstico y configuración de cuenta) mediante tarjetas clicables. Estilo y Diseño • Utiliza Bootstrap para la estructura de tarjetas y estilos generales. Carga Inicial: • Muestra un indicador de carga mientras verifica la autenticación del usuario. Personalización: • Configura el título de la página y el ícono en el navegador para mejorar la experiencia del usuario. Aplicación práctica 110 Esta es la apariencia de la página Menu Principal: Figura 141: Apariencia de Menú Principal Sección ‘Monitorizar’ (monitorizar.js) Esta es la página de monitorización donde los usuarios pueden ver datos climáticos detallados de diferentes lugares. Utiliza varios estados y efectos para gestionar la selección de ubicaciones, la obtención de datos climáticos y la visualización de gráficos. A continuación, se presentan las funcionalidades principales: Monitorización: • Lista de lugares:  Permite a los usuarios seleccionar un lugar para monitorizar desde una lista obtenida del servidor.  Muestra un mensaje si no hay lugares en la lista. • Mapa:  Permite la selección de ubicaciones mediante una caja de búsqueda.  Actualiza la ubicación seleccionada y muestra el lugar en un mapa interactivo. Gráficos:  Muestra gráficos de temperatura, temperatura máxima, temperatura mínima, sensación térmica, presión y humedad.  Permite cambiar entre visualizaciones básicas y avanzadas.  Utiliza ChartTodas para renderizar los gráficos con diferentes tipos y datos según la selección. Modo de visualización:  Modo básico: Muestra la temperatura de los últimos 3 días en un gráfico de líneas.  Modo avanzado: Muestra múltiples gráficos con datos climáticos detallados y permite cambiar entre diferentes parámetros como temperatura, presión, etc. 111 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes Informe climático:  Muestra un informe climático actual con descripción, dirección y velocidad del viento, y porcentaje de nubes.  Actualiza el informe en tiempo real con la información más reciente. Interfaz de usuario:  Utiliza componentes como Cabecera, Pie, y SearchBox para estructurar la página y mejorar la experiencia del usuario.  Implementa una ayuda visual emergente que se puede activar o desactivar.  Se utiliza Bootstrap para organizar y mejorar el estilo de los componentes de la página. Autenticación:  Comprueba si el usuario está autenticado mediante una cookie (username).  Redirige al usuario a la página principal si no está autenticado utilizando el componente withAuth Manejo de errores y mensajes:  Muestra mensajes de error si hay problemas al obtener la lista de lugares o los datos para los gráficos.  Muestra una advertencia si no hay suficientes datos para mostrar en los gráficos. A continuación, se muestra la apariencia de la página Monitorizar: Figura 142: Apariencia de Monitorizar Aplicación práctica 118 Tras observar el correcto funcionamiento de Podman Compose, realizaremos la traducción a kubernetes levantando el clúster local con kind. Para ello crearemos en el directorio aplicacionTFG/kubernetes/ un subdirectorio llamado kind. Utilizaremos el siguiente comando: mkdir -p aplicacionTFG/kubernetes/kind En este directorio crearemos todos los archivos correspondientes para levantar el clúster local con kind. Primero subiremos las imágenes Docker al siguiente repositorio docker.io/miguecrty diferenciados para cada servicio: Front-End: docker.io/miguecrty/tfg-frontend:latest Back-End: docker.io/miguecrty/tfg-backend:latest BBDD: docker.io/miguecrty/tfg-bbdd:latest Crearemos un script ‘build-push.sh’ para subir las imágenes de manera automática al repositorio. El script se encuentra en el directorio aplicacionTFG/ Previamente a la ejecución del script utilizaremos el siguiente comando para logearnos a docker.io con Podman: podman login docker.io Tras logearnos, ejecutaremos el script ‘build-push.sh’, que se encarga de construir las imágenes y subirlas al repositorio. Utilizaremos el comando: ./build-push.sh Tras la ejecución del script, en el repositorio se habrán subido correctamente las imágenes: Figura 147: Comprobación de subida de imágenes del proyecto 119 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes Una vez subidas las imágenes al repositorio crearemos los objetos kubernetes, para ello crearemos un objeto service y deployment para cada servicio. También crearemos dos nuevos objetos, que nos permitirán crear las variables de entorno para la aplicación de forma segura: ConfigMap y Secret Estos objetos serán creados en el directorio: aplicacionTFG/kubernetes/kind/ ConfigMap Un ConfigMap [30] en Kubernetes es un objeto que permite almacenar datos de configuración en formato clavevalor. Estos datos pueden ser consumidos por los contenedores en un pod como variables de entorno, archivos de configuración o argumentos de línea de comandos. Usar ConfigMaps permite que las aplicaciones sean más portables y la configuración sea manejada de forma centralizada. Para ello crearemos el archivo configmap.yaml, el cual contendrá dos objetos ConfigMap separados por la línea ‘---‘, que contendrá todas las variables de entorno de los servicios Front-End y Back-End. En este archivo pondremos las variables no sensibles de la aplicación, las sensibles estarán en el objeto Secret aplicacionTFG/kubernetes/kind/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: tfg-backend-configmap data: HOST_EMAIL: “smtp-es.securemail.pro” PORT_EMAIL: “465” HOST_CASSANDRA: “cassandra” DATACENTER_CASSANDRA: “datacenter1” KEYSPACE_CASSANDRA: “tfg” PORT_CASSANDRA: “9042” DOMINIO_APP: “meteostats.es” --- apiVersion: v1 kind: ConfigMap metadata: name: tfg-frontend-configmap data: SERVER: “backend:3000” • El primer objeto se llamará ‘tfg-backend-configmap’ y contendrá los valores de las variables de entorno del Back-End, se definirán: HOST_EMAIL, PORT_EMAIL, HOST_CASSANDRA, DATACENTER_CASSANDRA, KEYSPACE_CASSANDRA, PORT_CASSANDRA y DOMINIO_APP • El segundo objeto se llamará ‘tfg-frontend-configmap’ y contendrá los valores de las variables de entorno del Front-End, se definirá: SERVER Aplicación práctica 120 Secret En Kubernetes, un Secret [31] es un objeto que permite almacenar y gestionar información sensible, como contraseñas, claves APIs, tokens de OAuth, y claves SSH. Los Secrets son similares a los ConfigMaps, pero están diseñados específicamente para almacenar información confidencial de forma segura. Para ello crearemos el archivo secrets.yaml, el cual contendrá dos objetos Secrets separados por la línea ‘---‘, que contendrá todas las variables de sensibles de los servicios Front-End y Back-End. aplicacionTFG/kubernetes/kind/secrets.yaml apiVersion: v1 kind: Secret metadata: name: tfg-backend-secrets type: Opaque stringData: USER_EMAIL: [email protected] PASS_EMAIL: ********** USERNAME_CASSANDRA: cassandra PASSWORD_CASSANDRA: cassandra APIKEY_WEATHER: 854c5489c0f85d6f*************** --- apiVersion: v1 kind: Secret metadata: name: tfg-frontend-secrets type: Opaque stringData: GOOGLE_API_KEY: AIzaSyBsZnTlM4g1LCqmg*************** • El primer objeto se llamará ‘tfg-backend-secrets y contendrá los valores de las variables de entorno sensibles del Back-End, se definirán: USER_EMAIL, PASS_EMAIL, USERNAME_CASSANDRA, PASSWORD_CASSANDRA, APIKEY_WEATHER • El segundo objeto se llamará ‘tfg-frontend-secrets y contendrá los valores de las variables de entorno sensibles del Front-End, se definirá: GOOGLE_API_KEY Tras crear los archivos configmap.yaml y secrets.yaml, que contendrán la información sensible de nuestra aplicación, crearemos los objetos Service y Deployment para cada servicio. En el objeto Deployment cargaremos la imagen del servicio del repositorio y le pasaremos los objetos ConfigMap y Secret del servicio correspondiente. 121 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes aplicacionTFG/kubernetes/kind/backend-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: backend spec: [...] template: [...] spec: containers: - name: backend image: docker.io/miguecrty/tfg-backend:latest [...] envFrom: - configMapRef: name: tfg-backend-configmap - secretRef: name: tfg-backend-secrets • En este caso se ha cargado la imagen del Back-End del repositorio docker.io/miguecrty/tfg-backend:latest, que se ha subido previamente. • Crearemos dentro de la sección containers, la sección envFrom, que sirve para cargar variables de entornos desde otros objetos kubernetes, en nuestro caso cargaremos el ConfigMap (tfg-backend-configmap) y el Secret (tfg-backend-secrets) Se hará de manera similar para el objeto Deployment del Front-End (frontend-deployment.yaml) Tras la creación de todos los objetos Deployment crearemos los objetos Service tal y como se explicó en apartados anteriores: El Front-End actuará como LoadBalancer para exponer el servicio fuera del clúster. El Back-End actuará como ClusterIP (nodo del clúster) La Base de Datos actuará como ClusterIP (nodo del clúster) Esta debe ser la estructura del directorio kind aplicacionTFG/kubernetes/kind/ ├── backend-deployment.yaml ├── backend-service.yaml ├── bbdd-deployment.yaml ├── bbdd-service.yaml ├── configmap.yaml ├── frontend-deployment.yaml ├── frontend-service.yaml └── secrets.yaml Aplicación práctica 122 Tras tener configurados y creados todos los archivos necesarios para levantar el clúster local, crearemos el clúster de kind, con el comando: kind create cluster Tras crear el clúster nos ubicaremos en el directorio aplicacionTFG/kubernetes/kind/ y utilizaremos el siguiente comando para aplicar los objetos al clúster: kubectl apply -f . Tras aplicar los comandos se crearán todos los servicios y objetos necesarios para el clúster. Por último, abriremos el servicio cloud-provider con el comando, para obtener la dirección externa del clúster del Load Balancer, con el comando ( en otro terminal ): ~/go/bin/cloud-provider-kind Comprobaremos si los objetos Secret y ConfigMap y los servicios se han creado correctamente con los siguientes comandos: kubectl get configmap kubectl get secrets kubectl get svc -o wide Figura 148: Comprobación de la creación correcta del clúster (kind) Observamos que todo se ha creado correctamente y el servicio del LoadBalancer está expuesto en la dirección IP 10.89.1.8 y el puerto 8080 A continuación, realizaremos una serie de pruebas cortas para comprobar si la aplicación desplegada en el clúster local con kind, responde correctamente: • Iniciar sesión con el usuario de prueba ‘usuario’ y ‘clave’ • Monitorizar ‘Sevilla’ y ‘Huelva’ • Obtener el pronóstico de ‘Dos Hermanas’ • Cambiar la contraseña del usuario 123 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes Figura 149: Comprobación del funcionamiento de la aplicación en el clúster (kind) Observamos que la aplicación responde correctamente a las pruebas cortas realizadas en el clúster local de kind. Aplicación práctica 124 5.5.2 Despliegue del proyecto en la nube Tras levantar correctamente el clúster local con kind y comprobar que todo funciona correctamente y de la forma esperada, procederemos a desplegar el proyecto en la nube. Para ello crearemos dentro del directorio de aplicacionTFG/kubernetes/ un subdirectorio llamado gke el cual contendrá todos los archivos relevantes para levantar el clúster en la nube en la plataforma Google Kubernetes Engine, tal y como se realizó en apartados anteriores. Utilizaremos todos los ficheros que contienen todos los objetos kubernetes del apartado anterior y realizaremos algunas modificaciones para construir el clúster en la nube, incluyendo algunos objetos kubernetes nuevos, para mejorar la aplicación (incluyendo un dominio y certificado TLS). Para ello: 1) Compra del dominio de la aplicación 2) Crearemos el clúster en GKE y se establecerá conexión (como en apartados anteriores) 3) Se instalará: Helm (Gestor de paquetes de k8s <se instalará en el sistema>), Cert-Manager (Gestor de certificados <se instalará en el clúster>) y Traefik (LoadBalancer de la Aplicación) 4) Se crearán los archivos: (ClusterIssuer) cluster-issuer.yaml y (Ingress) frontend-ingress.yaml 5) Se copiará y se modificará el archivo: frontend-service.yaml Se copiarán y reutilizarán los archivos del apartado anterior (clúster kind): configmap.yaml, secrets.yaml, backend-deployment.yaml, backend-service.yaml, bbdd-deployment.yaml, bbddservice.yaml, frontend-deployment.yaml 6) Levantaremos el proyecto en el clúster (cargaremos los objetos al clúster) 7) Configuraremos el dominio a la dirección IP del Ingress (se realizará en la web del distribuidor de dominios <Nominalia>) Compra del dominio de la aplicación Dado que vamos a exponer la aplicación en un clúster en la nube, utilizaremos un dominio para alojarla en lugar de usar una dirección IP. Esto nos permitirá: • Facilitar el acceso: Los usuarios podrán acceder a la aplicación mediante un nombre de dominio fácil de recordar en lugar de una compleja dirección IP. • Mejorar la escalabilidad: Los dominios pueden ser gestionados mediante balanceadores de carga, lo que permite distribuir el tráfico entre múltiples servidores y garantizar un mejor rendimiento y disponibilidad. • Aumentar la seguridad: Un dominio puede utilizar certificados SSL/TLS para encriptar la comunicación entre los usuarios y la aplicación, proporcionando una capa adicional de seguridad. • Simplificar el mantenimiento: Cambiar la dirección IP de un servidor es sencillo, ya que solo requiere actualizar el registro DNS del dominio sin afectar a los usuarios finales. Para ello utilizaremos el host Nominalia: https://www.nominalia.com/dominios/comprar-un-dominio-con- hosting-gratis/ Cuando se compra un dominio con Nominalia, incluye 3 direcciones de correo y las credenciales para el servidor SMTP de envío de correos. Nos crearemos una cuenta y compraremos el dominio: meteostats.es Este será el dominio de nuestra aplicación. 125 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes Construcción y conexión del clúster Crearemos el clúster siguiendo los pasos del apartado: [4.2.4 Ejemplo de despliegue de un clúster en la nube (GKE)] El clúster tendrá el nombre: app-tfg y 2 nodos siempre disponibles Tras la creación del clúster nos conectaremos con el siguiente comando: gcloud container clusters get-credentials app-tfg --zone europe-west1-b --project app-tfg-425416 Cambiaremos el contexto de kubectl al nuevo clúster: kubectl config use-context gke_app-tfg-425416_europe-west1-b_app-tfg Tras esto debemos tener configurado kubectl para configurar el clúster. Instalación de Helm Helm [32] es un gestor de paquetes para Kubernetes que facilita la definición, instalación y actualización de aplicaciones en Kubernetes. Para instalar Helm seguiremos los pasos de la documentación oficial: curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 chmod 700 get_helm.sh ./get_helm.sh Comprobaremos si se ha instalado correctamente con el comando: helm version La salida deberá ser parecida a la siguiente: version.BuildInfo{Version:”v3.15.0-rc.2”, GitCommit:”c4e37b39dbb341cb3f716220df9f9d306d123a58”, GitTreeState:”clean”, GoVersion:”go1.22.3”} Instalación de Cert-Manager en el clúster Cert-Manager [33] es una extensión para Kubernetes que automatiza la gestión de certificados TLS. Facilita la emisión, renovación y administración de certificados dentro de un clúster de Kubernetes. Cert-Manager soporta múltiples proveedores de certificados, incluyendo Let's Encrypt, HashiCorp Vault, y proveedores de DNS para desafíos DNS-01. Para instalar Cert-Manager necesitaremos tener instalado Helm. Añadiremos a Helm el repositorio de Cert-Manager, con el comando: helm repo add jetstack https://charts.jetstack.io --force-update Tras añadir el repositorio, lo instalaremos en el clúster con el siguiente comando: helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ --version v1.15.0 \ --set crds.enabled=true Aplicación práctica 126 Verificaremos la instalación de Cert-Manager en el clúster con el siguiente comando: kubectl get pods --namespace cert-manager Figura 150: Comprobación de la instalación de Cert-Manager Observamos que se ha instalado correctamente ya que se ha creado un namespace llamado ‘cert-manager’ con una serie de pods que se encuentran en estado ‘Running’. Instalación de Traefik en el clúster Traefik [34] es un moderno proxy inverso y balanceador de carga diseñado para implementar y gestionar aplicaciones microservicios en entornos dinámicos como Kubernetes, Docker, y otras plataformas de orquestación de contenedores. Traefik se distingue por su capacidad para integrarse directamente con estos entornos y ajustar dinámicamente su configuración en respuesta a cambios en la infraestructura. Permite: • Soporte para Proxies Reversos y Balanceo de Carga: - Traefik puede enrutar el tráfico HTTP, HTTPS, TCP, y UDP. - Soporta balanceo de carga avanzado con varias estrategias (round-robin, least connection, etc.). • SSL Automático: - Integración nativa con Let's Encrypt para obtener y renovar automáticamente certificados SSL/TLS. El balanceador de carga de nuestra aplicación dejará de ser el servicio del Front-End y lo será traefik. Para instalar Traefik necesitaremos tener instalado Helm. Añadiremos a Helm el repositorio de Traefik, con el comando: helm repo add traefik https://traefik.github.io/charts --force-update Crearemos en el clúster el namespace ‘traefik’ e instalaremos traefik en dicho namespace, con los comandos: kubectl create namespace traefik helm install traefik traefik/traefik -n traefik Podemos comprobar si se ha instalado correctamente con el comando: kubectl get svc -n traefik Figura 151: Comprobación de la instalación de Traefik Observamos que se ha creado un servicio de tipo LoadBalancer en el namespace ‘traefik’ con la dirección pública 34.79.190.190, que acepta los puertos 80 y 443. Esta dirección será nuestro punto de acceso para la aplicación, donde redirigiremos todo el tráfico al Front-End. 127 Aplicación web de pronóstico del tiempo usando la base de datos NoSQL Cassandra y Kubernetes ClusterIssuer En Kubernetes, el objeto ClusterIssuer [35] es un recurso que define una manera centralizada de obtener certificados TLS (Transport Layer Security) para los recursos del clúster. Es parte del proyecto Cert-Manager. El objetivo principal de un ClusterIssuer es simplificar y centralizar la configuración de emisores de certificados, de modo que estos estén disponibles para cualquier namespace dentro del clúster, a diferencia de un Issuer, que está limitado a un namespace específico. Para ello crearemos un objeto ClusterIssuer específico utilizando certificados de Let's Encrypt, un proveedor gratuito de certificados SSL. Crearemos el archivo cluster-issuer.yaml, el cual contendrá el objeto ClusterIssuer que obtiene el certificado TLS de Let’s Encrypt y lo asociará al servicio traefik, creado previamente. aplicacionTFG/kubernetes/gke/cluster-issuer.yaml apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-production namespace: default spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: [email protected] privateKeySecretRef: name: letsencrypt-production solvers: - selector: {} http01: ingress: class: traefik Este objeto utiliza desafíos HTTP-01 gestionados por el controlador de Ingress Traefik para validar la propiedad del dominio, permitiendo que los certificados sean utilizados por cualquier aplicación. Ingress El objeto Ingress [36] en Kubernetes es un recurso API que gestiona el acceso externo a los servicios dentro de un clúster de Kubernetes. Define las reglas de enrutamiento del tráfico HTTP y HTTPS hacia los servicios basados en los nombres de los hosts y las rutas de las URL. Ingress actúa como una interfaz para la gestión de tráfico externo, permitiendo definir configuraciones complejas de enrutamiento y balanceo de carga de manera declarativa. Los componentes principales del objeto Ingress, son los siguientes: • Host: Define el nombre del dominio para el cual se aplica la regla de enrutamiento. • Path: Especifica las rutas dentro del dominio que deben ser enrutadas a servicios específicos. • Backend: Define el servicio de Kubernetes y el puerto al que se debe enrutar el tráfico para una ruta específica. • TLS: Permite la configuración de certificados TLS para asegurar el tráfico HTTPS. Se puede usar con proveedores de certificados automáticos como Cert-Manager.