Fallo silencioso de ETL: cuando una cuenta de servicio retirada de Active Directory tira todas tus cadenas
> Este artículo documenta un patrón observado en producción durante una incidencia real. Los nombres de cuentas, servidores y dominios han sido enmascarados; el comportamiento descrito y la cronología son los originales.
## El viernes que se cayó el data warehouse sin avisar
Un viernes por la tarde, todas las cadenas ETL del data warehouse empezaron a fallar simultáneamente. Sin error de aplicación, sin alerta del orquestador, sin nada raro en los dashboards de monitorización. Las cadenas, sencillamente, arrancaban y morían. Una detrás de otra. En menos de quince minutos, ninguna carga incremental estaba avanzando y los modelos analíticos del lunes empezaban a estar oficialmente en riesgo.
Lo primero que mira un DBA cuando un ETL se queda mudo no suele ser SQL Server. Mira el orquestador, mira la red, mira si hay un job atascado que está bloqueando a los demás. Yo hice exactamente eso, y exactamente eso me hizo perder los primeros veinte minutos del incidente. Porque esta vez el problema no estaba en el data warehouse, ni en el orquestador, ni en la red. Estaba en Active Directory. Y nadie del equipo DBA había sido informado del cambio.
Esta es la historia de cómo una decisión rutinaria de limpieza en AD acabó con todas las cargas de un viernes, y de la lista corta de comprobaciones que ahora tengo siempre lista para diagnosticar este patrón en cinco minutos en lugar de cuarenta.
## El contexto: una cuenta, muchas cadenas, cero redundancia
El escenario era el típico de cualquier plataforma analítica con algunos años a sus espaldas. Una cuenta de servicio del ETL, dada de alta en Active Directory hacía probablemente seis o siete años, ejecutaba la mayoría de los procesos del data warehouse: extracciones, transformaciones, cargas finales y un puñado de jobs programados que mantenían vivas las dimensiones lentas. La cuenta tenía permisos en los servidores ETL de producción y también en varias instancias SQL del entorno donde residían las fuentes.
El permiso, como suele pasar, no se había concedido directamente a la cuenta. Se había concedido a un grupo de AD del que la cuenta era miembro. Esto, sobre el papel, es la buena práctica: gestionas permisos a nivel de grupo, evitas el lío de los logins individuales repartidos por cuarenta servidores, y delegas el alta y baja en el equipo de identidad. El problema aparece cuando ese grupo de AD se considera obsoleto, alguien decide retirar cuentas de servicio de él como parte de una revisión de seguridad, y nadie avisa a los DBAs de que esa cuenta también ejecuta cargas críticas todas las noches.
No había login individual de respaldo. La cuenta dependía al 100 % de la pertenencia al grupo. Y el equipo de DBA no aparecía en la cadena de notificación del cambio.
## Qué pasó cuando se rompió todo a la vez
El patrón visible desde el orquestador era desconcertante. Las cadenas no fallaban en una fase concreta: fallaban en el primer paso, en la primera conexión a la primera instancia SQL. Los reintentos no servían de nada porque el problema no era transitorio. Las cadenas que tocaban fuentes más remotas tardaban un poco más en colapsar por culpa de timeouts de red, pero todas acababan en el mismo sitio: error de conexión, sin mensaje útil más allá del genérico.
Lo realmente engañoso es que los servicios de SQL Server estaban perfectamente vivos. Las instancias respondían a otros clientes, los logins de usuarios humanos funcionaban, y las queries lanzadas desde otras cuentas de servicio (las del frontend de BI, por ejemplo) seguían su curso normal. Solo las cadenas ETL morían. Solo las que usaban esa cuenta concreta.
La trampa diagnóstica es clara: cuando solo falla un subsistema y el motor SQL parece estar bien, el reflejo es pensar en problemas de aplicación. Buscas en el SSIS, en los packages, en las configuraciones del orquestador. Y vas a perder mucho tiempo, porque la causa está en el sitio donde nunca miras primero: el errorlog del propio SQL Server.
## El diagnóstico: leer el errorlog antes de cualquier otra cosa
La pista definitiva está en xp_readerrorlog. Cuando un grupo de AD se retira y la cuenta pierde su acceso por la única vía que tenía, SQL Server escribe en su errorlog un torrente de mensajes de login fallido. No son sutiles. Son ráfagas de fallos con el mismo timestamp, el mismo usuario y el mismo código de error.
`sql
-- Buscar errores de login recientes en el errorlog de la instancia
EXEC xp_readerrorlog 0, 1, N'Login failed', NULL,
DATEADD(HOUR, -2, GETDATE()), GETDATE(), N'desc';
`
Lo que apareció al lanzar esto en una de las instancias afectadas fueron decenas de líneas idénticas: Login failed for user 'DOMINIO\cuenta_servicio_etl', todas con código de error 18456 y, lo más importante, todas con state 11 o 12. Ese detalle es el que cierra el diagnóstico.
El error 18456 es el comodín de los fallos de login en SQL Server, pero el state que lo acompaña te dice exactamente qué tipo de fallo es. Los más conocidos son el state 5 (login inexistente) y el state 8 (contraseña incorrecta). Pero los states 11 y 12 son los que delatan este patrón concreto: significan que el login es válido, la cuenta existe en el dominio, la autenticación de Windows ha funcionado correctamente, pero el principal no tiene permiso para entrar a esta instancia SQL. Que es exactamente lo que ocurre cuando una cuenta deja de pertenecer al grupo que tenía el login.
Comprobación rápida en sys.server_principals para confirmar que efectivamente no hay un login directo de fallback:
`sql
SELECT name, type_desc, is_disabled, default_database_name
FROM sys.server_principals
WHERE name LIKE '%cuenta_servicio_etl%';
`
Resultado: cero filas. La cuenta nunca había tenido login individual en la instancia. Todo el acceso pasaba por el grupo del dominio, y el grupo ya no la tenía dentro.
A partir de ahí, la cronología cuadró: el ticket interno del equipo de identidad estaba abierto desde primera hora de la tarde, la retirada del grupo se había aplicado a las 16:42, y los primeros fallos de cadena coincidían exactamente con esa marca. No era casualidad, era causa raíz.
## La causa raíz: tres errores acumulados, no uno
Cuando reconstruyes este tipo de incidentes con calma, casi nunca encuentras un solo error. Encuentras una pila de decisiones razonables tomadas en momentos distintos que, combinadas, producen el desastre. En este caso fueron tres.
El primero fue no documentar la dependencia. El grupo de AD existía desde hacía años, pero en ningún sitio del inventario constaba que ese grupo era el portador del login que mantenía vivo el ETL. Para el equipo de identidad era un grupo más de los muchos que llevaban tiempo sin tocarse, candidato natural a una limpieza.
El segundo fue no tener fallback. Para una cuenta de servicio crítica, dejar todos los huevos en la cesta del grupo es asumir que ese grupo nunca cambiará. Para servicios de los que dependen las cargas nocturnas, lo razonable es tener el login también a nivel individual: si el grupo cae, la cuenta sigue entrando.
El tercero, y probablemente el más caro, fue no cerrar el círculo de notificación. El equipo de identidad había hecho su trabajo según un procedimiento establecido. El equipo DBA estaba operando con la asunción de que esa cuenta era estable. Y entre ambos no existía un paso obligatorio de "antes de retirar una cuenta de servicio de un grupo, comprobar si ese grupo tiene logins en SQL Server y avisar al equipo correspondiente".
Ninguno de los tres errores, por sí solo, habría causado el incidente. Los tres juntos lo garantizaron.
## Cómo evitar que vuelva a pasar
La resolución inmediata fue lo de menos: escalar al equipo de identidad, re-añadir la cuenta al grupo, validar que las cadenas volvían a ejecutarse y, paralelamente, dar de alta el login a nivel individual en las instancias afectadas como red de seguridad. Total, una hora larga de incidente que en circunstancias normales (con notificación previa) habría sido cero.
Lo importante son los cambios que quedan para los próximos viernes por la tarde. Tres acciones, en orden de impacto.
Primera, inventario de dependencias accionable. No basta con saber qué cuentas tienen permisos: hace falta saber qué grupos de AD aportan logins a qué instancias SQL y qué procesos dependen de cada combinación. Una tabla sencilla de tres columnas (grupo AD, instancia SQL, procesos que dependen) que se mantenga viva y se cruce con el inventario de cuentas de servicio. Si esa tabla hubiera existido, el equipo de identidad habría visto al instante que la retirada afectaba al data warehouse.
Segunda, redundancia obligatoria para cuentas críticas. Para cualquier cuenta de servicio que ejecute procesos de los que depende negocio (ETL, replicación, jobs de mantenimiento), el login se da de alta tanto en el grupo AD como individualmente. Sí, es un poco más de gestión. Sí, duplica el alta. Pero es el seguro más barato que existe contra este tipo de incidentes.
Tercera, alertas de login failed para cuentas conocidas. SQL Server permite capturar fallos de login con Extended Events. Configurar una sesión XE que vigile específicamente los login_failed de las cuentas de servicio críticas y dispare una alerta al primer fallo (no esperar a tener cincuenta acumulados) habría reducido el tiempo de detección de cuarenta minutos a uno. El coste de la sesión es despreciable; el coste de no tenerla, ya lo sabes.
## La lección que me llevo
La moraleja de este incidente no es técnica, es de procedimiento. SQL Server hizo exactamente lo que tenía que hacer: rechazó conexiones de un principal sin permisos y lo dejó perfectamente documentado en su errorlog. Active Directory hizo exactamente lo que se le pidió: retirar una cuenta de un grupo. El problema fue que dos sistemas que dependían el uno del otro estaban gestionados por dos equipos que no compartían visibilidad sobre esa dependencia.
Cada vez que un DBA dice "esto lo gestionan los de identidad" o un administrador de AD dice "yo no sé qué hace esa cuenta en SQL", existe el riesgo latente de este patrón. La única defensa real es asumir que cualquier cambio en AD sobre una cuenta o un grupo es potencialmente un cambio en SQL Server, y que el procedimiento de baja de cuentas tiene que tener una casilla obligatoria llamada "comprobar dependencias SQL" antes de ejecutarse.
Cuando vuelva a fallar todo a la vez sin causa aparente —y volverá—, el primer comando que voy a lanzar es xp_readerrorlog filtrando por login failed de las últimas dos horas. Si veo ráfagas con state 11 o 12 sobre la misma cuenta de servicio, no voy a perder ni un minuto mirando otra cosa. Voy directo a preguntar qué se ha tocado en AD esa tarde.