kind es una herramienta para ejecutar clústers de Kubernetes en los que cada “nodo” del clúster se ejecuta en un contenedor. k3d toma esta misma idea pero en vez de un clúster de Kubernetes vanilla, despliega un clúster de k3s usando contenedores como “nodos”.

k3s es una distribución certificada de Kubernetes, pero mucho más ligera. k3s usa SQLite3 como base de datos interna, en vez de etcd, por ejemplo y ejecuta los diferentes controladores como un sólo fichero ejecutable. Todas estas modificaciones permiten mantener toda la funcionalidad de Kubernetes, pero con un binario de menos de 40MB (según indica la documentación oficial k3s.io).

Siguiendo la idea de kind, se puede desplegar un clúster de k3s usando Docker como plataforma: cada nodo del clúster se despliega como un contenedor.

Instalación

La instalación de k3d requiere disponer de Docker instalado; para desplegar un clúster, se proporciona la herramienta k3d, con la que podemos especificar el número de nodos servidores y agentes del clúster, así como muchas otras opciones.

La documentación oficial indica cómo instalar k3d usando varios métodos, como por ejemplo usando un script de instalación:

Antes de ejecutar cualquier cosa desde internet, revisa si el script hace únicamente lo que se supone que debe hacer.

El cuerpo del script revisa el entorno local, descarga k3d y lo instala:

...
initArch
initOS
verifySupported
checkTagProvided || checkLatestVersion
if ! checkK3dInstalledVersion; then
  downloadFile
  installFile
fi
testVersion
cleanup

El detalle de la instalación:

...
# installFile verifies the SHA256 for the file, then unpacks and
# installs it.
installFile() {
  echo "Preparing to install $APP_NAME into ${K3D_INSTALL_DIR}"
  runAsRoot chmod +x "$K3D_TMP_FILE"
  runAsRoot cp "$K3D_TMP_FILE" "$K3D_INSTALL_DIR/$APP_NAME"
  echo "$APP_NAME installed into $K3D_INSTALL_DIR/$APP_NAME"
}
...

Una vez revisado el script, lanzamos la instalación:

curl -s https://raw.githubusercontent.com/rancher/k3d/main/install.sh | bash

Al finalizar, valida que k3d se ha instalado correctamente:

$ k3d version
k3d version v4.2.0
k3s version v1.20.2-k3s1 (default)

Crear un clúster (manualmente)

Como ejemplo, vamos a crear un clúster llamado dev-cluster con un nodo servidor (-s 1) y un nodo agente (-a 1):

$ k3d cluster create dev-cluster -s 1 -a 1
INFO[0000] Prep: Network                                
INFO[0000] Created network 'k3d-dev-cluster'            
INFO[0000] Created volume 'k3d-dev-cluster-images'      
INFO[0001] Creating node 'k3d-dev-cluster-server-0'     
INFO[0001] Creating node 'k3d-dev-cluster-agent-0'      
INFO[0001] Creating LoadBalancer 'k3d-dev-cluster-serverlb' 
INFO[0001] Starting cluster 'dev-cluster'               
INFO[0001] Starting servers...                          
INFO[0001] Starting Node 'k3d-dev-cluster-server-0'     
INFO[0010] Starting agents...                           
INFO[0010] Starting Node 'k3d-dev-cluster-agent-0'      
INFO[0023] Starting helpers...                          
INFO[0023] Starting Node 'k3d-dev-cluster-serverlb'     
INFO[0024] (Optional) Trying to get IP of the docker host and inject it into the cluster as 'host.k3d.internal' for easy access 
INFO[0030] Successfully added host record to /etc/hosts in 3/3 nodes and to the CoreDNS ConfigMap 
INFO[0030] Cluster 'dev-cluster' created successfully!  
INFO[0030] --kubeconfig-update-default=false --> sets --kubeconfig-switch-context=false 
INFO[0030] You can now use it like this:                
kubectl config use-context k3d-dev-cluster
kubectl cluster-info

k3d se ejecuta como contenedores, por lo que podemos ver las imágenes usadas para construir los “nodos” mediante:

$ docker images
REPOSITORY          TAG            IMAGE ID       CREATED        SIZE
rancher/k3d-proxy   v4.2.0         70ec1f255a8a   5 weeks ago    44.4MB
rancher/k3s         v1.20.2-k3s1   1b02adf07426   2 months ago   154MB

Del mismo modo, revisamos los “nodos” del clúster:

$ docker ps
CONTAINER ID   IMAGE                      COMMAND                  CREATED       STATUS     PORTS                             NAMES
311f7f0cf5dc   rancher/k3d-proxy:v4.2.0   "/bin/sh -c nginx-pr…"   20 hours ago  Up 2 hours 80/tcp, 0.0.0.0:32945->6443/tcp   k3d-dev-cluster-serverlb
81521d5ea62f   rancher/k3s:v1.20.2-k3s1   "/bin/k3s agent"         20 hours ago  Up 2 hours                                   k3d-dev-cluster-agent-0
d995147a0d08   rancher/k3s:v1.20.2-k3s1   "/bin/k3s server --t…"   20 hours ago  Up 2 hours                                   k3d-dev-cluster-server-0

Vemos que además de los nodos servidor y agente, k3d también ha desplegado un balanceador que expone la API de Kubernetes en un puerto aleatorio (en este caso, 32945). Si queremos especificar un puerto determinado, podemos usar la opción --api-port al crear el clúster.

Crear un clúster (a partir de un fichero de configuración)

Desde la versión v4.0.0 k3d permite especificar en un fichero de configuración cualquiera de las opciones que se pueden especificar desde línea de comando. Esto facilita todavía más integrar k3d en un workflow de integración contínua, por ejemplo, creando un clúster donde desplegar la nueva versión de la aplicación, realizar pruebas y destruirlo al finalizar.

La documentación oficial (en Config File) proporciona un fichero de configuración de ejemplo como referencia; sin embargo, para crear un clúster como el que hemos creado manualmente en la sección anterior, el fichero es sencillamente:

El fichero de configuración, en este caso, lo llamo config-1s1a.yaml.

apiVersion: k3d.io/v1alpha2 # this will change in the future as we make everything more stable
kind: Simple # internally, we also have a Cluster config, which is not yet available externally
# name: mycluster # name that you want to give to your cluster (will still be prefixed with `k3d-`)
servers: 1 # same as `--servers 1`
agents:  1 # same as `--agents 1`
options:
  kubeconfig:
    updateDefaultKubeconfig: true # add new cluster to your default Kubeconfig; same as `--kubeconfig-update-default` (default: true)
    switchCurrentContext: true # also set current-context to the new cluster's context; same as `--kubeconfig-switch-context` (default: true)

Aunque el fichero de configuración permite especificar el nombre del clúster, podemos especificarlo desde la línea de comando al ejecutar el comando k3d cluster create:

$ k3d cluster create stable-cluster --config config-1s1a.yaml
INFO[0000] Using config file config-1a1s.yaml           
INFO[0000] Prep: Network                                
INFO[0000] Created network 'k3d-stable-cluster'         
INFO[0000] Created volume 'k3d-stable-cluster-images'   
INFO[0001] Creating node 'k3d-stable-cluster-server-0'  
INFO[0001] Creating node 'k3d-stable-cluster-agent-0'   
INFO[0001] Creating LoadBalancer 'k3d-stable-cluster-serverlb' 
INFO[0001] Starting cluster 'stable-cluster'            
INFO[0001] Starting servers...                          
INFO[0001] Starting Node 'k3d-stable-cluster-server-0'  
INFO[0010] Starting agents...                           
INFO[0010] Starting Node 'k3d-stable-cluster-agent-0'   
INFO[0020] Starting helpers...                          
INFO[0020] Starting Node 'k3d-stable-cluster-serverlb'  
INFO[0021] (Optional) Trying to get IP of the docker host and inject it into the cluster as 'host.k3d.internal' for easy access 
INFO[0027] Successfully added host record to /etc/hosts in 3/3 nodes and to the CoreDNS ConfigMap 
INFO[0027] Cluster 'stable-cluster' created successfully! 
INFO[0027] --kubeconfig-update-default=false --> sets --kubeconfig-switch-context=false 
INFO[0027] You can now use it like this:                
kubectl config use-context k3d-stable-cluster
kubectl cluster-info

Validamos que el clúster se ha creado:

$ k3d cluster list
NAME             SERVERS   AGENTS   LOADBALANCER
dev-cluster      1/1       2/2      true
stable-cluster   1/1       1/1      true

Conexión a los clústers creados con k3d

Por defecto k3d fusiona en el fichero KUBECONFIG (por defecto, en $HOME/.kube/config) la configuración de acceso al nuevo clúster. (options.kubeconfig.updateDefaultKubeconfig: true).

Puedes revisar la lista de todos los contextos definidos en el fichero de configuración:

$ kubectl config get-contexts
CURRENT   NAME                 CLUSTER              AUTHINFO                   NAMESPACE
          k3d-dev-cluster      k3d-dev-cluster      admin@k3d-dev-cluster      
*         k3d-stable-cluster   k3d-stable-cluster   admin@k3d-stable-cluster

Como vemos, por defecto se cambia el contexto actual (current-context) a la configuración correspondiente al último clúster creado (el contexto activo es el indicado con un *).

Para usar otro contexto (e interactuar con otro clúster) usa:

$ kubectl config use-context k3d-dev-cluster
Switched to context "k3d-dev-cluster".

Añadir (o eliminar) nodos al clúster

k3d proporciona el comando k3d node create con el que añadimos nuevos nodos al clúster.

Para añadir un nodo agente al clúster dev-cluster:

k3d añade el prefijo k3d- y un sufijo numérico indicando el número de réplicas.

$ k3d cluster list
NAME          SERVERS   AGENTS   LOADBALANCER
dev-cluster   1/1       1/1      true
$ export CLUSTER_NAME=dev-cluster
$ k3d node create dev-cluster-agent-1 --cluster $CLUSTER_NAME --role agent
INFO[0000] Starting Node 'k3d-dev-cluster-agent-1-0'

Validamos que se ha creado el nuevo nodo con el rol de agente:

$ k3d node list
NAME                        ROLE           CLUSTER       STATUS
k3d-dev-cluster-agent-0     agent          dev-cluster   running
k3d-dev-cluster-agent-1-0   agent          dev-cluster   running
k3d-dev-cluster-server-0    server         dev-cluster   running
k3d-dev-cluster-serverlb    loadbalancer   dev-cluster   running

Resumen

Como ves, la creación de clústers usando k3d reduce al mínimo la fricción a la hora de generar clústers de Kubernetes para realizar pruebas sin consumir demasiados recursos. Por ello, es una solución conveniente para realizar pruebas prácticamente en cualquier sitio, desde localmente en el portátil del desarrollador, una Raspberry Pi o una instancia en algún servicio cloud.