Implementar un cluster Kubernetes
Introducción
Tenemos casi todos los elementos técnicos que necesitamos para hacer que nuestra aplicación sea casi infatigable. Con Kubernetes, los pasos de mantener un inventario y desplegar mediante Ansible se vuelven innecesarios, ya que el orquestador realiza todas las operaciones por nosotros.
Pero Kubernetes en sí aún no es tolerante a fallos: Minikube no es un entorno de producción. Es hora de rectificar este problema.
En el momento de escribir estas líneas, la versión actual de Kubernetes es la 1.29.3. No obstante, dado que el ecosistema de Kubernetes evoluciona rápidamente, consulte la documentación oficial en https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/high-availability/.
Topología
1. Componentes básicos
Es importante reiterar algunos de los principios tratados en el capítulo Orquestación. La configuración de un clúster es la aplicación práctica de todos los conceptos tratados (orquestación, Raft, quórum, etc.) en el capítulo anterior.
Kubernetes se compone de un control plane (control plane) y de nodos (nodes, compute nodes o workers). Los servidores del plan de control se denominan control-planes o masters y son los que controlan los procesos de toma de decisiones y gestionan todos los eventos. En adelante, utilizaremos uno de estos nombres o el otro, para estos servidores. Por tanto, controlan todo el clúster. Los masters y los nodos se comunican entre sí, y en los nodos se ejecutan aplicaciones en forma de pods.
Un control-plane se compone de varios servicios:
-
kube-apiserver: punto de entrada al clúster, expone el API de Kubernetes. Cuando tiene varios masters, se ubican detrás de un repartidor de carga.
-
etcd: base de datos clave-valor que contiene la configuración del clúster. Es en sí misma de alta disponibilidad.
-
kube-scheduler: responsable de la programación, selecciona los nodos en los que se ejecutarán los pods, en función de las restricciones (procesador, memoria, afinidad, etc.).
-
kube-controller-manager: gestiona los controladores (nodo, replicación, endpoints, etc.).
-
cloud-controller-manager: interactúa con el proveedor de la nube subyacente, en su caso. Se puede desactivar.
Un nodo también dispone de servicios especializados:
-
kubelet: servicio encargado de garantizar la ejecución de los pods, instanciar los contenedores, comprobar su funcionamiento y estado y gestionar su ciclo de vida.
-
kube-proxy: este proxy de red permite la implementación y gestión de servicios (en el sentido de Kubernetes) a nivel de nodo y, por tanto, la comunicación de red con los pods.
-
container runtime: es el entorno de ejecución del contenedor; hemos elegido Docker.
Con Minikube, la máquina donde se ejecuta k8s es, a la vez, control-plane y nodo, lo que resulta muy práctico para aprender...
Preparación
1. Recordatorios
Los tres masters y los nodos tienen las siguientes capacidades, que son suficientes para nuestra aplicación Si utiliza una distribución k8s como OpenShift o Rancher, seguramente tendrá que duplicar o incluso cuadruplicar los valores de CPU y memoria, debido a la cantidad de componentes que traen estas distribuciones:
-
2 procesadores (vCPU),
-
2 GB de memoria,
-
una raíz / 10 GB,
-
un directorio /var de 40 GB.
La configuración DNS es la siguiente, incluyendo al VIP para el acceso al API de Kube:
-
192.168.56.15 api.kube.diehard.net
-
192.168.56.20 k8s-cplane 01.diehard.net
-
192.168.56.21 k8s-cplane 02.diehard.net
-
192.168.56.22 k8s-cplane 03.diehard.net
-
192.168.56.23 k8s-worker01.diehard.net
-
192.168.56.24 k8s-worker02.diehard.net
-
192.168.56.25 k8s-worker03.diehard.net
-
192.168.56.26 k8s-worker04.diehard.net
Todos los servidores están instalados en Ubuntu 24.04 LTS. Se deben eliminar todas las trazas anteriores de Kubernetes (el proceso de eliminación se describe al final del capítulo, subsección Eliminación de un clúster). El usuario Ansible y los permisos sudo asociados deben estar presentes y Ansible desde infra01 debe ser capaz de conectarse. Resolver DNS debe estar configurado para utilizar nuestros servidores DNS primario y secundario.
2. HAProxy
Como cada master tiene el componente kube-apiserver, puede responder a peticiones de clientes y nodos, por defecto en el puerto 6443. Necesitamos configurar una VIP y colocar los tres masters detrás de un repartidor de carga o proxy inverso. Vamos a utilizar los repartidores de carga creados en el capítulo Infraestructura y servicios básicos.
La VIP es al aprovisionada en el capítulo Infraestructura y servicios básicos: 192.168.56.15. Estamos escuchando en el puerto 8443. El backend está en modo TCP y el control de estado está en /healthz.
Una configuración puede ser la siguiente:
frontend apikube
bind 192.168.56.15:8443
mode tcp
option tcplog
default_backend kubemasters
backend cplanes
option httpchk GET /healthz
http-check expect status 200
mode tcp
option ssl-hello-chk
balance roundrobin ...
Construcción
1. Imágenes de Kubernetes
Si aún no lo ha hecho, asegúrese de que el servicio kubelet esté activado en todos sus servidores.
$ ansible k8s -m shell --become -a "systemctl enable kubelet"
El comando central para la configuración inicial del cluster es kubeadm. Kubernetes en sí funciona en forma de pods, es decir, contenedores. A diferencia de las aplicaciones que vamos a desplegar, es el servicio kubelet el que los pondrá en marcha de forma estática cuando se active. En primer lugar, vamos a recuperar estas imágenes. Esto no es obligatorio (la creación de masters sí lo es), pero nos aseguraremos de que están ahí.
Recupera las imágenes de cada máster:
$ sudo kubeadm config images pull
Puede utilizar un Ansible:
$ ansible k8s_cplanes -m shell --become -a "kubeadm config images pull"
El resultado es el siguiente:
[config/images] Pulled registry.k8s.io/kube-apiserver:v1.29.3
[config/images] Pulled registry.k8s.io/kube-controller-manager:v1.29.3
[config/images] Pulled registry.k8s.io/kube-scheduler:v1.29.3
[config/images] Pulled registry.k8s.io/kube-proxy:v1.29.3
[config/images] Pulled registry.k8s.io/coredns/coredns:v1.11.1
[config/images] Pulled registry.k8s.io/pause:3.9
[config/images] Pulled registry.k8s.io/etcd:3.5.12-0
2. Primer master
a. Bootstrap
Los siguientes comandos se ejecutan como root.
El primer master será k8s-master01. El parámetro init se utiliza para crear el primer master del plano de control.
Cree el primer master con los siguientes parámetros:
# kubeadm init \
--control-plane-endpoint"api.kube.diehard.net:8443" \
--pod-network-cidr=10.244.0.0/16 \
--upload-certs
Como la salida es muy larga, nos limitaremos a comprobar que la inicialización ha ido bien. Algunas advertencias pueden ser ignoradas (versión de Docker por ejemplo). Se realizan las siguientes acciones:
-
comprobación de la versión de Docker,
-
recuperación de imágenes (ya realizada),
-
generación de todos los certificados,
-
creación de la configuración completa de los distintos componentes,
-
componentes de inicio: kubelet, kube-apiserver, kube-controller, kube-scheduler,
-
arranque de clústeres.
Debería aparecer el siguiente...
Procedimientos adicionales
1. Crash o apagado sucio de un nodo
Si un nodo se detiene violentamente, pueden ocurrir cosas extrañas. Cuando esto ocurre, el origen del problema no se notifica a los masters: no saben por qué el nodo ya no es visible. ¿Deben reiniciar los pods que faltan? Después de todo, el problema puede ser transitorio: el nodo se puede reiniciar rápidamente y los pods asociados pueden volver a ser accesibles.
También existe un mecanismo de timeout. El nodo se detecta como NotReady y con la configuración que aplicamos al crear los masters, se inicia un mecanismo de desalojo de pods tras un retardo variable, de unos 30 segundos a 1 minuto. Los pods que se estaban ejecutando en el nodo accidentado pasan al estado Terminating. A continuación, se aplica un periodo de gracia, que se ha fijado en 30 segundos, al final del cual Kubernetes sustituye el pod ausente por uno nuevo. A esto hay que añadir el tiempo que se tarda en detectar el nodo defectuoso, el tiempo que se tarda en reiniciar el nuevo pod (recuperar la imagen y lanzarla), etc.
Estos retrasos ponen de manifiesto la necesidad de comprobar el estado del clúster y de los servicios a través de la monitorización. Kubernetes reinicia los servicios, pero tarda más que en un apagado limpio. Puede acelerar la conmutación de pods manualmente, forzando el apagado de los pods que todavía están presentes...