Mi aplicación ASP.NET se reinicia doctor, ¿es grave?: Aplicaciones, Grupos de aplicaciones y Dominios de Aplicación en IIS y ASP.NET

18/05/2019
Artículo original

Los tres términos del título de este post suelen causar bastante confusión entre los desarrolladores ASP.NET que además deben administrar un servidor web con Internet Information Server bajo Windows. Mi objetivo hoy va a ser tratar de aclararlos y ver qué implicaciones tienen en el día a día de una aplicación Web creada con ASP.NET: cómo funcionan, cuándo se reciclan o se inician, cómo se afectan unos a otros...

En primer lugar considera la siguiente estructura de una aplicación web albergada en IIS:

Estructura básica de una aplicación ASP.NET en IIS

Como vemos existe una aplicación principal que hemos llamado en este caso AplicacionBase, y dentro de ésta, aparte de sus archivos propios, podemos tener varias carpetas con más archivos de la aplicación, así como otras sub-aplicaciones, en este ejemplo concreto tenemos 2: AplicacionA y AplicacionB.

¿Para qué sirven estas sub-aplicaciones? ¿Por qué querríamos crearlas? Pues por muchos motivos, pero sobre todo porque seguramente son aplicaciones diferentes a la principal que se ocupan de cosas diferentes. Por ejemplo:

  • La aplicación principal puede que sea nuestra web, que usa por debajo algún tipo de gestor de contenidos.
  • La aplicación "A" es el producto que vendemos, una aplicación de tipo SaaS que es lo que usan los usuarios tras haberse autenticado y que no está en su propio subdominio.
  • La aplicación "B" podría ser un sistema de facturación Open Source que utilizamos para cobrar a los clientes y que no hemos construido nosotros.

Como, en nuestro ejemplo, cada aplicación es completamente distinta y aislada de las demás, pero queremos tenerlas todas juntas bajo el mismo dominio, hemos decidido montarlas así.

Además, cada una de ellas puede que necesite una versión de .NET diferente y tenga unos requisitos de recursos hardware diferentes (memoria, uso de CPU...). Esto es algo que nos permite gestionar fácilmente IIS.

Nota: crear una sub-aplicación en un sitio de IIS es muy sencillo: basta con pulsar con el botón derecho sobre el sitio raíz y elegir Añadir aplicación:

Añadir aplicación

Aplicación, sitio, sub-aplicación...

Como no, siendo de Microsoft, la primera dificultad ya viene por la propia terminología de IIS. A las aplicaciones "raíz" se les denomina "Sitios" y no aplicaciones. IIS viene con un sitio por defecto y luego puede crear más sitios según vayas necesitando. Pero, salvo que vayas a albergar tan solo páginas estáticas sin ningún tipo de código de lado servidor, estos sitios son en realidad aplicaciones.

Anteriormente a los sitios se les llamaba "servidores virtuales", que hasta me parecía más adecuado que el lacónico término actual.

Es decir, IIS tiene sitios y sub-aplicaciones, pero todos ellos son en realidad aplicaciones. Cuando se habla del término "Aplicación" nos referimos siempre al sentido que le da Internet Information Server.

Grupos de aplicaciones

Cada aplicación de IIS se ejecuta bajo lo que se denomina un Grupo de Aplicaciones o Application Pools (AppPools). Al crear una aplicación (o sub-aplicación, es lo mismo), IIS nos obliga a elegir un grupo de aplicaciones, que puede ser uno ya existente o uno nuevo creado para la nueva aplicación.

Un grupo de aplicaciones es una manera de agrupar y aislar procesos dentro de IIS. Es decir, todas las aplicaciones que comparten un mismo grupo de aplicaciones comparten los mismos recursos de memoria, CPU, etc. y además se gestionan de manera conjunta, reiniciándose a la vez si es necesario. Cada grupo de aplicaciones está aislado de los demás. Esto permite que tengamos muchas aplicaciones distintas y no relacionadas dentro de IIS y que si una se cuelga o debe reiniciarse por algún motivo (reinicio programado, acapara demasiada memoria o CPU...), no afecte al resto de ellas que estñan en otros AppDomains.

Esto es genial, desde luego, para los que se dedican al hosting puesto que asignando un AppPool diferente para cada cliente evita que lo que hagan pueda afectar a los demás. Aparte de que puedes controlar el máximo de recursos que pueden utilizar, sin que ninguno acapare demasiado. Pero para un uso normal de cualquier empresa, sin ser para hosting, también está my bien pues permite tener recursos dedicados a cada aplicación, versiones diferentes de ASP.NET en cada una y, si alguna de ellas o varias no son aplicaciones propias, tenerlas aisladas de las demás.

Podemos ver y gestionar los grupos de aplicaciones desde el apartado correspondiente del gestor de IIS:

Gestión de grupos de aplicaciones

Cada uno de estos grupos aislados nos permite configurar multitud de cuestiones sobre su funcionamiento, siendo lo básico la versión de .NET a utilizar (que puede ser ninguna en caso de usar otro lenguaje de servidor, como PHP, Node.js, ASP Clásico...), si .NET debe interceptar todas las peticiones (modo integrado) o solo las que le correspondan or extensión de archivo (clásico). Pero también muchas otras cuestiones, como

Animación que muestra todas las opciones disponibles en las propiedades avanzadas de un AppPool

Entre todas estas propiedades las más importantes se refieren al reciclado del AppPool. Por defecto los grupos de aplicaciones se reciclan periódicamente cada 1740 minutos. Es decir, por defecto, cada 29 horas, si no indicamos lo contrario, IIS le "pega" un reinicio al grupo de aplicaciones, reiniciando todas las aplicaciones que tenga dentro.

Obviamente esto se puede (y se debe) cambiar y elegir otro tipo de criterios, como por ejemplo que se alcance un uso exorbitado de memoria o que durante más de un tiempo determinado se intente acaparar demasiado uso de procesador, por ejemplo. No voy a entrar en detalles hoy porque no es el objeto, pero es algo muy interesante y útil para tratar de mantener a raya el servidor.

Esto lo gestiona IIS.

Dominios de Aplicación

Al igual que IIS aísla los subprocesos de sus aplicaciones dentro de grupos de aplicaciones, ASP.NET aísla sus subprocesos en lo que se denominan Dominios de Aplicación o AppDomains.

Esto es un concepto de .NET y no tanto de ASP.NET, pero por supuesto ASP.NET les saca partido también. Para entendernos, los dominios de aplicación reemplazan a los subprocesos del sistema operativo y son gestionados por .NET. Al igual que un subproceso real del sistema operativo, un AppDomain aísla sus recursos de los demás AppDomains de modo que no puedan interferir entre ellos y, por ejemplo, el fallo de uno eche abajo a los demás.

Los dominios de aplicación son útiles porque son menos costosos de crear y gestionar que los subprocesos del sistema operativo, cada uno de ellos puede ejecutarse con un nivel de seguridad de .NET diferente, pero todos bajo un mismo proceso del sistema y todos están controlados por el runtime de .NET.

Aunque no es lo más normal, podemos gestionar desde nuestro código los dominios de aplicación como nos plazca.

Generalmente una aplicación creada con ASP.NET genera automáticamente un dominio de aplicación cuando arranca y todos los datos de la aplicación (variables de sesión y aplicación, caché y clases estáticas) están contenidos dentro de éste.

Si en un servidor con IIS abrimos el gestor de procesos (CTRL+MAYs+ESC) podremos ver que cada grupo de aplicaciones tiene un proceso abierto en el sistema operativo (el ejecutable responsable es w3wp.exe):

El gestor de tareas de Windows con los procesos de IIS

Cada uno de ellos se ejecuta bajo un determinado contexto de seguridad (como si fueran usuarios ficticios del sistema) y, si debajo tienen aplicaciones ASP.NET, contienen uno o varios dominios de aplicación de .NET.

Una cosa muy importante a tener en cuenta es que en IIS las sub-aplicaciones de una aplicación, aunque tienen dominios de aplicación diferentes, comparten un dominio de aplicación común que es el de la aplicación "raíz" o base. Así, en el ejemplo del principio, existe un AppDomain para las aplicaciones "A" y "B", y un AppDomain para la aplicación base, pero los de "A" y "B" se dependen del de la aplicación base. Ahora veremos las implicaciones de esto...

Reciclajes, reinicios y su relación con todo lo anterior

Vale, recapitulemos los 3 puntos clave hasta ahora:

  • Los grupos de aplicaciones son la manera que tiene IIS de aislar unos procesos de otros para proteger las aplicaciones que ejecuta. Cada uno de estos grupos de aplicaciones se ejecuta en un proceso del sistema operativo.
  • Cada grupo de aplicaciones puede gestionar una o varias aplicaciones de IIS.
  • En .NET, el mecanismo de aislamiento de procesos se denomina Dominios de Aplicación. Cada aplicación de IIS tiene su propio dominio de aplicación, pero puede tener otros dependientes (los de las sub-aplicaciones).

Bien. En una situación como la que describe la primera figura de este texto, supongamos que las tres aplicaciones tienen sendos grupos de aplicaciones en IIS, con el mismo nombre. Así, la aplicación base se ejecuta en un AppPool llamado AppPoolBase, la "A" en uno llamado AppPoolA y la "B" en AppPoolB (podrían todos compartir el mismo, por supuesto, pero lo dejaré así para dar una explicación más general). Cada uno de estos ejecuta una aplicación ASP.NET por lo que tendrá a su vez un dominio de aplicación propio en .NET.

En el caso de IIS, los grupos de aplicaciones se pueden reciclar, tanto automáticamente en función de las reglas que les hayamos establecido, o a mano en el momento en el que lo deseemos:

Opción del menu contextual de un AppPool para reciclarlo a mano

El reciclaje implica que se crea un nuevo proceso de IIS para atender a las nuevas peticiones que lleguen para esa aplicación, y el proceso anterior se elimina en cuanto se hayan procesado todas las peticiones en curso o pendientes que tuviese. O sea, se levanta un nuevo proceso "fresco" para la aplicación Web, con lo que si el anterior tuviese algún problema (estuviese "colgado", ocupase mucha memoria...) dejaría de dar problemas.

Cuando reciclamos un AppPool de IIS, eliminamos también el dominio de aplicación de la aplicación ASP.NET subyacente. Pero podemos hacerlo de manera independiente sin afectarlos los unos a los otros. En nuestro ejemplo podríamos reciclar el AppDomainB, reiniciando la aplicación "B", pero ni la aplicación "A" ni la aplicación base se enterarían de nada. Tan solo la aplicación "B" se iniciaría de nuevo obteniendo un nuevo dominio de aplicación en el proceso.

Al mismo tiempo, se puede reiniciar un dominio de aplicación de ASP.NET sin tener que reciclar el AppDomain bajo el que está gestionado por IIS. Son cosas independientes. Así, por ejemplo, si todas las aplicaciones de nuestro ejemplo del comienzo compartiesen el mismo grupo de aplicaciones de IIS, podríamos reiniciar el dominio de aplicación de la aplicación "B" sin que se enteraran ninguna de las otras dos (ahora mismo veremos un caso especial que echa por tierra esto).

¿Cómo se reinicia un dominio de aplicación? Pues cuando:

  • Cambias el archivo web.config o el global.asax.
  • Modificas el contenido de la carpeta Bin de la raíz del proyecto, que contiene las DLLs de la aplicación.
  • ¡Si borras cualquier subcarpeta de la aplicación!. Esto no lo sabe mucha gente, pero es una causa común de que se reinicie el AppDomain y por lo tanto que se reseteen las sesiones y otros datos. A más de uno lo puede volver loco.

Hay algunos casos más, pero ya son mucho más raros.

Entonces:

  1. Podemos reciclar un AppPool sin afectar a los demás, creando un AppDomain nuevo para las aplicaciones ASP.NET que contenga.
  2. Podemos reiniciar el AppDomain de una aplicación sin que se enteren las demás, incluyendo las que están en el mismo AppPool de IIS.

Como vemos, es muy flexible.

Sin embargo...

El AppDomain raíz

Este es un caso particular que echa por tierra la segunda afirmación final del apartado anterior. Y es que, como dije antes, el AppDomain de la aplicación raiz o "base" es el que genera los AppDomain de las aplicaciones "hija" o sub-aplicaciones.

En nuestro ejemplo del principio, tanto el AppDomain de la aplicación "A" como el la "B", aún siendo independientes, dependen del AppDomain de la aplicación "Base".

¿Y esto que significa? Pues que aunque podríamos resetar la aplicación "A" sin afectar a la "B" y viceversa, en el momento en el que reseteemos el AppDomain de la aplicación base, también se resetearán los de las aplicaciones "A" y"B", aunque no tengan nada que ver y aunque estén en AppPools de IIS diferentes.

Esto es importante porque si, por ejemplo, cambiamos un parámetro en el web.config de la aplicación base (el "sitio" de IIS), se reiniciará su dominio de aplicación .NET, y automáticamente estaremos reiniciando los dominios de aplicación de las aplicaciones "A" y "B", aunque este parámetro no tenga influencia alguna sobre ellas y aunque las tengamos en dominios de aplicación de IIS diferentes. De hecho, el reinicio de los AppDomain no implica el reinicio de los AppPool en los que están las aplicaciones. No tiene nada que ver en ese sentido (en el sentido contrario sí, pues reiniciando el AppPool se reinicia el AppDomain).

Suena a trabalenguas, pero en el fondo es muy sencillo.

Como moraleja de la historia me gustaría que te llevaras que una aplicación Web de ASP.NET en IIS se puede reiniciar por que se recicle su AppPoool (que implica el reinicio de los AppDomains), o simplemente porque se resetee su AppDomain, y estas acciones se producen por motivos muy diversos y no relacionados.

Por eso, si tu aplicación web presenta problemas y se te reinicia sin saber muy bien por qué, debes primero averiguar qué es lo que se está produciendo (el reciclaje o el reseteo) y entonces buscar las causas subyacentes con las pistas que te he dado en este texto.

¡Espero que te resulte útil!