Nos centramos en esta entrada en la función de gestión de sesiones.
En el ecosistema de microservicios cloud, el tema de sesiones puede ser un problema para la transformación de aplicaciones monolíticas en microservicios. Habitualmente todas las aplicaciones monolíticas, de una u otra forma, persisten datos de manera que se permita una navegación fluida para un usuario dado.
Por ejemplo, todas las aplicaciones protegidas por login, han de proveer una gestión de datos que garantice una navegación con "estado" mediante un protocolo, como http, sin estado. Al menos han de guardar el usuario logado (al margen de algunos datos asociados al mismo), asignar una sesión http con su caducidad, etc..., de forma que pueda navegar por aquellos apartados permitidos en la web protegida.
Cuando pasamos a microservicios cloud, donde las buenas prácticas prohíben usar sesiones de servidor, qué soluciones podemos utilizar. Bien, una de ellas es Redis.
Utilizaremos para esta prueba
- Un contenedor docker Redis
- Un microservicio Spring boot
- Entorno Kubeadm de prueba.
Pod
Usaremos una image docker proporcionada por Bitnami
El contenido del redis.yaml (deploy y service) es este:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: redis
spec:
replicas: 1
template:
metadata:
labels:
app: redis
role: master
tier: backend
spec:
containers:
- name: redis-master
image: bitnami/redis:latest
ports:
- containerPort: 6379
env:
- name: ALLOW_EMPTY_PASSWORD
value: "yes"
---
kind: Service
apiVersion: v1
metadata:
name: redis
labels:
name: redis
spec:
type: NodePort
ports:
- port: 6379
protocol: TCP
targetPort: 6379
selector:
app: redis
Podríamos configurar varias réplicas, pero para nuestra prueba nos basta con una, la del pod master.
Como vemos, el puerto expuesto es el 6379. Es a este puerto al que haremos apuntar nuestra app.Para levantar el pod lanzaremos el comando
kubectl apply -f redis.yaml
Tras unos instantes ya podemos ver el pod levantado
kubectl get pods
Revisamos el arranque
kubectl logs -f redis-587d9bd764-hzbzj
Podemos acceder al pod para ver la información que iremos generando
kubectl exec -it redis-587d9bd764-hzbzj bash
Una vez dentro lanzamos el cliente redis
redis-cli monitor
nos devuelve un OK y queda a la espera de accesos
Spring boot
Construyamos un microservicio básico de prueba con Spring boot
pom.xml
Como dependencias específicas redis debemos añadir<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
Incorporamos también swagger para facilitar las pruebas
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
Java
Añadimos una clase de configuración con el siguiente contenido@Configuration
@EnableRedisHttpSession
public class RedisConfig {
private @Value("${spring.redis.host}") String redisHost;
private @Value("${spring.redis.port}") int redisPort;
@Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory(redisHost, redisPort);
}
}
Para usar la session lo hacemos de la forma habitual
HttpSession misession= request.getSession(true);
misession.setAttribute("variable", "valor");
etc...
El endpoint que explicamos en la prueba de más abajo tiene este código
@GetMapping("/fecha")
public @ResponseBody String date(HttpServletRequest request) {
log.info("Acceso al metodo rest 'fecha'");
return "Hola desde el RestController en fecha <br><h2>" +
new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()) +
" !!!<h2>" + "<hr/>" + "<h3>app: '" + appName + "'</h3>" + "<p>host name / ip: <b>" +
Utils.getIp() + "</b></p>" + "<p>Session: <b>" +
request.getSession().getId() + "</b></p>" + "<hr/><hr/>";
}
Sólo queremos que muestre el server (pod en el caso de kubernetes) desde el que se ejecuta, la hora y la session http.
application.yml
Las propiedades específicas son estas (application.yml)spring:
application:
name: redis-session
profiles:
active: pro
session:
store-type: redis
redis:
host: redis
port: 6379
En host hemos puesto el nombre "redis" que se corresponde con el servicio creado según el fichero "redis.yaml" descrito más arriba.
Dockerfile
Este es su contenido:FROM java:openjdk-8-jdk-alpine
# add el jar con el nombre del "finalName" del pom.xml
ADD redis-session.jar /app.jar
# se modifica la fecha a la actual
RUN sh -c 'touch /app.jar'
# comando a ejecutar
CMD ["java", "-jar", "/app.jar"]
lo necesitamos para poder construir la imagen a desplegar en kubeadm.
Debemos hacer un "mvn clean install" para generar el artefacto "redis-session.jar".
copiamos el jar a nuestro ubuntu y lanzamos el siguiente comando
sudo docker build -t "redis-session:1.0.0" .
desde el directorio que contiene el Dockerfile y el redis-session.jar (el nombre de la imagen siempre ha de ser en minúsculas).
Validamos con "sudo docker images" (en extras veremos como eliminar el molesto sudo)
Deployment y service
Creamos, a partir de la imagen nuestro deploy y service. Para ello necesitamos el siguiente yaml (redis-session.yaml):apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: redis-session
labels:
name: redis-session
spec:
replicas: 3
template:
metadata:
labels:
app: redis-session
tier: backend
spec:
containers:
- name: redis-session
image: redis-session:1.0.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /redis-session/readiness
port: 8080
initialDelaySeconds: 60
timeoutSeconds: 5
livenessProbe:
httpGet:
path: /redis-session/liveness
port: 8080
initialDelaySeconds: 60
timeoutSeconds: 5
---
kind: Service
apiVersion: v1
metadata:
name: redis-session
labels:
name: redis-session
tier: backend
spec:
type: LoadBalancer
ports:
- port: 8080
protocol: TCP
selector:
app: redis-session
tier: backend
Aplicamos:
kubectl apply -f redis-session.yaml
Importante, usamos tres réplicas, que nos generarán tres pods que darán servicio a las peticiones que reciba nuestra aplicación.
Podemos revisarlo con "kubectl get pods"
También hemos añadido dos "probes", el readinessProbe (indica a kubernetes cuando un pod está preparado para recibir peticiones) y livenessProbe (indica a Kubernetes si un pod está activo). Se corresponden con dos endpoints ubicados en el "MonitorController.java".
Pruebas
Para acceder desde fuera del ubuntu lanzamos el siguiente comando en nuestro ubuntu:
sudo kubectl port-forward svc/redis-session 9090:8080
y mapeo desde VirtualBox de Windows:
Ahora, desde un navegador de nuestro windows tenemos varios endpoints arrancados para probar:
el de swagger: http://localhost:9090/redis-session/swagger-ui.html
endpoint "fecha" que nos permite ver el juego de sesiones
http://localhost:9090/redis-session/fecha
Si nos centramos en este último endpoint y probamos obtenemos el siguiente resultado:
Si ahora paramos el pod que nos ha respondido (vemos su id) con
kubectl delete pod id
Se levantará de forma automática uno nuevo en su sustitución. Si ahora refrescamos en nuestro navegador (el mismo que hemos usado antes) veremos el cambio de pod que responde, pero seguiremos viendo la misma session id:
Es decir, mientras no cerremos navegador, aunque se paren todos los pods de nuestro microservicio y se levanten otros en su sustitución, la session permanecerá inalterable, ya que su gestión, se está derivando hacia el pod de redis. Si introducimos datos en session, estos los seguiremos encontrando ahí. Está todo persistido en el pod de redis.
Si me permitís una imagen gráfica (con sólo dos pods spring boot) esto es lo que está pasando:
Podemos jugar ahora con swagger con los endpoints "addDataSession" y "getDataSession" del "main-controller", parando pods, etc... Cada vez que hagamos un delete del pod deberemos relanzar el "sudo kubectl port-forward..." descrito más arriba.
También lo podemos hacer vía curl de la siguiente forma.Para insertar datos en session (hemos de grabar la session en fichero):
curl -X PUT -c sessionfile http://localhost:9090/redis-session/addDataSession?name=Antonio
Session grabada: 760d26e8-3ab1-4103-bada-8fe8d999a1d0
Ahora recuperamos el dato enviando la session grabada en el sessionfile:
curl -X GET -b sessionfile http://localhost:9090/redis-session/getDataSession
respuesta: ["Antonio"]
De nuevo añadimos datos, esta vez el comando es:
curl -X PUT -b sessionfile -c sessionfile http://localhost:9090/redis-session/addDataSession?name=es
Session grabada: 760d26e8-3ab1-4103-bada-8fe8d999a1d0
curl -X PUT -b sessionfile -c sessionfile http://localhost:9090/redis-session/addDataSession?name=panadero
Session grabada: 760d26e8-3ab1-4103-bada-8fe8d999a1d0
Como vemos, siempre se nos devuelve el mismo valor de session (760d26e8-3ab1-4103-bada-8fe8d999a1d0). No nos importa que pod pueda estar respondiendo.
Finalmente recuperamos todo:
curl -X GET -b sessionfile http://localhost:9090/redis-session/getDataSession
["Antonio","es","panadero"]
Eliminamos los tres pods "kubectl delete pods id1 id2 id3". Tras unos segundos se reinician y volvemos a levantar el port-forward y lanzamos de nuevo la consulta, obteniendo el mismo resultado
curl -X GET -b sessionfile http://localhost:9090/redis-session/getDataSession
["Antonio","es","panadero"]
Todas estas peticiones dejan logs en el pod de redis, que mediante su acceso "kubectl exec -it id_pod bash" y posterior lanzamiento del cliente redis "redis-cli monitor" vemos:
Recordad que podemos tracear el pod mediante "kubectl logs -f id". Ahí podremos ver el pod que recibe la petición, etc...
Además de lo dicho, también veremos en los logs de cada pod, cada 30 segundos, la salida que se generan con la monitorización que hemos introducido (liveness y readiness).
Extra
Si queremos usar redis en kubeadm desde nuestro entorno de desarrollo local deberemos realizar estas acciones previas
La primera, lanzar el siguiente comando
sudo kubectl port-forward svc/redis 6379:6379
es decir, damos acceso al puerto expuesto por el servicio (svc/redis), el 6379, desde fuera
Ahora, en nuestro VirtualBox (donde corre el ubuntu con kubeadm) configuramos el mapeo de puertos:
Por otro lado, si no queremos usar siempre "sudo" para la ejecución de cualquier comando "docker XXX" podemos incluir nuestro usuario ubuntu en el grupo "docker" de esta forma
sudo usermod -aG docker $(whoami)
Deberemos cerrar sesión y abrir una nueva para que sea efectiva la nueva configuración. De esta forma, ahora para usar comandos del tipo "docker images", "doker ps", etc... no necesitamos añadir "sudo" delante.
Más info
Por supuesto, todo el código está disponible en
https://gitlab.com/gincol-blog/redis-session.git
Como siempre, la información principal está en las páginas oficiales
Spring: https://projects.spring.io/spring-data-redis/
Redis: https://redis.io/
https://redis.io/commands/monitor
https://docs.bitnami.com/virtual-machine/components/redis/
https://docs.bitnami.com/kubernetes/get-started-kubernetes/
Otras funciones Redis:
https://spring.io/guides/gs/spring-data-reactive-redis/
https://spring.io/guides/gs/messaging-redis/
No hay comentarios:
Publicar un comentario