Ingeniería de plataforma

Esto no es un diagrama de lo que haría.
Es la flota que está en el aire ahora.

Diseñé, construí y opero solo un ecosistema SaaS multi-tenant en producción, con clientes reales que pagan. Esta página muestra los números medidos, cómo ocurre una publicación y — la parte que importa — los incidentes que tumbaron el sistema y qué cambió después de cada uno.

Todos los números de abajo fueron medidos en la flota, no estimados
Lectura de 30 segundos

Lo que diseño, implemento, opero y mido

Esta tabla existe para que no tengas que leer la página entera. La columna que más importa es la última: medir es lo que separa operar de suponer. Y donde solo tengo formación, no práctica, está dicho — quien marca todo no informa nada.

Dominio DiseñoImplemento OperoMido
Linux, red y diagnóstico firewall con solo SSH · red segmentada en dos subredes · diagnóstico de contención de bloqueo en la base
Contenedores y composición 224 contenedores · límites de CPU y concurrencia declarados · una imagen por repositorio
Entrega continua y estrategia de publicación canario con validación y rollback por digest · bloqueo de flota · libro de registro de publicación
Observabilidad y respuesta a incidentes 9 objetivos · 12 reglas · tres post-mortems escritos, cada uno con la guarda que dejó
Bases de datos y continuidad primario + réplica caliente · WAL continuo · RTO y RPO cronometrados
Seguridad, identidad y multi-tenant cero puerto expuesto · aislamiento fail-closed en la base · OAuth2 y JWKS propios · bóveda de secretos
Arquitectura distribuida y mensajería outbox transaccional · 12 grupos de consumo · deduplicación activa · cola de fallos vigilada
Infraestructura como código módulos Terraform propios para nube gestionada (red, base, seguridad, observabilidad) — no es lo que opero hoy: la flota es declarativa por composición
Kubernetes formación concluida y manifiestos escritos; decisión consciente de no operarlo — el porqué está en el Acto III
Nube gestionada de terceros formación concluida; la operación real es en nube pública con servicios propios — lo digo a propósito
en producción, hoy parcial o fuera de producción no — y lo digo
I

Qué está en juego

No es un laboratorio. Hay clientes pagando, y cuando esto cae alguien deja de vender. Todo lo demás de esta página existe por esa frase.

La flota

El tamaño de la operación

31
microservicios con despliegue de producción
224
contenedores en el aire (112 por nodo)
87
publicaciones por día (promedio de 13 días)
11.047
pruebas automatizadas en 3 de los 31 servicios

Dos nodos activo-activo

ARM64 en nube pública, 8 vCPU y 30 GB cada uno, detrás de un túnel Cloudflare. Los dos sirven tráfico: no existe un nodo ocioso esperando el desastre.

Multi-tenant con aislamiento en la base de datos

Row-Level Security en PostgreSQL, fail-closed: sin el alcance del cliente en la sesión, la consulta devuelve cero filas. El rol de runtime no tiene BYPASSRLS.

Una puerta de calidad que reprueba

Cobertura mínima que reprueba el build: 95% en dos servicios, 90,3% en otro. No es una meta de pared — es condición para publicar.

De dónde viene cada número de arriba
la pregunta que yo haría si fuera tú — y la respuesta, con el límite de cada medición
abrir

Un número sin procedencia es una opinión con cara de dato. Cada línea de abajo dice qué se contó, cómo, cuándo — y qué NO prueba la medición. La última columna es la que me interesa: una medición honesta declara su propio límite.

NúmeroCómo fue medidoQué NO prueba esto
31 conteo de archivos de composición de producción en los repositorios que todos estén activos al mismo tiempo — cuenta lo que tiene deploy definido
224 docker ps en los dos nodos: 112 cada uno. De esos 112, 59 son procesos de aplicación (web, trabajador, publicador, consumidor, programador); los otros 53 son bus, datos, observabilidad, borde y bóveda no es medida de carga ni de utilización — es conteo de procesos
87 libro de registro de publicación de un nodo, 1.212 líneas en 14 días. Cada línea = una recreación de servicio con fecha, digest y commit. Mediana 79; varía de 2 a 172 por día no es “87 versiones nuevas”: una publicación lógica recrea el servicio en los dos nodos, y el promedio esconde la variación
11.047 pruebas ejecutadas en una corrida real de 3 servicios. En paralelo: 31.438 funciones de prueba definidas en 14 servicios (conteo estático) los dos números miden cosas distintas, y ninguno es cobertura — la cobertura es el piso que reprueba el build
RPO medido ahora: atraso de replicación de 1,01 s y cero bytes de WAL pendientes de envío al standby es una muestra, no una prueba de pérdida del primario. El RPO real bajo fallo solo se conoce provocando el fallo — está en el plan, no en el historial
47 min ejercicio de aprovisionar un nodo desde cero y subir la flota en él, en septiembre de 2026, al día siguiente de la migración a la nube actual (instancias creadas el 12 y el 16, verificadas en los metadatos del proveedor). Criterio de “listo”: autenticarse con usuario y contraseña y entrar al sistema — lo que ejercita borde, túnel, enrutamiento, identidad, base y sesión de punta a punta no prueba que los 31 servicios estuvieran sanos ni que el trabajo de fondo ya procesara — prueba que el camino crítico del usuario estaba en pie. Es un criterio de punta a punta, más fuerte que “el contenedor subió” y más estrecho que “la flota entera convergió”
12 min tiempo de que la flota entera suba y converja, cronometrado en el mismo ejercicio de septiembre de 2026, con el mismo criterio de punta a punta misma salvedad: un ejercicio, en una fecha, con un proveedor. Repetirlo en otro proveedor es lo que convertiría el número en evidencia de portabilidad, no solo de recuperación
12 reglas archivo de alertas leído directo del servicio de métricas; 9 objetivos activos, todos respondiendo al momento de la lectura que exista una regla no es alerta probada: no todas fueron disparadas a propósito para verificar el camino hasta mí

“Muéstrame el diagrama y desconfío. Muéstrame qué pasa cuando falla y te creo.”

lo que esta página intenta responder
II

Cómo se sostiene

El dibujo completo: por dónde entra el tráfico, dónde corre cada pieza, cómo se llega al dato — y qué pasa cuando cada parte falla.

Antes de la infraestructura: el sistema visto desde fuera, y por dentro

Dos vistas que vienen antes del dibujo de implantación. La primera responde “quién usa esto y con qué mundo externo habla”. La segunda responde “qué aplicaciones existen y cómo conversan” — sin ninguna infraestructura, a propósito: mezclar las dos cosas en un solo diagrama es el error que vuelve ilegibles a la mayoría.

Diagrama de contexto: quién usa el sistema y con qué mundo externo habla NIVEL 1 · CONTEXTO quién lo usa, y con qué mundo de afuera conversa Operador del cliente vende, compra, factura Cliente final portal, recibo, firma Operador de la plataforma una persona — yo Zenithus SaaS multi-tenant 31 microservicios un origen único para todas las pantallas Adquirente · TEF tarjeta y conciliación Bureaus · Open banking crédito y extracto APIs de gobierno fiscal y laboral Firma · Marketplace contrato y venta externa Mensajería WhatsApp y correo todo lo que sale pasa por UN servicio de integración Frontera de confianza ningún actor alcanza un servicio directamente: toda entrada pasa por el borde gestionado y la identidad propia ● persona ■ el sistema ▣ sistema externo
Diagrama de contenedores: qué aplicaciones existen y cómo se hablan NIVEL 2 · CONTENEDORES las aplicaciones y cómo conversan — sin infraestructura, a propósito Navegador del usuario UN ORIGEN — todo lo de abajo responde bajo la misma dirección Identidad Java · Spring Boot emite el token publica las claves endpoints estándar OAuth2/OIDC Cáscara de la aplicación Python · Django menú, contexto del cliente y sesión — embute las verticales 12 verticales de negocio Python · Java · Go ventas · inventario · fiscal finanzas · RRHH · flota… montadas por ruta Plano de control quién es el cliente, qué plan tiene, de quién es hijo llamada SÍNCRONA, tipada Bus de eventos un hecho → 12 grupos dedup · cola de fallos camino POR DEFECTO: asíncrono Integración 22 conectores · la ÚNICA que habla con terceros devuelve HECHO normalizado PostgreSQL — aislamiento de cliente en la propia base una base por vertical · la regla de quién-ve-qué no depende del código de la aplicación La infraestructura (nodos, red, balanceo) está en el diagrama de implantación justo abajo — aquí, solo aplicación.
Arquitectura de la flota Zenithus — diagrama lógico y físico Del usuario a la base: borde Cloudflare con WAF y túnel de salida, ningún puerto de entrada en el firewall, identidad propia sirviendo OAuth2 y JWKS en el mismo origen único donde las verticales se montan por ruta, red segmentada en subred de aplicación y subred de datos, dos nodos activo-activo, PostgreSQL primario con réplica caliente y archivado continuo de WAL, backup fuera del proveedor, y una capa transversal de autorización, observabilidad y entrega continua. Los puntos únicos de fallo están marcados. Arquitectura Zenithus — lógica y física Medida en la flota, no estimada · identificadores y direcciones omitidos a propósito 12 minRTO — flota entera 47 minRTO — nodo nuevo desde cero segundosRPO — WAL continuo 1 Usuarios, socios y APIs HTTPS BORDE GESTIONADO — CLOUDFLARE el ataque muere antes de llegar al nodo DNS + TLS certificado gestionado WAF filtro antes del origen 2 Tunnel — un conector por nodo conexión de SALIDA; drenar = publicar sin riesgo R2 — backup fuera del proveedor dump cada 3h · base semanal · medios · retención Sin egress cobrado · sin lock-in el origen puede cambiar de proveedor sin reescritura FIREWALL DEL SISTEMA — servicios permitidos: solo SSH. Los puertos 80 y 443 no existen en la regla: el tráfico entra porque el nodo SALE. OCI · región São Paulo · red virtual privada SPOF aceptado: los 2 nodos en el mismo dominio de disponibilidad SUBRED DE APLICACIÓN · /24 sin IP pública · egreso por NAT NODO A — canario (publica primero) ACTIVO ARM64 · 8 vCPU · 30 GB · 112 contenedores cloudflared Traefik — enruta host + ruta 3 IDENTIDAD PROPIA (Spring Boot) — OAuth2 + JWKS autenticación y claves en el MISMO origen que las pantallas sesión en un solo dominio: sin CORS, sin cookie de terceros ORIGEN ÚNICO — 12 verticales montadas por RUTA /app/<vertical> — un prefijo por dominio de negocio una cáscara hospeda a las demás; ninguna vertical tiene ruta pública propia Bus local — 14 Redis (uno por vertical) outbox transaccional → relay → Streams → inbox con dedup → DLQ 5 HAProxy — puerta única de base (un backend; promoción manual) NODO B — estable ACTIVO idéntico · ninguno de los dos es reserva ociosa cloudflared Traefik 4 SDK ÚNICO DE LA PLATAFORMA una implementación de autorización para todas las verticales — no 12 copias divergentes alcance por petición, fail-closed: sin alcance, la llamada es DENEGADA máquina a máquina: clave por servicio la verdad de plan y tenant viene del Control Plane STACKS POLÍGLOTAS, UN SOLO CONTRATO Java/Spring Boot · Go · Rust · Python el contrato es el evento y el alcance, no el lenguaje Bus local — 15 Redis · cola en disco PostgreSQL 16 RÉPLICA CALIENTE streaming activo instancia separada SSH cerrado + pg_receivewal archiva WAL continuo recuperación a punto en el tiempo lectura, no escritura SUBRED DE DATOS · /24 — segmentada de la aplicación alcanzable solo desde dentro de la red privada pgbouncer entrando en el camino pool en transaction — decenas de servicios sin abrir conexión a voluntad PostgreSQL 16 — PRIMARIO host dedicado, fuera de los nodos de app · 24 bases aislamiento de cliente por Row-Level Security, fail-closed rol de runtime sin BYPASSRLS: olvidar el alcance devuelve cero filas SPOF CONOCIDO el primario es el punto único. Sin failover automático — a propósito: evita split-brain. replicación continua → ⑥ backup cifrado fuera del proveedor SALIDA CONTROLADA — UNA SOLA PUERTA AL MUNDO medido: cero SDK de terceros en las verticales de negocio 7 Integración — 22 conectores adquirente · bureau · gobierno · open banking firma · marketplace · mensajería · almacenamiento Disyuntor · reintento con retroceso · idempotencia un tercero lento no se vuelve cola atascada aquí dentro la credencial viene de la BÓVEDA, nunca del entorno Retorno del tercero: origen SEPARADO para webhooks firma HMAC verificada — payload sin firma se descarta el tercero nunca toca el origen de las pantallas el resultado vuelve al ecosistema como HECHO normalizado en el bus — el consumidor nunca habla con el tercero CAPA TRANSVERSAL — vale para todo lo de arriba SECRETOS Bóveda OpenBao · token por consumidor menor privilegio · MFA en operación crítica alerta dedicada de token por expirar OBSERVABILIDAD Prometheus · Grafana · Alertmanager cAdvisor · exporters · sondas externas 9 objetivos · 12 reglas — 3 vigilan AUSENCIA ENTREGA CONTINUA rama → PR → puerta local → canario valida en el nodo drenado · rollback por digest producción nunca compila · bloqueo global de flota CONTINUIDAD dump cada 3h · base semanal · WAL continuo destino fuera del proveedor · restore verificado libro de registro: fecha, digest y commit de cada servicio QUÉ PASA CUANDO FALLA — la parte que un diagrama bonito suele omitir Cae un nodo de aplicación El otro ya sirve el 100% del tráfico. La cola del nodo parado está en disco y retoma cuando vuelve. Impacto al usuario: ninguno. Sin acción humana. Cae la base primaria Parada real. La réplica caliente existe y está al día; la promoción es manual, con runbook escrito. Elección consciente: automatizar sin fencing da split-brain. Se pierde la región entera El backup cifrado vive fuera del proveedor. Nodo nuevo aprovisionado y flota en pie: 47 minutos, medidos. Pérdida de datos: segundos, por el WAL continuo.
borde gestionado cómputo y red privada datos y continuidad límite de seguridad · punto único de fallo asumido
  Por qué los puntos únicos de fallo están marcados. Un diagrama que esconde el SPOF no es un diagrama, es un folleto. Los tres que existen aquí — los dos nodos en el mismo dominio de disponibilidad, la base primaria sin promoción automática y la puerta única de base — son elecciones, y cada una tiene su motivo registrado más abajo.
La plataforma

Capa por capa

Los 224 contenedores no son 224 copias de lo mismo. Contados por papel, dibujan la plataforma — y cada capa existe porque algún dolor la pidió.

Borde — sin puerto abierto a internet

Ninguno de los dos nodos expone puerto público. El tráfico entra por túnel Cloudflare (conector de salida) y cae en Traefik, que hace enrutamiento por host, TLS automático y sirve de punto de balanceo. Drenar un nodo del túnel es lo que hace posible la publicación canaria: así se saca un nodo del balanceo sin tumbar nada.

Datos — pool, proxy y aislamiento en la base

PostgreSQL en host dedicado, compartido por las verticales, con HAProxy como punto único de acceso y pgbouncer provisionado pero todavía fuera del camino (medido: cero clientes activos; las aplicaciones van directo a HAProxy) — entra cuando el número de conexiones lo pida. Son dos instancias: primaria y réplica caliente. El aislamiento de cliente es Row-Level Security, no WHERE tenant_id esparcido por el código: el rol de runtime no tiene BYPASSRLS, así que olvidar el alcance devuelve cero filas en vez de filtrar datos.

Honestidad sobre el objetivo: no hay failover automático de base de datos. La promoción es manual y documentada — y prefiero decirlo a dibujar un Patroni que no existe.

Mensajería — 12 relays, 12 consumers, 10 programadores

Los servicios no se llaman en cadena: publican hechos. El camino es outbox transaccional (el hecho commitea junto con el dato, o ninguno de los dos) → relay → Redis Streams → inbox con dedup por grupo de consumo → DLQ para lo que falló. Son 14 instancias de Redis: el bus es por vertical y por nodo, y justamente por eso la convergencia se resuelve en Postgres, no en un lock distribuido que no cruzaría los nodos.

Secretos — bóveda, no archivo .env

Las credenciales viven en bóveda (OpenBao) con token de servicio por consumidor. Hay una alerta dedicada para token de la bóveda por expirar — porque un secreto que vence en silencio tumba todo de una vez, en el peor horario.

Cómo conversan los servicios — y por qué casi nunca es por llamada directa

La regla es publicar hecho, no llamar servicio. El camino por defecto es asíncrono: outbox transaccional (el hecho solo existe si el dato existe), relay, cola, inbox con deduplicación por grupo de consumo y DLQ para lo que falló. Eso significa que un servicio caído atrasa al otro — no lo tumba con él.

Cuando la respuesta se necesita ahora — “¿este cliente tiene derecho a esta funcionalidad?”, “¿esta empresa es hija de aquella?” — la llamada es síncrona, pero nunca es HTTP suelto esparcido por el código: pasa por un cliente tipado del SDK único, con operaciones nombradas y autenticación máquina a máquina por clave propia de cada servicio. La autoridad sobre plan, jerarquía y derecho de uso tiene un solo dueño; los demás preguntan, no deducen.

El mismo SDK está cargado en las 9 verticales Python — una implementación de autorización, no nueve divergentes

El bus de eventos — un hecho, doce lectores, ningún acoplamiento

Cuando ocurre una venta, el servicio de ventas no avisa a nadie. Publica el hecho — y doce grupos de consumo independientes lo leen al ritmo de cada uno. Inventario descuenta la pieza, finanzas genera el cobro, fiscal prepara el documento, contabilidad asienta. Ninguno sabe que los otros existen, y uno de ellos caído atrasa su propia cola, no la venta.

la escritura y el hecho en la MISMA transacción el relay publica stream por dominio 12 grupos de consumo el inbox descarta repetidos DLQ para lo que falló
10.003hechos en el stream de la plataforma
12grupos de consumo independientes
242consumidores registrados
0pendientes — el bus está al día
5.362marcas de deduplicación activas
20en la cola de fallos, bajo vigilancia

  Trabajadores con límite escrito

Por nodo: 13 trabajadores, 11 publicadores, 10 consumidores y 10 programadores. Cada uno corre con concurrencia 1, declarada en el archivo de composición. El valor por defecto de la herramienta es 8 — y así fue como la flota cayó una vez. Límite que no está escrito no existe.

  Colas separadas por naturaleza

El trabajo de IA, de análisis y de rutina tienen colas propias. Una llamada de modelo que tarda treinta segundos no puede sentarse delante de la cola que confirma una venta. Separar es lo que impide que lo lento atrase lo urgente.

  Cola en disco: atraso, no pérdida

El bus escribe en disco. Si un nodo cae con trabajo en cola, ese trabajo no se evapora — espera a que el nodo vuelva y retoma. La diferencia entre “el cliente esperó” y “el pedido desapareció” es esta línea de configuración.

  El detalle distribuido que más me costó entender. El bus es por nodo — cada uno tiene el suyo. Eso significa que un bloqueo distribuido vía bus no funciona entre los dos: cada nodo vería su propio bloqueo y ambos ejecutarían. Como la base es el único recurso realmente compartido, la convergencia se resuelve allí, con reclamo atómico de la fila — quien la tome primero ejecuta, el otro sigue sin bloquear. No es elección de líder, y es a propósito: la elección concentra el trabajo en un nodo y reduce el paralelismo que los dos nodos existen para dar.

Observabilidad — y el cuidado de alertar sobre la ausencia

Prometheus raspando 9 objetivos (los dos nodos, cAdvisor, exportador de PostgreSQL, Traefik, sondas HTTP), Grafana, Alertmanager, cAdvisor, node y postgres exporter, blackbox sondeando rutas de fuera hacia dentro. 12 reglas de alerta en el aire.

Lo que considero la parte madura no es la cantidad — es qué vigilan. Tres de ellas no preguntan “¿algo se rompió?”, preguntan “¿algo dejó de ocurrir?”: backup off-site vacío, backup atrasado, backup sin la base dentro. Una bóveda de backup que existe pero está vacía pasa por saludable en cualquier verificación de presencia — y solo se descubre el día del restore. Las otras cubren caída de PostgreSQL, disco llenándose en cada nodo, nodo fuera del aire, slot de WAL acumulando y contenedor en ciclo de reinicio.

9 de 9 objetivos respondiendo al momento de esta medición

“La mejor herramienta es la planificación.”

por qué cada pieza está donde está
III

Por qué es así

Toda elección aquí tiene una ganancia, un costo que yo pago y una consecuencia comercial. Quien solo lista la ganancia está vendiendo, no diseñando.

Docker Compose — y no Kubernetes ni k3s
la decisión que más me preguntan, y la que tiene el trade-off más claro
ver trade-off

El presupuesto de cómputo entero son dos nodos de 8 vCPU y 30 GB, y ya corren 224 contenedores. Un plano de control de Kubernetes — servidor de API, etcd, planificador, controladores, red superpuesta, controlador de entrada — consume CPU y memoria del mismo presupuesto, de forma sostenida. Y la carga sostenida de CPU es exactamente lo que ya me costó dos suspensiones de infraestructura.

qué gano Un modelo mental que una sola persona puede operar a las 3 de la mañana. La unidad de despliegue es la misma cosa que corre en mi máquina. Sin red superpuesta, sin controlador de entrada, sin ciclo de actualización de clúster, sin recursos personalizados que depurar durante un incidente.
qué pago por eso No tengo reprogramación automática entre nodos ni escalado horizontal automático. La capacidad es decisión humana. Si los dos nodos se saturan a la vez, no existe nada que crezca solo — tendría que aprovisionar, y eso lleva minutos, no segundos.

Como decisión de negocio: Kubernetes resuelve un problema de escala que todavía no tengo, cobrando hoy en complejidad operativa y en consumo del recurso más escaso que tengo. Mientras la carga quepa en dos nodos, adoptarlo sería pagar por adelantado un seguro contra un riesgo que no existe — y crear uno nuevo y real: una superficie que no puedo sostener solo en guardia. Esto no es rechazar la herramienta; es no cambiar de herramienta por moda. El día en que la cuenta cambie — más de un operador, o carga que no quepa en dos nodos — la migración ya está diseñada, y es barata justamente porque todo ya son imágenes de contenedor y configuración declarativa.

Túnel de salida — y no balanceador con IP pública
la superficie de ataque que deja de existir
ver trade-off
qué gano No existe puerta de entrada para escanear. El firewall permite solo SSH; 80 y 443 no figuran en la regla. El filtro de aplicación, el certificado y la mitigación de volumen quedan en el borde gestionado, antes de que un byte toque mi nodo. Y drenar un nodo del balanceo es detener un proceso — eso es lo que hace posible la publicación canaria.
qué pago por eso Dependencia de un tercero en el camino crítico de entrada: si el borde cae, el sistema queda inalcanzable aun estando en pie. Es un proveedor más en la cadena, y no controlo su ventana de mantenimiento.

Como decisión de negocio: un balanceador gestionado cuesta por hora y por volumen, y encima me devuelve una IP pública que defender. El túnel cuesta cero en el plan en uso, elimina la superficie expuesta y entrega filtro de aplicación junto. Cambié un riesgo que tendría que operar (exposición) por un riesgo que monitoreo (disponibilidad de terceros) — y este segundo tiene sonda externa y alerta.

Promoción manual de la base — y no failover automático
la decisión más incómoda de defender, y la más correcta
ver trade-off
qué gano Vuelve imposible el split-brain. Dos instancias aceptando escritura al mismo tiempo, en un sistema multi-tenant que mueve dinero, es un daño que ningún backup deshace bien — porque los dos lados quedan “correctos”.
qué pago por eso La base primaria es punto único de fallo declarado. Si cae, hay parada real hasta la promoción, y el tiempo depende de que un humano atienda. No lo escondo en el diagrama: está marcado allí.

Como decisión de negocio: el failover automático sin capa de consenso y aislamiento del nodo defectuoso cambia una parada rara y recuperable por una corrupción silenciosa y permanente. Para quien guarda dato financiero de terceros, ese cambio es malo en cualquier escenario. La mitigación no es automatizar la promoción: es que la réplica esté caliente y al día, que el procedimiento esté escrito, y que exista recuperación a punto en el tiempo — todo medido.

Dos nodos activo-activo — y no un nodo más grande
resiliencia que se usa todos los días, no que duerme esperando el desastre
ver trade-off
qué gano Los dos sirven tráfico todo el tiempo, así que el camino de resiliencia se ejercita en cada petición — no el día del desastre. Es también lo que permite publicar en un nodo al que ningún usuario está accediendo.
qué pago por eso Todo tiene que ser seguro por partida doble: una tarea programada corre en los dos, así que la protección contra duplicación vive en la base, no en un bloqueo distribuido — que ni funcionaría, porque el bus es local a cada nodo.

Como decisión de negocio: la reserva ociosa es un costo que solo paga en el peor día — y que suele fallar justo entonces, porque nadie la ejercita. Con los dos activos, el mismo dinero compra capacidad y resiliencia, y la prueba de que funciona es el tráfico de ayer. Honestidad sobre el límite: hoy los dos están en el mismo dominio de disponibilidad, así que un fallo correlacionado de dominio todavía me tumba. Está marcado en el diagrama porque es el próximo paso, no porque no lo vea.

Cadena y puerta de calidad propias
sin ejecutor hospedado — con una consecuencia que digo en voz alta
ver trade-off
qué gano La suite corre contra PostgreSQL con aislamiento de cliente armado, no contra una base de juguete — un defecto de seguridad multi-tenant aparece en la puerta, no en producción. Sin cola de ejecutor, sin minuto cobrado, sin secreto de producción en un servicio de terceros.
qué pago por eso No existe red de seguridad remota. Si yo no corro la puerta, nadie la corre — y ya descubrí que podía terminar verde sin haber ejecutado ni una prueba.

Como decisión de negocio: el valor de la puerta no está en dónde se ejecuta, está en qué reprueba. Un ejecutor hospedado corriendo la suite contra una base sin aislamiento de cliente da sensación de cobertura y deja pasar la clase de defecto que más duele en un SaaS multi-tenant: fuga entre clientes. Compensación del costo que asumí: una prueba que vigila la propia puerta y falla alto.

Identidad propia y origen único — y no identidad tercerizada
la decisión que elimina una clase entera de ataque, en vez de filtrarla
ver trade-off

Las doce verticales no tienen dirección pública propia. Todas se montan por ruta bajo un origen único, y el servidor de identidad — OAuth2 y publicación de claves — vive en ese mismo origen. La autorización no queda solo en una puerta al frente: viaja con el servicio, por un SDK único que todas las verticales cargan y que deniega la llamada cuando el alcance no está presente.

qué gano Origen único significa sesión en un solo dominio: sin compartir entre orígenes, sin cookie de terceros — que los navegadores están quitando — y sin la superficie que crearía un portal por subdominio. Y como la autorización está en el servicio, no solo en el borde, quien llegue desde dentro de la red también es bloqueado: no existe “ya pasé la puerta, ahora soy confiable”.
qué pago por eso La identidad es el componente más caro de operar con responsabilidad: rotación de claves, expiración, federación. Ya me costó cinco horas y media de login fuera del aire — había rotado el conjunto de claves de firmar creyendo que era el de publicar. Tercerizar habría evitado ese incidente específico.

Como decisión de negocio: el producto es multi-tenant y vende a empresas que necesitan heredar jerarquía de cliente, plan contratado y segregación de funciones. La identidad tercerizada cobra por usuario activo — el costo crece con la base justo cuando el margen debería mejorar — y empuja la regla de negocio fuera del sistema que es su dueño. Manteniéndola en casa, el costo es de ingeniería (una vez) en vez de suscripción (siempre), y la regla de quién-ve-qué queda donde es verdad. El costo se mitiga en la operación: hay alerta dedicada para clave y token por expirar, y la puerta de publicación valida que el emisor de claves responde antes de devolver el nodo al tráfico.

Cuatro lenguajes — y un solo contrato
políglota por decisión, no por accidente histórico
ver trade-off

Java con Spring Boot en la identidad, la contabilidad y el motor de flujo. Go en los servicios nuevos — binario único, sin runtime, arranca rápido y ocupa poca memoria, que es el recurso escaso aquí. Rust en dos lugares precisos: el agente que observa el núcleo del sistema operativo y el motor de cálculo fiscal. Python con Django en las verticales de negocio, donde la velocidad de cambio vale más que los microsegundos.

qué gano Cada problema en la herramienta que mejor lo resuelve, sin reescribir lo que ya funciona. Como el contrato entre servicios es el evento y el alcance — no el lenguaje — cambiar la implementación de un servicio no obliga a nadie más a cambiar. Es lo que hace posible la migración gradual, en vez de un salto arriesgado.
qué pago por eso Cuatro cadenas de construcción, cuatro conjuntos de dependencias que mantener actualizados y cuatro formas de depurar bajo presión. Para un equipo pequeño eso es real — por eso cada lenguaje está confinado a donde paga, y no esparcido por gusto.

Como decisión de negocio: reescribir lo que funciona es la forma más cara de no entregar nada. El legado productivo sigue rindiendo en Python mientras lo que nace ya nace en Go, y el contrato estable entre ellos es lo que permite cambiar pieza por pieza sin parar la operación — sin el riesgo de la reescritura total, que históricamente tumba empresas.

Una sola puerta al mundo — y no SDK de terceros en cada servicio
22 integraciones externas, un único lugar donde existen
ver trade-off

Adquirente de tarjeta, bureau de crédito, APIs de gobierno, open banking, firma electrónica, marketplace, mensajería — todo entra y sale por un solo servicio. Él traduce cada proveedor a una capacidad normalizada y devuelve el resultado al ecosistema como hecho en el bus. Ninguna vertical de negocio conoce el nombre del proveedor; la regla de negocio queda en quien consume, no en el adaptador.

qué gano Cambiar de proveedor es cambiar un adaptador, no cazar llamadas en doce servicios. La credencial del tercero existe en un solo lugar y viene de la bóveda. Y el alcance de un fallo externo queda contenido: disyuntor, reintento con retroceso e idempotencia viven ahí — un tercero lento no se vuelve cola atascada dentro de casa.
qué pago por eso Un salto de red más en el camino, y un servicio que, si cae, tumba todas las integraciones de una vez. Es concentración de riesgo — asumida, y por eso es de los más vigilados.

Como decisión de negocio: la integración con terceros es donde vive el riesgo contractual — credencial filtrada, cobro indebido, dato de cliente saliendo afuera. Concentrar significa que auditar “¿quién habla con el mundo de afuera, y con qué credencial?” es mirar un servicio, no doce. Y esto es verificable, no promesa: medí las seis verticales de negocio y ninguna tiene SDK de terceros instalado — solo existen en el servicio de integración. Una regla que depende de disciplina humana falla callada; esta se puede medir.

Costo como requisito de arquitectura — y no como factura a fin de mes
la cuenta que decide qué entra y qué se queda fuera
ver trade-off

Operar solo con clientes reales impone una restricción que una empresa grande no tiene: cada servicio gestionado que contrato es dinero todos los meses y una pieza que no controlo. Por eso el diseño evita las tres fuentes clásicas de factura que crece sin aviso: tráfico de salida cobrado, balanceador por hora e identidad por usuario activo.

qué gano Los datos salen sin cobro por volumen, porque el backup va a un almacenamiento sin tarifa de egreso y el tráfico entra por túnel en vez de un balanceador tarifado. La identidad propia significa costo fijo de ingeniería, no suscripción que crece con la base de usuarios — justo cuando el margen debería mejorar. Y el margen libre está medido: 47% de la memoria usada y carga media por debajo de 1 en los dos nodos.
qué pago por eso Pago en trabajo. Un servicio gestionado es caro en dinero y barato en tiempo; mi arreglo es el inverso. Cada pieza que no contraté es una pieza que opero, actualizo y arreglo a las 3 de la mañana. Esa cuenta solo cierra mientras la escala quepa en dos máquinas y la operación quepa en una persona.

Como decisión de negocio: en un SaaS, el costo de infraestructura es lo que separa margen de pérdida cuando la base crece. La pregunta que hago antes de adoptar cualquier servicio es “¿este costo crece con el número de clientes?” — si crece linealmente con la receta, se come la economía de escala que el modelo debería dar. El dimensionamiento, los límites de CPU y concurrencia declarados y el libro de registro de publicación existen tanto por confiabilidad como por costo: un recurso que nadie mide es un recurso que nadie recorta.

Y la prueba que casi nadie hace: ¿cuánto tarda en volver?

Un plan de recuperación que nunca fue cronometrado es intención, no plan. Estos tres números fueron medidos de verdad, no estimados.

12 min
para que la flota entera suba y converja
47 min
para aprovisionar un nodo desde cero y tener la flota en pie en él
segundos
de pérdida máxima de datos — el WAL se archiva continuamente
  Los 47 minutos son también la prueba más honesta de portabilidad de cómputo y aplicación: es el tiempo de reconstruir la capa de cómputo y aplicación en cualquier proveedor que entregue una máquina Linux — lo que corre son imágenes de contenedor, PostgreSQL y Redis, sin servicio propietario. No es independencia total — el borde, el registro de imágenes y el propio proveedor siguen siendo dependencias externas, y cambiar cada una tiene su propio costo.

“Nada se publica a mano — y nada compila en el servidor de producción.”

una regla que nació de dos suspensiones
IV

Cómo cambia sin caer

Publicar es el momento en que un sistema estable se vuelve peligroso. El camino de abajo existe para que cambiar deje de ser un acto de coraje.

Entrega continua

Cómo ocurre una publicación

Nada se publica a mano y nada compila en el servidor de producción. La restricción nació de dolor real: la infraestructura anterior fue suspendida dos veces por carga sostenida de CPU — compilar en el host era uno de los disparadores. La arquitectura cambió por eso.

1

Construye fuera de producción

La imagen se construye en la estación/CI y se publica en el registry con la tag del commit. El servidor de producción solo ejecuta pull — nunca compila.

2

Drena el nodo canario del balanceo

El conector del túnel se detiene en el nodo A. 100% del tráfico va al nodo B. La publicación ocurre en un nodo al que ningún usuario está accediendo.

3

Valida con dato real — y con la pregunta correcta

Sin crashloop, emisor de claves respondiendo y cero migraciones pendientes. Esta última nació de un incidente: proceso de pie no significa base al día.

4

¿Reprobó? Rollback automático por digest

Vuelve al digest anterior en el propio nodo y aborta — el nodo estable nunca fue tocado. Y el conector del túnel se restaura con verificación: si falla, grita en stderr con el comando del arreglo manual.

5

¿Aprobó? Vuelve al balanceo y espeja

El nodo validado vuelve al túnel y solo entonces el segundo nodo recibe la misma imagen, un servicio a la vez. Un lock global serializa la flota entera: dos deploys simultáneos ya tumbaron la infraestructura una vez.

Y queda escrito

Cada servicio recreado entra en un libro de registro en el nodo con fecha, digest de la imagen y el commit. Así se responde “¿qué exactamente está corriendo?” sin adivinar.

La cadena

La puerta es local — y por eso tiene dientes

La cadena es propia: no hay runner hospedado revisando el pull request. GitHub guarda el código y sirve de registry de las imágenes; la puerta de calidad corre antes, en la estación. Esto tiene una consecuencia incómoda que prefiero decir en voz alta: no existe red de seguridad remota — si yo no corro la puerta, nadie la corre.

  Qué exige la puerta

Formato y lint con catraca (el piso sube, nunca baja), verificación de integridad del proyecto y la suite corriendo en PostgreSQL con RLS armado — no en base de juguete. Cobertura bajo el piso reprueba. En el servicio en Java, la misma puerta suma pruebas de arquitectura, integración con contenedores reales y análisis estático.

  Nada entra directo a la línea principal

Trabajo en rama, publicación por pull request, y la línea principal está protegida. push --force, reset --hard y borrar la principal son operaciones bloqueadas por política. Antes de una refactorización grande, una marca de retorno.

  Puerta verde que nunca empezó

El objetivo de la puerta llamaba al intérprete del sistema y moría justo después de que el lint imprimiera “todo bien”. Quien leyera la pantalla veía verde en una puerta que no había ejecutado ni una prueba. Hoy una prueba vigila la propia puerta — y falla alto.

“Un sistema que nunca se rompió es un sistema que nadie usa.”

tres incidentes reales, y la cicatriz de cada uno
V

Cuando se rompió — y qué quedó

La parte que normalmente no se publica. Causa, qué falló en la detección, qué empeoré intentando arreglarlo, y la guarda que impide la reincidencia.

Confiabilidad

Incidentes, y qué cambió después

Sistema que nunca se rompió es sistema que nadie usa. Lo que diferencia no es la ausencia de incidente — es lo que queda después. Estos tres son reales, del mismo día, y cada uno dejó una guarda en el código.

Producción cayó

La migración que se trabó contra sí misma

Síntoma
Toda pantalla que leía ventas respondía error: columna inexistente. La publicación había sido reportada como completa.
Causa
El servicio usa dos roles de base — uno dueño, que ejecuta DDL, y uno de runtime, con RLS. Una migración creó la columna en la transacción del dueño y, en la línea siguiente, actualizó los datos por el rol equivocado: la segunda conexión no veía la columna y se quedaba esperando el bloqueo que la propia migración sostenía. Auto-deadlock — y PostgreSQL ni lo acusa, porque la sesión dueña no espera bloqueo, espera al cliente.
Por qué pasó
La suite corre con los dos roles colapsados en uno. El defecto solo existe en producción. Y la validación del deploy medía “el proceso subió”, no “la base acompañó”.
Yo lo empeoré
Mi primer intento de aplicar la migración a mano murió por timeout y dejó la sesión idle in transaction aún sosteniendo el bloqueo. Apilé dos intentos más encima antes de entender.
Resolución
Terminar las sesiones atascadas, revertir trayendo la imagen anterior del registry y recrear un nodo a la vez. Después: reproducir el defecto en una base descartable — la versión rota se cuelga, la corregida aplica — antes de volver a publicar.
Qué quedó
Guarda estructural por AST en la suite (migración de datos tiene que usar la conexión de la migración) y conteo de migración pendiente en el deploy, que aborta y revierte.

La guarda nació con el mismo vicio que fue a cazar: contaba solo la pendencia, y grep -c en salida vacía imprime cero — igual a “ninguna pendiente”. Hoy cuenta el total también: lista vacía no es schema al día, es falta de evidencia.

Falla silenciosa · dinero

La divergencia de tarjeta que era tragada

Síntoma
Ninguno. Ese es el problema.
Causa
La conciliación diaria compara las transacciones del adquirente con los pagos registrados. Cada divergencia publica un evento — pero el tipo del evento nunca fue declarado en el catálogo, y el publicador rechaza tipo no declarado. La excepción caía en un except ancho que envolvía al cliente entero: toda divergencia era detectada y descartada. Transacción fantasma en el adquirente, o pago sin confirmación — dinero — se volvía una línea de log.
Cómo apareció
No apareció: fue una guarda nueva que barre el código buscando evento publicado fuera del catálogo. Encontró dos — este y otro.
El segundo defecto
Declarar el evento, por sí solo, iba a soltar una alerta falsa por venta con tarjeta: el adaptador en producción no sabe listar transacciones del adquirente, y el código trataba “no pude leer” como “el adquirente no tiene nada”.
Qué quedó
Sin lectura, la rutina no acusa nada y reporta explícitamente que no hubo evidencia. Lista vacía proveniente de lectura exitosa sigue valiendo como divergencia real.

Ausencia de medición no es ausencia del hecho. Y alerta que grita siempre ciega al guardián — habría sido peor que el defecto original.

Integración muerta en el medio

El evento que nadie escuchaba

Síntoma
La mercancía devuelta al proveedor salía del inventario correctamente — y la deuda con él seguía íntegra. La tienda pagaba por pieza que devolvió.
Causa
El servicio de inventario publicaba el evento de la devolución, con la documentación diciendo literalmente que el documento financiero “vive en el otro servicio”. Ningún servicio consumía. Suite verde en las dos puntas, funcionalidad muerta en el medio.
Y no se podía consumir
El evento identificaba al proveedor por el identificador local de quien publicaba. Del otro lado, ese identificador no significa nada — y el resolvedor crea un proveedor genérico cuando no reconoce la referencia. Consumirlo tal como estaba crearía crédito colgado en un proveedor fantasma.
Qué quedó
El evento pasó a cargar la identidad que atraviesa la frontera (registro central o documento fiscal). Sin ella, no se publica — y la pantalla avisa al operador, por el mismo predicado, que el crédito no fue generado y qué hacer.

Al medir una integración, no pares en “el evento se publica”. Pregunta quién escucha — y si lo que lleva significa algo del otro lado.

Calidad por construcción

Guardas como código

Todo incidente de arriba se volvió una prueba que reprueba el build si la clase del defecto vuelve. Convención escrita en documento envejece; guarda ejecutable, no. Y toda guarda se prueba con un mutante: si reintroduzco el defecto y sigue verde, no sirve.

  Una pregunta, un dueño

“¿Este depósito cuenta como saldo vendible?” estaba escrita, literal, en cinco lugares. Con un segundo tipo de depósito, cada copia olvidada se vuelve un hueco silencioso. La regla pasó a tener dueño único — y una prueba reprueba a quien la escriba a mano.

  Evento publicado está en el catálogo

Barrido por AST: todo tipo de evento publicado en el código tiene que estar declarado. Encontró dos casos vivos — uno de ellos tragaba divergencia de tarjeta.

  La migración escribe por la conexión correcta

Guarda estructural, no comportamental: en pruebas los dos roles de base colapsan, así que el defecto solo existe en producción. Una prueba de comportamiento quedaría verde con el sistema en el suelo.

  El enlace simbólico no escapa del árbol

Dos enlaces absolutos apuntando a la máquina de quien los creó terminaron dentro de la imagen de producción — y eran autorreferentes: recursión infinita para cualquier barrido que los siga.

Cultura SRE

No es el cargo — es lo que la práctica obliga

SRE, aquí, no es un título: es un conjunto de reglas que ya me costaron caro aprender y hoy están escritas en script, en prueba y en política. Seis de ellas:

  Post-mortem sin culpable — inclusive sobre mí

El relato del primer incidente de esta página tiene un campo llamado “Yo lo empeoré”: mi intento de arreglo manual apiló tres sesiones atascadas antes de que entendiera el problema. Post-mortem que omite el error del autor no sirve para que nadie aprenda — sirve para proteger al autor.

  Restaurar primero, entender después

Ante producción en el suelo, el camino es volver al digest anterior y solo entonces investigar. El rollback es automático y por digest — no por “reconstruir la imagen de antes”, que no es lo mismo y ya me engañó.

  Alerta que grita siempre ciega al guardián

Una alerta disparada por total acumulado queda encendida para siempre y pasa a ser ignorada — entonces la muerte nueva llega sin que nadie la vea. La alerta tiene que medir el cambio, y silenciarse cuando el cambio para.

  La restricción viene del dolor, no del manual

La infraestructura anterior fue suspendida dos veces por carga sostenida de CPU, y compilar en el servidor era uno de los disparadores; la tercera suspensión sería definitiva. Hoy “producción nunca compila” no es preferencia de estilo — es regla con revisión que bloquea el pull request.

  Cambio serializado, nunca en paralelo

Dos deploys simultáneos ya tumbaron la flota. Existe un lock global: un servicio a la vez, un nodo a la vez, y el lock huérfano se reclama en vez de trabar la flota para siempre.

  Todo cambio deja rastro consultable

Cada recreación de servicio entra en un libro de registro en el nodo: fecha, digest y commit. La pregunta “¿qué exactamente está corriendo?” tiene respuesta por consulta, no por memoria de quien publicó. De ahí salió el promedio medido de 92 publicaciones por día.

Cómo trabajo

Tres principios que vinieron de cicatrices

Medir antes de afirmar

Ya reporté “es configuración del operador” sobre un campo que era imposible de configurar — el tipo no cabía el identificador del otro servicio. Cero de doce registros llenados en la flota entera no era negligencia: era un defecto. Hoy mido antes de concluir.

Falta de evidencia no es ausencia de defecto

Una validación que no logró medir no puede reportar “todo bien”. Esa confusión ya apareció tres veces: en el deploy, en la conciliación de tarjeta y en una guarda mía — siempre con cara de verde.

Gate verde no es funcionalidad funcionando

Suite con miles de pruebas verdes convivía con la pantalla principal rota. Prueba con doble no mide la regla que solo existe en el borde real — y la verificación visual agarra lo que ninguna prueba agarra.

  Sobre esta página. Los incidentes son reales y los números fueron medidos en la flota. Nombres de clientes, direcciones de red e identificadores internos fueron omitidos a propósito — es información de terceros y superficie de ataque. Quien quiera profundizar, lo demuestro en vivo.

“Lo que todavía no probé vale tanto como lo que sí probé — mientras yo diga cuál es cuál.”

el estado honesto de lo que falta
VI

Capacidad, continuidad y gobernanza

Qué está hecho, qué está parcial y qué todavía no existe. Separar los tres es lo que vuelve confiables a las dos primeras columnas.

Ingeniería de capacidad

hechoLímites de CPU, memoria y concurrencia declarados por servicio en el archivo de composición — no en el valor por defecto de la herramienta
hechoMargen medido: 47% de la memoria usada y carga media por debajo de 1 en los dos nodos
hechoColas segregadas por naturaleza, para que el trabajo lento no atrase el urgente
parcialPercentiles de latencia: medidos. p50 50 ms · p95 96 ms · p99 100 ms, del histograma del enrutador de borde, con cero respuestas 5xx en el período. La salvedad que importa: fue a 0,43 req/s — prueba camino saludable, no resistencia. La prueba de estrés, empujando hasta saturar para hallar el codo de la curva, sigue sin hacerse
parcialN-1 ejercitado en la práctica, decenas de veces al día. La publicación canaria drena un nodo del balanceo por ~200 s (mediana) por servicio: fueron 82 ventanas en 72 h. Durante ellas el nodo restante sostuvo todo el tráfico con carga entre 0,13 y 2,5 en 8 vCPU y cero respuestas 5xx. La salvedad: es N-1 bajo tráfico normal, no bajo pico — probarlo bajo carga máxima es el próximo ejercicio

Ingeniería de seguridad

hechoSuperficie de entrada eliminada por diseño: firewall solo con SSH, sin puerto de aplicación abierto
hechoIdentidad propia, autorización por alcance en cada servicio y secretos en bóveda con token por consumidor
hechoAislamiento de cliente aplicado en la base, fail-closed — no depende de que el código de la aplicación acierte
parcialRotación de credenciales: existe alerta de expiración y procedimiento, pero la rotación no es automática — y ya me costó un apagón de login
noModelado de amenazas formal, inventario de dependencias firmado y gestión continua de vulnerabilidades. Hay actualización de dependencias y revisión, no un proceso con artefacto

Continuidad y recuperación

hechoBackup cifrado fuera del proveedor, en tres cadencias: dump cada 3 h, base semanal y WAL continuo
hechoRestauración ya ejercitada, y alerta que vigila la ausencia de backup — no solo el fallo de backup
hechoRTO cronometrado y documentado: 12 min para la flota, 47 min para un nodo desde cero — septiembre de 2026, en el proveedor actual, con criterio de punta a punta (autenticarse y entrar al sistema). Lo que falta para que sea evidencia de portabilidad: repetirlo en otro proveedor
noEjercicio de desastre con pérdida real del primario, cronometrando la promoción manual de punta a punta. El RPO de hoy es muestra de atraso de replicación, no resultado de fallo provocado

Gobernanza

hechoLínea principal protegida, trabajo en rama y publicación por revisión; operaciones destructivas bloqueadas por política
hechoLibro de registro de publicación: fecha, digest y commit de cada recreación de servicio
hechoDecisiones de arquitectura registradas con el costo que cada una impone — es el Acto III de esta página
parcialLa puerta de calidad corre en la estación, por ejecución manual. Es rigurosa (base real, aislamiento armado, cobertura que reprueba), pero no es una cadena automatizada de punta a punta: sin ejecución remota por revisión, la red de seguridad depende de que yo recuerde
  Por qué publicar la columna “no”. Porque la alternativa es peor. Quien evalúa este tipo de sistema va a preguntar exactamente por prueba de carga, N-1 y ejercicio de desastre — y descubrir la laguna en la conversación vale menos que encontrarla escrita aquí, con la razón y el próximo paso. Laguna declarada es plan; laguna descubierta es sorpresa.

¿Quieres verlo corriendo?

Puedo demostrar una publicación canaria de punta a punta — incluyendo el rollback automático cuando la validación reprueba.

contact@magnosilva.com LinkedIn Voltar ao início