En la entrada anterior k3d: desplegar un clúster de Kubernetes como código incluí un registry en el despliegue del clúster con k3d.

En esta entrada veremos cómo usarlo para desplegar aplicaciones en el clúster.

Como parte del despliegue de k3d se puede incluir un registry. Con la configuración especificada, el registry está disponible en registry.localhost en el puerto 5000 (el puerto por defecto usado por Docker).

En Linux el “dominio” .localhost resuelve siempre a 127.0.0.1

Configuración de registro inseguro

El registry de Docker requiere el uso de conexiones cifradas por TLS, lo que requiere un certificado.

Aunque el despliegue mediante k3d permite especificar los certificados para configurar un registry seguro, para un entorno local de pruebas podemos usar conexiones inseguras -esto es, sin TLS- para acceder al registry.

Para permitir conexiones inseguras, debemos configurar el daemon de Docker a través del fichero /etc/docker/daemon.json. Revisa las instrucciones ofrecidas por Docker en Test an insecure registry.

Crea el fichero /etc/docker/daemon.json (si no existe) y añade la URL del registro:

{
    "insecure-registries": ["http://registry.localhost:5000/"]
}

Debes reiniciar Docker para que los cambios sean efectivos.

Imagen de contenedor

En este caso, vamos a descargar de Docker Hub la imagen de nginx, pero del mismo modo podríamos usar la imagen de nuestra aplicación durante el proceso de desarrollo (generada, por ejemplo, con docker build).

Descargamos la imagen de Nginx (sobre Alpine) de Docker Hub:

$ docker pull nginx:1.23-alpine
1.23-alpine: Pulling from library/nginx
63b65145d645: Already exists
8c7e1fd96380: Pull complete
86c5246c96db: Pull complete
b874033c43fb: Pull complete
dbe1551bd73f: Pull complete
0d4f6b3f3de6: Pull complete
2a41f256c40f: Pull complete
Digest: sha256:6318314189b40e73145a48060bff4783a116c34cc7241532d0d94198fb2c9629
Status: Downloaded newer image for nginx:1.23-alpine
docker.io/library/nginx:1.23-alpine

Ejecuto localmente un contenedor a partir de la imagen de Nginx y valido que sirve la página por defecto:

$ docker run -p 8080:80 -d nginx:1.23-alpine
fdecff0efba7dc6e4db0f66cd873f7d4f972ac1b8a8e057c6755527bccc4a436

$ docker ps | grep -i alpine
fdecff0efba7   nginx:1.23-alpine                "/docker-entrypoint.…"   17 seconds ago   Up 16 seconds   0.0.0.0:8080->80/tcp, :::8080->80/tcp   blissful_herschel

$ curl -s localhost:8080 | grep -i 'welcome'
<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1>

Tenemos la imagen de Nginx localmente, pero lo que queremos es subirla al registry. El objetivo es que el clúster de Kubernetes despliegue los contenedores desde el registry de k3d, no desde nuestra cache local.

Etiquetado y subida de la imagen al registry

Etiquetamos la imagen local (en este caso, de Nginx) con la URL y namespace del registry en k3d:

$ docker images | grep -i 'nginx'
nginx                      1.23-alpine    2bc7edbc3cf2   6 weeks ago   40.7MB

$ docker tag nginx:1.23-alpine registry.localhost:5000/xaviaznar/nginx:v1.23-alpine

$ docker images | grep -i 'nginx'
nginx                                     1.23-alpine    2bc7edbc3cf2   6 weeks ago   40.7MB
registry.localhost:5000/xaviaznar/nginx   v1.23-alpine   2bc7edbc3cf2   6 weeks ago   40.7MB

Una vez taggeada, subimos la imagen al registry en k3d:

$ docker push registry.localhost:5000/xaviaznar/nginx:v1.23-alpine
The push refers to repository [registry.localhost:5000/xaviaznar/nginx]
042cd3f87f43: Pushed
f1bee861c2ba: Pushed
c4d67a5827ca: Pushed
152a948bab3b: Pushed
5e59460a18a3: Pushed
d8a5a02a8c2d: Pushed
7cd52847ad77: Pushed
v1.23-alpine: digest: sha256:3eb380b81387e9f2a49cb6e5e18db016e33d62c37ea0e9be2339e9f0b3e26170 size: 1781

Eliminamos la copia local de la imagen de Nginx:

$ docker rmi nginx:1.23-alpine
Untagged: nginx:1.23-alpine
Untagged: nginx@sha256:6318314189b40e73145a48060bff4783a116c34cc7241532d0d94198fb2c9629

$ docker rmi registry.localhost:5000/xaviaznar/nginx:v1.23-alpine 
Untagged: registry.localhost:5000/xaviaznar/nginx:v1.23-alpine
Untagged: registry.localhost:5000/xaviaznar/nginx@sha256:3eb380b81387e9f2a49cb6e5e18db016e33d62c37ea0e9be2339e9f0b3e26170
Deleted: sha256:2bc7edbc3cf2fce630a95d0586c48cd248e5df37df5b1244728a5c8c91becfe0
Deleted: sha256:9ca6be4cd63171f17f0a5e2ea28d5361a299672f41bd65223e7eac7d3d57e76d
Deleted: sha256:f7aa4d1226879fb1018ed05617572994840f4c75e5d04df2fffe04980cef11b9
Deleted: sha256:f83cdd3286b839bef51f1ae0f1f6164b16e1059a0e131035bfa0bb8bb0021357
Deleted: sha256:61b0680052fcdb48f47c8d68687c0b5bbb279b6e3740701885b39ea22ef7b008
Deleted: sha256:9045770a1273553bb8bd7ccd2d490ecb56ce762ac993ad74698d14186e39bda6
Deleted: sha256:60d9493158e562e8510cd4cabbd7460c03ad39fe2250bbd43bdcd1e75f64ba6f

Despliegue de un contenedor basado en la imagen del registry de k3d

Creamos un fichero de deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2 # tells deployment to run 2 pods matching the template
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: registry.localhost:5000/xaviaznar/nginx:v1.23-alpine
        ports:
        - containerPort: 80

Establecemos el clúster de k3d como current-context en kubectl:

$ kubectl config set current-context k3d-onthedock
Property "current-context" set.

Desplegamos el deployment:

$ kubectl apply -f nginx/deployment.yaml 
deployment.apps/nginx created

Para validar que los pods están sirviendo la página por defecto de Nginx, usamos port-forward:

$ kubectl port-forward pods/nginx-566d6d4899-qtzp8 8080:80 &
$ curl -s  localhost:8080 | grep -i 'welcome'
Handling connection for 8080
<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1>

Traemos el proceso a primer plano (con fg) y lo finalizamos.

Si miramos en detalle cualquiera de los dos pods, vemos que están usando la imagen del registry en k3d y no una imagen descargada desde Docker Hub:

$ kubectl get pods nginx-566d6d4899-qtzp8 -o jsonpath='{.spec.containers[].image}'
registry.localhost:5000/xaviaznar/nginx:v1.23-alpine

Observación

En realidad, el registry desplegado por k3d no está desplegado en el clúster, sino que es un contenedor adicional…

Usando el subcomando list de k3d cluster:

$ k3d cluster list
NAME        SERVERS   AGENTS   LOADBALANCER
onthedock   1/1       2/2      true

La salida indica que tenemos un nodo server y dos agents. Que bajo la columna loadbalancer se muestre true significa que tenemos un balanceador “delante” del clúster. Estrictamente el balanceador no forma parte del clúster…

Del mismo modo, si usamos k3d node list obtenemos que uno de los nodos es el registry:

$ k3d node list
NAME                     ROLE           CLUSTER     STATUS
k3d-onthedock-agent-0    agent          onthedock   running
k3d-onthedock-agent-1    agent          onthedock   running
k3d-onthedock-server-0   server         onthedock   running
k3d-onthedock-serverlb   loadbalancer   onthedock   running
registry.localhost       registry       onthedock   running

Pero esto es lo que muestra k3d; si usamos kubectl, está claro que el balanceador no forma parte del clúster:

$ kubectl get nodes
NAME                     STATUS   ROLES                  AGE     VERSION
k3d-onthedock-agent-0    Ready    <none>                 6h21m   v1.25.7+k3s1
k3d-onthedock-server-0   Ready    control-plane,master   6h21m   v1.25.7+k3s1
k3d-onthedock-agent-1    Ready    <none>                 6h21m   v1.25.7+k3s1

Del mismo modo, el registry no está desplegado en el clúster (como un pod), sino en un contenedor “externo”:

$ kubectl get pods --all-namespaces | grep -i 'registry'
$

Conclusión

Independientemente de cómo o dónde está desplegado el registry desplegado por k3d, Kubernetes puede usar el registro como origen de las imágenes desde las que desplegar pods.

Esto permite agilizar los despligues y las pruebas sobre la aplicación, ya que no es necesario hacer push a un registry remoto sólo para que al desplegar sobre el clúster, Kubernetes tenga que descargar la imagen de nuevo para poder arrancar los pods.

Con k3d disponemos de un entorno autocontenido que nos permite trabajar incluso desconectados de la red.