Después de hablar de WASM en Kubernetes: AKS, y de WebAssembly en el servidor , toca expandir la visión de ejecución de módulos WASM en Kubernetes, esta vez de mano de Red Hat OpenShift, usando crun como runtime capaz de ejecutar workloads WASM (crun-wasm) y Podman Desktop en Windows para construir y probar en contenedores el código que desarrollemos. ¡Os compartiremos también una idea sobre WebAssembly y Operadores de K8s que seguro que os parece muy interesante!

Nuevamente, la elección de estas tecnologías y plataformas en concreto vienen dadas por la compatibilidad y limitaciones de entre unas y otras. ¡Comencemos!

Código

En el post anterior, usamos una template de spin para crear el código de un servidor web con un endpoint custom, ya que usar los ejemplos de aplicaciones preconstruidas nos quitaba bastante perspectiva de la integración de las tecnologías que usamos (y porque nos limitaba la integración con AKS ).

En este caso crearemos un módulo WASM muy simple en Rust que solo mostrará un Hello Paradigma World! por el stdout.

Comenzamos creando un fichero hello.rs con el código:

// hello.rs
fn main() {
    println!("Hello, Paradigma World!");
}

E igual que en el anterior post, debemos tener previamente instalado Rust para Windows y añadir el target wasm32-wasi:

C:\Users\ydguala\wasm\hello>rustup target add wasm32-wasi
info: downloading component 'rust-std' for 'wasm32-wasi'
info: installing component 'rust-std' for 'wasm32-wasi'
 17.6 MiB /  17.6 MiB (100 %)  13.4 MiB/s in  1s ETA:  0s

Compilamos el código para que cree el módulo WASM:

C:\Users\ydguala\wasm\hello>rustc hello.rs --target wasm32-wasi

Esto creará el fichero hello.wasm el cual podemos testear usando un runtime de webAssembly, en este caso usaremos wasmedge.

Para instalarlo usaremos winget install wasmedge:

C:\Users\ydguala\wasm\hello>winget install wasmedge
The `msstore` source requires that you view the following agreements before using.
Terms of Transaction: https://aka.ms/microsoft-store-terms-of-transaction
The source requires the current machine's 2-letter geographic region to be sent to the backend service to function properly (ex. "US").

Do you agree to all the source agreements terms?
[Y] Yes  [N] No: Y
Found WasmEdge [WasmEdge.WasmEdge] Version 0.13.4
This application is licensed to you by its owner.
Microsoft is not responsible for, nor does it grant any licenses to, third-party packages.
This package requires the following dependencies:
  - Packages
      Microsoft.VCRedist.2015+.x64
Downloading https://github.com/WasmEdge/WasmEdge/releases/download/0.13.4/WasmEdge-0.13.4-windows.msi
██████████████████████████████  16.7 MB / 16.7 MB
Successfully verified installer hash
Starting package install...
Successfully installed

Añadiremos la nueva ruta donde se encuentra wasmedge a nuestro Path:

C:\Users\ydguala\wasm\hello>set PATH=%PATH%;C:\Program Files\WasmEdge\bin

Y confirmamos que nuestro código funciona:

C:\Users\ydguala\wasm\hello>set PATH=%PATH%;C:\Program Files\WasmEdge\bin

Una vez tenemos el código, deberemos contenerizarlo con Podman, pero antes vamos a hablar del Runtime de bajo nivel que este y OpenShift pueden compartir: crun.

Crun

Hemos explicado en post anteriores que el container runtime runc (escrito en Golang), es el runtime de bajo nivel que usan otros runtime de alto nivel como containerd. Este runtime es tradicionalmente utilizado como el runtime de contenedor predeterminado para los clústeres de Kubernetes.

Sin embargo, existen otros runtimes de bajo nivel, como crun, que es un Runtime de contenedores OCI que destaca por ser rápido y con una huella de memoria baja, escrito completamente en C.

Los colaboradores proyecto crun han estado añadiendo soporte para ejecutar cargas de trabajo WASM directamente a través de las API de bibliotecas compartidas en C proporcionadas por varios runtime de WASM, como wasmtime.

Podman Desktop

Para desarrollar y contenerizar nuestro código WASM con crun Red Hat ha desarrollado y liberado crun-wasm, el cual de momento solo podemos ejecutar en Podman Desktop (ya que usa una VM Fedora 38), Red Hat Enterprise Linux CoreOS para OpenShift.

Para habilitar crun-wasm en Podman, en nuestro caso no debemos hacer nada, ya que tenemos una versión >= v4.7.0. Para otros OS, consultad aquí.
Comenzaremos creando nuestro Containerfile:

#Containerfile
FROM scratch
COPY hello.wasm /
CMD ["/hello.wasm"]

Y construyendo nuestra imagen. Observemos que la plataforma es wasi\wasm32 y no wasi\wasm como indica en algunas documentaciones.

C:\Users\ydguala\wasm\hello>podman build --annotation "module.wasm.image/variant=compat"  --platform=wasi/wasm32 -t podman-win-hello .
STEP 1/3: FROM scratch
STEP 2/3: COPY hello.wasm /
--> Using cache b878d1e51591bebf54202bfbdf56ee9962c5fd7e4edc977bcc36a89cd5dd15a2
--> b878d1e51591
STEP 3/3: CMD ["/hello.wasm"]
--> Using cache 4305d68843db14e5b399232a7ecf3d02488080aef722aca6ae7e009704528f94
COMMIT podman-win-hello
--> 4305d68843db
Successfully tagged localhost/podman-win-hello:latest
4305d68843db14e5b399232a7ecf3d02488080aef722aca6ae7e009704528f94

C:\Users\ydguala\wasm\hello>podman images localhost/podman-win-hello
REPOSITORY                  TAG         IMAGE ID      CREATED            SIZE
localhost/podman-win-hello  latest      4305d68843db  About an hour ago  2.18 MB

C:\Users\ydguala\wasm\hello>podman tag localhost/podman-win-hello docker.io/ydguala/podman-win-hello:latest     

C:\Users\ydguala\wasm\hello>podman push docker.io/ydguala/podman-win-hello:latest
Getting image source signatures
Copying blob sha256:fab4a1230e5372e4ca914cbb886c159378063182fe459f39cc6e6e6dcab6ac13
Copying config sha256:4305d68843db14e5b399232a7ecf3d02488080aef722aca6ae7e009704528f94
Writing manifest to image destination

Para probar la ejecución del pod:

C:\Users\ydguala\wasm\hello>podman run --platform=wasi/wasm32 -t --rm localhost/podman-win-hello

Hello, Paradigma World!

OpenShift

La ejecución de módulos WASM está disponible en OCP como una developer preview feature en la versión 4.14 de OpenShift.

En nuestro caso, vamos usamos un Single Node OpenShift (SNO) en AWS con versión 4.14.6:

Cluster settings

Para instalar crun en OpenShift, debemos utilizar un recurso MachineConfig para configurar los nodos con crun-wasm además del runtime predeterminado.

Debido a que el entorno en el que nos encontramos es un SNO, el MachineConfigPool de nuestra máquina es el de master:

Machineconfigpool master
Machineconfigpoo-waster

Por lo que usamos el siguiente MachineConfig de nodos master para configurar crun-wasm en nuestro nodo.

# server-machineConfig.yaml
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
metadata:
 labels:
   machineconfiguration.openshift.io/role: master
 name: 99-masters-wasm-workloads
spec:
 config:
   ignition:
     config: {}
     security:
       tls: {}
     timeouts: {}
     version: 3.2.0
   networkd: {}
   passwd: {}
   storage:
     files:
     - contents:
         source: data:text/plain;charset=utf-8;base64,W2NyaW8ucnVudGltZV0KZGVmYXVsdF9ydW50aW1lID0gImNydW4td2FzbSIKW2NyaW8ucnVudGltZS5ydW50aW1lcy5jcnVuLXdhc21dCnJ1bnRpbWVfcGF0aCA9ICIvdXNyL2Jpbi9jcnVuIgpwbGF0Zm9ybV9ydW50aW1lX3BhdGhzID0geyJ3YXNpL3dhc20zMiIgPSAiL3Vzci9iaW4vY3J1bi13YXNtIn0K
       mode: 0644
       overwrite: true
       path: /etc/crio/crio.conf.d/99-crun-wasm.conf
 extensions:
   - wasm

Esta MachineConfig creará un archivo en /etc/crio/crio.conf.d/99-crun-wasm.conf que contendrá lo siguiente (la cadena en base64 decodificada en la MachineConfig anterior):

[crio.runtime]
default_runtime = "crun-wasm"
[crio.runtime.runtimes.crun-wasm]
runtime_path = "/usr/bin/crun"
platform_runtime_paths = {"wasi/wasm32" = "/usr/bin/crun-wasm"}

⚠️ Mientras se instala puede que perdamos momentáneamente la conexión con el cluster.

Creamos la configuración y al cabo de unos minutos podemos confirmar que ha funcionado correctamente entrando al nodo y ejecutando crun-wasm -v y viendo que se ha creado el fichero 99-crun-wasm.conf.

$ oc create -f server-machineConfig.yaml
machineconfig.machineconfiguration.openshift.io/99-masters-wasm-workloads created

$ oc debug node/ip-10-0-185-41.eu-west-1.compute.internal
Starting pod/ip-10-0-185-41eu-west-1computeinternal-debug ...
To use host binaries, run `chroot /host`
Pod IP: 10.0.185.41
If you don't see a command prompt, try pressing enter.

sh-4.4# chroot /host

sh-5.1# ls -la /etc/crio/crio.conf.d/
total 8
drwxr-xr-x. 2 root root   49 Dec 19 10:01 .
drwxr-xr-x. 4 root root   78 Dec 19 10:01 ..
-rw-r--r--. 1 root root 2677 Dec 19 10:00 00-default
-rw-r--r--. 1 root root  174 Dec 19 10:00 99-crun-wasm.conf

sh-5.1# crun-wasm -v
crun version 1.8.5
commit: b6f80f766c9a89eb7b1440c0a70ab287434b17ed
rundir: /run/crun
spec: 1.0.0
+SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP +EBPF +WASM:wasmedge +YAJL

Con esto habremos habilitado crun con soporte WASM en OpenShift.

¡Vamos a testear! Usaremos un pod que crearemos con el siguiente manifiesto:

# pod-hello.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: hello
  name: hello
spec:
  restartPolicy: OnFailure
  containers:
  - image: ydguala/podman-win-hello:latest
    command: ["/hello.wasm"]
    imagePullPolicy: IfNotPresent
    name: hello
    resources:
      requests:
        cpu: 10m
        memory: 10Mi
  dnsPolicy: ClusterFirst

Creamos el pod y vemos los logs:

$ oc create -f pod-hello.yaml 
pod/hello created

$ oc get po
NAME                   READY   STATUS      RESTARTS   AGE
hello                  0/1     Completed   0          8s

$ oc logs hello
Hello, Paradigma World!

Con esto hemos podido confirmar que podemos ejecutar módulos WASM desde el código más sencillo en nuestro entorno local, construir sus imágenes, ejecutarlas y ejecutarlas en Kubernetes.

Como último punto, vale la pena observar que dentro del nodo de OpenShift la imagen que hemos construido para revisar la architecture y el os que indican que se trata de un contenedor que ejecuta workloads WASM:

$ oc debug node/ip-10-0-185-41.eu-west-1.compute.internal
Starting pod/ip-10-0-185-41eu-west-1computeinternal-debug ...
To use host binaries, run `chroot /host`
Pod IP: 10.0.185.41
If you don't see a command prompt, try pressing enter.

sh-4.4# chroot /host

sh-5.1# crictl images docker.io/ydguala/podman-win-hello
IMAGE                                TAG                 IMAGE ID            SIZE
docker.io/ydguala/podman-win-hello   latest              4305d68843db1       2.18MB
sh-5.1# crictl inspecti docker.io/ydguala/podman-win-hello
{
  "status": {
    "id": "4305d68843db14e5b399232a7ecf3d02488080aef722aca6ae7e009704528f94",
    "repoTags": [
      "docker.io/ydguala/podman-win-hello:latest"
    ],
    "repoDigests": [
      "docker.io/ydguala/podman-win-hello@sha256:ed4c9a889ed10195737fc6cd6d7e0549fa38d4f4e25623c24e892c746a4f0bc9"
    ],
    "size": "2181326",
    "uid": null,
    "username": "",
    "spec": {
      "image": "",
      "annotations": {
        "module.wasm.image/variant": "compat",
        "org.opencontainers.image.base.digest": "",
        "org.opencontainers.image.base.name": ""
      }
    },
    "pinned": false
  },
  "info": {
    "labels": {
      "io.buildah.version": "1.32.0"
    },
    "imageSpec": {
      "created": "2023-12-20T12:49:43.777273472Z",
      "architecture": "wasm32",
      "os": "wasi",
      "config": {
        "Env": [
          "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
        ],
        "Cmd": [
          "/hello.wasm"
        ],
        "Labels": {
          "io.buildah.version": "1.32.0"
        }
      },
      "rootfs": {
        "type": "layers",
        "diff_ids": [
          "sha256:fab4a1230e5372e4ca914cbb886c159378063182fe459f39cc6e6e6dcab6ac13"
        ]
      },
      "history": [
        {
          "created": "2023-12-20T12:49:43.599605163Z",
          "created_by": "/bin/sh -c #(nop) COPY file:98e1a19e647ae991aed42f782ac46f567680be56ffef2aece3914a634fbafcde in / "
        },
        {
          "created": "2023-12-20T12:49:43.777677934Z",
          "created_by": "/bin/sh -c #(nop) CMD [\"/hello.wasm\"]",
          "comment": "FROM b878d1e51591",
          "empty_layer": true
        }
      ]
    }
  }
} 

WebAssembly en OpenShift Service Mesh

Señalar que además de soportar la ejecución de workloads como hemos visto, OpenShift Service Mesh, cuyo proyecto upstream es Istio, han implementado extensiones de WebAssembly para agregar nuevas características directamente a los proxies de Red Hat OpenShift Service Mesh.

Esto permite trasladar aún más funcionalidades comunes fuera de nuestras aplicaciones e implementarlas en un solo lenguaje que compila a bytecode de WebAssembly.

Además, nos puede dar una idea del potencial que tienen los módulos WASM y la adopción que está haciendo la industria de lo que parece que será un estándar de ejecución de aplicaciones en Servidor en el futuro.

Una mirada al futuro: operadores de Kubernetes en WebAssembly

Si hablamos de OpenShift y de Red Hat, cabe recordar que este fabricante ha impulsado, junto con la comunidad Opensource, una de las herramientas o toolkits que más han aumentado la adopción de Kubernetes en la industria: el Operators Framework.

Este framework ha estandarizado un conjunto de herramientas, patrones y directrices que facilita el desarrollo, implementación y gestión de operadores personalizados en Kubernetes.

Hoy en día existen iniciativas que quieren implementar WebAssembly como Runtime para la ejecución de operadores de Kubernetes.

Los operadores son un estándar para extender, mejorar y aprovechar al máximo Kubernetes, pero tienen desventajas como que, al igual que muchos servicios y aplicaciones dentro de Kubernetes, son procesos que se ejecutan en contenedores que la mayor parte del tiempo están esperando a recibir una llamada para ‘hacer algo’ desaprovechando mientras tanto recursos preciados.

Esto hace que junto con la falta de gestión adecuada de limits y requests veamos constantemente clusters de Kubernetes en estado overcommitted de memoria o CPU que no consumen ni el 50 por ciento de lo que reservan.

Ejecutar Operadores de Kubernetes en WebAssembly podría reducir significativamente su sobrecarga de memoria y permitir iniciarlos o detenerlos dinámicamente. De esta manera, solo se ejecutarán cuando hay algo que hacer realmente.

Por esto, en el IDLab de la Universidad de Ghent (Gante, Bélgica) han creado un prototipo de runtime para ejecutar Operadores eficientemente llamado wasm-operator. Este prototipo reduce la sobrecarga de tres maneras:

En las pruebas que hicieron:

En este video podemos ver al equipo explicando en detalle cómo funciona internamente esta aproximación. A modo de resumen, hay un Parent Operator que monitoriza los CRDs y recursos de Kubernetes y levanta Child Operators cuando hace falta.

Podemos intuir que estos beneficios podrían aplicarse no solo a los Operadores sino a todo el ecosistema de Kubernetes, tanto internamente como en las aplicaciones que se ejecutan sobre él.

Conclusiones

¿Se pueden cambiar paradigmas de ejecución en servidor que damos por sentado? Viendo el panorama actual de Wasm en la CNCF y que tanto grandes de la tecnología e iniciativas de universidades públicas están poniendo esfuerzos e innovando con WASM y ARM como vimos en el post anterior podemos esperar en 2024 muchas más novedades relacionadas con WebAssembly de las cuales estaremos al tanto y os iremos contando.

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete

Estamos comprometidos.

Tecnología, personas e impacto positivo.