Tras el desalojo del 2026-08-12 no quedaba nada que mirar: el emptyDir
muere con el pod, así que la causa hubo que deducirla por descarte.
Ahora el janitor mide /dind (montado en solo lectura) antes y después de
cada barrido y lo escribe en su log, cada 6h. La próxima vez habrá curva
de crecimiento medida en vez de conjeturas.
No se usa Prometheus porque no puede verlo: kubelet_volume_stats_* solo
emite series para PVCs y container_fs_usage_bytes viene vacío en k3s.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El pod fue desalojado el 2026-08-12 tras 21 días: el emptyDir de dind
superó su límite de 10Gi.
La caché de buildx no era la causa (los workflows hacen `buildx rm` al
inicio de cada run y se recicla sola, ~330M). Lo que se acumula son
imágenes sin tag: al republicar upstream catthehacker/ubuntu:act-22.04,
la anterior (1,5G) queda colgada para siempre.
Ni el GC de dockerd ni el de buildkit lo recogen porque ambos calculan
su umbral contra el disco del nodo (913G), no contra el cap del pod.
Nunca saltan antes del desalojo.
El sidecar hace `docker image prune -f` cada 6h por localhost:2375.
Sin -a: solo dangling, nunca una imagen con tag, así que la caché de
act y la de buildkit siguen intactas y los builds no se frenan.
Se descartó un CronJob externo: exigiría pods/exec o publicar el 2375
como Service, y ese puerto va sin TLS ni auth sobre un docker
privilegiado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El worker llegó al 80,9% de disco: 223 imágenes para ~20 pods, 62 GB en
containerd. Cada build de CI publica un tag y nadie recoge los viejos —
97 tags de researchowl y 105 de polymarket-bot, decomisada en julio.
El GC de kubelet no basta: solo entra al 85% de imagefs, o sea cuando ya
no queda sitio. Un prune manual dejó el worker en 23% (54 GB) y el master
en 15% (58 GB).
Un CronJob por nodo porque un CronJob programa un solo pod; el nodo se
fija con nodeSelector. Domingos 06:30 y 07:15, el hueco entre backups.
El script hace varias pasadas a propósito: borrar snapshots satura a
containerd y las llamadas siguientes dan DeadlineExceeded. No es fallo,
es cola. Se reintenta mirando la salida, no el código de retorno, porque
crictl devuelve 0 aunque haya borrados fallidos.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Las tres secretKeyRef van optional: true: mientras no existan en Infisical
el pod arranca igual y el comando contesta 'no configurado'. Sin optional,
una clave que aún no está deja el Deployment en CreateContainerConfigError
y tira el bot entero por una función sin estrenar.
YOUTUBE_PRIVACY=private documenta el candado: los vídeos por API de un
proyecto sin auditar quedan privados los pidas como los pidas.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Renovate seguia escaneandolo 6 veces al dia y empujando ramas a un repo
muerto: renovate/uvicorn-0.x se actualizo el 2-ago, 16 dias despues del
decomiso. Misma familia que el bloque de Polymarket del Resumen Diario:
decomisar un servicio no lo saca de las cosas que lo miran.
Quedan 11 ramas renovate/* en el repo; se limpian aparte.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El primer render real murio OOMKilled con el limite de 1Gi que salia de medir en
la maquina de desarrollo. El host lleva ffmpeg 4.4.2 y la imagen 7.1.5, y 7.0
reescribio la transcodificacion sobre un planificador con colas de fotogramas
decodificados entre componentes: pasarle el WAV como segunda entrada dejaba al
decodificador de video correr por delante y llenar una cola con fotogramas de
6,2 MB. Arreglado partiendo el encode en dos pasos (repo shortsmith), y aqui van
las cifras nuevas, medidas dentro del pod.
requests 768Mi (pico anon 640 MB), limits 1536Mi. El limite no es 1Gi por el
caso techo de 180 s: ahi el pico era 940 MB de 1024 y sobrevivia solo porque
quedaba cache reclamable que soltar. A 1.5Gi memory.events da max 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Renderizador determinista JSON->MP4 para Shorts. API interna sin Ingress, en
shortsmith-svc:8080; la cola es por proceso y la SQLite va en RWO, de ahí la
replica unica con estrategia Recreate.
Los recursos van medidos, no estimados, y la tabla con las cifras queda en el
propio manifiesto: el pico son 522 MB de anon y lo pone entero el encode, no el
render. Ojo al comentario sobre por que los workers salen del limite de CPU y no
del request.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Un barrido hecho con cp dijo que un cambio recien escrito no estaba, media
hora despues de escribirlo. La copia no tiene los ultimos commits y no da
ningun error.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El GET de comprobacion no miraba su propio codigo HTTP. Un 403 devuelve un
objeto Status, que parsea como JSON perfectamente y luego revienta en
dep.spec.replicas ("Cannot read properties of undefined (reading 'replicas')").
El aviso resultante decia "reinicio fallido, intervencion manual necesaria"
cuando el reinicio habia ido BIEN y lo unico roto era la verificacion.
Falso negativo, mucho menos grave que el falso positivo de ayer -- pero te
levanta de la cama para nada, y estando Jose fuera eso importa.
Ahora el nodo marca verificable:false cuando no ha podido comprobar (HTTP no
2xx, cuerpo ilegible o error de red) y el aviso lo dice con esas palabras,
con el codigo de la comprobacion aparte del codigo del PATCH.
Reproducido en produccion: POST legitimo a las 08:43:22 (PATCH ok, restartedAt
cambia), permiso retirado a los 15 s, GET a los 60 s con 403. Ejecucion de
60,394 s, o sea el camino completo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Anade tambien como leer por que rama fue una ejecucion cuando
EXECUTIONS_DATA_SAVE_ON_SUCCESS=none ya la ha vaciado: por la duracion.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tres agujeros que encontro una review adversarial de Codex sobre el trabajo
de ayer. Los tres verificados contra el fichero antes de tocar nada.
1. El webhook publico fallaba en ABIERTO. Con `optional: true` y un
`if (secreto && ...)`, la ausencia del secreto equivalia a autorizacion:
cualquier POST con heartbeat.status=0 y un monitor.name del mapa reiniciaba
n8n, Gitea, ArgoCD, Vaultwarden o los blogs. El argumento para dejarlo asi
era "que un despiste no te deje sin avisos", y era falso: el aviso de caida
lo manda Kuma por su notificacion 1, directa, no por este webhook.
Ahora el secreto es obligatorio (sin `optional`) y la puerta rechaza si
falta. Secreto ausente da error ruidoso; cabecera que no casa, vacio.
2. El ClusterRole permitia patch sobre CUALQUIER deployment de 10 namespaces,
y la SA va montada en el pod entero de n8n: cualquier workflow con un nodo
Code heredaba eso. Ahora son Roles por namespace con resourceNames sobre
los 12 workloads exactos del SERVICE_MAP.
3. El flujo daba exito aunque el PATCH fallara. `restartOk` se calculaba y se
tiraba: `K8s API Check Status` construia un objeto nuevo sin el, y el IF
final solo miraba `statusOk`. Un 403 sobre un servicio ya sano mandaba
"reiniciado correctamente". Ahora hay rama de fallo tras el PATCH y el
exito exige observedGeneration >= la generation que devolvio el PATCH.
Se compara con >= porque selfHeal de ArgoCD revierte la anotacion y vuelve
a subir la generation; con == daria un fallo falso.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Este directorio es el WorkingDirectory de hermes-bot, o sea el sitio donde un
agente lee el contexto del cluster, y no tenia ninguno.
Se ha notado hoy: tras poner EXECUTIONS_DATA_SAVE_ON_SUCCESS=none en n8n, las
ejecuciones exitosas quedan como status='running' con deletedAt puesta hasta que
pasa el barrido. Hermes las vio, las tomo por ejecuciones colgadas y se gasto un
turno entero de quince minutos diagnosticando una averia inexistente en un
workflow que corria clavado cada cinco minutos.
Solo van aqui las cosas que enganan: lo que parece roto y no lo esta, y lo que
parece inofensivo y no lo es. El resto sigue en el skill k8s-infra.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
/webhook/uptime-kuma-restart es publico y sin autenticar. Mientras no reiniciaba
nada daba igual; desde hoy reinicia doce servicios, los dos blogs incluidos, asi
que cualquiera que diera con la ruta podia zarandear el cluster.
Uptime Kuma manda ahora la cabecera X-Kuma-Token (webhookAdditionalHeaders de la
notificacion "n8n Auto-Restart") y el primer nodo Code la compara con
KUMA_WEBHOOK_TOKEN antes de hacer nada.
La comprobacion falla en ABIERTO si el secreto no esta configurado en n8n: un
despiste de configuracion degrada al comportamiento de antes en vez de dejar de
avisar en silencio, que es el fallo que no se ve venir. Si esta configurado y no
coincide, se devuelve vacio: ni reinicio, ni Telegram, ni fila de error que se
pueda llenar a base de peticiones.
El secret n8n-kuma-webhook se crea con kubectl y NO esta en git; el env va con
optional: true para que la ausencia no impida arrancar el pod.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Una Application de tipo directorio lee *.yaml, *.yml y *.json indistintamente,
asi que la copia del workflow que acabo de meter en n8n/ entraba como si fuera
un manifiesto y dejaba la app en ComparisonError: "Object 'Kind' is missing".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Los workflows viven solo en la SQLite, que ya se revirtio sola una vez (16-jul,
a un snapshot de abril) y se llevo por delante mes y medio de trabajo. Este en
concreto es el que reacciona a las caidas, asi que conviene tenerlo fuera.
Ya no hay nada secreto que ocultar: los tres nodos de Telegram leen el token de
$env.TELEGRAM_BOT_TOKEN en vez de llevarlo incrustado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ghost EN y ES son lo unico del mapa que da la cara al publico y lo que peor se
lleva con dos semanas sin nadie mirando. Entran en el SERVICE_MAP con la clave
igual al nombre exacto del monitor de Kuma.
El monitor "www.zonadeexclusion.com (301->apex)" se queda fuera a proposito:
que ese 301 falle es cosa de Traefik o del DNS, no de Ghost.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El workflow "Uptime Kuma -> K8s Auto-Restart" lleva desde siempre sin poder
reiniciar nada: hacia el PATCH con el token de la ServiceAccount "default" del
namespace, que no tiene ni un permiso, y el API contestaba 403. Se iba entonces
por la rama de "reinicio fallido" y mandaba el aviso por Telegram -- que se
enviaba bien, y por eso los 23 "exitos" de workflow_statistics. La integracion
parecia viva y no lo estaba.
SA propia (n8n-restarter) en vez de la default, y un RoleBinding por namespace
en lugar de un ClusterRoleBinding: el webhook que dispara esto es publico y sin
autenticar, asi que el alcance se limita al SERVICE_MAP del workflow.
El ClusterRole incluye get ademas de patch porque el workflow relee el objeto a
los 60 s para comprobar readyReplicas (sin get: "Cannot read properties of
undefined"), y statefulsets ademas de deployments porque Gitea es lo primero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tres workflows ACTIVOS fallaban con "Module 'https' is disallowed" sin que
saltara ninguna alerta, porque estaba NODE_FUNCTION_ALLOW_EXTERNAL (paquetes
npm) pero no NODE_FUNCTION_ALLOW_BUILTIN (modulos internos):
- Uptime Kuma -> K8s Auto-Restart ultimo exito 2026-07-23 07:51
- TLS Certs -> Alerta Telegram ultimo exito 2026-04-13
- Resumen Diario - Metricas K8s 30 fallos, 0 exitos
Es decir: el auto-reinicio que dispara Uptime Kuma llevaba una semana
muerto. Se rompio al reiniciarse el pod la tarde del 23-jul, cuando el task
runner empezo a bloquear require() de modulos internos.
Lista explicita (fs,http,https) en vez de '*': es justo lo que piden los
nodos Code de esos workflows, y asi un modulo nuevo falla a la vista en vez
de estar concedido de antemano.
La poda por edad ya estaba activa de serie (n8n 2.15.1, prune=true, 336 h):
los IDs de ejecucion empiezan en 22.634 con 7.052 filas, o sea que ya habia
borrado ~22.600 el solo. El tar diario crecia 0,4 MB/dia y estaba llegando a
meseta, asi que no habia desbocamiento: la premisa era falsa.
El problema real era la composicion. De 322 MB de execution_data, 246 MB
(76%) eran las 3.436 ejecuciones EXITOSAS de 'Real Madrid RSS -> Twitter',
que corre cada 5 min y guarda 73 KB por vuelta. Sus errores -lo unico que
sirve para depurar- ocupaban 2 MB.
Y no es un problema de disco (21%, 699 GB libres) sino de backup: n8n esta
en CROWN, asi que ese .tar.gz de 101 MB viajaba entero a MEGA cada dia y a
B2 en las dos ultimas copias. Es justo el gasto que revento la cuota el
2026-07-25.
Nada depende de ese historico: el monitor de reversion solo lee
workflow_entity, el panel de Grafana mira replicas del deployment y
'Resumen Diario' saca metricas de k8s por un nodo Code.