Implementa apps de .NET en Google Cloud

En este artículo, se proporciona una descripción general de cómo implementar apps de .NET en Google Cloud y orientación sobre cómo elegir el método de implementación adecuado para la app.

Introducción

El marco de trabajo de Microsoft .NET proporciona un amplio conjunto de herramientas y bibliotecas para el desarrollo de aplicaciones. Con la llegada de la compatibilidad con Docker en Windows y la capacidad de ejecutar aplicaciones .NET Core en Linux, las aplicaciones .NET ahora también son compatibles con una variedad de objetivos de implementación.

Para que el desarrollo y las pruebas sean eficientes, puedes automatizar la implementación de aplicaciones y convertirla en parte de una canalización de integración continua/entrega continua (IC/EC). Para elegir las herramientas adecuadas y crear una canalización de CI/CD, primero debes identificar cómo ejecutar la app en producción y qué enfoque de implementación deseas usar.

No existe un método que resulte el mejor para implementar una app de .NET en Google Cloud. Las mejores opciones de implementación dependen de la app y tus requisitos. Por ejemplo, si tu app requiere .NET Framework completo o debe ejecutarse en IIS, tu implementación se basará en Windows. Por otro lado, si tu app puede ejecutarse con las funciones que .NET Core admite, tienes la opción de implementar en Linux.

En este artículo, se analizan las distintas formas en que puedes ejecutar apps de .NET e implementarlas en Google Cloud, incluidas las condiciones que hacen que cada opción resulte adecuada. Al final, las opciones de implementación se resumen en un árbol de decisión a fin de ayudarte a decidir qué componentes y métodos de Google Cloud son mejores para la app .NET.

Modelos de implementación

Existen dos formas básicas de realizar una implementación automatizada de una app. El paquete de implementación se envía a los servidores de apps, o estos últimos extraen el paquete de la app de una ubicación conocida. En las siguientes secciones, se analizan las diferencias entre estos dos modelos.

Implementaciones basadas en envíos

En una implementación basada en envíos, el artefacto de implementación (un archivo zip, un paquete NuGet o algún otro artefacto) está disponible en principio solo para un servidor de implementación. El servidor de implementación puede ser una máquina dedicada o una función que asume el sistema de IC.

Para realizar una implementación, un proceso en el servidor de implementación se conecta a un servidor de aplicaciones, copia el artefacto de implementación y luego inicia su instalación. Si hay más de un servidor de aplicaciones, este proceso se repite en paralelo o, más a menudo, en secuencia para que los artefactos se implementen en todos los servidores de aplicaciones.

En el siguiente diagrama, se ilustra este flujo.

Implementaciones basadas en envíos

Existe una variedad de herramientas de administración de configuración que te permiten automatizar las implementaciones de esta manera. Algunas de estas herramientas siguen un enfoque imperativo donde la secuencia de pasos de implementación se define de manera similar a una secuencia de comandos. Aunque este enfoque es intuitivo, es propenso a la desviación de la configuración, es decir, después de un cierto tiempo, los estados de varias máquinas pueden no ser idénticos y es posible que no reflejen por completo tu estado previsto. Por lo tanto, muchas herramientas te permiten definir el estado que deseas y luego descubren por su cuenta los pasos necesarios para llegar a este estado.

En Windows, las herramientas más usadas para este modelo de implementación son las siguientes:

Dentro de las herramientas populares de código abierto, se incluyen Ansible, Chef y Puppet. Aunque estas herramientas están orientadas principalmente a Linux, también son capaces de implementar objetivos de Windows.

Seguridad

Para que el servidor de implementación envíe una implementación a un servidor de aplicaciones, debe haber un canal de respaldo disponible. Por ejemplo, Web Deploy y Octopus Deploy usan un protocolo y un puerto personalizados para esta tarea, mientras que Ansible usa SSH.

Sin importar qué protocolo use la herramienta, es fundamental que la comunicación sea segura a fin de evitar que los atacantes usen el canal de respaldo para implementar aplicaciones maliciosas. Lo más importante es que la comunicación segura requiere que el servidor de implementación pueda autenticarse con el servidor de aplicaciones.

SSH puede usar la autenticación de clave pública. Si usas la configuración de IAM adecuada, puedes dejar que Google Cloud se encargue de forma automática de distribuir la clave pública que se usa para la conexión SSH a los servidores de apps. Sin embargo, si no usas IAM, Google Cloud no puede administrar la clave por ti; debes administrar esta tarea tú mismo.

Una opción es Active Directory. Cuando el servidor de implementación y el servidor de aplicaciones usan Windows y son miembros de un dominio de Active Directory, la autenticación se maneja mediante Kerberos. Sin embargo, a fin de ejecutar un entorno de Active Directory tolerante a errores, se requieren al menos dos instancias de VM adicionales para ejecutar los controladores de dominio. Si tu configuración usa el ajuste de escala automático, todos los servidores también deben unirse de forma dinámica al dominio, lo que demora el proceso de creación de un servidor. El ajuste de escala automático también puede llevar a que se acumulen objetos informáticos obsoletos en el directorio, lo que requiere una lógica de borrado adicional. Si usas Active Directory en un entorno basado en la nube, debes tener en cuenta estos factores adicionales.

Si no se usa Active Directory, la autenticación debe manejarse mediante NTLM o por otros medios, como la autenticación HTTP básica. Ambos enfoques requieren que las credenciales se mantengan sincronizadas entre el servidor de implementación y los servidores de aplicaciones, y que se almacenen de forma segura. Ambas tareas tienen sus dificultades.

Ya sea que uses Linux o Windows, la protección de la comunicación entre los servidores de implementación y de apps requiere mecanismos independientes de IAM. Sin embargo, el uso de múltiples mecanismos para controlar el acceso a los sistemas aumenta la complejidad general y, por lo tanto, aumenta el riesgo de una mala configuración accidental.

Actualizaciones del sistema operativo

Es importante poder implementar de manera eficiente las versiones nuevas de paquetes de aplicaciones en los servidores de aplicaciones, pero también es fundamental hacer mantenimiento del sistema operativo subyacente en esos servidores. Esto significa instalar parches de seguridad. Para flotas de servidores más grandes, debes automatizar este proceso de forma que minimice el riesgo y el número de servidores que no están disponibles durante la actualización.

También puedes usar un enfoque de envíos para las actualizaciones del sistema operativo, en el que el servidor de implementación activa estas actualizaciones en los servidores de aplicaciones. En Linux, es habitual que se use una conexión SSH para ejecutar comandos de actualización de forma remota. En Windows, una opción que se suele usar es PowerShell Remoting (que se basa en WinRM). Para ambos mecanismos, debes poder autenticarte y almacenar credenciales de forma segura.

Ajuste de escala automático

En un entorno estático donde la cantidad de servidores de aplicaciones no cambia, el servidor de implementación conoce de antemano todos los objetivos de implementación. En un entorno de nube, suele ser beneficioso aumentar y reducir el número de servidores de aplicaciones de forma automática. Esto crea dos desafíos cuando usas implementaciones basadas en envíos:

  • Cuando se agrega un servidor de aplicaciones nuevo, regístralo con el servidor de implementación para asegurarte de que el servidor nuevo se incluya en implementaciones futuras.
  • El servidor nuevo necesita recibir su implementación inicial.

El servidor de implementación no inicia un evento de ajuste de escala automático. En su lugar, lo inicia el grupo de instancias administrado subyacente, que funciona a un nivel inferior al del servidor de implementación.

La instancia nueva del servidor de aplicaciones debe registrarse con el servidor de implementación y activar una implementación antes de que el servidor de aplicaciones nuevo pueda entregar solicitudes. En el siguiente diagrama, se ilustra este proceso.

Ajuste de escala automático con implementaciones basadas en envíos

Para que este enfoque funcione, no es suficiente que el servidor de implementación pueda contactarse y autenticarse con los servidores de apps. Los servidores de aplicaciones también deben contactarse y autenticarse con el servidor de implementación.

Por último, el servidor nuevo también debe tener los últimos parches de seguridad del SO. Iniciar una actualización durante el proceso de ajuste de escala automático lo retrasaría mucho. Por lo tanto, la imagen a partir de la cual se crea la VM del servidor de aplicaciones debe tener las actualizaciones ya instaladas. Puedes controlar esto de dos maneras:

  • Mediante las imágenes públicas que proporciona y mantiene actualizadas Google Cloud. Debido a que estas imágenes solo contienen el SO, debes llevar a cabo cualquier personalización (el código de la app, las herramientas y las configuraciones del SO) mediante secuencias de comandos de inicio o como parte de la implementación de la app.
  • Conserva una imagen de SO personalizada y actualizada. Esto te permite aplicar personalizaciones a la imagen, pero aumenta la complejidad general de la administración de tus implementaciones.

La realización de implementaciones basadas en envíos es intuitiva, pero puede llevar a una complejidad sustancial si se tienen en cuenta la seguridad, las actualizaciones del SO y el ajuste de escala automático. En la siguiente sección, se abordan las implementaciones basadas en extracciones, que son el enfoque más común para las implementaciones en la nube.

Implementaciones basadas en extracciones

En las implementaciones basadas en extracciones, estas se realizan de manera indirecta. Una vez que el sistema de IC produce una versión nueva de un artefacto de implementación, lo publica en un repositorio. En el siguiente diagrama, se ilustra este flujo.

Implementaciones basadas en extracciones

Cuando se realiza una implementación (que puede ser inmediatamente después de publicar el artefacto o en una etapa posterior), es el servidor de implementación el que la activa. Una vez más, el servidor de implementación puede ser un sistema separado o una función que asume el sistema de IC. La activación de la implementación implica conectarse al servidor de aplicaciones para que extraiga y luego instale el artefacto de implementación desde el repositorio central.

Aunque las diferencias entre un modelo basado en envíos y uno basado en extracciones al principio pueden parecer menores, realizar una implementación basada en extracciones tiene algunas implicaciones importantes:

  • No es necesario que la activación de un servidor de apps para extraer un artefacto de implementación ocurra a nivel de la app o del SO. En su lugar, el servidor de implementación puede activar la operación de extracción si le indica a Compute Engine que reinicie o reemplace la VM. Esto puede evitar los problemas de seguridad que se asocian con las implementaciones basadas en envíos.
  • En lugar de solo contener archivos de la app, el artefacto de implementación puede ser una imagen de Docker o de VM, de modo que se puede unificar el proceso de actualización de la app y del SO.

Seguridad

El servidor de implementación no necesita interactuar con el servidor de aplicaciones en absoluto para ciertos tipos de implementaciones. Por ejemplo, no es necesaria ninguna interacción si el artefacto de implementación es cualquiera de los siguientes:

  • Una imagen de VM.
  • Una imagen de Docker que se implementará en Google Kubernetes Engine.
  • Un paquete que se implementará en App Engine

En cambio, el servidor de implementación solo necesita interactuar con las API de Google Cloud para iniciar la implementación. A su vez, esto significa que el proceso de implementación puede depender de los mecanismos de autenticación que proporciona IAM, lo que quita la necesidad de administrar claves o credenciales.

Cuando usas artefactos de implementación como los paquetes zip o NuGet, que contienen solo los archivos y objetos binarios de la app, puedes activar una implementación de las siguientes maneras:

  • Si el servidor está configurado para extraer e instalar el artefacto de implementación más reciente cuando se inicia el sistema operativo, puedes activar una actualización si haces que Google Cloud reinicie la VM. Aunque un reinicio puede implicar una pérdida de tiempo innecesaria, evita que el servidor de implementación tenga que autenticarse con el servidor de apps.
  • Al igual que con las implementaciones basadas en envíos, el servidor de implementación puede activar la actualización de forma remota a través de un canal de respaldo. Sin embargo, este enfoque está sujeto a las mismas implicaciones y desafíos de seguridad de la administración de credenciales que las implementaciones basadas en envíos.
  • El servidor de implementación puede ejecutar un agente que busque artefactos de implementación nuevos en el repositorio. Cuando se detecta un artefacto nuevo, el servidor puede aplicarlo de manera automática. Un problema potencial es que varios servidores de aplicaciones podrían terminar instalando actualizaciones de forma simultánea y, por lo tanto, no estar disponibles. Para evitar esto, el agente puede rastrear el estado del servidor en el repositorio y usar esta información de estado del servidor para implementar actualizaciones de manera controlada.

En cada uno de estos casos, asegúrate de controlar el acceso de escritura al repositorio para evitar que los servidores extraigan paquetes maliciosos y los instalen.

Actualizaciones del sistema operativo

Cuando las imágenes de Docker o de VM se usan como artefactos de implementación, estos artefactos combinan las dependencias y los archivos de la app. Esto te permite usar el mismo mecanismo de implementación para actualizar el sistema operativo y la app. En este caso, debes asegurarte de que se pueda compilar y publicar un artefacto de implementación nuevo para dos casos distintos. En el primer caso, hay una versión nueva de la app disponible. El segundo caso es cuando hay actualizaciones de seguridad nuevas para el sistema operativo y otras dependencias.

En otros casos, cuando el artefacto de implementación solo contiene los archivos de la app, mantener el sistema operativo actualizado es una tarea aparte. Por lo tanto, existen las mismas implicaciones analizadas en el contexto de las implementaciones basadas en envíos.

Ajuste de escala automático

Hacer que los servidores de aplicaciones extraigan artefactos de implementación evita gran parte de la complejidad que surge de la combinación de las implementaciones basadas en envíos con el ajuste de escala automático, y se adapta muy bien a este último. Cada vez que se inicia un servidor de apps nuevo debido a un evento de ajuste de escala automático, el servidor contacta al repositorio, extrae el paquete de implementación más reciente y lo instala.

Si usas imágenes de VM o de Docker, GCP proporciona los mecanismos para extraerlas. Si usas otros paquetes, como los archivos ZIP o NuGet, debes configurar los servidores de apps para que comiencen una implementación después del inicio. Para hacer esto, puedes personalizar la imagen de VM o usar secuencias de comandos de inicio.

Objetivos de implementación

Antes, las aplicaciones .NET solo se ejecutaban en Windows, el cual no admitía contenedores. Esto no te dejaba elegir en qué entorno ejecutar tu app.

Con la llegada de .NET Core, puedes elegir ejecutar una app en Windows o en Linux. Además, debido a que ambos sistemas operativos son compatibles con los contenedores, ahora puedes elegir entre múltiples entornos.

Sistema operativo

Aunque Mono ha ofrecido una forma de implementar aplicaciones .NET en plataformas distintas de Windows durante muchos años, no fue hasta el lanzamiento de .NET Core que Linux se convirtió en una plataforma totalmente compatible con la pila de desarrollo de Microsoft.

.NET Core proporciona solo un subconjunto de las capacidades de .NET Framework. Por lo tanto, la orientación a .NET Core impone ciertas restricciones en las apps. Más importante aún para las aplicaciones existentes, la migración de .NET Framework a .NET Core no siempre es fácil y rentable; en ciertos casos, podría ser imposible.

Por lo tanto, una decisión fundamental al momento de elegir un modelo de implementación y una orientación es si usar Linux (que requiere .NET Core) o Windows (que admite .NET Core y .NET Framework).

Ejecutar aplicaciones .NET en Linux ofrece los siguientes beneficios potenciales:

  • Puedes usar el entorno flexible de App Engine, un entorno completamente administrado.
  • Puedes usar GKE, un entorno administrado que admite la organización de contenedores.
  • Puedes evitar el costo adicional de las imágenes premium de Compute Engine asociadas con las licencias de Windows.

Debes considerar estos beneficios frente a los siguientes inconvenientes potenciales de usar .NET Core en Linux:

  • Los posibles ahorros en costos podrían terminar contrarrestados por el esfuerzo requerido para migrar una app de .NET existente a .NET Core. También podría ser imposible migrar una app de .NET existente a .NET Core, como ya se indicó.
  • Linux no es compatible con IIS. Kestrel, el servidor web de .NET Core, presenta un rendimiento muy bueno, pero no ofrece el mismo conjunto de funciones que IIS. Por lo tanto, es posible que tengas que usar Kestrel junto con un servidor web como Nginx.
  • Los servicios de Windows no tienen equivalentes directos en Linux. Aunque, por lo general, tienes la opción de convertir los servicios de Windows en aplicaciones de consola de Linux que pueden ejecutarse como un daemon, esta conversión podría ser difícil.
  • La solución de problemas y la depuración de aplicaciones .NET Core en Linux requieren herramientas y habilidades diferentes a las que usas en .NET en Windows. Esto puede resultar desafiante si tu equipo tiene experiencia limitada con Linux.

Contenedores

Los contenedores se prestan muy bien para aplicaciones que se ejecutan en un solo proceso. Estos son algunos ejemplos:

  • Servicios de Windows
  • Aplicaciones de consola de Linux que actúan como daemons
  • Servicios WCF que se alojan a sí mismos
  • Apps de API web o ASP.NET MVC alojadas en Kestrel

Muchas aplicaciones .NET se orientan a IIS. Por lo general, se usa para administrar aplicaciones múltiples (en grupos de aplicaciones y directorios virtuales separados) y, por lo tanto, podría diferir del patrón de proceso único.

Cuando migras una configuración basada en IIS a un contenedor, puedes adoptar diferentes métodos:

  • Coloca IIS, con todos los directorios y grupos virtuales, en una sola imagen de Docker basada en Windows con la imagen microsoft/iis como base. A menos que las aplicaciones estén estrechamente vinculadas, por lo general, se recomienda evitar este método, ya que no permite que las aplicaciones se actualicen ni se implementen por separado.
  • Usa imágenes de Docker basadas en Windows independientes para cada app, y que cada una ejecute IIS. Esto garantiza que puedas administrar las apps de forma independiente. Sin embargo, IIS genera una sobrecarga que puede llegar a ser significativa si necesitas operar una gran cantidad de estos contenedores.
  • Migra algunas o todas las aplicaciones de IIS a Kestrel. Debido a que Kestrel se puede implementar en un contenedor basado en Windows o en un contenedor Docker basado en Linux, este enfoque te permite administrar contenedores de forma individual.

IIS permite que múltiples aplicaciones web se ejecuten en un solo sitio web y compartan un solo nombre de dominio. Cuando empaquetas aplicaciones en contenedores separados, puedes obtener la misma funcionalidad con el balanceo de cargas basado en el contenido. De manera similar, un balanceador de cargas HTTP de Google hace que no sea necesario implementar un proxy inverso personalizado frente a los servidores Kestrel.

La mayoría de las aplicaciones se pueden dividir en contenedores; son muy pocas las que no pueden. Sin embargo, algunas situaciones de creación de contenedores presentan desafíos:

  • Para las aplicaciones administradas por IIS, es común que la implementación de la app ya esté automatizada. Sin embargo, los pasos para configurar IIS (crear grupos de aplicaciones, vinculaciones, etc.) se llevan a cabo de forma manual. Cuando realizas la transferencia a contenedores, también tienes que automatizar todos estos pasos iniciales.
  • Las aplicaciones que se basan en archivos de configuración o en datos que se encuentran en discos pueden requerir cambios. Por ejemplo, la información de configuración se puede obtener de las variables de entorno y los archivos y carpetas relevantes se pueden activar como un volumen. Esto mantiene la imagen sin estado y libre de una configuración específica del entorno.

Por último, si usas contenedores de Docker basados en Windows, ten en cuenta que Google Cloud, en la actualidad, no es compatible con Hyper-V, por lo que no permite ejecutar contenedores de Hyper-V. Por lo tanto, solo puedes implementar contenedores de Windows Server en Google Cloud. Los contenedores de Windows Server son más livianos que los de Hyper-V y ofrecen un nivel diferente de aislamiento.

Restricciones de implementación

Algunos factores de la compilación de tu app pueden imponer restricciones sobre el enfoque de implementación que debes usar, como se explica en esta sección.

Arquitectura de apps

Un factor clave que debes considerar cuando eliges el destino y el modelo de implementación es la arquitectura de la app. Por un lado, una app puede seguir un patrón de arquitectura monolítica, en el que toda la lógica de la app se implementa en una sola base de código y se ejecuta en un único proceso o grupo de apps IIS. Por otro lado, una app puede seguir un patrón de microservicios. En este enfoque, la app consiste de una serie de servicios que se ejecutan de forma independiente en procesos, grupos de aplicaciones IIS o servicios de Windows separados.

Por último, puedes tener varias aplicaciones independientes que se implementan con una estrategia uniforme, en la que cada app podría ser monolítica. Para los fines de esta discusión, este enfoque puede considerarse equivalente al de microservicios.

En una arquitectura de microservicios, la app debe ejecutarse de manera rentable y, a la vez, mantener los servicios aislados y administrables de forma independiente. Puedes asignar VM dedicadas para cada servicio, lo que garantiza que cada uno se pueda implementar y administrar de forma individual. Sin embargo, este enfoque puede dar como resultado un gran número de VM con poco uso, lo que genera costos innecesarios. Para aplicaciones como estas, es probable que los modelos de implementación que permiten empaquetados más compactos (en particular, los modelos basados en contenedores) sean más rentables.

Estado y falta de estado

Cuando diseñas aplicaciones para la nube, intenta mantenerlas sin estado y administra el estado de forma externa con un servicio de almacenamiento basado en GCP. Las aplicaciones sin estado ofrecen ciertas ventajas, incluidas las siguientes:

  • Se pueden implementar de forma redundante para aumentar la disponibilidad y la capacidad.
  • Las solicitudes se pueden distribuir libremente entre las instancias.
  • Se prestan bien al ajuste de escala automático.
  • En caso de fallo, el entorno (ya sea contenedor o VM) puede recrearse sin riesgo de pérdida de datos.

Diseñar aplicaciones para que no tengan estado no siempre es fácil, y muchas aplicaciones antiguas no siguen esta práctica. Sin embargo, vale la pena analizar si puedes hacer que una app no tenga estado.

Estado de sesión

Las aplicaciones ASP.NET y ASP.NET MVC por lo general usan sesiones para rastrear el estado del usuario, lo que hace que la app tenga estado. Sin embargo, hay varias opciones para limitar el impacto de las sesiones:

  • Si la cantidad de datos de la sesión es pequeña, puedes almacenar el estado en una cookie encriptada o firmada.
  • En lugar de usar el proveedor de estado de sesión InProc predeterminado, puedes usar el proveedor SQLServer. Sin embargo, esto requiere una instancia de SQL Server, que genera un costo adicional y puede afectar la latencia y la disponibilidad de la app.
  • Puedes aprovechar la afinidad de sesión en Cloud Load Balancing. Esta característica garantiza que todas las solicitudes de un solo cliente se enruten a la misma instancia de la app. Sin embargo, el uso de la afinidad de sesión puede tener un impacto negativo en la equidad del balanceo de cargas; es decir, ciertas instancias de aplicaciones pueden terminar recibiendo más solicitudes que otras. Además, si una instancia de la app se cierra por algún motivo, se perderán las sesiones que maneja la instancia, lo que podría causar un impacto en el usuario final. Por lo tanto, confiar en la afinidad de sesión no es una solución ideal, pero a menudo puede ser un compromiso viable entre la solidez y el costo.

Cachés en memoria

Las aplicaciones suelen usar cachés en memoria para evitar búsquedas en bases de datos o cálculos redundantes. Esto se vuelve problemático si varias instancias de la app se ejecutan de forma simultánea, porque las cachés pueden volverse incoherentes.

Para evitar incoherencias, usa una caché distribuida, ya sea directamente o mediante la interfaz IDistributedCache. Por lo general, los servidores de almacenamiento en caché como Redis o Memcached tienen demandas de recursos relativamente bajas, pero agregan complejidad a la configuración general.

Almacenamiento

Los datos en forma de imágenes, archivos adjuntos o archivos multimedia por lo general se almacenan en disco. El uso de un disco persistente en una VM para este propósito no suele ser opción, ya que evita que los datos se compartan entre varias máquinas y corre el riesgo de que se pierdan datos si se recrea una instancia de VM. En su lugar, puedes usar uno de los enfoques siguientes:

  • Transfiere los datos a un servidor de archivos compartidos. De este modo, se minimiza el impacto en la app. Sin embargo, operar un servidor SMB o NFS con alta disponibilidad implica un costo adicional y más esfuerzo de mantenimiento.
  • Transfiere los datos a Cloud Storage. Aunque esto requiere cambios en la app, Cloud Storage tiene alta disponibilidad, es mucho más económico que ejecutar un servidor de archivos y no requiere ningún trabajo de mantenimiento adicional.

Estrategias de implementación

Cuando implementas una versión nueva de una app, debes minimizar el riesgo y el impacto en el usuario final. Las tres estrategias más comunes para lograr esto son Recrear, Azul-verde y las Implementaciones progresivas.

Estrategia “Recrear”

La idea de esta estrategia es detener la app en ejecución en todos los servidores, implementar una versión nueva de ella e iniciarla. Esta estrategia presenta una desventaja evidente, que consiste en causar una interrupción del servicio, pero se evitan posibles problemas que pueden surgir cuando dos versiones diferentes de una app coexisten y acceden a datos comunes.

Estrategia “Azul-verde”

La idea de la estrategia Azul-verde (también conocida como Rojo-negro) es implementar una versión nueva de la app en un conjunto nuevo de servidores. Cuando se completa la implementación, se transfiere todo el tráfico del conjunto de servidores antiguo al nuevo. Este enfoque requiere de forma temporal hasta el doble de servidores que se necesitan para la producción, pero evita la interrupción del servicio.

Un requisito previo para esta estrategia es que dos versiones de una app puedan coexistir de manera temporal y no interferir entre sí. Para las aplicaciones que acceden a bases de datos, esto requiere que cada iteración de cambios en los esquemas de base de datos sea compatible al menos con la versión anterior.

Estrategia “Implementaciones progresivas”

La idea de una implementación progresiva es actualizar un servidor tras otro. Al igual que con la estrategia Azul-verde, esto significa que, durante un cierto tiempo, coexisten dos versiones diferentes de una app. Sin embargo, a diferencia de la implementación Azul-verde, el tráfico se transfiere de la versión anterior a la nueva de forma gradual. A medida que se actualizan más servidores, se enrutan más usuarios a la versión nueva hasta que todos la usan cuando se actualiza el último servidor. Un beneficio clave de este enfoque es que los problemas potenciales se pueden detectar temprano, antes de que todos los usuarios se vean afectados, lo que ayuda a reducir el riesgo general.

Debido a que las implementaciones progresivas requieren que coexistan dos versiones de la app, esta estrategia a menudo también requiere una configuración de balanceador de cargas que evite el rebote de usuarios entre versiones.

Opciones de implementación

Hasta ahora, en este artículo se analizaron los modelos, los objetivos y las estrategias de implementación. En las siguientes secciones, se analizan las opciones específicas para implementar apps .NET en Google Cloud.

Entorno flexible de App Engine (Linux)

El entorno flexible de App Engine proporciona un entorno de plataforma como servicio (PaaS) para aplicaciones .NET Core. Debido a que el entorno flexible de App Engine se basa en Linux, solo es útil para aplicaciones de .NET Core.

El entorno flexible de App Engine usa contenedores de forma interna para ejecutar y escalar aplicaciones, pero te libera de tener que crear o administrar imágenes de contenedores. En su lugar, los binarios de la app se pueden implementar directamente. Mediante el uso de servicios, el entorno flexible de App Engine te permite ejecutar apps que se desglosan en varios microservicios más pequeños.

El entorno flexible de App Engine puede escalar de forma automática la cantidad de instancias de la app según la carga, y se presta atención a cualquier límite que hayas configurado para la app. De forma predeterminada, se mantiene un mínimo de dos instancias, pero puedes cambiarlo.

El entorno flexible de App Engine resulta muy adecuado para apps sin estado. Aunque puedes inhabilitar el ajuste de escala automático para adaptarse a las aplicaciones con estado, esto implica que no se aprovecharán muchos de los beneficios del entorno administrado. Además, aunque el entorno flexible de App Engine permite el acceso al disco, los discos se consideran efímeros y, por lo tanto, no son útiles para el seguimiento del estado persistente.

Para cada instancia de una app, el entorno flexible de App Engine mantiene una VM dedicada. Debido a que los precios se basan en la cantidad de VM en ejecución, el entorno flexible de App Engine es más rentable cuando la app se usa en gran medida. Sin embargo, cuando se accede a las apps con poca frecuencia, es posible que las VM subyacentes se usen muy poco, lo que, a su vez, puede hacer que el entorno flexible de App Engine sea menos rentable que otras opciones de implementación, especialmente GKE.

Implementación basada en extracciones con la herramienta de línea de comandos de gcloud

La forma más común de implementar en el entorno flexible de App Engine es usar la herramienta de línea de comandos de gcloud. Esta herramienta primero se usa para publicar artefactos de implementación en un repositorio mantenido en Cloud Storage. Cada vez que se inicia una instancia, los artefactos se extraen de este repositorio. Las implementaciones usan la estrategia Azul-verde de forma predeterminada.

Cada implementación se identifica por un número de versión. En caso de problemas, una implementación puede revertirse a una versión anterior. Mediante la división del tráfico, el entorno flexible de App Engine también te permite ejecutar múltiples versiones de la app en paralelo y dirigir una parte determinada del tráfico a cualquiera de ellas. Esto te permite realizar implementaciones canary o pruebas A/B con un esfuerzo mínimo.

GKE (Windows o Linux)

GKE proporciona un entorno Kubernetes completamente administrado. Las funciones de organización de Kubernetes hacen que GKE sea ideal para ejecutar aplicaciones de microservicios complejas que constan de muchos contenedores. Sin embargo, incluso para las apps que no siguen el patrón de microservicios, GKE te permite ejecutar muchos contenedores en una infraestructura compartida de una manera eficiente en cuanto a recursos y simple de mantener.

GKE requiere que todas las partes de la app se empaqueten como contenedores Docker. Los contenedores basados en Linux requieren el uso de .NET Core y un entorno basado en Linux. La compilación de contenedores en Linux puede resultar un desafío si tu sistema de CI está basado en Windows. Sin embargo, Azure Pipelines o Team Foundation Server y Cloud Build ofrecen compatibilidad integrada con la compilación de aplicaciones .NET Core, además de la compilación y publicación de imágenes de contenedores basados en Linux.

GKE ofrece la mayor flexibilidad para aplicaciones sin estado. Mediante el uso de conjuntos con estado y volúmenes persistentes, también puedes ejecutar ciertos tipos de aplicaciones con estado en GKE.

Un clúster GKE incluye una serie de instancias de VM, denominadas nodos, en las que se programan los contenedores. En un clúster multizona o regional, GKE puede distribuir nodos y cargas de trabajo en varias zonas para garantizar una alta disponibilidad.

El precio se basa en el número de nodos que se ejecutan. Por lo tanto, GKE es más rentable cuando los nodos se usan bien. Puedes ejecutar cargas de trabajo más grandes en el mismo clúster o mediante el ajuste de escala automático en el número de nodos según sea necesario.

Implementación basada en extracciones con comandos kubectl

Implementar una app en GKE implica dos pasos:

  1. Publica imágenes de Docker en Container Registry o en un registro de Docker externo mediante docker push o con otros medios. Por lo general, el sistema de CI se encarga de este paso.
  2. Activa la implementación mediante kubectl. De este paso se puede encargar el sistema de CI o se puede llevar a cabo de forma independiente. Debido a que la implementación se inicia de forma remota, no importa si kubectl se ejecuta en Linux o Windows.

GKE tiene compatibilidad integrada en la estrategia de recreación y en la estrategia de implementación progresiva. Aunque las primitivas que controlan las implementaciones son lo bastante flexibles para permitir otras estrategias de implementación, usar una estrategia diferente requiere herramientas o secuencias de comandos adicionales.

Implementación basada en extracciones con Spinnaker

Si las capacidades integradas de organización de implementaciones de GKE no son suficientes para tu objetivo, puedes combinar GKE con Spinnaker. Spinnaker tiene compatibilidad integrada con GKE y te permite usar estrategias de implementación más avanzadas, como la Azul-verde.

Como Spinnaker no es un servicio administrado, debes implementarlo y mantenerlo por separado. Puedes implementar Spinnaker en instancias de VM de Linux independientes o en un clúster de GKE.

Compute Engine (Windows o Linux)

Compute Engine te permite crear y administrar instancias de VM. Admite una variedad de versiones de Windows Server y distribuciones de Linux, además de varios tamaños y opciones de configuración. Debido a esta flexibilidad, puedes usar instancias de VM de Compute Engine para una amplia gama de cargas de trabajo.

Para garantizar que las aplicaciones se implementen y mantengan de forma individual, implementa solo una app o servicio para cada instancia de VM. Para garantizar una alta disponibilidad, ejecuta al menos dos instancias de VM por app, cada una ubicada en una zona diferente. Por lo tanto, puedes suponer que necesitas el doble de instancias de VM que el número de aplicaciones o servicios que deseas implementar, sin importar la carga prevista.

Compute Engine brinda una forma sencilla de implementar el ajuste de escala automático a través de grupos de instancias administrados. Los grupos de instancias administrados también proporcionan una forma de aplicar implementaciones progresivas, como se explica más adelante en este artículo.

Debido a que Compute Engine se cobra por instancia de VM, puedes suponer que ejecutar aplicaciones en Compute Engine es más rentable cuando las aplicaciones reciben una carga considerable, lo que implica un alto uso de las instancias de VM. En contraste, si la cantidad de servicios y aplicaciones es grande, pero el uso promedio es bajo, otras opciones de implementación, como GKE, suelen ser más económicas, ya que permiten que múltiples aplicaciones usen una infraestructura común sin sacrificar el aislamiento de las cargas de trabajo.

La ejecución de instancias de VM de Windows requiere que uses imágenes premium. Estas imágenes contienen copias con licencia de Windows, lo que implica tarifas adicionales. Como resultado, las VM de Windows por lo general son menos rentables que las que usan distribuciones de Linux como CentOS o Debian, que no implican ninguna tarifa de licencia.

Puedes usar SSH o RDP a fin de configurar una instancia de VM de forma manual, ya sea con el objetivo de implementar una app de forma manual o de manejar cualquier configuración inicial necesaria a fin de preparar una máquina para una primera implementación. Sin embargo, esto puede llevar a que las máquinas tengan configuraciones únicas, que difieren de otras instancias de VM. A largo plazo, la configuración manual de una instancia de VM puede ser complicada y requerir mucho trabajo. Por lo tanto, es aconsejable automatizar el proceso para hacerlo repetible.

La automatización de implementaciones de aplicaciones en Compute Engine incluye las tareas siguientes:

  1. Aprovisionamiento y preparación de instancias de VM para una primera implementación de la app.
  2. Realización de una implementación de la app.
  3. Mantenimiento del SO (instalación de actualizaciones de seguridad).

En las dos secciones siguientes, se describe cómo puedes manejar estos tres pasos de manera unificada con un enfoque de implementaciones basadas en extracciones. Si bien los mecanismos y las herramientas difieren según los enfoques descritos en estas secciones, la idea general es similar a cómo se implementa una app basada en contenedores con GKE.

Implementación basada en extracciones con un grupo de instancias administrado

Los grupos de instancias administrados por lo general se usan para implementar el ajuste de escala automático, pero también brindan una forma de manejar implementaciones progresivas. Después de que se crea una plantilla de instancias que hace referencia a la versión nueva de la app, se puede usar la función de reemplazo progresivo para reemplazar instancias de VM que usan la plantilla antigua por instancias que usan la plantilla nueva.

Un requisito previo para este enfoque es que la versión nueva de la app esté disponible como una plantilla de instancias. Puedes lograr esto de dos maneras:

  • Define una plantilla de instancias que use una de las imágenes de SO públicas. Usa una secuencia de comandos de inicio para configurar el sistema y luego instalar la app desde un bucket de Cloud Storage, un repositorio de NuGet, un registro de Docker o alguna otra fuente. En el siguiente diagrama, se ilustra este enfoque.

    Implementaciones basadas en extracciones mediante un grupo de instancias administrado y una imagen pública

  • Crea una imagen de VM personalizada como parte del proceso de CI/CD, un proceso que a menudo se conoce como preparación de imágenes. Con este método, se usa una de las imágenes de SO públicas para generar una instancia de VM nueva, instalar la app más reciente en ella, crear una imagen de VM a partir de la instancia y hacer que la imagen esté disponible en el proyecto de Google Cloud. Todo este proceso se puede automatizar por completo mediante una herramienta como Packer. La imagen resultante luego se puede referenciar en una plantilla de instancias. En el siguiente diagrama, se ilustra este enfoque.

    Implementaciones basadas en extracciones mediante un grupo de instancias administrado y una imagen personalizada

Un inconveniente de crear una imagen personalizada (la segunda opción) es que la preparación de imágenes es un proceso bastante lento, que a menudo toma varios minutos. Por lo tanto, el enfoque no solo agrega complejidad al proceso de IC/EC, sino que también lo demora. La ventaja es que iniciar VM nuevas con una imagen personalizada es un proceso simple y rápido, lo cual es beneficioso cuando se usa el ajuste de escala automático.

El uso de secuencias de comandos de inicio para implementar la app (la primera opción) tiene las ventajas y desventajas opuestas. No agrega la sobrecarga de la preparación de imágenes al proceso de IC/EC, pero demora el proceso de creación de instancias de VM. Además, si la secuencia de comandos de inicio no es por completo confiable o si los sistemas de donde se descargan los archivos binarios de la app no tienen alta disponibilidad, este enfoque puede causar una menor disponibilidad.

El enfoque más adecuado para tu app depende de la app en sí y de la complejidad de la configuración. En algunas situaciones, incluso puede ser mejor combinar ambos enfoques:

  • Una imagen personalizada contiene toda la configuración y las dependencias, pero no los archivos binarios de la app. Se prepara una imagen nueva cuando la configuración o cualquiera de las dependencias cambian, pero no con cada compilación de la app. Esto ayuda a evitar demoras en la canalización de IC/EC de la app.
  • La app se instala con una secuencia de comandos de inicio. Para minimizar el riesgo y las demoras, este proceso debe ser lo más simple posible.

En una situación en la que deseas implementar muchas aplicaciones o servicios diferentes que tienen una configuración base común, este enfoque híbrido puede evitar la compilación y el mantenimiento de decenas o cientos de imágenes casi idénticas.

Puedes usar grupos de instancias administrados a fin de organizar implementaciones para cargas de trabajo de Linux y Windows. En Linux, el uso de grupos de instancias administrados para implementar contenedores de Docker en instancias de VM es posible y es compatible con la plataforma. Sin embargo, solo se recomienda para aplicaciones muy usadas. En otros casos, la implementación de un solo contenedor de Docker por VM no brinda ventajas significativas en comparación con el uso de GKE o el entorno flexible de App Engine.

Si usas contenedores de Windows Server, sigue las siguientes pautas para ejecutar los contenedores con Compute Engine y los grupos de instancias administrados:

  • Usa una imagen personalizada con Docker preinstalado o una de las siguientes imágenes públicas:
    • Windows Server 2019 Datacenter Core for Containers
    • Windows Server 2019 Datacenter for Containers
  • Usa una secuencia de comandos de inicio para extraer la imagen de Docker y, luego, iniciarla como un contenedor de Windows Server durante el inicio de la VM. Puedes usar las asignaciones de puertos adecuadas para exponer los servicios que se ejecutan dentro del contenedor.

Ten en cuenta que no se garantiza que una secuencia de comandos de inicio solo se ejecute después de iniciar el servicio Docker. Para manejar de forma adecuada el caso en el que la secuencia de comandos se ejecute antes de que Docker esté disponible, incorpora la lógica de reintento adecuada en la secuencia.

Cuando creas imágenes basadas en Windows en un entorno que no está en la nube, puedes usar Microsoft Deployment Toolkit (MDT) o Windows Deployment Services (WDS). Sin embargo, debido a que la administración de imágenes y la creación de instancias de VM con base en imágenes personalizadas son características principales de Compute Engine, esta herramienta adicional no es necesaria. Compute Engine no solo admite secuencias de comandos de inicio, sino también secuencias de comandos de especialización para instancias de VM basadas en Windows. Por lo tanto, no suele ser necesario trabajar con archivos unattend.xml personalizados. Sin embargo, sigue siendo importante que una instalación de Windows se generalice mediante GCESysprep antes de crear una imagen.

Implementación basada en extracciones con Spinnaker

Los grupos de instancias administrados proporcionan una forma ligera y robusta de aplicar implementaciones progresivas, pero sus capacidades pueden ser insuficientes para ciertas aplicaciones. Para implementar canalizaciones y estrategias de implementación más sofisticadas, puedes usar Spinnaker.

El enfoque básico que adopta Spinnaker para organizar las implementaciones en Compute Engine es similar al que se analizó en la sección anterior, es decir, también se basa en la preparación de imágenes. Por lo tanto, aplican las mismas consideraciones.

Debido a que Spinnaker no es un servicio administrado, debes implementarlo y mantenerlo por separado de la app. Puedes implementar Spinnaker en distintas instancias de VM de Linux o en un clúster de GKE.

Implementación remota basada en envíos

Las opciones de implementaciones basadas en extracciones que se analizaron en las secciones anteriores ofrecen una variedad de beneficios. Sin embargo, no son adecuadas para todo tipo de apps. En particular, las apps con estado suelen no adaptarse bien a este método y podrían ser más adecuadas para uno basado en envíos.

En el enfoque basado en envíos, las tres tareas de implementación (el aprovisionamiento de instancias de VM, la implementación de la app y el mantenimiento del SO) deben llevarse a cabo de manera individual. Es posible usar la misma herramienta para las tres tareas, pero es común que cada una se realice con herramientas diferentes.

Puedes aprovisionar las instancias de VM del servidor de apps de la misma manera en que se aprovisionan otras infraestructuras mediante herramientas de automatización, como Terraform. Puedes usar secuencias de comandos de inicio o de especialización para instalar las herramientas necesarias a fin de automatizar la implementación de la app. Por ejemplo, si usas Puppet, Octopus Deploy o Chef, debes asegurarte de que el software de agente para estas herramientas esté instalado.

Desde una perspectiva de seguridad, para reducir la superficie de ataque, asegúrate de que cualquier comunicación entre el servidor de implementación y los agentes que se ejecutan en las instancias de VM del servidor de aplicaciones use la red interna. También asegúrate de que los puertos que se usan no estén expuestos a la Internet pública.

En un entorno en el que no se usa el ajuste de escala automático, unir los servidores de aplicaciones basados en Windows a un dominio de Active Directory es una forma viable de centralizar la configuración. El uso de Active Directory también te permite controlar tareas de administración como el mantenimiento del SO.

Elige una opción de implementación

Como se mencionó al comienzo de este artículo, no existe un método que resulte el mejor para implementar una app de .NET en Google Cloud. Las mejores opciones de implementación dependen de la app y tus requisitos. Para elegir el modelo correcto, uno de los primeros pasos es elegir entre .NET Core o .NET Framework y, en función de esa elección, decidir si se implementará en Linux o en Windows. Una vez que hayas identificado el sistema operativo de destino, usa los esquemas de decisiones siguientes para ayudarte a identificar un modelo de implementación adecuado.

Para implementar apps de .NET Core en Linux, consulta el siguiente esquema de decisiones:

Árbol de decisión para implementar mediante .NET Core y Linux

Para implementar una app de .NET Core o .NET Framework en Windows, consulta el siguiente esquema de decisiones:

Árbol de decisión para implementar mediante Windows

¿Qué sigue?