Introducción a Kubernetes y Minikube

12 de marzo de 2018

El objetivo de esta entrada es dar unas nociones básicas de cómo podemos usar Kubernetes para desplegar una pequeña aplicación basada en microservicios.

Para ello usaremos un repositorio ya creado con un pequeño proyecto que contiene dos microservicios y todos los scripts de Kubernetes que usaremos en esta entrada: https://github.com/abenitezsan/kubernetesDemo

Así mismo, disponemos de un repositorio en DockerHub con las imágenes de nuestros microservicios (2 pequeñas aplicaciones creadas en Spring boot) preparadas para usar: https://hub.docker.com/r/abenitezsan/

Nota: En esta entrada se asume que el lector tiene ya un conocimiento básico sobre contenedores y Docker, sino los conceptos pueden ser algo complicados de entender.

¿Qué es Kubernetes?

Kubernetes es una herramienta de orquestación de contenedores que requiere al menos 3 nodos, uno actuando como master (o director de la orquesta) y el resto de nodos como repositorios de contenedores:

La interacción con Kubernetes se hace únicamente con el master, al cual le daremos órdenes de despliegue, arranque, parada, etc. de nuestros contenedores.

Podemos desglosar Kubernetes en los siguientes elementos:

Los servicios se sirven de servidores DNS,  instalados en la red (cómo https://github.com/kubernetes/dns)  para registrarse en esta y permitir el acceso por nombres de servicio a sus pods, facilitando el descubrimiento de servicios.

Por ejemplo, para acceder a nuestros pods, que contienen una aplicación  de gestión de clientes desplegada en un tomcat, solo necesitaríamos esta url: http://customerService:8080

¿Qué es Minikube?

Cómo comentamos antes, Kubernetes necesita al menos 3 nodos para funcionar, lo cual es un engorro  para poder hacer pruebas locales. Para eso se creó Minikube, el cual es una versión reducida de Kubernetes, corriendo en una única máquina virtual, la cual  actúa de maestro y esclavos a la vez.

Para instrucciones de instalación y más información de Minikube podéis consultar  la página del proyecto: https://github.com/kubernetes/minikube

Así mismo, se necesitará instalar kubectl para poder comunicarse con el servidor Kubernetes.  Podéis encontrar instrucciones de instalación en el siguiente enlace: https://kubernetes.io/docs/tasks/tools/install-kubectl/

Nota para usuarios Windows: Aunque en la documentación se comenta  que Minikube funciona correctamente con hyper-v es muy posible que encontréis multitud de  problemas, en ese caso es recomendable usar VirtualBox.

Arrancando Minikube

Una vez tengamos Minikube instalado en nuestra máquina virtual, lo arrancaremos usando línea de comandos:

minikube start

Si todo ha ido bien veremos un texto como el siguiente:

Podemos comprobar que minikube (y kubectl) se han instalado correctamente, usando kubectl para obtener la lista de servicios desplegados:

kubectl get services

Actualmente solo deberíamos ver el servicio kubernetes  en la lista.

Kubernetes es manejado  normalmente por comandos, aunque puede usarse algún dashboard para  manejarlo. Minikube viene instalado con uno por defecto  que podemos arrancar con el siguiente comando:

Minikube dashboard

Al acabar una ventana del navegador se abrirá automáticamente:

Desplegando nuestra aplicación

Desplegando MYSQL

Comenzaremos desplegando un servidor Mysql, para el cual necesitaremos antes crear un volumen persistente, ya que necesita almacenar sus datos entre arranques.
Para ello usaremos el siguiente script: https://github.com/abenitezsan/kubernetesDemo/blob/master/scripts/persistentVolume.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /c/Users/adbe/minikubeSD

¿Qué hemos hecho aquí?

Y ejecutamos el script usando el siguiente comando:

kubectl create -f persistentVolume.yaml

Y luego verificamos que se ha creado correctamente:

kubectl get pv
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGEMysql-pv 20Gi RWO Retain Bound 0d

Con nuestro volumen persistente creado estamos listos para desplegar Mysql, usando el siguiente script : https://github.com/abenitezsan/kubernetesDemo/blob/master/scripts/mysqlDeploy.yaml

apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
ports:
- port: 3306
selector:
app: mysql
type: NodePort
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pv-claim
spec:
accessModes:
- ReadWriteOnce
storageClassName: ""
resources:
requests:
storage: 20Gi
---
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: mysql
spec:
strategy:
type: Recreate
template:
metadata:
labels:
app: mysql
spec:
containers:
- image: mysql:5.6
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: password
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- mountPath: /var/lib/mysql
name: mysql-persistent-storage
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pv-claim

En este script tenemos  realmente 3 scripts diferentes de Kubernetes, separados por —-

Echemos un vistazo a cada uno de ellos  distinguiéndolos por su parámetro “kind”:

No hemos especificado ningún valor para replicas,  por lo que por defecto,  Kubernetes creará una sola replica de Mysql.

Más sobre tipos de servicios en  https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services—service-types. Este es el único disponible usando Minikube.

Aunque estemos usando targetPort,  al no especificar un targetPort,  no estamos exponiendo el servicio con un puerto determinado, sino que se generará automáticamente.

Cuando hayamos terminado de revisar la configuración podemos  crear los nuevos elementos usando:

kubectl create -f persistentVolume.yaml

Y podemos ver si ahora hay algún claim sobre nuestro almacenamiento persistente:

kubectl get pv

NAME   CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS   CLAIM                 STORAGECLASS   REASON    AGE

mysql-pv   20Gi       RWO       Retain          Bound     default/mysql-pv-claim                 0d

Podemos ver también si nuestros pods han arrancado correctamente:

kubectl get pods

NAME                         READY      STATUS             RESTARTS   AGE

mysql-2703443597-wr2t3        1/1       running                 0     27s

Y también el servicio:

kubectl get services

NAME                 CLUSTER-IP   EXTERNAL-IP   PORT(S)          AGE

kubernetes           10.0.0.1     <none>        443/TCP          15d

mysql                10.0.0.54    <nodes>       3306:32077/TCP   45s

Que tal como vemos, está registrado internamente en el puerto 3306, pero expuesto en el 32077. En este punto, podemos usar algún gestor de Mysql para conectar a nuestra Base de datos y administrarla. Podemos obtener la IP expuesta por Minikube ejecutando:

minikube status

minikube: Running

localkube: Running

kubectl: Correctly Configured: pointing to minikube-vm at 192.168.99.100

Para que los siguientes servicios funcionen correctamente necesitamos crear el esquema “kubernetesDemo” en nuestra base de datos.
Así mismo, en este punto es interesante reiniciar Minikube para ver que el almacenamiento persistente funciona correctamente y nuestro esquema se mantiene tras reinicios.

Desplegando MicroServicios

Una vez que tenemos nuestra base de datos funcionando con el esquema creado, estamos listos para desplegar nuestros Microservicios (aplicaciones SpringBoot).
Es interesante observar la configuración de acceso a base de datos de ellas:

spring.jpa.hibernate.ddl-auto=create

spring.datasource.url=jdbc:mysql://mysql:3306/kubernetesDemo

spring.datasource.username=root

spring.datasource.password=password

Como podemos ver, estamos estableciendo como única cadena de conexión “mysql”, que es el nombre del servicio creado en el paso anterior, a través del servicio de DNS Kubernetes, encontrar nuestro servicio y nos redirigirá al pod correspondiente.
Podemos ver ahora los scripts preparados para crear nuestros dos servicios, en ambos estamos creando un despliegue y un servicio asociado a él.

https://github.com/abenitezsan/kubernetesDemo/blob/master/scripts/CustomerDeployment.yaml 

apiVersion: extensions/v1beta1

kind: Deployment

metadata:

name: customerws

spec:

replicas: 2

template:

metadata:

labels:

app: customerws

spec:

containers:

- name: customerws

image: abenitezsan/customerservice

imagePullPolicy: "Always"

ports:

- containerPort: 8080

---

kind: Service

apiVersion: v1

metadata:

name: customerws-service

spec:

selector:

app: customerws

ports:

- protocol: TCP

port: 8080

targetPort: 8080

type: NodePort

https://github.com/abenitezsan/kubernetesDemo/blob/master/scripts/productDeployment.yaml

apiVersion: extensions/v1beta1 # forversions before 1.6.0use extensions/v1beta1

kind: Deployment

metadata:

name: productws

spec:

replicas: 2

template:

metadata:

labels:

app: productws

spec:

containers:

- name: productws

image: abenitezsan/productservice

imagePullPolicy: "Always"

ports:

- containerPort: 8080

---

kind: Service

apiVersion: v1

metadata:

name: productws-service

spec:

selector:

app: productws

ports:

- protocol: TCP

port: 8080

targetPort: 8080

type: NodePort

Ambos son muy similares y su especificación debería ser bastante clara con lo comentando durante la descripción de Mysql, sin embargo, un par de apartados merecen una mención:

Ejecutamos los dos scripts:

kubectl create -f productDeployment.yaml

kubectl create -f CustomerDeployment.yaml

Y miramos el estado de nuestros nuevos pods:

kubectl get pods

NAME                          READY     STATUS    RESTARTS   AGE

customerws-3577278556-30m4l   1/1Running   188d

customerws-3577278556-xx239   1/1Running   138d

mysql-2703443597-kss92        1/1Running   447m

productws-1360159399-655rq    1/1Running   88d

productws-1360159399-w0t4p    1/1       Running   18         8d

Podemos ver cuatro nuevos pods, dos por cada uno de los servicios que hemos creado.
Y nuestros nuevos servicios:

kubectl get services

NAME                 CLUSTER-IP   EXTERNAL-IP   PORT(S)          AGE

customerws-service   10.0.0.215<nodes>       8080:30471/TCP   10d

kubernetes           10.0.0.1<none>        443/TCP          15d

mysql                10.0.0.54<nodes>       3306:32077/TCP   8d

productws-service    10.0.0.158<nodes>       8080:31400/TCP   8d

En este punto es interesante acceder a nuestros nuevos servicios y comprobar que están funcionando correctamente, usando la IP obtenida con Minikube status y el puerto obtenido en el listado anterior.

Enrutando nuestra aplicación-Ingress

En este punto tenemos nuestra aplicación expuesta al exterior, pero son algo difíciles de alcanzar, al tener que ir manteniendo los puertos de cada. Para solventar esto usaremos Ingress, creando reglas para enrutar las peticiones a nuestra aplicación directamente desde los puertos 80/443.

Echemos un vistazo a la siguiente regla Ingress: https://github.com/abenitezsan/kubernetesDemo/blob/master/scripts/ingress-rule.yaml

>apiVersion: extensions/v1beta1

kind: Ingress

metadata:

name: customer-ingress

annotations:

ingress.kubernetes.io/rewrite-target: /

spec:

rules:

- host:

http:

paths:

- path: /customer

backend:

serviceName: customerws-service

servicePort: 8080

- path: /product

backend:

serviceName: productws-service

servicePort: 8080

Aspectos a destacar del script:

NOTA: En este caso usamos el puerto configurado como interno del servicio.

Ejecutamos nuestro script:

kubectl create -f ingress-rule.yaml

Y vemos si se ha desplegado correctamente:

kubectl get ing

NAME               HOSTS     ADDRESS          PORTS     AGE

customer-ingress   *         192.168.99.100   80        8d

Esto por sí solo no bastará, además de reglas Ingress, necesitamos algún controlador que las gestione en entornos de producción. Uno bastante popular es Traefik : https://github.com/containous/traefik .

En Minikube en cambio podemos usar su controlador, incluido por defecto (nginx), que necesitamos activar:

minikube addons enable ingress

Y ya está, ahora nuestros servicios deberían estar expuestos en nuestras rutas, usando la ip obtenida con Minikube status. Podemos acceder a nuestros servicios:

http://192.168.99.100/customer

http://192.168.99.100/product

Sobre el autor: Adolfo Benítez
Comments
Únete a nosotros