Hoy vamos a poner la guinda en el pastel de la serie de entradas sobre el mundo de los microservicios, esto es, los contenedores, hablando de alta disponibilidad con Kubernetes. Os explicaré cómo construir un entorno de alta disponibilidad para desplegar nuestros contenedores.
Para ello utilizaremos nuestro proveedor de confianza: Clouding.io. Mi intención es explicar paso a paso el montaje. Nos serviremos, para realizar el proceso, de un nodo «master» y dos «workers«.
Digamos que el nodo master cumple el rol de director de orquesta y los workers son miembros de esta y actúan según aquél les indica. Evidentemente, en entornos productivos con gran volumen esta cantidad de participantes no sería suficiente. Quizá entonces serían tres masters y cinco workers o aún más (sí, lo recomendado es que los nodos siempre sean impares). Lo mismo sucede con el número de núcleos de procesador y memoria.
En lo que respecta al disco, en el caso de Clouding.io disfrutamos de discos sólidos SSD. Y, por último, que no menos importante, en lo que respecta a la red también se nos garantiza una Cisco de 20 Gbps.
Si has llegado hasta aquí y no sabes en qué consisten los contenedores y los microservicios, te recomiendo que eches un vistazo a esta entrada y a todas las que vinieron después.
Crear un entorno de alta disponibilidad con Kubernetes
Antes de nada, listemos lo que vamos a necesitar. Lo primero es un nodo master y dos workers. Para ello vamos a utilizar una distribución GNU/Linux, concretamente Debian 10, aprovechando que recientemente han añadido esta distribución a nuestro proveedor, Clouding.io.
La creación de los VPS no tiene excesivo misterio desde el panel de administración de nuestro proveedor. Eso sí, le asignaremos a cada nodo 4 GB de memoria (aunque con 2 GB sería más que suficiente para empezar) y 2 núcleos de procesador.
Una vez creados los tres hosts contamos con los siguientes datos:
- servmaster.bitsandlinux.com (Nodo master)
- servworker01.bitsandlinux.com (Nodo worker 1)
- servworker02.bitsandlinux.com (Nodo worker 2)
Asignaremos 20 GB de espacio de disco a cada uno. Punto importante, cuando tengamos las máquinas creadas, es realizar el correspondiente intercambio de llaves entre sí, para facilitar el montaje y la configuración.
Además vamos a rizar el rizo, como se suele decir, y utilizaremos un cuarto host como nodo de almacenaje, donde compartiremos un recurso NFS. Para ello utilizaremos un pequeño nodo también con Debian 10, pero que tenga suficiente disco. En esta entrada no daré muchos detalles al respecto pero, para montar el nodo NFS, os podéis servir de esta entrada que escribí en su día: Compartiendo carpetas en red con NFS.
Definiendo el entorno
Lo primero que haremos, antes de empezar a instalar los paquetes necesarios, es igualar el fichero /etc/hosts para todos los nodos, quedando así:
# Kubernetes 185.166.214.124 servmaster.bitsandlinux.com servmaster 93.189.88.105 servworker01.bitsandlinux.com servworker01 93.189.94.83 servworker02.bitsandlinux.com servworker02 85.208.21.25 servstorage.bitsandlinux.com servstorage
En el caso de Clouding.io debemos modificar otro fichero, ya que los cambios realizados en /etc/hosts/
se perderán al reiniciar. Para evitarlo debemos editar el fichero /etc/cloud/templates/hosts.debian.tmpl.
Además, en los nodos habilitamos un usuario operador con el nombre que queramos y lo añadimos al grupo sudo (wheel en el caso de Centos).
useradd -c "Usuario operador" -d "/home/operador" \ -s "/bin/bash" -G sudo operador # Y definimos una contraseña mkdir /home/operador chown operador:operador /home/operador passwd operador
Deshabilitamos el acceso para el usuario root para mejorar la seguridad:
sed -i 's/PermitRootLogin yes/PermitRootLogin no/g' \ /etc/ssh/sshd_config
Reiniciamos el servicio y continuamos:
sudo systemctl restart sshd
Y, para facilitar el acceso entre los hosts, creamos con ssh-keygen una llave pública y privada para ssh, añadiendo el intercambio de llaves con ssh-copy-id entre los hosts.
Deshabilitando SELinux
En muchos casos, SELinux está habilitado por defecto, pero no nos interesa. Debemos deshabilitarlo como sigue:
sudo sed -i 's/SELINUX=enabled/SELINUX=disabled/g' \ /etc/selinux/config
Para que este cambio tenga efecto debemos reiniciar el host:
sudo reboot
Configurar el cortafuegos
Si tenemos el cortafuegos habilitado (véase firewalld en el caso de Centos) debemos abrir los siguientes puertos:
firewall-cmd --permanent --add-port=6443/tcp firewall-cmd --permanent --add-port=2379-2380/tcp firewall-cmd --permanent --add-port=10250/tcp firewall-cmd --permanent --add-port=10251/tcp firewall-cmd --permanent --add-port=10252/tcp firewall-cmd --permanent --add-port=10255/tcp firewall-cmd –reload
Expliquemos en qué consiste cada uno:
Puerto o rango | Protocolo | Función | Dirección | Servicios que lo utilizan |
---|---|---|---|---|
6443 | TCP | API de Kubernetes | Entrante | Todos |
2379-2380 | TCP | Servicios etcd | Entrante | kubi-apiserver, etcd |
10250 | TCP | Kubelet API | Entrante | Self, control plane |
10251 | TCP | kube-scheduler | Entrante | Self |
10252 | TCP | kube-controller-manager | Entrante | Self |
En el caso de nuestro proveedor de confianza solo debemos añadir los puertos correspondientes al cortafuegos que tengamos definido desde el panel de administración.
Habilitar e instalar Docker-CE
Ahora nos toca habilitar el repositorio comunitario de Docker en los tres hosts, tanto en el manager como en los workers. Para no ir uno a uno podemos utilizar herramientas como pssh para facilitar nuestro trabajo, aprovechando que tenemos el intercambio de llaves hecho. En mi caso, he creado un fichero con información de todos los nodos, para interactuar a la vez con todos ellos. Al final, como todo, esto es a gusto del consumidor.
Antes de nada instalamos una serie imprescindible para el buen funcionamiento del entorno:
sudo apt update sudo apt install apt-transport-https ca-certificates sudo apt install curl gnupg2 software-properties-common
Para habilitar el repositorio en los nodos primero añadimos la llave correspondiente:
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
Y luego el repositorio:
sudo add-apt-repository \ "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"
Nos aseguramos de utilizar el repositorio de Docker y no el de Debian:
sudo apt update apt-cache policy docker-ce
Ya podemos realizar la instalación:
sudo apt install docker-ce
Asegurarse de que iptables no use el backend de nftables
No hace mucho os hablé de nftables y cómo era el reemplazo en muchas distribuciones para los sistemas clásicos de cortafuegos como iptables. El problema es que este sistema no es compatible con las últimas versiones de los paquetes de administración de Kubernetes, como kubeadm, porque causa reglas de firewall duplicadas y rompe kube-proxy.
En Debian 10, que es la distribución que estamos utilizando, nftables está habilitado como backend para iptables, por lo que tendremos que hacer algunos cambios (pasar el sistema al modo heredado). Esto me ha causado muchos quebraderos de cabeza ya que, inicialmente, quería utilizar Centos 8 para esta entrada y no es compatible, justamente por esto.
Para habilitar el modo heredado (legacy), seguimos estos pasos:
# Nos aseguramos que los binarios heredados estén instalados sudo apt-get install -y iptables arptables ebtables # Cambiamos a versiones legacy sudo update-alternatives --set iptables /usr/sbin/iptables-legacy sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy sudo update-alternatives --set arptables /usr/sbin/arptables-legacy sudo update-alternatives --set ebtables /usr/sbin/ebtables-legacy
Instalar Kubernetes
Debemos habilitar el correspondiente repositorio de la siguiente manera, en el nodo con el rol de master y en los workers:
sudo vi /etc/apt/sources.list.d/kubernetes.list
Y añadimos:
deb https://apt.kubernetes.io/ kubernetes-xenial main
El paquete Kubeadm nos ayuda a construir nuestro entorno de alta disponibilidad. Además, también es compatible con otras funciones del ciclo de vida del clúster, como actualizaciones, degradación y administración de tokens de arranque. Y también es compatible con la integración con otras herramientas de orquestación, como Ansible o Terraform.
Para poder utilizar el repositorio debemos añadir su correspondiente llave:
curl -s \ https://packages.cloud.google.com/apt/doc/apt-key.gpg \ | sudo apt-key add -
Pasa a instalarlo:
sudo apt-get update sudo apt-get install -y kubelet kubeadm kubectl sudo apt-mark hold kubelet kubeadm kubectl
Utilizar cgroup para kubelet
Al usar Docker, kubeadm detectará automáticamente el controlador cgroup para el kubelet y lo configurará en el archivo /var/lib/kubelet/kubeadm-flags.env
durante el tiempo de ejecución.
Si estamos utilizando un CRI diferente, debe modificar el archivo /etc/default/kubelet
(/etc/sysconfig/kubelet
para CentOS, RHEL, Fedora) con el valor de su controlador cgroup, así:
KUBELET_EXTRA_ARGS=--cgroup-driver="value"
Este archivo será utilizado por kubeadm init y kubeadm join para generar argumentos adicionales definidos por el usuario para el kubelet. Solo debemos hacerlo si el controlador cgroup de su CRI no es cgroupfs, porque ese es el valor predeterminado en el kubelet.
Si hacemos algún cambio debemos reiniciar el daemon:
systemctl daemon-reload systemctl restart kubelet
Iniciar el nodo master
El nodo con el rol de master ejecuta algunos servicios esenciales para el clúster. Antes de iniciarse realizará una serie de comprobaciones previas, esenciales para saber si el nodo cumple con todos los requisitos. Estas comprobaciones previas exponen advertencias y sobresalen en caso de errores. kubeadm init luego descarga e instala los componentes del plano de control del clúster.
Pero antes de hacer nada debemos deshabilitar la memoria transaccional o SWAP.
sudo swapoff -a
Iniciamos Kubernetes con la siguiente orden:
sudo kubeadm init --pod-network-cidr=10.244.0.0/16
Si todo ha ido bien se nos indicarán las instrucciones para añadir el primer nodo master para, más adelante, añadir los nodos con el rol de worker. Es importante apuntar los datos que nos ofrece.
Seguimos las instrucciones, en este caso para el usuario root:
mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config
Comprobamos que el nodo se ha incorporado correctamente al entorno:
operador@servmaster:/etc/selinux$ kubectl get nodes NAME STATUS ROLES AGE VERSION servmaster NotReady master 5m12s v1.17.22
El mensaje de estado «NotReady» es normal, ya que todavía no hemos generado la red de pod para el entorno.
La red de pod es la red superpuesta para el clúster, que se implementa en la parte superior de la red de nodo actual. Está diseñado para permitir la conectividad a través del pod.
Crear y configurar la red pod
La implementación del clúster de red es un proceso muy flexible que depende de nuestras necesidades y hay muchas opciones disponibles. Para este tutorial utilizaremos una configuración simple: Flannel. Es una red superpuesta que satisface los requisitos de Kubernetes.
Estos son los comandos indispensables para obtener la configuración de la red de pod:
kubectl apply -f \ https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
Con el resultado:
podsecuritypolicy.policy/psp.flannel.unprivileged created clusterrole.rbac.authorization.k8s.io/flannel created clusterrolebinding.rbac.authorization.k8s.io/flannel created serviceaccount/flannel created configmap/kube-flannel-cfg created daemonset.apps/kube-flannel-ds-amd64 created daemonset.apps/kube-flannel-ds-arm64 created daemonset.apps/kube-flannel-ds-arm created daemonset.apps/kube-flannel-ds-ppc64le created daemonset.apps/kube-flannel-ds-s390x created
Ahora sí, el nodo master ya debería estar activo:
operador@servmaster:/etc/selinux$ kubectl get nodes NAME STATUS ROLES AGE VERSION servmaster Ready master 12m v1.17.2
Bueeeno, ya está, pasemos a trabajar con los nodos worker.
Añadir los nodos worker
Recordar que en los nodos hosts worker ya hemos configurado el fichero /etc/hosts, deshabilitado SELinux y habilitado los puertos en el cortafuegos. Además hemos instalado los paquetes necesarios, tanto docker como kubeadm.
Utilizando la línea que se nos indicó anteriormente, desde cada nodo worker nos conectamos con el master:
sudo su - kubeadm join ip:puerto \ --token codigo-de-token \ --discovery-token-ca-cert-hash \ sha256:6db57220b2c4cecf84526a969d960501e6d1ecefa9f4980a736a2322cff8ca27
Una vez añadidos los nodos, desde el master podemos ver que, efectivamente, se han añadido:
operador@servmaster:~$ kubectl get nodes NAME STATUS ROLES AGE VERSION servmaster Ready master 42m v1.17.2 servworker01 Ready 11m v1.17.2 servworker02 Ready 24s v1.17.2
Y voilà, ya tenemos montado nuestro entorno de alta disponibilidad con HA en Kubernetes.
No hace falta que diga que esto es una guía inicial y seguro que me he dejado muchas cosas. Además, en entornos productivos un solo master es un punto de fallo muy importante. Lo ideal es, por lo menos, tres nodos master y cinco workers.
Empezar a trabajar con Kubernetes
A modo de muestra vamos a desplegar un contenedor con el servidor web Nginx para poder mostrar su funcionamiento.
Pero antes vamos a montar en el nodo master el recurso NFS que hemos creado al principio de la entrada en el nodo de storage. Bien, creamos la carpeta donde se montará el sistema de ficheros:
sudo mkdir /srv/storage
Y añadimos la línea correspondiente en /etc/fstab:
sudo vi /etc/fstab
Con el contenido:
servstorage.bitsandlinux.com:/srv/storage /srv/storage nfs defaults 0 0
Y montamos:
sudo mount -a
Accedemos a la carpeta /serv/storage/ y creamos el fichero en la ruta para almacenar los ficheros del nginx:
cd /srv/storage mkdir nginx cd nginx
Aquí creamos nuestro fichero en formato YAML con el nombre que queramos:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: selector: matchLabels: app: nginx replicas: 2 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx ports: - containerPort: 80
Para validar que todo ha ido bien y la información está bien formateada podemos utilizar esta herramienta en línea jsonformatter.org.
Creamos un nuevo recurso utilizando el fichero YAML:
kubectl create -f nginx.yaml
Con un mensaje similar a este al crear el recurso:
deployment.apps/nginx-deployment created
Podemos mostrar la información de todo lo desplegado con el comando kubectl get deployments
:
NAME READY UP-TO-DATE AVAILABLE AGE nginx-deployment 1/2 2 1 6m39s nginx-server 1/1 1 1 20h redis-master 1/1 1 1 20h
Y si queremos mostrar más información:
kubectl describe deployment nginx-deployment
Por último, que no menos importante, mostramos información sobre los pods existentes:
NAME READY STATUS RESTARTS AGE nginx-deployment-54f57cf6bf-8zt8m 0/1 ContainerCreating 0 8m52s nginx-deployment-54f57cf6bf-xrz6l 1/1 Running 0 8m52s nginx-server-7d9b6b687c-kd877 1/1 Running 0 19h redis-master-7db7f6579f-f9kd9 1/1 Running 0 20h
También podemos obtener información de un pod en concreto:
kubectl describe pod nginx-deployment-54f57cf6bf-xrz6l
Para terminar, en esta parte es necesario crear un servicio. Si no, no veremos el resultado de nuestro servidor HTTP.
Creamos el fichero YAML correspondiente para el servicio:
apiVersion: v1 kind: Service metadata: name: nginx-svc labels: run: nginx-svc spec: type: NodePort ports: - port: 80 protocol: TCP selector: app: nginx
Ídem que en el apartado anterior: es importante seguir el formato correcto para el fichero.
Y creamos el servicio:
kubectl create -f nginx-svc.yaml
Podemos listar los servicios existentes:
operador@servmaster:~/yaml$ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 443/TCP 18m nginx-svc NodePort 10.106.79.19 80:30732/TCP 102
Consultamos en nuestro navegador web preferido la dirección del nodo master más el puerto:
Y esto es todo, creo que la entrada ya ha quedado suficientemente extensa.
¿Qué os ha parecido? ¿Tenéis experiencia con entornos de alta disponibilidad con contenedores? ¿Sois de Docker Swarm o de Kubernetes? Podéis dejar vuestras respuestas en los comentarios 🙂
Derechos de la imagen de portada en Pixabay.
Fuentes consultadas
Kubernetes – Cluster Networking
Kubernetes – Installing kubeadm
Clouding.io – Introducción a Kubernetes
Github – Coreos Flannel