Introducción
En diciembre de 2019 me tuve que enfrentar a varias tareas que implicaron la refactorización en Python de varios proyectos de ciencia de datos que corrían sobre SnowFlake y Spark y también tuve que desarrollar un API en Django Rest Framework que gestionaba el lanzamiento y ejecución de jobs en kubernetes con alguno de los proyectos que refactoricé. Este fue mi primer acercamiento al mundo DevOps. Terminado aquel proyecto, no continué trabajando con Kubernetes, aunque la cultura de Docker ya se quedó fijada en mi forma de trabajar.
En la empresa que trabajo en la actualidad, me ha tocado iniciar desde cero el desarrollo de un GIS, tanto desde el área de desarrollo como de sistemas. Así que he tenido carta blanca de montarlo como quería. Para evitar la complejidad de Kubernetes (y mi falta de experiencia como administrador en esta tecnología), en una primera fase he usado un cluster clásico Proxmox con contenedores LXC (primo de Docker en cuanto a que ambos están basados en las funcionalidades de namespaces y cgroups del kernel Linux). Eso sí, el proyecto está «dockerizado» con los servicios de Backend (nginx – Django Rest Framework), Frontend (nginx – React), Celery, Redis, PostgreSQL, Grafana y HAProxy. Poco a poco el sistema va creciendo y ha llegado el momento de mover el sistema hacia un orquestador de contenedores más orientado a equipos de desarrollo independientes y recursos más escalables horizontalmente como es Kubernetes. Además, se ha convertido prácticamente en el estándar de los sistemas clusterizados y creo que a partir de ahora, va a costar encontrar administradores de sistemas clásicos, frente a los ingenieros de sistemas DevOps actuales.
Pero para construir los distintos escenarios que me permitiesen encontrar y diseñar la solución más «acertada» he seguido usando Proxmox para simular los nodos, de ese modo podía recrear distintas situaciones y configuraciones. He probado distintas distros de kubernetes como son K3s (por ser más simple) y K8s. Finalmente me quedo con K8s pensando a futuros.
Para que os pueda servir de guía o de ayuda, este ha sido el proceso de instalación y configuración que he seguido. Queda fuera de esta guía como instalar y configurar Proxmox
Para esta guía me he basado en estas dos fuentes
https://howtoforge.es/como-configurar-un-cluster-de-kubernetes-con-kubeadm-en-ubuntu-22-04/
https://computingforgeeks.com/install-kubernetes-cluster-ubuntu-jammy/
El problema que he encontrado en ella es que algunos de sus pasos no funcionan bien y he tenido que consultar en la documentación oficial de Kubernetes y en foros de Proxmox.
Descripción «física» de los nodos.
El setting de máquinas puede ser muy variopinto; montar tres máquinas físicas, tres raspberry pi, o como en mi caso, tres máquinas virtualizadas con VirtualBox
En cada máquina virtualizada he instalado Proxmox desde su ISO y dentro de cada «máquina» he creado un contenedor LXC donde irá una Ubuntu con k8s. He creado tres máquinas y no tres LXC en una sóla máquina, porque quiero tener el contenido de los LXC en un device de disco Ceph. De este modo si una máquina muere, mediante HA el cluster proxmox levantará en otra máquina y en segundos el contenedor LXC que había en esa máquina. Es posible que esté equivocado pero para tener HA de los nodos Control Plane se necesitan tres nodos… por el momento yo voy a implementar HA con la ayuda de Proxmox. En un futuro veré qué implicaciones tiene. En cualquier caso en esta entrada de blog, no voy a explicar nada sobre ceph, para no complicar. Quería explicar por tanto por qué he creado 3 máquinas y 1 contenedor LXC en cada máquina y no una máquina con 3 contenedores LXC, ya que sonaba raro y redundante.
Finalmente me ha quedado del siguiente modo
Los nodos portainer y jenkins-one que se ven en la imagen, quedan fuera también de esta guía ya que son para unos servicios externos al cluster Kubernetes que no tengo claro todavía si dejarlos fuera o dentro del cluster ya que es parte de mi infraestructura antigua y estoy todavía puenteando, aunque seguramente lo integraré todo dentro de kubernetes. En otra entrada explicaré Ceph por un lado y que estoy haciendo con estos contenedores.
Fijaros lo enrevesado que se pueden construir con Linux (en mi caso en un PC con i5 y 32Gb de RAM); tres máquinas virtualizadas, en cada una contenedores LXC y dentro de cada LXC contenedores Docker… o lo que es lo mismo, tres capas de «virtualización». Linux es mágico.
Así que resumiendo, ya para abstraernos, tenemos tres contenedores LXC que a partir de ahora las vamos a llamar nodos o máquinas.
A configurar los nodos
Ahora ya sí, vamos al lío. Kubernetes requiere lo siguiente para funcionar:
- Al menos 2 GiB de RAM o más por máquina, ya que menos dejaría poco espacio para tus aplicaciones.
- Al menos 2 CPUs en la máquina que utilices como nodo de control.
- Conectividad completa de red entre todas las máquinas del clúster. Puedes utilizar una red pública o privada.
Cada nodo necesita estar visible y accesible por nombre en la misma red, sin NAT, es decir, conectividad de red completa entre todas las máquinas en el clúster (la red pública o privada está bien). También nombre de host único, dirección MAC y product_uuid para cada nodo. Así que generemos esta configuración: cada nodo tiene una IP en la red 10.0.0.0/24 y vamos a asignar un nombre y decirle a cada máquina como se llama y en que IP se encuentra. Añade lo siguiente al /etc/hosts de cada máquina
1
2
3 10.0.0.11 cplane1.k8s cplane1
10.0.0.12 worker1.k8s worker1
10.0.0.13 worker2.k8s worker2
y cada host se tiene que llamar como se apunta en el fichero anterior, para ello ejecutamos
1
2
3
4
5
6
7 hostname cplane1
hostname worker1
hostname worker2
respectivamente en cada máquina que corresponda, por ejemplo, en la máquina con IP 10.0.0.12, ejecutaríamos
hostname worker1
Esto modificaría el nombre del host de forma temporal. Para hacerlo permanente editamos a mano el fichero /etc/hostname de cada máquina y ponermos cplane1, worker1 o worker2, el que le toque. Otro modo de hacerlo de una sola vez es usando hostnamectl, por ejemplo en worker2 sería así:
1 hostnamectl set-hostname worker2
Alguna vez me ha ocurrido que a pesar de hacer esto cuando reinicio el contenedor, se vuelve a poner con el nombre antiguo. Desde la máquina host se le puede pasar este comando
1
2 pct set <VMID> --hostname <HOSTNAME>
pct set 102 --hostname worker1
Hecho esto, debemos asegurarnos que está instalado ssh en cada máquina. Si no,
1 apt-get install openssh-server
Olvidé comentar que es necesario tener desactivada la memoria swap. Yo he instalado mis máquinas sin swap, pero si en tu caso está activa, primero desactívalo mediante:
1 swapoff -a
y luego comenta la línea que provisiona swap en /etc/fstab de tal modo que cuando preguntes la memoria que tienes con el comando free se vea tal que así
Por último habilitamos el forwarding
1
2 echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.conf
sysctl --system
Instalación del Container runtime
Para ejecutar contenedores en Pods, Kubernetes utiliza un runtime de contenedor. Es posible usar varios runtime de contenedores para los despliegues, como por ejemplo:
- Containerd
- CRI-O
- Mirantis
Nosotros vamos a usar Containerd. Manos a la obra. Lo primero permitir usar apt sobre https
1
2 apt-get update
apt-get install ca-certificates curl gnupg
A continuación añadimos la GPG key oficial de Docker
1
2
3
4 apt-get install curl gpg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
Añadimos el repositorio a los sources list de apt
1
2
3
4 echo \
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
y finalmente instalamos Containerd
1
2 apt-get update
apt-get install containerd.io
Ya solo queda comprobar que funciona el servicio
1
2 systemctl is-enabled containerd
systemctl status containerd
deberías ver algo como esto
Ya está instalado. Ahora asegúrate de hacer el mismo proceso en todas las máquinas del cluster
Instalar Kubernetes
Ya queda menos. Ahora vamos a añadir sources list de kubernetes y a instalar kubelet, kubeadm y kubectl en las tres máquinas
1
2
3
4
5
6
7 apt-get update
apt-get install -y apt-transport-https ca-certificates curl
curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-archive-keyring.gpg
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
apt-get update
apt-get install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl