Raspberry Pi: Microk8s cluster

MicroK8s es un sistema de código abierto para automatizar el despliegue, la escalabilidad y la gestión de aplicaciones en contenedores. Proporciona la funcionalidad de los componentes centrales de Kubernetes en una huella pequeña y escalable, desde un solo nodo hasta un clúster de producción de alta disponibilidad.

Al reducir los recursos necesarios para ejecutar Kubernetes, MicroK8s hace posible llevar Kubernetes a nuevos entornos. En este artículo vamos a explorar como crear un clúster de Kubernetes en Raspberry Pi utilizando Microk8s.

El hardware que vamos a utilizar es el siguiente:

  • 3 Raspberry Pi 4 de 8GB
  • 3 USB de 32GB para instalar el sistema operativo

Instalar el sistema operativo

En este artículo, cómo sistema operativo vamos a utilizar Ubuntu Server 22.04.2 LTS, la versión de 64 bits, pero puedes utilizar otro si así lo deseas. Pero ten en cuenta que MicroK8s está desarrollada por Canonical y por lo tanto garantizan alta compatibilidad con Ubuntu.

Para instalar el sistema operativo, entre otras opciones, podemos utilizar Raspberry Pi Imager, una herramienta que nos permite grabar el sistema operativo de forma sencilla. Para instalarlo, podemos descargarlo desde la página oficial. En este artículo, no vamos a entrar en detalle sobre como instalar el sistema operativo, pero es un proceso bastante simple y que está detallado en múltitud de sitios web.

Arranque USB

En el caso de utilizar USB's o discos duros externos, es necesario habilitar el arranque USB en la Raspberry Pi. En el siguiente enlace se explica con detalle como habilitar el arranque USB en la Raspberry Pi.

Habilitar cgroups

Una vez instalado el sistema operativo en todas las Raspberries que van a formar parte del cluster y tenemos acceso SSH, debemos proceder a habilitar los cgroups.

Para habilitar los cgroups hay que modificar los boot parameters en el fichero: /boot/firmware/cmdline.txt

Hay que añadir la siguiente linea: cgroup_enable=memory cgroup_memory=1

En mi caso, el contenido original del fichero es el siguiente:

console=serial0,115200 dwc_otg.lpm_enable=0 console=tty1 root=LABEL=writable rootfstype=ext4 rootwait fixrtc quiet splash

Y una vez modificado:

console=serial0,115200 dwc_otg.lpm_enable=0 console=tty1 root=LABEL=writable rootfstype=ext4 rootwait fixrtc quiet splash
cgroup_enable=memory cgroup_memory=1

Ahora podemos reiniciar la raspberry: sudo reboot

Instalar microk8s

Microk8s se instala a través de snap y permite especificar un canal (channel), cada canal se compone de dos componentes; el track (o versión) y el nivel de riesgo.

Para listar todos los canales disponibles podemos utilizar el comando snap info microk8s el cuál nos devuelve el siguiente output:

name:      microk8s
summary:   Kubernetes for workstations and appliances
publisher: Canonical✓
store-url: https://snapcraft.io/microk8s
contact:   https://github.com/canonical/microk8s
license:   Apache-2.0
description: |
  MicroK8s is a small, fast, secure, certified Kubernetes distribution that installs on just about
  any Linux box. It provides the functionality of core Kubernetes components, in a small footprint,
  scalable from a single node to a high-availability production multi-node cluster. Use it for
  offline developments, prototyping, testing, CI/CD. It's also great for appliances - develop your
  IoT apps for K8s and deploy them to MicroK8s on your boxes.
snap-id: EaXqgt1lyCaxKaQCU349mlodBkDCXRcg
channels:
  1.26/stable:           v1.26.3         2023-03-31 (4966) 149MB classic
  1.26/candidate:        v1.26.3         2023-03-30 (4966) 149MB classic
  1.26/beta:             v1.26.3         2023-03-30 (4966) 149MB classic
  1.26/edge:             v1.26.3         2023-04-07 (5070) 149MB classic
  latest/stable:         v1.26.3         2023-03-31 (4966) 149MB classic
  latest/candidate:      v1.26.3         2023-03-29 (4998) 156MB classic
  latest/beta:           v1.26.3         2023-03-29 (4998) 156MB classic
  latest/edge:           v1.26.3         2023-04-07 (5080) 157MB classic
  1.27/stable:           –                                       
  1.27/candidate:        v1.27.0-rc.1    2023-04-08 (5085) 156MB classic
  1.27/beta:             v1.27.0-beta.0  2023-03-20 (4975) 154MB classic
  1.27/edge:             v1.27.0-alpha.3 2023-03-04 (4839) 156MB classic
  1.26-strict/stable:    v1.26.1         2023-02-06 (4495) 148MB -
  1.26-strict/candidate: v1.26.3         2023-03-31 (5035) 149MB -
  1.26-strict/beta:      v1.26.3         2023-03-31 (5035) 149MB -
  1.26-strict/edge:      v1.26.3         2023-03-30 (5035) 149MB -
  1.25-strict/stable:    v1.25.6         2023-02-02 (4510) 146MB -
  1.25-strict/candidate: v1.25.8         2023-03-31 (5022) 147MB -
  1.25-strict/beta:      v1.25.8         2023-03-31 (5022) 147MB -
  1.25-strict/edge:      v1.25.8         2023-03-30 (5022) 147MB -
  1.25-eksd/stable:      v1.25-10        2023-04-07 (5053) 147MB classic
  1.25-eksd/candidate:   v1.25-10        2023-04-05 (5053) 147MB classic
  1.25-eksd/beta:        v1.25-10        2023-04-05 (5053) 147MB classic
  1.25-eksd/edge:        v1.25-10        2023-04-07 (5083) 147MB classic
  1.25/stable:           v1.25.6         2023-02-02 (4573) 146MB classic
  1.25/candidate:        v1.25.8         2023-03-31 (5033) 147MB classic
  1.25/beta:             v1.25.8         2023-03-31 (5033) 147MB classic
  1.25/edge:             v1.25.8         2023-04-07 (5066) 147MB classic
  1.24-eksd/stable:      v1.24-14        2023-04-08 (5032) 145MB classic
... 

Si al instalar no se selecciona un canal, la opción por defecto será latest/stable. En la página oficial se puede obtener información más detallada acerca de los canales.

En este caso, la versión seleccionada para instalar es 1.26/stable y podemos proceder a la instalación con el siguiente comando:

sudo snap install microk8s --classic --channel=1.26/stable

El output debería ser el siguiente:

microk8s (1.26/stable) v1.26.3 from Canonical✓ installed

Si utilizas una versión de Ubuntu superior a la 21.10, hay que instalar unos kernel modules adicionales, esto se puede hacer con el siguiente comando: sudo apt install linux-modules-extra-raspi

Ahora podemos iniciar el servicio de microk8s con el siguiente comando: sudo microk8s start y ver el estado con sudo microk8s status. El estado debería ser algo parecido a:

microk8s is running
high-availability: no
  datastore master nodes: 127.0.0.1:19001
  datastore standby nodes: none
addons:
  enabled:
    ha-cluster           # (core) Configure high availability on the current node
    helm                 # (core) Helm - the package manager for Kubernetes
    helm3                # (core) Helm 3 - the package manager for Kubernetes
  disabled:
    cert-manager         # (core) Cloud native certificate management
    community            # (core) The community addons repository
    dashboard            # (core) The Kubernetes dashboard
    dns                  # (core) CoreDNS
    host-access          # (core) Allow Pods connecting to Host services smoothly
    hostpath-storage     # (core) Storage class; allocates storage from host directory
    ingress              # (core) Ingress controller for external access
    kube-ovn             # (core) An advanced network fabric for Kubernetes
    mayastor             # (core) OpenEBS MayaStor
    metallb              # (core) Loadbalancer for your Kubernetes cluster
    metrics-server       # (core) K8s Metrics Server for API access to service metrics
    minio                # (core) MinIO object storage
    observability        # (core) A lightweight observability stack for logs, traces and metrics
    prometheus           # (core) Prometheus operator for monitoring and logging
    rbac                 # (core) Role-Based Access Control for authorisation
    registry             # (core) Private image registry exposed on localhost:32000
    storage              # (core) Alias to hostpath-storage add-on, deprecated

Para obtener información acerca del cluster de microk8s, también podemos utilizar el siguiente comando: sudo microk8s kubectl cluster-info

Kubernetes control plane is running at https://127.0.0.1:16443

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

Este proceso hay que realizarlo para todos los nodos que queramos unir al cluster.

Añadir nodos al cluster

Una vez instalado microk8s en todos los nodos que van a formar el cluster, hay que elegir cuál será el master node y des de ese nodo ejecutar el siguiente comando:

sudo microk8s add-node

Este comando, nos dará las instrucciones para añadir los otros nodos al cluster, con un output parecido al siguiente:

From the node you wish to join to this cluster, run the following:
microk8s join 192.168.10.51:25000/437d2c700259a917b7b733585f70daf8/2923c9d8c2c6

Use the '--worker' flag to join a node as a worker not running the control plane, eg:
microk8s join 192.168.10.51:25000/437d2c700259a917b7b733585f70daf8/2923c9d8c2c6 --worker

If the node you are adding is not reachable through the default interface you can use one of the following:
microk8s join 192.168.10.51:25000/437d2c700259a917b7b733585f70daf8/2923c9d8c2c6
microk8s join fdc7:a53f:3e37:0:da3a:ddff:fe02:c2ef:25000/437d2c700259a917b7b733585f70daf8/2923c9d8c2c6

En este caso, voy a añadir los otros nodos simplemente como workers, por lo que vamos a utilizar el siguiente comando en cada uno de los nodos adicionales del cluster:

microk8s join 192.168.10.51:25000/437d2c700259a917b7b733585f70daf8/2923c9d8c2c6 --worker

El output debería ser algo parecido a:

Contacting cluster at 192.168.10.51

The node has joined the cluster and will appear in the nodes list in a few seconds.

This worker node gets automatically configured with the API server endpoints.
If the API servers are behind a loadbalancer please set the '--refresh-interval' to '0s' in:
    /var/snap/microk8s/current/args/apiserver-proxy
and replace the API server endpoints with the one provided by the loadbalancer in:
    /var/snap/microk8s/current/args/traefik/provider.yaml

Hay que tener en cuenta que hay que realizar este proceso para cada nodo que queramos añadir al cluster ya que el token generado con add-node solo es válido una vez.

Una vez realizado el proceso, des del nodo maestro podemos utilizar el comando sudo microk8s kubectl get nodes para verificar que los nuevos nodos se han añadido correctamente al cluster, en mi caso he añadido dos nodos adicionales y el output es el siguiente:

NAME   STATUS   ROLES    AGE     VERSION
kn-1   Ready    <none>   26m     v1.26.3
kn-3   Ready    <none>   2m52s   v1.26.3
kn-2   Ready    <none>   1s      v1.26.3

Addons de MicroK8s

Una funcionalidad interesante de MicroK8s son los "addons", los cuales permiten añadir servicios comunes al cluster de manera muy simple y sin prácticamente configuración.

Cuándo previamente hemos usado microk8s status, hemos podido ver el listado de addons habilitados y los disponibles. Por defecto, los siguientes addons están instalados:

  • ha-cluster
  • helm
  • helm3

Para habilitar un addon, podemos utilizar el comando:

microk8s enable <ADDON>

Y para ver el mensaje de ayuda de un addon concreto:

microk8s enable <ADDON> -- --help

Por defecto, solo los addons esenciales que mantiene el equipo de MicroK8s están disponibles, estos addons están marcados como core. Para añadir los repositorios que contienen addons creados por la comunidad podemos ejecutar el siguiente comando:

microk8s enable community

Habilitar MetalLB

MetalLB es una implementación de balanceador de carga para clústeres de Kubernetes, que utiliza protocolos de enrutamiento estándar.

Para habilitarlo, podemos utilizar el siguiente comando:

microk8s enable metallb

Para configurar MetalLB, nos va a pedir que introduzcamos el rango de direcciones IP que queremos que use el Load Balancer para exponer servicios, en este caso le he asignado el siguiente rango 192.168.10.70-192.168.10.100. Hay que tener en cuenta que este valor debe ser adaptado para ajustarse a la red en la que se encuentre el cluster.

El resultado debería ser parecido al siguiente:

Infer repository core for addon metallb
Enabling MetalLB
Enter each IP address range delimited by comma (e.g. &#39;10.64.140.43-10.64.140.49,192.168.0.105-192.168.0.111&#39;): 192.168.10.70-192.168.10.100
Applying Metallb manifest
customresourcedefinition.apiextensions.k8s.io/addresspools.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bfdprofiles.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bgpadvertisements.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bgppeers.metallb.io created
customresourcedefinition.apiextensions.k8s.io/communities.metallb.io created
customresourcedefinition.apiextensions.k8s.io/ipaddresspools.metallb.io created
customresourcedefinition.apiextensions.k8s.io/l2advertisements.metallb.io created
namespace/metallb-system created
serviceaccount/controller created
serviceaccount/speaker created
clusterrole.rbac.authorization.k8s.io/metallb-system:controller created
clusterrole.rbac.authorization.k8s.io/metallb-system:speaker created
role.rbac.authorization.k8s.io/controller created
role.rbac.authorization.k8s.io/pod-lister created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:controller created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:speaker created
rolebinding.rbac.authorization.k8s.io/controller created
secret/webhook-server-cert created
service/webhook-service created
rolebinding.rbac.authorization.k8s.io/pod-lister created
daemonset.apps/speaker created
deployment.apps/controller created
validatingwebhookconfiguration.admissionregistration.k8s.io/validating-webhook-configuration created
Waiting for Metallb controller to be ready.
error: timed out waiting for the condition on deployments/controller
MetalLB controller is still not ready
deployment.apps/controller condition met
ipaddresspool.metallb.io/default-addresspool created
l2advertisement.metallb.io/default-advertise-all-pools created
MetalLB is enabled

Como se puede ver en el output, se ha creado el namespace metallb-system donde se han instalado los diferentes recursos necesarios para MetalLB, los podemos listar con:

microk8s kubectl get all -n metallb-system

El output en mi caso es:

NAME                             READY   STATUS    RESTARTS   AGE
pod/controller-9556c586f-b96b6   1/1     Running   0          8m10s
pod/speaker-psxxt                1/1     Running   0          8m10s
pod/speaker-nqvhr                1/1     Running   0          8m10s
pod/speaker-6d6hh                1/1     Running   0          8m10s

NAME                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
service/webhook-service   ClusterIP   10.152.183.164   &lt;none&gt;        443/TCP   8m10s

NAME                     DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
daemonset.apps/speaker   3         3         3       3            3           kubernetes.io/os=linux   8m10s

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/controller   1/1     1            1           8m10s

NAME                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/controller-9556c586f   1         1         1       8m10s

Habilitar CoreDNS

CoreDNS es un servidor de DNS implementado en Go que utilizaremos para DNS y service discovery.

Para habilitarlo, podemos utilizar el siguiente comando:

microk8s enable dns

El resultado debería ser parecido al siguiente:

Enabling DNS
Using host configuration from /run/systemd/resolve/resolv.conf
Applying manifest
serviceaccount/coredns created
configmap/coredns created
deployment.apps/coredns created
service/kube-dns created
clusterrole.rbac.authorization.k8s.io/coredns created
clusterrolebinding.rbac.authorization.k8s.io/coredns created
Restarting kubelet
Adding argument --cluster-domain to nodes.
Adding argument --cluster-dns to nodes.
Restarting nodes.
DNS is enabled

Habilitar NGINX Ingress Controller

NGINX Ingress Controller es un ingress controller para Kubernetes que utiliza NGINX como proxy inverso y balanceador de carga.

Para habilitarlo, podemos utilizar el siguiente comando:

microk8s enable ingress

El resultado debería ser parecido al siguiente:

Infer repository core for addon ingress
Enabling Ingress
ingressclass.networking.k8s.io/public created
ingressclass.networking.k8s.io/nginx created
namespace/ingress created
serviceaccount/nginx-ingress-microk8s-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-microk8s-clusterrole created
role.rbac.authorization.k8s.io/nginx-ingress-microk8s-role created
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-microk8s created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-microk8s created
configmap/nginx-load-balancer-microk8s-conf created
configmap/nginx-ingress-tcp-microk8s-conf created
configmap/nginx-ingress-udp-microk8s-conf created
daemonset.apps/nginx-ingress-microk8s-controller created
Ingress is enabled

En el caso de querer utilizar Traefik como ingress controller en lugar de NGINX, también hay disponible un addon mantenido por la comunidad el cuál se puede habilitar con el siguiente comando:

microk8s enable traefik

Habilitar Kubernetes Dashboard

Kubernetes Dashboard es una interfaz web que nos permite administrar nuestro cluster de Kubernetes.

Para habilitarlo, podemos utilizar el siguiente comando:

microk8s enable dashboard

El resultado debería ser parecido al siguiente:

Infer repository core for addon dashboard
Enabling Kubernetes Dashboard
Infer repository core for addon metrics-server
Addon core/metrics-server is already enabled
Applying manifest
serviceaccount/kubernetes-dashboard unchanged
service/kubernetes-dashboard unchanged
secret/kubernetes-dashboard-certs unchanged
secret/kubernetes-dashboard-csrf unchanged
secret/kubernetes-dashboard-key-holder unchanged
configmap/kubernetes-dashboard-settings unchanged
role.rbac.authorization.k8s.io/kubernetes-dashboard unchanged
clusterrole.rbac.authorization.k8s.io/kubernetes-dashboard unchanged
rolebinding.rbac.authorization.k8s.io/kubernetes-dashboard unchanged
clusterrolebinding.rbac.authorization.k8s.io/kubernetes-dashboard unchanged
deployment.apps/kubernetes-dashboard unchanged
service/dashboard-metrics-scraper unchanged
deployment.apps/dashboard-metrics-scraper unchanged
secret/microk8s-dashboard-token unchanged

If RBAC is not enabled access the dashboard using the token retrieved with:

microk8s kubectl describe secret -n kube-system microk8s-dashboard-token

Use this token in the https login UI of the kubernetes-dashboard service.

In an RBAC enabled setup (microk8s enable RBAC) you need to create a user with restricted
permissions as shown in:
https://github.com/kubernetes/dashboard/blob/master/docs/user/access-control/creating-sample-user.md

Microk8s tiene un comando para habilitar el dashboard en caso de que no lo esté, configurar el port forwarding para permitir el acceso al dashboard desde la máquina local y generar un token para acceder al dashboard, todo ello en un mismo comando:

microk8s dashboard-proxy

Que nos devolverá algo parecido a lo siguiente:

Checking if Dashboard is running.
Infer repository core for addon dashboard
Waiting for Dashboard to come up.
Trying to get token from microk8s-dashboard-token
Waiting for secret token (attempt 0)
Dashboard will be available at https://127.0.0.1:10443
Use the following token to login:
&lt;token&gt;

Este comando es útil si queremos acceder al dashboard desde la máquina en que Kubernetes está ejecutándose, pero si queremos acceder al dashboard de manera remota, necesitaremos configurar un proxy inverso para poder acceder al dashboard desde fuera de la máquina local y/o hacer el port forwarding manualmente. Esta última opción es la que vamos a utilizar en este caso con el siguiente comando:

microk8s kubectl port-forward -n kube-system service/kubernetes-dashboard 10443:443 --address 0.0.0.0

Ahora deberíamos poder acceder al dashboard remotamente a través de la dirección https://<IP>:10443:

Dashboard photo 1

Podemos utilizar el token que nos ha generado el comando microk8s dashboard-proxy para acceder al dashboard o generar un nuevo token con el siguiente comando:

microk8s kubectl create token default

Dashboard photo 2