En nuestro caso, en entorno local usamos Minikube, instalado sobre un Ubuntu virtualizado corriendo en VMWare player (mejor opción que Virtualbox).
Bien, veremos que Kubernetes ya nos provee de un servicio de descubrimiento y balanceo que hace innecesario usar el stack Netflix en lo que respecta a eureka y ribbon (para este último hay una forma, kubeflix, de poder usarlo dentro de kubernetes).
Por tanto, deberemos hacer una serie de cambios sobre el código que hemos ido compartiendo en gitlab. Directamente hemos obviado EUREKA y hemos modificado los dos microservicios para evitar el uso de RIBBON (sólo en el profile "prod", para el resto de profiles "des" y "dev" vale todo lo dicho en las entradas anteriores).
Explicamos aquí como proceder, entonces, en Minikube. Recordamos, minikube es un cluster kubernetes de un sólo nodo.
Lo arrancamos con "minikube start". Habitualmente se atuconfigura con la ip "192.168.99.100". Podemos verlo con "minikube ip". Cuando acabemos podemos parar con "minikube stop"
Todo lo que mostramos a continuación lo hacemos sobre nuestro ubuntu.
Ingress y Secret
En un entorno kubernetes, cada pod (equivalente a container en docker) responderá a una determinada ip, gestionada por el mismo kubernetes. Por tanto, para evitar el conocimiento detallado de la infraestructura, dejando que el mismo kubernetes haga de forma autónoma, lo mejor es crear un "dominio" de trabajo.
En nuestro caso hemos creado "gincol.blog.com". Esta entrada la introduciremos en el "/etc/host" del ubuntu virtualizado:
192.168.99.100 gincol.blog.com
Además, como demo, proveeremos protocolo seguro para acceder a este dominio. Para ello generamos lo que en kubernetes se llama secret. Ejecutamos el siguiente comando en un directorio de trabajo
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout gincol.blog.com.key -out gincol.blog.com.cert -subj "/CN=gincol.blog.com/O=gincol.blog.com"
A continuación creamos el secret desde el mismo directorio desde el que tenemos los certificados creados:
kubectl create secret tls gincol.blog.com-secret --key gincol.blog.com.key --cert gincol.blog.com.cert
Podemos verificarlo
kubectl get secret
y obtener información con
kubectl describe secret gincol.blog.com-secret
Crearemos un ingres para cada uno de los microservicios (lo veremos más adelante). El esquema simple nos lo muestra la propa api de kubernetes:
Ingress nos proporciona una forma de resolución de nombre interpuesto entre las peticiones de clientes externos (ya sean de internet, intranet...) y nuestros servicios desplegados en kubernetes.
Con secret, además, nos permite de una forma sencilla la adición de protección ssl.
Imagenes y Registro
En nuestro caso, el laboratorio local que hemos configurado, para el despliegue en Kubernetes usamos un entorno CI. El código lo tenemos en nuestro eclipse windows. En ubuntu tenemos un jenkins y un gitlab (ambos gratuitos).
Por cada microservicio creamos:
- repositorio gitlab con un webhook apuntando al job jenkins
- un job jenkins de tipo pipeline
Cuando hacemos "git push ....." a nuestro repo, se dispara el webhook que informa a jenkins que tiene a su disposición nuevo código. Jenkins recoge el código, dentro del cual hay un Jenkinsfile que describe los pasos (compilación y creación del artefacto, push al registro privado de imágenes, deploy en Kubernetes de los diferentes ingress, deploys y services) a realizar.
En nuestro caso, con cada "git push..." se desencadena todo ese hilo de acciones, que tras unos minutos vemos operativo.
No es trivial hacerlo y menos aún explicarlo en esta entrada, sería kilométrica. Por lo que no nos detenemos en ello. Para simularlo deberéis copiar los artefactos generados en vuestro windows, llevadlos al ubuntu, junto con el docker-compose y Dockerfile que vimos en la anterior entrada,
Antes de lanzarlo haced que estos comando operen sobre Minikube, lanzando previamente
eval $(minikube docker-env)
De esta forma, todos los comandos docker y docker-compose que lancéis lo haréis sobre Minikube.
Para subir la imagen generada al registro local debéis:
- Crear el registro: docker run -d -p 5000:5000 --restart=always --name registry-srv -v /data/docker-registry:/var/lib/registry registry:2
- subir la imagen al registro: docker push localhost:5000/MI_IMAGEN
- ver las imágenes del registro: curl -X GET http://192.168.99.100:5000/v2/_catalog
- eliminar una imagen del registro: docker image remove localhost:5000/MI_IMAGEN
Microservicio uno
De este levantaremos 3 réplicas. Proveemos en un mismo yaml (App.yaml) la configuración de deploy y servicio.
Nos detenemos sólo en lo esencial de dicho fichero:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: client-one
...
spec:
replicas: 3
spec:
containers:
- name: client-one
image: localhost:5000/client-one-load-balancing
ports:
- containerPort: 8090
env:
- name: SPRING_PROFILES_ACTIVE
value: prod
readinessProbe:
httpGet:
path: /client-one-load-balancing/readiness
port: 8090
initialDelaySeconds: 30
timeoutSeconds: 5
livenessProbe:
httpGet:
path: /client-one-load-balancing/liveness
port: 8090
initialDelaySeconds: 30
timeoutSeconds: 5
---
kind: Service
apiVersion: v1
metadata:
name: client-one
...
spec:
type: NodePort
ports:
- port: 8090
protocol: TCP
selector:
app: client-one
tier: backend
Los "---" son necesarios para separar el deploy del service. Los "..." indican que no es completo (está completo en el gitlab)
Se han añadido "readinessProbe" y "livenessProbe" como peticiones de "health check" que permiten a kubernetes ver la salud de nuestros pods. Para ello hemos creado un "MonitorController.java" con ambos endpoints. En ellos sólo devolvemos un OK. Si kubernetes no obtiene un 200 como código de respuesta http, no pasará peticiones a nuestro pod.
En dichos endpoints podemos hacer aquellas validaciones que creamos oportunas para asegurar que nuestro microservicio está realmente disponible (conexión a bdd o a un recurso externo, etc...).
Con NodePort en el service declaramos el puerto por el que será visible nuestro microservicio.
El nombre del microservicio "client-one" es semejante al nombre que descubríamos en EUREKA en las versiones de profiles "des" y "dev".
Para ejecutar el fichero yaml hacemos
kubectl apply -f App.yaml (o bien kubectl create -f App.yaml)
Si necesitamos modificar algo y volverlo lanzar
kubectl apply -f App.yaml (o bien kubectl replace --force --cascade=true -f App.yaml)
El Ingress.yaml de este microservicio es el siguiente:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: client-two-ing
annotations:
kubernetes.io/ingress.class: nginx
spec:
tls:
- hosts:
- gincol.blog.com
secretName: gincol.blog.com-secret
rules:
- host: gincol.blog.com
http:
paths:
- path: /client-two-load-balancing
backend:
serviceName: client-two
servicePort: 9999
Se observa la referencia al secret anteriormente creado, la definición del dominio gincol.blog.com, el serviceName, que ha de corresponderse con el nombre del servicio. Se observa también el path asociado al context de nuestro microservicio (definido en el application-prod.yml) y el puerto que sigue el mismo patrón.
Se lanza como antes "kubectl apply -f Ingress.yaml"
Microservicio dos
Muy semejante al primero, con los cambios obvios siguiendo el mismo patrón.
No lo detallamos, se puede encontrar en el gitlab.
Unicamente indicamos como se enlaza el dos con el uno, ahora sin RIBBON. Se hace mediante la url
http://client-one:8090/client-one-load-balancing/saludo
donde se ve que la referencia es al nombre del servicio uno, que como dijimos más arriba viene a ser el nombre "descubierto" en EUREKA en las versiones anteriores. Se podría haber configurado como nombre de dominio, el nombre "DNS entry", que sigue el patrón "service.namespace.svc.cluster.local", que en nuestro caso hubiese sido:
http://client-one.default.svc.cluster.local:8090/client-one-load-balancing/saludo
Prueba
Abrimos un firefox en nuestro ubuntu y lazamos la url directa de nuestro microservicio uno: "https://gincol.blog.com/client-one-load-balancing/saludo". Si vamos refrescando veremos que cada vez responde un pod diferente (de los tres que levantamos). Hemos añadido código para verificar el host que reponde
Ahora invocamos al microservicio dos "https://gincol.blog.com/client-two-load-balancing/hola" (que acaba llamando al uno):
Ahora podemos jugar un poco con los pods, borramos uno de ellos
primero vemos los activos "kubectl get pods", luego eliminamos uno "kubectl delete pod ID_POD". Vemos que Kubernetes lo que hace es levantar un nuevo pod de forma que siempre haya tres activos
Seguimos lanzando peticiones. Obsrevamos que no se pasan peticiones al pod borrado y tampoco al nuevo hasta que esté totalmente activo, gracias al "readinessProbe". Aquí lo tenemos, con nueva ip:
Código y más info
El código lo podéis descargar de
https://gitlab.com/gincol-blog/client-one-load-balancing.git
https://gitlab.com/gincol-blog/client-two-load-balancing.git
Tenéis más info en
https://www.paradigmadigital.com/dev/microservicios-2-0-spring-cloud-netflix-vs-kubernetes-istio/
http://blog.christianposta.com/microservices/netflix-oss-or-kubernetes-how-about-both/
https://github.com/fabric8io/kubeflix