WSL2 + Kubernetes: la configuración que no derrite tu portátil

¿Te fue útil?

Instalaste Kubernetes localmente porque querías velocidad: iterar rápido, probar charts, depurar controladores, quizá ejecutar una pequeña pila de plataforma.
Entonces los ventiladores despegan, el SSD empieza a hacer flexiones y tu «clúster de desarrollo rápido» se convierte en la razón por la que Slack pregunta si estás en línea.

WSL2 puede ser un excelente sitio para ejecutar Kubernetes—si lo tratas como una VM real con límites de almacenamiento y memoria, no como una carpeta mágica de Linux.
Esta es la configuración práctica que evita los modos de fallo clásicos: RAM descontrolada, I/O lenta, rarezas de DNS y «¿por qué se queda colgado kubectl?».

Qué estás construyendo realmente (y por qué derrite portátiles)

En Windows con WSL2, «ejecutar Kubernetes» rara vez es solo «ejecutar Kubernetes».
Es una pila de abstracciones anidadas que cada una tiene opiniones sobre programación de CPU, recuperación de memoria, semántica del sistema de archivos y redes.
Cuando cualquiera de las capas adivina mal, tu portátil paga el precio.

La configuración típica de Kubernetes en WSL2 se ve así:

  • Sistema operativo host Windows
  • VM WSL2 (una VM ligera de Hyper-V con un disco virtual)
  • Distribución Linux userland (Ubuntu, Debian, etc.)
  • Runtime de contenedores (Docker Engine, containerd o pila nerdctl)
  • Distribución de Kubernetes (kind, k3d, minikube, microk8s o el clúster integrado de Docker Desktop)
  • Tus cargas: bases de datos, operadores, pipelines de build, controladores de ingress, mallas de servicio—aka «producción pequeña»

El derrame suele venir de uno de tres lugares:

  1. Memoria: WSL2 cachea el page cache y no siempre lo devuelve con rapidez. Kubernetes programa pods hasta que el nodo está «bien» justo hasta que deja de estarlo.
  2. Almacenamiento: cruzar la frontera Windows/Linux puede convertir un I/O normal en un incidente a cámara lenta. Las bases de datos amplifican esto con fsync y escrituras aleatorias pequeñas.
  3. Red/DNS: kube-dns, DNS de Windows, NICs virtuales de WSL2 y clientes VPN forman un triángulo de tristeza.

El objetivo no es «hacerlo rápido en benchmarks». El objetivo es «hacerlo predeciblemente lo suficientemente rápido»,
y más importante, hacer obvios los modos de fallo.
La fiabilidad en desarrollo importa porque en dev decides qué lamentarás en producción.

Algunos hechos y contexto histórico (para que las partes raras tengan sentido)

Son cortos a propósito. Son las actualizaciones del modelo mental que evitan horas de caza de fantasmas.

  1. WSL1 era traducción de syscalls; WSL2 es un kernel Linux real en una VM. Por eso WSL2 puede ejecutar Kubernetes correctamente, pero también por eso se comporta como una VM con su propio disco y políticas de memoria.
  2. WSL2 guarda archivos Linux en un disco virtual (VHDX) por defecto. Ese disco puede crecer rápido y no siempre se encoge a menos que lo compactes explícitamente.
  3. Acceder a archivos Linux desde Windows no es lo mismo que acceder a archivos Windows desde Linux. Las características de rendimiento difieren mucho, y la vía lenta dañará bases de datos y builds de imágenes.
  4. Kubernetes no fue diseñado para portátiles. Fue diseñado para clusters donde «un nodo es una VM de ganado» y puedes gastar CPU en bucles de reconciliación sin escuchar ventiladores.
  5. kind ejecuta Kubernetes en contenedores Docker. Eso es excelente para reproducibilidad, pero añade otra capa donde almacenamiento y red pueden volverse «creativos».
  6. k3s (y por extensión k3d) fue construido para ser ligero. Usa SQLite por defecto, lo cual está bien localmente, pero aún presiona el almacenamiento si lo combinas con muchos controladores.
  7. cgroups v2 cambió cómo se comporta el aislamiento de recursos. Muchas conversaciones de «por qué se ignora mi límite de memoria» son en realidad conversaciones de «¿en qué modo de cgroup estoy?».
  8. DNS es un modo de fallo de primer nivel en clusters locales. No porque DNS sea difícil, sino porque las VPN corporativas y el DNS de horizonte dividido pueden anular todo silenciosamente.
  9. Las construcciones de imágenes castigan los metadatos del sistema de archivos. La diferencia entre un sistema de archivos rápido y uno enlazado se hace obvia cuando ejecutas builds multi-stage con miles de archivos pequeños.

Elige tu Kubernetes local: kind vs k3d vs minikube (y qué recomiendo)

Mi recomendación por defecto: kind dentro de WSL2, con límites

Para la mayoría de desarrolladores y SREs que trabajan en plataforma, kind te da repetibilidad, velocidad y teardown limpio.
El clúster es «solo contenedores», y puedes fijar versiones de Kubernetes fácilmente.
La clave es restringir recursos de WSL2 y mantener tus cargas en el sistema de archivos Linux.

Cuándo preferir k3d (k3s en Docker)

Si tu objetivo es «ejecutar una pila de plataforma práctica con sobrecarga mínima», k3d es excelente.
k3s recorta lo innecesario: menos componentes, menos memoria. Es indulgente en portátiles más modestos.
Además está más cerca de lo que muchos entornos edge ejecutan, útil si despliegas a entornos con restricciones.

Cuándo usar minikube

minikube está bien cuando quieres «una herramienta que haga muchos drivers».
Pero en WSL2, minikube puede dejarte en una matriz de drivers confusa: driver Docker, KVM (generalmente no), Hyper-V (lado Windows),
y entonces empiezas a depurar el driver más que el clúster.
Si ya estás cómodo con Docker dentro de WSL2, el driver Docker de minikube funciona.

Qué evitar (a menos que tengas una razón)

  • Ejecutar cargas con estado pesadas en /mnt/c y luego culpar a Kubernetes por la lentitud. Eso no es Kubernetes; es la frontera del sistema de archivos pidiéndote que pares.
  • Sobreasignar CPUs y RAM «porque es local». WSL2 lo tomará, Windows contraatacará y tu navegador quedará sin recursos.
  • Hacer «como en prod» con todo activado. Malla de servicio + tracing distribuido + tres operadores + una base de datos + un sistema CI no es un clúster de dev; es un centro de datos hobby.

Un chiste corto, como prometí: Kubernetes es como una cocina con 30 chefs—nadie cocina más rápido, pero todos presentan un informe de estado.

Línea base de WSL2: límites, kernel y las cosas que Windows no te dirá

Establece límites de recursos de WSL2 o acepta el caos

Por defecto, WSL2 escalará el uso de memoria hasta una gran fracción de la RAM del sistema.
No es malicioso. Es Linux haciendo cosas de Linux—usando memoria para cache.
El problema es que Windows no siempre la recupera de una manera que parezca educada.

Pon un archivo .wslconfig en tu directorio de perfil de usuario de Windows (lado Windows).
Quieres un techo para memoria y CPU, y quieres swap pero no que se convierta en sustituto de la RAM.

cr0x@server:~$ cat /mnt/c/Users/$WINUSER/.wslconfig
[wsl2]
memory=8GB
processors=4
swap=4GB
localhostForwarding=true

Decisión: Si tienes 16GB de RAM total, 8GB para WSL2 suele ser sensato.
Si tienes 32GB, puedes usar 12–16GB. Más que eso tiende a ocultar fugas y límites malos de pods.

Comportamiento de reclamación de WSL2: usa las herramientas que tienes

La reclamación de memoria de WSL2 ha mejorado, pero aún puede sentirse pegajosa después de builds pesados o churn del clúster.
Si necesitas recuperar memoria rápido, apagar WSL es la herramienta contundente que funciona.

cr0x@server:~$ wsl.exe --shutdown
...output...

Significado de la salida: No mostrar nada es normal. Detiene todas las distros WSL y la VM WSL2.
Decisión: Úsalo cuando Windows esté sin memoria y necesites RAM ahora, no cuando estés depurando un problema dentro del clúster.

Usa systemd en WSL2 (si está disponible) y sé explícito

WSL moderno soporta systemd. Eso hace que ejecutar Docker/containerd y las herramientas de Kubernetes sea menos incómodo.
Comprueba si systemd está habilitado.

cr0x@server:~$ ps -p 1 -o comm=
systemd

Significado de la salida: Si PID 1 es systemd, puedes usar la gestión normal de servicios.
Si es otra cosa (como init), necesitarás gestionar los demonios de forma diferente.
Decisión: Prefiere WSL con systemd habilitado para más estabilidad y menos misterios de «¿por qué no arrancó esto?».

Almacenamiento en WSL2: la diferencia entre «funciona» y «no te odia»

Regla #1: mantiene los datos de Kubernetes en el sistema de archivos Linux

Coloca el estado del clúster, las imágenes de contenedores y los datos de volúmenes persistentes bajo el sistema de archivos de tu distro Linux (por ejemplo, /home, /var).
Evita almacenar cargas de escritura intensiva en /mnt/c.
La capa de interoperabilidad puede estar bien para editar código, pero es una trampa para bases de datos y todo lo que sea fsync-intenso.

Regla #2: entiende a dónde van tus bytes

Docker/containerd almacenan imágenes y capas copiables en algún lugar bajo /var/lib.
kind y k3d añaden sus propias capas.
Si construyes muchas imágenes o ejecutas cargas tipo CI, tu VHDX crece. Puede que no se reduzca.
Eso no es un fallo moral; así funcionan los discos virtuales.

Regla #3: mide I/O de la forma aburrida

No necesitas herramientas de almacenamiento sofisticadas para detectar los grandes problemas. Necesitas dos comparaciones:
rendimiento del FS Linux y rendimiento del FS montado desde Windows.
Si uno es 10x más lento, no «ajustes Kubernetes». Mueve la carga.

cr0x@server:~$ dd if=/dev/zero of=/home/cr0x/io-test.bin bs=1M count=512 conv=fdatasync
512+0 records in
512+0 records out
536870912 bytes (537 MB, 512 MiB) copied, 1.24 s, 433 MB/s

Significado de la salida: Esto es una escritura secuencial aproximada con flush. Cientos de MB/s es lo esperado en almacenamiento con SSD.
Decisión: Si esto es de un solo dígito MB/s, tu VM está bajo presión de I/O o tu disco está mal. Arregla eso antes de tocar Kubernetes.

cr0x@server:~$ dd if=/dev/zero of=/mnt/c/Users/$WINUSER/io-test.bin bs=1M count=256 conv=fdatasync
256+0 records in
256+0 records out
268435456 bytes (268 MB, 256 MiB) copied, 12.8 s, 21.0 MB/s

Significado de la salida: Si ves esta caída, esa es la vía de interoperabilidad.
Decisión: No pongas /var/lib/docker, /var/lib/containerd o directorios de PV en /mnt/c.
Mantén el código allí si es necesario, pero conserva caches de build y bases de datos en espacio Linux.

Crecimiento del VHDX: compactar es una tarea de mantenimiento, no un arreglo único

Si tu disco virtual WSL2 crece por builds de imágenes y churn, puedes compactarlo—pero compactar no es automático.
Primero, limpia dentro de Linux: elimina imágenes no usadas, prune de volúmenes, borra caches.
Luego compacta desde Windows. Los pasos exactos difieren según la build de Windows y las herramientas, pero el principio es consistente:
elimina bloques no usados en el invitado y luego di al host que compacte.

La verdad aburrida: los clústeres locales crean datos. Rara vez eliminan datos tan agresivamente como crees.
Si tratas tu entorno de dev como producción, programarás mantenimiento como en producción. Ese es el trato.

Red: NodePorts, ingress, DNS y la trampa de localhost

Localhost no es una filosofía; es una decisión de enrutamiento

En WSL2, «localhost» puede significar localhost de Windows o localhost de WSL dependiendo de cómo iniciaste el proceso.
Con localhostForwarding=true, WSL puede reenviar puertos a Windows, pero no es magia para todos los patrones de tráfico.

Si ejecutas kind dentro de WSL2 y expones un servicio con NodePort, a menudo lo accederás desde Windows vía puertos reenviados
o golpeando la IP de la VM WSL. Ambos pueden funcionar. Lo importante es ser consistente y documentar cuál usa tu equipo.

DNS: el asesino silencioso de kubectl

Muchas quejas de «Kubernetes está lento» son en realidad timeouts de DNS.
kubectl parece colgado, pero espera llamadas al servidor API que nunca se resuelven con limpieza.
Los clientes VPN empeoran esto inyectando resolvers o forzando DNS de horizonte dividido.

Tu trabajo es separar «API server lento» de «resolución de nombres lenta» en menos de cinco minutos.
Después puedes discutir con la herramienta VPN corporativa.

Tareas prácticas (comandos, salidas y decisiones)

Estas son tareas reales que harás al configurar y al depurar.
Cada una incluye qué significa la salida y la decisión que tomas a partir de ella.
Hazlas en orden cuando construyas un nuevo entorno; hazlas selectivamente cuando estés en llamas.

Task 1: Confirma que estás en WSL2 (no WSL1)

cr0x@server:~$ wsl.exe -l -v
  NAME            STATE           VERSION
* Ubuntu-22.04    Running         2

Significado de la salida: VERSION 2 significa una VM con kernel Linux real. VERSION 1 significa traducción de syscalls.
Decisión: Si estás en VERSION 1, detente y convierte. Kubernetes necesita el comportamiento del kernel real.

Task 2: Comprueba el kernel y el modo de cgroup

cr0x@server:~$ uname -r
5.15.146.1-microsoft-standard-WSL2
cr0x@server:~$ mount | grep cgroup2
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)

Significado de la salida: cgroup2 montado significa jerarquía unificada.
Decisión: Si tu tooling asume cgroup v1 (configuraciones Docker antiguas, algunos agentes de monitorización), espera sorpresas.

Task 3: Verifica systemd y control de servicios

cr0x@server:~$ systemctl is-system-running
running

Significado de la salida: systemd está activo y estable.
Decisión: Si ves degraded, revisa las unidades fallidas antes de culpar a Kubernetes por «problemas aleatorios».

Task 4: Confirma la salud del runtime de contenedores (ejemplo Docker)

cr0x@server:~$ docker info --format '{{.ServerVersion}} {{.CgroupVersion}}'
24.0.7 2

Significado de la salida: Docker es accesible e informa la versión de cgroup.
Decisión: Si este comando es lento o se cuelga, arregla Docker antes de tocar kind/k3d.

Task 5: Mide la presión de memoria rápidamente

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:           7.7Gi       5.9Gi       310Mi       160Mi       1.5Gi       1.3Gi
Swap:          4.0Gi       1.2Gi       2.8Gi

Significado de la salida: available importa más que free. El uso de swap indica presión.
Decisión: Si available está por debajo de ~1Gi y el swap sube, reduce el tamaño del clúster, baja límites o sube la memoria asignada a WSL.

Task 6: Comprueba el uso de disco donde importa

cr0x@server:~$ df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/sdb       250G  180G   71G  72% /

Significado de la salida: Este es el sistema de archivos de tu distro respaldado por VHDX.
Decisión: Por encima de ~85% de uso, el rendimiento y el comportamiento de compactación empeoran. Haz prune de imágenes y PVs antiguos.

Task 7: Instala y crea un clúster kind con valores sensatos

cr0x@server:~$ kind create cluster --name dev --image kindest/node:v1.29.2
Creating cluster "dev" ...
 ✓ Ensuring node image (kindest/node:v1.29.2) 🖼
 ✓ Preparing nodes 📦
 ✓ Writing configuration 📜
 ✓ Starting control-plane 🕹️
 ✓ Installing CNI 🔌
 ✓ Installing StorageClass 💾
Set kubectl context to "kind-dev"
You can now use your cluster with:

kubectl cluster-info --context kind-dev

Significado de la salida: Clúster creado, contexto establecido. StorageClass instalado (normalmente standard).
Decisión: Si la instalación del CNI se cuelga, sospecha de DNS/proxy/VPN o problemas de pull de imágenes—no sigas reintentando a ciegas.

Task 8: Verifica la vivacidad del clúster y la salud de componentes

cr0x@server:~$ kubectl cluster-info
Kubernetes control plane is running at https://127.0.0.1:40685
CoreDNS is running at https://127.0.0.1:40685/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
cr0x@server:~$ kubectl get nodes -o wide
NAME                STATUS   ROLES           AGE   VERSION   INTERNAL-IP   OS-IMAGE
dev-control-plane   Ready    control-plane   2m    v1.29.2   172.18.0.2    Debian GNU/Linux 12 (bookworm)

Significado de la salida: El API server es accesible, el nodo está Ready, se le asignó IP interna.
Decisión: Si el nodo está NotReady, ve directamente a kubectl describe node y logs del CNI—no reinstales todo todavía.

Task 9: Detecta los verdaderos consumidores de recursos (nodos + pods)

cr0x@server:~$ kubectl top nodes
NAME                CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
dev-control-plane   620m         15%    2240Mi          56%

Significado de la salida: Metrics-server funciona (kind a menudo lo incluye vía addons o lo instalaste).
Decisión: Si la memoria está alta en reposo, revisa controladores chatos, builds que filtran memoria o un shipper de logs atascado.

Task 10: Valida DNS dentro del clúster (prueba barata)

cr0x@server:~$ kubectl run -it --rm dns-test --image=busybox:1.36 --restart=Never -- nslookup kubernetes.default.svc.cluster.local
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes.default.svc.cluster.local
Address 1: 10.96.0.1 kubernetes.default.svc.cluster.local
pod "dns-test" deleted

Significado de la salida: CoreDNS responde, el descubrimiento de servicios funciona.
Decisión: Si esto hace timeout, arregla DNS antes de depurar tu app. Tu app es inocente hasta que se demuestre lo contrario.

Task 11: Confirma si kubectl lento es red o latencia del API

cr0x@server:~$ time kubectl get pods -A
NAMESPACE     NAME                                        READY   STATUS    RESTARTS   AGE
kube-system   coredns-76f75df574-2lq9r                    1/1     Running   0          4m
kube-system   coredns-76f75df574-9x2ns                    1/1     Running   0          4m
kube-system   etcd-dev-control-plane                      1/1     Running   0          4m
kube-system   kindnet-4cdbf                               1/1     Running   0          4m
kube-system   kube-apiserver-dev-control-plane            1/1     Running   0          4m
kube-system   kube-controller-manager-dev-control-plane   1/1     Running   0          4m
kube-system   kube-proxy-4xw26                            1/1     Running   0          4m
kube-system   kube-scheduler-dev-control-plane            1/1     Running   0          4m

real    0m0.312s
user    0m0.127s
sys     0m0.046s

Significado de la salida: 300ms está bien para local. Varios segundos sugieren DNS, confusión de contexto kubeconfig o un API server con problemas.
Decisión: Si está lento, ejecuta kubectl get --raw /readyz?verbose a continuación.

Task 12: Comprueba endpoints de readiness del API server para el bloqueo real

cr0x@server:~$ kubectl get --raw='/readyz?verbose'
[+]ping ok
[+]log ok
[+]etcd ok
[+]informer-sync ok
[+]poststarthook/start-apiserver-admission-initializer ok
[+]poststarthook/start-apiextensions-informers ok
[+]poststarthook/start-apiextensions-controllers ok
readyz check passed

Significado de la salida: Si etcd o informer sync están lentos/fallando, el plano de control es el cuello de botella.
Decisión: etcd lento suele significar latencia de almacenamiento. Esa es tu señal para revisar disco y evitar I/O cruzada entre sistemas de archivos.

Task 13: Encuentra el pod que está quemando tu portátil (CPU/memoria)

cr0x@server:~$ kubectl top pods -A --sort-by=memory | tail -n 10
default       api-7b7c7d8f6c-9bq5m             1/1     980Mi
observability prometheus-0                      2/2     1210Mi
observability loki-0                            1/1     1530Mi

Significado de la salida: Puedes ver los mayores consumidores.
Decisión: Si tu clúster de dev incluye Prometheus/Loki por defecto, acabas de encontrar el porqué de «¿por qué hace calor?».
Reduce escala, reduce retención o usa herramientas más ligeras localmente.

Task 14: Comprueba uso de disco del runtime de contenedores (Docker)

cr0x@server:~$ docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          42        12        18.4GB    12.1GB (65%)
Containers      19        6         1.2GB     720MB (60%)
Local Volumes   27        9         9.8GB     6.3GB (64%)
Build Cache     124       0         22.6GB    22.6GB

Significado de la salida: El build cache suele ser el comecolillas silencioso del disco.
Decisión: Si lo reclaimable es grande, haz prune deliberadamente. No esperes a que tu VHDX llegue al 95% y todo se vuelva lento.

Task 15: Prune con seguridad (y acepta el trade-off)

cr0x@server:~$ docker builder prune --all --force
Deleted build cache objects:
v8m3q8qgk7yq4o0l5u3f7s1m2
...
Total reclaimed space: 22.6GB

Significado de la salida: Recuperaste espacio, a costa de reconstruir capas después.
Decisión: En dev, el espacio en disco y la estabilidad valen más que ahorrar 90 segundos en el siguiente build.

Task 16: Confirma que no ejecutas cargas accidentalmente en /mnt/c

cr0x@server:~$ kubectl get pv -A -o wide
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                      STORAGECLASS   REASON   AGE   VOLUMEMODE
pvc-1d2c7f7e-2f5d-4c0d-9a3a-2c6f9d8b1a7c   10Gi       RWO            Delete           Bound    default/db-data           standard                8m    Filesystem

Significado de la salida: Esto no muestra la ruta host. En kind, local-path y el provisioning por defecto difieren.
Decisión: Inspecciona la storage class y el comportamiento del provisionador; si enlaza a hostPath en una ruta montada desde Windows, arréglalo inmediatamente.

Task 17: Diagnostica problemas de kubelet/contenedor vía logs del nodo (contenedor de nodo de kind)

cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Status}}' | grep dev-control-plane
dev-control-plane   Up 6 minutes
cr0x@server:~$ docker logs dev-control-plane | tail -n 20
I0205 09:10:12.123456       1 server.go:472] "Kubelet version" kubeletVersion="v1.29.2"
I0205 09:10:15.234567       1 kubelet.go:2050] "Skipping pod synchronization" error="PLEG is not healthy"
...

Significado de la salida: Si PLEG está poco saludable, el kubelet está teniendo problemas—a menudo por I/O de disco o lentitud del runtime de contenedores.
Decisión: Revisa latencia de disco, salud del runtime de contenedores y la cantidad de churn en logs.

Task 18: Revisión rápida de tormentas de logs (pueden ser tu carga más caliente)

cr0x@server:~$ kubectl logs -n kube-system deploy/coredns --tail=20
.:53
[INFO] plugin/reload: Running configuration SHA512 = 7a9b...
[INFO] 10.244.0.1:52044 - 46483 "A IN kubernetes.default.svc.cluster.local. udp 62 false 512" NOERROR qr,aa,rd 114 0.000131268s

Significado de la salida: Algunos logs de DNS son normales. Miles por segundo no lo son.
Decisión: Si los logs están calientes, reduce la verbosidad, arregla el loop de reintentos del cliente o pagarás en CPU y escrituras de disco.

Tres mini-historias corporativas desde las trincheras

Mini-historia 1: El incidente causado por una suposición errónea

Un equipo con el que trabajé estandarizó en WSL2 para clústeres de desarrollo porque era «prácticamente Linux».
Dejaron su repo en el sistema de archivos de Windows para integración fácil con IDE, y luego lo montaron en contenedores para builds y pruebas.
Parecía bien para servicios pequeños. Luego el grupo de plataforma añadió un Postgres local, un operador y una suite de pruebas que ejecutaba migraciones en cada ejecución.

El síntoma fue extraño: las migraciones algunas veces tardaban 30 segundos, otras veces 10 minutos.
La gente culpó «la sobrecarga de Kubernetes» y «ese operador que añadimos».
Un ingeniero intentó arreglarlo aumentando límites de CPU. Empeoró—más CPU solo hizo que el sistema alcanzara antes el cuello de botella de almacenamiento.

La suposición errónea fue simple: asumieron que /mnt/c era «solo otro sistema de archivos».
No lo es. Es una frontera con distinto comportamiento de caché, distinto rendimiento de metadatos y distintas semánticas de flush.
Su volumen de base de datos y caches de build estaban en la vía lenta.

La solución fue poco glamorosa: mover la base de datos y el cache de build al sistema de archivos Linux, y solo mantener el checkout en Windows si hacía falta.
También añadieron un script de preflight que rechazaba iniciar el stack si detectaba PVs apuntando a /mnt/c.
El rendimiento se estabilizó al instante. Nadie celebró, que es como sabes que fue la solución correcta.

Mini-historia 2: La optimización que salió mal

Otra compañía quería «clústeres locales más rápidos», así que preinstalaron todo: stack de observabilidad, ingress, cert-manager y un par de operadores.
La idea era noble: reducir tiempo de onboarding, asegurar que todos tuvieran la misma base, evitar «funciona en mi portátil».
Incluso crearon un script interno que creaba el clúster e instalaba todos los charts de una sola vez.

Luego llegaron los tickets. Los portátiles se calentaban en reuniones. La batería se desplomó.
Los comandos kubectl a veces laggeaban varios segundos. Los desarrolladores empezaron a desactivar componentes «temporalmente», lo que se volvió deriva permanente.
El equipo de plataforma respondió empujando más límites por defecto y más réplicas, por la «paridad con prod».

El contragolpe vino de la actividad de controladores y trabajo en segundo plano.
Scrapes de Prometheus, ingestión de Loki, reconciliación de cert-manager y bucles de operadores no son gratis.
En un clúster real, ese coste se amortiza entre servidores. En un portátil, lo sientes cada vez que el ventilador sube de vueltas.

La solución fue definir dos perfiles: core (ingress + DNS + storage + metrics-server) y full (la pila pesada).
Core fue el defecto; full opt-in para depuración.
También recortaron intervalos de scrape y retención en modo local. La ironía: el onboarding fue más rápido porque la gente dejó de pelear con su entorno.

Mini-historia 3: La práctica aburrida pero correcta que salvó el día

Una empresa regulada (de las que aman las hojas de cálculo) tuvo una experiencia sorprendentemente fluida con Kubernetes en WSL2.
Su secreto no fue una cadena de herramientas elegante. Fue disciplina: fijado de versiones, creación repetible de clústeres y limpieza agresiva.
Cada desarrollador tenía la misma imagen de nodo kind, las mismas versiones de charts y los mismos límites por defecto.

También tenían una rutina semanal de mantenimiento: prune de imágenes no usadas, borrar namespaces y PVs sin uso, y compactar el entorno dev cuando era necesario.
Fue programado, documentado y aburrido.
Al principio los desarrolladores se quejaban—nadie quiere «día de mantenimiento» en un portátil.

Luego vino el día en que una actualización de Windows cambió algo sutil en la red.
La mitad de los clústeres de la organización empezaron a tener fallos intermitentes de DNS.
Los equipos con entornos con deriva tuvieron un desmadre: versiones diferentes de CNI, kubeconfigs aleatorios, overrides locales de DNS inconsistentes.
Los equipos disciplinados pudieron reproducir rápido y comparar manzanas con manzanas.

Aislaron el problema al comportamiento del resolver bajo VPN, desplegaron una solución estándar y volvieron al trabajo.
La práctica aburrida salvó el día: versiones consistentes y bases iguales hacen que la depuración sea finita.

Guía rápida de diagnóstico: qué comprobar primero, segundo, tercero

Cuando tu portátil se está derritiendo o tu clúster está «lento», no tienes tiempo para admirar la arquitectura.
Necesitas un camino determinista hacia el cuello de botella.

Primero: ¿Está el host (Windows + WSL2) bajo presión de recursos?

  1. Comprueba uso de memoria y swap de WSL2: free -h. Si available es bajo y swap sube, estás limitado por memoria.
  2. Comprueba llenado de disco: df -h /. Si está casi lleno, todo se vuelve más lento y frágil.
  3. Comprueba sanity I/O rápido: dd ... conv=fdatasync en FS Linux vs /mnt/c. Si el FS Linux también es lento, tienes presión de I/O a nivel de sistema.

Segundo: ¿El plano de control de Kubernetes está sano o atascado?

  1. API readyz: kubectl get --raw='/readyz?verbose'. Si las comprobaciones etcd son lentas, sospecha latencia de almacenamiento.
  2. Estado de nodos: kubectl get nodes y kubectl describe node. Si NotReady, mira síntomas del CNI y kubelet.
  3. Smoke test de CoreDNS: ejecuta un nslookup en busybox. Si DNS está roto, deja de fingir que la app es el problema.

Tercero: ¿Qué carga está realmente quemando CPU/memoria/I/O?

  1. Mayores consumidores: kubectl top pods -A --sort-by=memory y --sort-by=cpu.
  2. Tormentas de logs: revisa logs de los pods sospechosos. Alta tasa de escritura equivale a presión I/O y ralentización global.
  3. Churn de imágenes/build: docker system df y prune del build cache si ha crecido desmesuradamente.

Idea parafraseada de Werner Vogels: «Todo falla, todo el tiempo.» Construye tu entorno local para que el fallo sea rápido de identificar, no misterioso.

Errores comunes: síntoma → causa raíz → solución

1) Síntoma: Los ventiladores del portátil suben cuando el clúster está «idle»

Causa raíz: controladores en segundo plano (stack de observabilidad, operadores) haciendo reconciliación constante; o un pod en crash loop escribiendo logs.

Solución: kubectl top pods -A, encuentra el comecolillas; escálalo hacia abajo; reduce retención/intervalos de scrape; arregla crash loops; establece requests/limits sensatos.

2) Síntoma: comandos kubectl tardan 5–30 segundos aleatoriamente

Causa raíz: timeouts de DNS o interferencia de resolvers de VPN; a veces kubeconfig apunta a un contexto muerto.

Solución: ejecuta time kubectl get pods -A, luego kubectl get --raw='/readyz?verbose'. Si readyz está bien, prueba DNS dentro del clúster. Si DNS falla, arregla resolv.conf/políticas de DNS de la VPN o trabaja fuera de la VPN para tareas locales.

3) Síntoma: Postgres/MySQL en el clúster es dolorosamente lento

Causa raíz: PV o bind mounts están en /mnt/c u otra frontera lenta; cargas fsync-intensas lo amplifican.

Solución: mantén datos de PV en el sistema de archivos Linux; usa un provisioner local-path que escriba en /var dentro de WSL, no en montajes de Windows.

4) Síntoma: WSL2 se come RAM y nunca la devuelve

Causa raíz: page cache de Linux + comportamiento de reclamación de WSL2; builds grandes y pulls de imágenes llenan la cache; no se configuró límite de memoria.

Solución: establece tope de memoria en .wslconfig; reinicia WSL con wsl.exe --shutdown cuando haga falta; reduce huella del clúster y evita ejecutar todo a la vez.

5) Síntoma: Disco se llena «misteriosamente»

Causa raíz: capas de imagen de contenedores, build cache, datos PV sobrantes y logs; VHDX crece; no se hace pruning.

Solución: docker system df y docker builder prune --all; borra namespaces/PVs sin uso; vigila df -h.

6) Síntoma: Nodo pasa a NotReady; pods quedan en ContainerCreating

Causa raíz: CNI roto o kubelet/runtime de contenedores con problemas por presión de I/O; contenedor de nodo kind poco saludable.

Solución: revisa logs del contenedor del nodo kind; comprueba pods del CNI en kube-system; arregla presión de disco; recrea el clúster si la imagen del nodo está corrupta.

7) Síntoma: Ingress funciona desde WSL pero no desde Windows

Causa raíz: expectativas de reenvío de puertos erróneas; firewall/VPN de Windows; confusión entre IP de WSL y localhost de Windows.

Solución: decide un método de acceso (localhost reenviado vs IP de la VM WSL); documéntalo; expón ingress con mapeo predecible; verifica con curl desde ambos lados.

8) Síntoma: Builds dentro de contenedores son más lentos que builds en Windows

Causa raíz: árbol de código en montaje de Windows; operaciones de metadatos intensas a través de la frontera; antivirus escaneando rutas de Windows.

Solución: mantén el código fuente dentro del sistema de archivos Linux para builds; usa el IDE de Windows vía integración WSL; excluye directorios de build del AV de Windows si la política lo permite.

Segundo y último chiste corto: Si tu clúster de dev necesita un runbook, felicitaciones—has construido un pequeño entorno de producción con peor financiación.

Listas de verificación / plan paso a paso

Plan de configuración (haz esto una vez por portátil)

  1. Configura límites de WSL2 en .wslconfig: limita memoria y CPU; habilita swap razonable.
  2. Habilita systemd en WSL (si es compatible) y estandariza su uso en el equipo.
  3. Elige un runtime: Docker Engine dentro de WSL2 está bien; evita mezclar Docker Desktop + Docker en WSL salvo que disfrutes la ambigüedad.
  4. Elige una herramienta de Kubernetes: kind (recomendado) o k3d. Escoge una y estandariza scripts de creación de clúster.
  5. Mantén datos del clúster en FS Linux: asegúrate de que el almacenamiento del runtime y las rutas de PV no estén en /mnt/c.
  6. Define perfiles: «core» por defecto; «full» opt-in. Tu portátil no es un clúster de staging.
  7. Fija versiones: versión de la imagen del nodo kind, versiones de charts y addons críticos.

Checklist de flujo diario (mantente rápido, mantén cordura)

  1. Antes de trabajo pesado: free -h y df -h /. Si estás bajo, prune primero.
  2. Después de un día de builds intensos: docker system df. Si el build cache es enorme, prunealo.
  3. Cuando algo se siente lento: ejecuta la prueba de DNS y las comprobaciones /readyz antes de cambiar nada.
  4. Mantén tu repo donde tus herramientas sean más rápidas: si builds están en Linux, mantén el working copy en Linux.

Checklist semanal de mantenimiento (aburrido, efectivo)

  1. Prune del build cache: docker builder prune --all.
  2. Prune imágenes y contenedores no usados: docker system prune (con cuidado: entiende lo que borra).
  3. Borra namespaces y PVs no usados en el clúster de dev.
  4. Recrea el clúster si ha acumulado demasiada deriva. El teardown es una característica.
  5. Recupera memoria si Windows está justo: wsl.exe --shutdown.

Preguntas frecuentes

1) ¿Debería usar Docker Desktop o Docker Engine dentro de WSL2?

Si quieres la integración más simple con Windows y tu empresa lo estandariza, Docker Desktop está bien.
Si quieres menos piezas móviles y comportamiento Linux más claro, usa Docker Engine dentro de WSL2.
Elige uno y cúmplelo; configuraciones mixtas crean folklore de depuración.

2) ¿Por qué es un problema guardar datos en /mnt/c?

Porque cruzas una frontera de virtualización/interop con distintas semánticas de caché y metadatos.
Las bases de datos hacen muchas escrituras pequeñas y llamadas fsync. Esa vía las castiga.
Mantén I/O intenso dentro del sistema de archivos Linux y trata /mnt/c como «bueno para documentos, no para datos calientes».

3) kind o k3d: ¿cuál es menos propenso a derretir mi portátil?

k3d suele usar menos memoria en base porque k3s es más pequeño.
kind es increíblemente predecible y fácil de fijar en versiones. Ambos pueden ser seguros en portátil si mantienes la carga ligera y pones límites a WSL2.
Mi sesgo: kind para trabajo de plataforma y simulación multinodo; k3d para «simplemente ejecutar la pila».

4) ¿Cuánta RAM debería asignar a WSL2?

En 16GB totales: 6–8GB es un buen techo.
En 32GB: 12–16GB está bien.
Si asignas demasiado, ocultarás límites malos de pods y dejarás a las apps de Windows con recursos insuficientes de forma sutil.

5) ¿Por qué WSL2 mantiene la memoria después de parar cargas?

Linux usa agresivamente memoria para filesystem cache. Eso normalmente es bueno.
La reclamación de WSL2 de vuelta a Windows puede ser más lenta de lo deseado.
Si necesitas RAM inmediatamente, apaga WSL; si quieres cordura a largo plazo, limita memoria y reduce churn en segundo plano.

6) ¿Por qué mis comandos kubectl son lentos solo cuando la VPN está activada?

Los clientes VPN suelen inyectar resolvers DNS y reglas de enrutamiento.
Kubernetes depende del DNS internamente, y kubectl depende de conectividad fiable al endpoint del API.
Diagnostica con la prueba de DNS y las comprobaciones readyz; luego decide si usar split-tunnel, ajustar resolvers o hacer tareas locales fuera de la VPN.

7) ¿Cómo expongo servicios a Windows desde un clúster que corre en WSL2?

Decide si usas puertos locales reenviados o la IP de la VM WSL.
Para una UX de dev predecible, muchos equipos hacen port-forward (kubectl port-forward) o ejecutan un ingress que mapea a puertos conocidos.
Documenta el método para que tu equipo no depure «localhost» por diversión.

8) ¿Puedo ejecutar cargas con estado (Postgres, Kafka) localmente en Kubernetes en WSL2?

Sí, pero sé realista. Postgres está bien para dev si sus datos viven en el sistema de archivos Linux y no ejecutas otras cinco pilas pesadas.
Kafka es posible, pero suele ser donde la «paridad local» se vuelve «castigo local». Considera sustitutos más ligeros salvo que depures comportamiento específico de Kafka.

9) ¿Con qué frecuencia debería recrear mi clúster local?

Si trabajas en plataforma con muchos CRDs e instalaciones de controladores, recrearlo semanal o quincenalmente es normal.
Si tu clúster es estable y lo mantienes ligero, puede durar más.
Recrea inmediatamente cuando sospeches deriva: DNS raro, webhooks atascados o fallos de admission misteriosos.

10) ¿Cuál es la causa raíz más común de «todo está lento»?

Almacenamiento. O la carga está en la ruta de sistema de archivos equivocada, o el disco está casi lleno, o el sistema escribe logs como si le pagaran por línea.
La memoria es la segunda. DNS la tercera. Kubernetes en sí rara vez es la primera causa; solo es el escenario donde el problema actúa.

Siguientes pasos que puedes hacer hoy

  1. Configura tus límites de WSL2 (memoria, CPU, swap). Si haces solo una cosa, haz esto.
  2. Mueve datos calientes fuera de /mnt/c: almacenamiento del runtime de contenedores, PVs, bases de datos, caches de build—mantenlos en el filesystem Linux.
  3. Elige un perfil de clúster ligero por defecto: kind o k3d solo con addons core. Haz que «full stack» sea un perfil opt-in.
  4. Adopta la guía rápida de diagnóstico: comprueba presión del host, luego salud del plano de control, luego los mayores consumidores. Deja de adivinar.
  5. Programa mantenimiento aburrido: prune de caches, borra namespaces sin uso y recrea el clúster cuando aparezca deriva.

El objetivo final no es construir el clúster local más impresionante. Es construir uno que se comporte de forma consistente bajo estrés.
La predictibilidad es lo que mantiene tu portátil frío—y tu cerebro más calmado.

← Anterior
Industria TI: la mentira de «reescribir desde cero» — por qué falla y qué funciona
Siguiente →
Audio con chasquidos en Windows 11: solucionar latencia sin comprar hardware nuevo

Deja un comentario