Diseccionando DevSpaces (i)

DevSpaces es una de las grandes novedades para desarrolladores que nos trae Microsoft (ehm sí… es otro producto más que está en preview :p). Básicamente se trata de la posibilidad de desplegar parcialmente y depurar nuestros contenedores ejecutándose en un clúster de Kubernetes. En lo que Hanselman llama un “entorno que huele como producción”.

¿Pero… cómo funciona esa magia? Vamos a ver, dentro de lo posible, qué ha hecho Microsoft para proporcionarnos esta experiencia, dentro de un Kubernetes completamente normal…

Este post no pretender ser un tutorial de DevSpaces, ya que la propia documentación lo explica bastante bien. La idea es intentar ver “como” funciona DevSpaces, más que como lo podemos usar.

Lo primero es que crees un AKS. Los dos requisitos es que tenga “HTTP Application routing habilitado (si lo creas desde el portal esta opción está marcada por defecto) y que lo crees en una zona con soporte DevSpaces (como p. ej. “East US”). Una vez tengas tu cluster creado usa az aks para obtener una configuración para poder usarlo con kubectl:

az aks get-credentials -n <nombre-aks> -g <resource-group>

Que nos instala Devspaces en el clúster

Vale, ha llegado el momento de usar devspaces. Lo primero, asegúrate de que tienes al menos la versión 2.0.38 de la cli de Azure y ya puedes lanzar los comandos:

az extension add --name dev-spaces-preview 
az aks use-dev-spaces -n <nombre-aks> -g <resource-group>

Eso te instalará también la CLI propia de DevSpaces (azds) y configurará el clúster de Kubernetes para DevSpaces. A nivel de clúster se nos instalaran varias cosas, todas ellas en el espacio de nombres azds:

  1. Un deployment y su correspondiente servicio ejecutando Tiller. Los despliegues contra Devspaces los haremos usando Helm.
  2. Un deployment azds-initializer que crea un pod que ejecuta la imagen azds/azds-initializer-service.
  3. Un daemon set azds-image-prepull que crea un pod en cada nodo que ejecuta la imagen azds/azds-image-prepull. Estos pods parece ser que hacen un pull de varias imágenes (node, aspnet core, el depurador de netcore, etc) y luego se ponen a dormir “para siempre”.

De hecho estos elementos (menos Tiller claro) se instalan vía Helm (aunque para nosotros es transparente). Pero si haces “helm ls –tiller-namespace azds” verás una release de Helm llamada azds-<guid>.

Probando Devspaces desde la CLI

Para probar DevSpaces y ver lo que ocurre vamos a partir de una situación inicial:

  1. Un servicio escrito en NetCore llamado Consumer
  2. Un servicio escrito en NetCore llamado Producer

El servicio Consumer hace una llamada a Producer para que este le devuelva un item y poder consumirlo. Estamos hablando de una llamada en HTTP.

Para ello, si quieres seguir el paso a paso, descárgate el código Zip de ambos servicios.  Si descomprimes el zip, verás dos carpetas (producer y consumer). Levanta ambos y establece (en el consumer) la variable de entorno “URLS_PRODUCER” con el valor de la URL raiz del producer. Con esto si navegas a http://<consumer>/api/demo deberías ver un json con dos productos:

Servicios corriendo en local

Perfecto, ya tenemos dos servicios que se comunican entre ellos 🙂 Ha llegado el momento de desplegarlos en nuestro AKS usando DevSpaces.

Para ello vete a la carpeta del producer y teclea azds prep. Ese comando básicamente crea dos Dockerfile (uno estándard multi-stage y otro llamado Dockerfile.develop), así como el chart de Helm para instalar. Si ahora tecleas azds up, se construirá la imagen de Docker a partir del Dockerfile.develop y se instalará en k8s el chart de Helm. El chart generado te instala un servicio y un deployment que ejecuta tu servicio.

La diferencia entre el Dockerfile y el Dockerfile.develop es que el primero es el tradicional multi-stage de netcore, mientras que el segundo usa la imagen del SDK para terminar haciendo un “dotnet run”. Si ahora miras las releases de helm deberías ver la release del producer instalada. Esta release nos ha instalado un servicio (ClusterIP) , un deployment y el pod. ¡Genial!. Pero… indaguemos un poco más.

Si inspeccionas el deployment verás que consta de un contenedor (nuestro producer, claro) pero fíjate la imagen del contenedor (puedes usar kubectl get deployment producer -o yaml para ver la descripción del deployment). En mi caso tengo:

spec:
  containers:
  - image: producer:mindaro-012f89e6
    imagePullPolicy: Never

¿De donde sale esa imagen “producer:mindaro-012f89e6“? La clave nos la da el imagePullPolicy, que está a Never. Eso significa que Kubernetes nunca hará un pull de la imagen, es decir esta debe estar instalada en el clúster. ¿Pero como llega al clúster? En mi máquina no está, y obviamente no está en ningún registro. Pues, la imagen está en el clúster porque se construye allí, es decir el proceso de build ocurre en el clúster, por lo que, la imagen ya está allí antes de que se incie el pod (de ahí claro que el imagePullPolicy esté a Never, no queremos que Kubernetes se descargue esa imagen de ningún lugar, puesto que en ningún lugar existe).

Vale… ¿pero quien dentro del clúster construye esa imagen? Bueno… Pues lo hace un init containerUn día escribiré sobre ellos en el blog, pero por ahora quédate en que es un contenedor que se ejecuta antes de iniciar un pod. En este caso, este contenedor es quien ejecuta el docker build y crea la imagen en el cluster. Si tecleas kubectl describe pod <nombre-pod-producer> verás que parte de la salida es algo parecido a:

salida de kubetl describe

En el recuadro rojo he señalado el nombre del contenedor de inicialización (mindaro-build) y en la elipse amarilla uno de los parámetros que se le pasan, que puedes ver que se corresponde con el nombre de la imagen del producer. Observa que otro parámetro es el Dockerfile.Develop. Este es pues, quien nos construye la imagen.

Vale, ahora toca irnos a la carpeta del consumer y repetir los pasos,  aunque ahora usa azds prep –public. Con eso le indicas a DevSpaces que el chart que se genere exponga un endpoint público para el servicio. Al igual que antes, usa azds up para lanzar el servicio. Esto, al igual que antes, te creará un servicio, el deployment ¡y un ingress con un DNS propio! (debido a que hemos usado –public):

Vista de deployments, servicios y ing

Quien proporciona el DNS es el “Http Application Routing” que en el fondo es una instalación preconfigurada de external-dns, un producto que puedes instalar en cualquier Kubernetes para configurar DNS externos y vincularlos a servicios expuestos. En AKS se trabaja, obviamente, con Azure DNS y gracias al addon de “Http application routing” ya lo tenemos todo configurado. Eso permite tener distintos servicios, expuestos bajo el mismo ingress controller (por lo tanto misma IP pública) pero enlazados a distintas direcciones DNS.

Si ahora tecleas “azds list-up” verás una lista con los dos servicios que estás ejecutando y con “azds list-uris” verás una lista con los puntos de acceso a los distintos servicios:

Salida de azds list-up y list-uris

De hecho si navegas a la URL indicada con el path /api/demo debería aparecerte una página de error. Esto es porque no hemos establecido la configuración del consumer (no le hemos indicado la url del producer).

Vamos a establecer esta configuración. En la carpeta raíz del consumer (es la misma carpeta donde hay el values.yaml) crea un fichero llamado values.dev.yaml con este contenido:

secrets:
  urls:
    producer: http://producer

Nota importante: En la documentación pone que el fichero values.dev.yaml debe crearse en el directorio charts/consumer (donde está el values.yaml). A mi, eso no me ha funcionado. No sé si es un error de la doc o qué, pero el fichero values.dev.yaml debe estar en el mismo directorio donde hay el fichero azds.yaml.

Ojo: Es un poco curioso como se mapean estos ficheros de valores a las variables de entorno que termina recibiendo el contenedor. Así este fichero termina generando la variable de entorno “urls_producer”. Observa que para este mismo fichero tratado como si fuese un appsettings en netcore usaríamos la variable de entorno “urls__producer” (doble subrayado) o “urls:producer” en Windows. Es algo pues, a tener presente.

El siguiente punto es modificar el fichero azds.yaml dentro de la carpeta raíz del consumer para que use este fichero de secretos que hemos creado. Para ello agregamos la entrada “values” dentro de container (que ya debe existir):

container:
  values:
    - "charts/consumer/values.dev.yaml"

Ahora puedes eliminar el consumer (azds down) y volverlo a instalar con azds up.

Si ahora navegas a la URL (DNS) asignado al consumer (recuerda que la puedes ver con azds list-uris) ya te debería funcionar. Nota: Los DNS pueden tardar un cierto tiempo a refrescarse (bueno… ojo que lo de “cierto tiempo” en pruebas que he hecho yo, ha excedido una hora. La URL aparece en estado de “Pending” durante este tiempo.

Así que es posible que quizá el principio no te funcione. Lo que sí te deberían funcionar son las urls tunneled (que te permiten acceder a los servicios del clúster mediante direcciones http://localhost:puerto). Estas urls también aparecen con “azds list-uris”.

Bueno: Lo dejaremos aquí en este primer post. Como te dije, la idea era ver un poco como funcionaba DevSpaces, y en post sucesivos intentaré ir diseccionando más partes!

Saludos!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *