Expediente 4/5: la capa que llegó tarde

El caso de la config que nombró una capa que su imagen no llevaba, del aviso que se imprimió correcto durante días, y del diagnóstico que dedujo porque no podía mirar

Jesús Pérez
Una jornada entera convirtiendo copias en cascada compartida terminó con el sitio caído dos veces por el mismo fallo: una config que importa una capa que su imagen no lleva. El clasificador acertó las dos veces —marcó los ficheros como rebuild— y no bastó, porque «viajan inertes» solo es cierto mientras nada reinicie, y el mismo publish reinicia. La segunda caída la provocó quien acababa de escribir el guard contra la primera. Con el contenedor caído no había exec, y el hueco se rellenó con una deducción cronológicamente impecable y falsa: los dos sites comparten digest. La cura no fue una lista de lo que la imagen debería llevar, sino preguntárselo a la imagen.
Caso 4/5: la capa que llegó tarde

🕵️ Mostrar expediente completo → 📋 Protocolo de sesión →

Expediente · Depto. de Homicidios de Código

Un día entero convirtiendo copias en cascada compartida, y al final el sitio caído dos veces por lo mismo: una config que nombra una capa que su artefacto no lleva. La segunda caída, provocada por quien acababa de escribir el guard contra la primera.

Caso Nº 4/5Clasificación: ANTI-PAP · UNA CONFIG NOMBRÓ UNA CAPA QUE SU ARTEFACTO NO LLEVABAEstado: RESUELTO
Mostrar glosario
cascada de tres capas
rustelo (framework) → website-htmx-rustelo (implementación) → site consumidor. Un fichero presente en una capa superior gana; uno ausente se hereda de la de abajo. La config de un site importa `htmx-site/.ncl`, que es la capa intermedia, y esa capa vive en un directorio que el binario monta al arrancar.
clase rebuild
La clasificación que dice «estos bytes solo son válidos contra una imagen que los resuelva». Su comportamiento declarado era «viajan inertes y se reportan como debidos», y ahí está el patógeno: inerte solo mientras nada reinicie. El mismo `publish` reinicia.
leído al arrancar
Un fichero que el servidor evalúa UNA vez, al iniciar el proceso. Entregarlo no cambia nada visible; el siguiente reinicio lo activa. Es lo que separa un error que se ve al instante de uno que espera días con el detonador puesto.
entrega por exec
El contenido viaja al PV mediante `kubectl exec` dentro del contenedor. La consecuencia es cruel y define este caso: cuando el contenedor deja de arrancar, la herramienta pierde la vía por la que deshacer su propio cambio.
preguntar al artefacto
La regla que este caso compró. Un gate no consulta una lista, una fecha ni un razonamiento sobre qué debería llevar la imagen: le pregunta a la imagen. El contenido de un artefacto es propiedad del artefacto y cambia debajo de ti.
no puedo mirar
El estado que ningún check debe reportar como verde. Con el contenedor caído no había `exec`, y el hueco se rellenó con una deducción cronológicamente impecable y falsa. Un «no pude mirar» es un hallazgo.

Protocolo para declarar, versionar y verificar esto → ontoref.dev

El doble balance — lo que costó, y lo que dejó

Conviene no adornar la parte incómoda. La segunda caída la provocó quien acababa de escribir el guard contra la primera, y el error de diagnóstico que la siguió —afirmar que dos sites corrían imágenes distintas— se cometió exactamente por el vicio que este repositorio lleva un día quitando de todas partes: no podía mirar, así que deduje.

Lo que costó que la capa llegara después

  • Caídas de ontoref.dev el mismo día 2 — misma clase, ~19 h de diferencia
  • Reinicios en CrashLoopBackOff (segunda) 6 en el primer pod, 3 en su reemplazo
  • Capas que la config nombraba / que la imagen llevaba 5 / 4
  • Ficheros realmente rotos / revertidos en caliente 1 / 4
  • Recuperación escalar a 0 + pod temporal montando el PVC — dos veces, porque exec muere con el contenedor
  • Afirmación falsa emitida bajo presión «los dos sites corren imágenes distintas» — un solo digest, 097bf919
  • Sondas descartadas al construir el gate 3 (ConfigMap, MANIFEST horneado, host)
  • Mecanismo final 105 líneas de preflight + 115 del gate de compatibilidad

Lo que quedó en pie

  • Copias de mecanismo eliminadas 3 scripts × 3 sites, y 5 stems de config que 3 sites escribían a mano
  • Gates que preguntan al artefacto 2 — uno en el bucle de desarrollo, otro en la entrega
  • Sites que nacen con la defensa puesta todos: el gate entra en la plantilla del generador

Los sospechosos — las pistas falsas

El script de publicación se saltó el clasificador“Falso: `update-content.sh` llama a content.nu, y el clasificador marcó los cuatro ficheros como rebuild, correctamente. El guard funcionó; lo que fallaba era lo que la etiqueta significaba.”DESCARTADO
Los dos sites corren imágenes distintas“Falso, y lo afirmé con el contenedor caído y sin poder mirar. Ambos pods corrían el mismo digest. La deducción era cronológicamente impecable: el clúster no va por fechas, va por digests.”REFUTADO POR EL DIGEST
Montar las capas del perfil con un ConfigMap“Insuficiente: las capas del perfil importan bases del framework que la imagen también precedía. Dos overlays a ciegas sobre una imagen que nadie podía inspeccionar.”INSUFICIENTE
Hornear un MANIFEST del perfil dentro de la imagen“Innecesario: el directorio `htmx-site/` YA es el manifiesto. Un fichero habría sido una segunda copia de un listado — la forma exacta que llevaba un día borrando.”REDUNDANTE
`host $cp_node` para saber si el destino responde“Equivocada: `cp_node` es un alias de ~/.ssh/config, no un nombre DNS. Daba por irresoluble el control-plane real y saltaba el check justo en el único site verificable.”PROBADA CON UNA SOLA ENTRADA

El arma — Dos líneas del mismo `publish`. La primera declara la deuda; la segunda la detona.

[publish] 4 file(s) still owe a rebuild — bytes ship inert,
          the rest of this changeset does not wait on them
[publish] 21 files → class=restart → rolling restart

El aviso llevaba días imprimiéndose correcto. Una deuda que llora en cada ejecución es una deuda que se aprende a saltar, y así se leyó la noche que importaba.

El giro — Tres preguntas, ninguna a una lista.

# el gate le pregunta al pod, nunca a una lista
kubectl exec deploy/$D -- printenv NICKEL_IMPORT_PATH   # su orden, no el mío
kubectl exec deploy/$D -- nickel export …/index.ncl     # su nickel, su imagen
kubectl exec deploy/$D -- ls …/nickel/htmx-site/        # el directorio ES el manifiesto

Falsificado en las tres ramas: verde con las cinco capas, rojo nombrando la que falta, y SKIP dicho en voz alta cuando el destino aún no está configurado.

El veredicto

El instrumento que habría respondido —config-check— validaba contra las raíces instaladas en el portátil, que van siempre por delante de cualquier imagen. Por eso un site podía estar verde en local y ser inarrancable en un pod, y por eso el fallo no se sintió como un descuido: cada pieza contestaba, y ninguna contestaba eso.

Lo que queda escrito no es una advertencia. Es que la pregunta ahora la hace una máquina, dos veces —en el bucle de desarrollo y en la entrega—, y las dos se la hacen al artefacto.

Una config nombra una capa que la imagen no llevaprofile-compat-check: los imports contra el ls del pod, en el bucle de desarrollo.
Bytes irresolubles llegan al PVpreflight: exporta index.ncl dentro del pod. Rojo no entrega nada.
«rebuild» leído como puerta cuando era etiquetaEl aviso se redacta desde la prueba, no desde la clasificación.
La lista de ficheros dependientes de la imagenRegla derivada del import, jamás una lista: la lista nace una entrada corta.
No poder mirar el artefactoSKIP dicho en voz alta · ROJO si no responde · veredicto si responde. Ningún verde por ignorancia.

La jurisprudencia — qué exige hoy la lección

  • Una config que importa una capa compartida se verifica contra la imagen del destino, no contra las raíces del portátilgate:just profile-compat-check
  • La config del site debe exportar contra las tres raíces que monta el servidor, no contra las del desarrolladorgate:just config-check
  • Las dos anteriores corren en cada check, incluido el primero de un site recién generadogate:just check
  • El preflight de entrega rehúsa publicar bytes que el pod no puede cargar, y ahora alguien puede apuntarlotest:provisioning/tests/test_preflight_refuses.nu

Un «no pude mirar» es un hallazgo. Lo escribimos en un gate esa misma tarde, y no me lo apliqué.

Del vocabulario del proyecto (2)
Gate
Prerrequisitos tipados y políticas que controlan las transiciones de estado de la máquina de estados de un proyecto.
ontoref
El protocolo en sí: una superficie tipada y consultable en la que un proyecto declara LO QUE ES (ontología) y CÓMO ACTÚA (reflexión), de modo que una afirmación sobre el proyecto pueda ser contradicha por una máquina y no sólo por un lector.

Vocabulario completo del proyecto →

¿Te ha resultado útil? Valóralo
¿Tienes algo que aportar? Cuéntame qué opinas, qué sugieres, o si seguimos explorando este tema.
· lecturas

Usamos cookies para que este sitio funcione, entender el uso del servicio y apoyar acciones de marketing. Política de cookies para más información.