Docker sin Docker Desktop - Parte 1: Windows WSL2

17 de enero de 2022

En esta primera parte exploramos cómo ejecutar Docker aprovechando WSL2 de Windows. Y nada más.

No es por no pagar

El 31 de agosto de 2021 Docker introdujo un cambio en el licenciamiento de Docker Desktop. En resumidas cuentas, las empresas debían pagar por el uso del producto. No es objeto de este artículo entrar a valorar esta decisión. La empresa es muy libre de decidir bajo qué condiciones han de usarse sus productos.

Esta decisión ha producido cierto revuelo. El poder ejecutar contenedores se ha convertido en una necesidad demasiado importante para prácticamente cualquiera que esté en IT y no esté anclado en el pasado. En la red han aparecido muchos artículos con las diferentes alternativas que existen y lo único que ha quedado claro es que esto abre un nuevo mundo de posibilidades y que con él va a ser necesario que tomemos algunas decisiones y adoptemos ciertas soluciones de compromiso.

La decisión más inmediata es pasar por caja. Pagar la suscripción de Docker Desktop nos garantiza poder continuar exactamente igual que antes. Además, cabe esperar que Docker ofrecerá alguna ventaja adicional a sus clientes para así retenerlos y que todo el mundo esté contento.

La alternativa es pararse a pensar en cómo usamos docker, qué necesitamos y qué soluciones técnicas tenemos a nuestra disposición. Si usamos Windows se da la feliz circunstancia de que hace ya un tiempo que Microsoft lanzó la segunda versión de su sub-sistema Linux para Windows: WSL2 o simplemente WSL. En este artículo vamos a explorar una configuración para poder ejecutar docker tal y como lo veníamos haciendo, pero haciendo uso únicamente de WSL2.

¿Qué es lo mejor? Docker Desktop es un gran producto que nos ha facilitado la vida durante años y parece justo seguir usándolo adquiriendo una licencia. Al mismo tiempo, WSL2 es una tecnología nativa de Windows que a día de hoy ofrece un rendimiento bastante bueno y simplemente "está ahí". Metámonos en harina para tomar la decisión por nosotros mismos.

¿Cómo funciona?

Antes de nada, es importante saber qué tenemos entre manos. En palabras de Pablo Fredrikson, también conocido como Pelado Nerd : "WSL2 no es magia", y así es. Al final WSL2 es una arquitectura basada en virtualización.

WSL2 Architecture

WSL2 aprovecha la misma capa de abstracción que el propio kernel de Windows NT para acceder al hardware. Microsoft ha desarrollado su propia versión del kernel de Linux para dar servicio a prácticamente cualquier ejecutable de Linux, incluido obviamente, docker.

Cuando lanzamos WSL, Windows lanza este kernel de Linux y monta el sistema de archivos de la distribución que hayamos elegido. El kernel no pone en marcha ningún sistema de inicialización tipo "Systemd" y se limita a arrancar la terminal que tenga configurada el usuario.

WSl2 startup

Éste es el primer motivo por el cual podemos considerar esta manera de correr linux verdaderamente un "peso ligero". Tenemos un kernel de Linux listo para usar sin haber pasado por la inicialización completa que haría una máquina virtual convencional tipo VirtualBox, VMWare, etc.

Además, WSL nos ofrece algunas cualidades muy útiles de cara a correr contenedores. En primer lugar, los ficheros del host Windows se montan en la VM de linux bajo /mnt. De forma similar, los ficheros de las VMs Linux que tengamos en WSL se mostrarán como unidades en el explorador de Windows. Todo esto se consigue con el servicio de ficheros 9P de Microsoft.

WSL2 filesharing

En segundo lugar, los puertos que abramos dentro de la VM se expondrán automáticamente a Windows en localhost. Esto también lo hace Docker Desktop sin que nos demos cuenta.

Con todo esto, ya tenemos lo suficiente como para correr docker:

  1. Un kernel de linux corriendo sobre una VM
  2. Un sistema para compartir ficheros entre el host y la VM
  3. Un sistema de networking que expone los puertos de la VM al host

Se acabó la teoría. Pasemos a la práctica.

Prerequisitos

Antes de empezar, vamos a asegurarnos de que disponemos de los componentes de software que vamos a necesitar:

Instalación de docker en WSL2

Lo primero que hay que hacer es instalar docker en la distro Ubuntu de WSL. Abrid una terminal WSL2 y ejecutad los siguientes comandos:

#Actualizar Ubuntu
sudo apt update && sudo apt upgrade

## Configuración del repositorio Debian/Ubuntu para instalar Docker

# Carga de algunas variables de entorno que nos vendrán bien
source /etc/os-release

# Adición de la clave GPG del repositorio a APT
curl -fsSL https://download.docker.com/linux/${ID}/gpg | sudo apt-key add -

# Adición del repositorio de docker a APT
echo "deb [arch=amd64] https://download.docker.com/linux/${ID} ${VERSION_CODENAME} stable" | sudo tee /etc/apt/sources.list.d/docker.list

# Actualización de APT
sudo apt update

# Instalación de los paquetes necesarios para correr docker
sudo apt install docker-ce docker-ce-cli containerd.io

# Adición del grupo docker a tu usuario
sudo usermod -aG docker $USER

Cerrad la terminal donde habéis ejecutado todos estos comandos. Para probar que todo ha ido bien vamos primero a abrir una nueva terminal Ubuntu y ejecutaremos en ella el servidor de docker:

sudo dockerd

El último mensaje mostrará algo parecido a esto:

API listen on /var/run/docker.sock

Abrid una nueva terminal Ubuntu y ejecutad un contenedor de prueba:

docker -H unix:///var/run/docker.sock run --rm hello-world

La salida del comando será un "Hello from Docker!" y una breve explicación de lo que ha pasado.

Instalación de los binarios de docker en Windows

Necesitaremos dos binarios en Windows para acceder al server de docker en Ubuntu: docker.exe y docker-compose.exe.

Podemos obtener los binarios de los repositorios de Stefan Scherer de docker-cli y el oficial de docker para docker-compose.

Hay que añadir la carpeta donde se encuentren los dos binarios de docker al PATH del usuario para que los ejecutables se puedan correr desde cualquier sitio, incluido, VSCode.

Automatización del arranque del servidor

Para evitar que cada vez que lancemos el demonio de docker se nos pida la contraseña de root, vamos a añadir una línea a la configuración de sudo. Para ello ejecutaremos el comando sudo visudo que arrancará el editor por defecto nano con el fichero /etc/sudoers. Solo tenemos que añadir la siguiente línea:

%docker ALL=(ALL)  NOPASSWD: /usr/bin/dockerd

Para salir de nano pulsad Ctrl+x, la tecla y para confirmar la modificación y la tecla Enter para confirmar el nombre del fichero temporal de sudoers que se va a guardar.

Tras haber establecido la configuración del demonio docker, vamos a crear un script powershell que podremos lanzar para arrancar docker y establecer la variable de entorno que vamos a utilizar para localizar el servidor docker en WSL. Os sugiero que coloquéis el script en un directorio que esté en el PATH del usuario de Windows.

# En primer lugar, obtenemos la ip principal del host de wsl
$ip = (wsl sh -c "hostname -I").Split(" ")[0]

# Establecemos la variable local (al script) DOCKER_HOST, que luego utilizaremos
$DOCKER_HOST = $ip = "tcp://"+$ip+":2375"

# Un poco de información de lo que está pasando nunca viene mal
echo "The DOCKER_HOST of the WSL environment is: $DOCKER_HOST"

# Establecemos la variable DOCKER_HOST como variable de entorno para el usuario
[System.Environment]::SetEnvironmentVariable('DOCKER_HOST',$DOCKER_HOST,[System.EnvironmentVariableTarget]::User)

# Arrancamos el servidor docker 
wsl sh -c "sudo dockerd --tls=false -H tcp://$ip"

Ya podemos lanzar este script y observar el arranque de docker.

PS C:\tu-path-al-script-donde-hayas-guardado-el-script> start_docker.ps1
The DOCKER_HOST of the WSL environment is: tcp://172.xxx.xxx.xxx:2375
INFO[2022-01-03T09:05:20.996017400+01:00] Starting up
WARN[2022-01-03T09:05:20.996894900+01:00] Binding to IP address without --tlsverify is insecure and gives root access on this machine to everyone who has access to your network.  host="tcp://172.xxx.xxx.xxx:2375"
WARN[2022-01-03T09:05:20.996960600+01:00] Binding to an IP address, even on localhost, can also give access to scripts run in a browser. Be safe out there!  host="tcp://172.xxx.xxx.xxx:2375"
INFO[2022-01-03T09:05:22.002127000+01:00] libcontainerd: started new containerd process  pid=3608

bla, bla, bla...

INFO[2022-01-03T09:05:22.278904600+01:00] Docker daemon                                 commit=459d0df graphdriver(s)=overlay2 version=20.10.12
INFO[2022-01-03T09:05:22.279044400+01:00] Daemon has completed initialization
INFO[2022-01-03T09:05:22.298451400+01:00] API listen on 172.xxx.xxx.xxx:2375

El demonio nos advierte de que el binding a la dirección IP sin establecer la seguridad de la conexión SSL no es una configuración recomendada. Ni que decir tiene que esta configuración no está pensada para entornos de producción. De hecho, WSL2 en sí mismo no está pensado para entornos de producción. Es para lo que es, ejecución local en el contexto de un entorno de desarrollo.

Cuando ha terminado el arranque nos lo anuncia con el mensaje "API listen on 172.xxx.xxx.xxx:2375". Ahora ya podemos probar que podemos conectarnos a docker desde Windows y lanzar algún contenedor de prueba. Para ello lanzaremos una nueva ventana de terminal powershell (no una nueva tab). En la terminal podemos probar los siguientes comandos.

# Para obtener las versiones del .exe de docker y el servidor docker que está corriendo en WSL
docker version

# Para obtener la lista de contenedores activos (que debe de estar vacía)
docker container ls

# Para probar el contenedor "hello-world" que simplemente dice "Hello from Docker!"
docker run --rm hello-world

# Para probar que podemos abrir puertos, lanzad este comando y acceded a http://localhost:8080
docker run -it --rm -p 8080:80 uzyexe/tetris

# Cuando os hayáis cansado de jugar, pulsad Ctrl+c y el contenedor se cerrará.

También podemos probar docker-compose. Cread un fichero flowable-ui.yml con el siguiente contenido:

version: '3.6'
services:
    flowable-ui-app:
        image: flowable/flowable-ui
        depends_on:
            - flowable-ui-db
        environment:
            - SERVER_PORT=8080
            - SPRING_DATASOURCE_DRIVER-CLASS-NAME=org.postgresql.Driver
            - SPRING_DATASOURCE_URL=jdbc:postgresql://flowable-ui-db:5432/flowable
            - SPRING_DATASOURCE_USERNAME=flowable
            - SPRING_DATASOURCE_PASSWORD=flowable
            - FLOWABLE_COMMON_APP_IDM-ADMIN_USER=admin
            - FLOWABLE_COMMON_APP_IDM-ADMIN_PASSWORD=test
        ports:
            - 8080:8080
        entrypoint: ["./wait-for-something.sh", "flowable-ui-db", "5432", "PostgreSQL", "/flowable-entrypoint.sh"]
    flowable-ui-db:
        image: postgres:9.6-alpine
        container_name: flowable-ui-postgres
        environment:
            - POSTGRES_PASSWORD=flowable
            - POSTGRES_USER=flowable
            - POSTGRES_DB=flowable
        ports:
            - 5433:5432
        command: postgres

Para probar docker-compose ejecutaremos el siguiente comando:

docker-compose -f flowable-ui.yml up

El comando arrancará Flowable en su versión OSS en más o menos un minuto y podréis acceder a la UI mediante la URL http://localhost:8080/flowable-ui. El usuario por defecto es "admin" y la contraseña "test".

Flowable UI

Para parar los contenedores y terminar docker-compose, pulsad Ctrl+c.

Visual Studio Code

La integración de Visual Studio code con Docker se consigue mediante las extensión oficial de Docker de Microsoft.

Una vez instalada la extensión, aparecerá el icono de Docker (Docker icon) en el panel lateral.

La integración es automática y la extensión detectará automáticamente si tenemos el servidor de docker corriendo. Desde VSCode podremos manejar los contenedores, imágenes docker, volúmenes, etc.

VSCode Docker Extension

Visual Studio Code también ofrece la posibilidad de ejecutarse en modo remoto con su extensión WSL, pero esa forma de ejecución da para otro extenso artículo.

No todo va a ser néctar y ambrosía

Docker Desktop automáticamente gestiona el montaje de carpetas entre el host y la VM que ejecuta el demonio de docker. También se encarga de crear un alias para la dirección de red del host a la que llama "host.docker.internal". Estas automatizaciones se dan por hecho cuando en los proyectos se crean ficheros docker-compose.yml ya que hasta la fecha, Docker Desktop para Mac y Windows era ubicuo.

Para funcionar sin estas automatizaciones es necesario que hagamos dos modificaciones a nuestros ficheros docker-compose.yml:

  1. En el apartado volumes: de cada uno de los servicios, reemplazar el uso del comodín ~ por nuestro directorio home de WSL2, o sea, /mnt/c/Users/tu-nombre-de-usuario-en-windows.
  2. Añadir una sección extra_hosts en cada servicio que haga uso del alias host.docker.internal de la siguiente manera:
    extra_hosts:
      - host.docker.internal:host-gateway

Conclusión

En este artículo hemos aprendido cómo es posible ejecutar docker sin necesidad de usar Docker Desktop haciendo uso de WSL2.

Esta instalación es mejorable en muchos aspectos, pero particularmente:

Llegados a este punto, ¿qué opinas? ¿Vale la pena pagar la licencia de Docker Desktop o crees que puedes funcionar sólo con WSL2 y un poco de trabajo? Si has llegado hasta aquí, quizás opines que vale la pena probar. Seguramente, con el tiempo esta configuración se irá haciendo más sencilla según WSL2 sigue desarrollándose. También es posible que nuevos proyectos open source surjan alrededor de esta arquitectura.

Referencias

Sobre el autor: Jorge Mora
Comments
Únete a nosotros