Cómo transferir actualizaciones en producción automáticamente

Lanzar la nueva versión en operaciones de combate siempre es un evento nervioso. Especialmente si el proceso involucra muchas operaciones manuales. El factor humano es algo terrible. "Sería bueno automatizar este proceso": esta idea es tan antigua como todo el mundo de TI. Y hay un término para esto: despliegue continuo. Sí, el problema es que no hay una forma única de configurar esta implementación continua. En gran medida, este proceso está vinculado a la pila tecnológica del proyecto y su entorno.

En este artículo quiero compartir la experiencia práctica en la configuración de actualizaciones automáticas del sistema sin interrumpir su funcionamiento para un entorno tecnológico específico, a saber: la aplicación web ASP.NET MVC + Azure SQL + Entity Framework en modo Code First, la aplicación se implementa en Azure como App Service , y el ensamblaje y la implementación se realizan a través de Azure DevOps (anteriormente Visual Studio Team Services).



A primera vista, todo es muy simple, Azure App Service tiene el concepto de ranura de implementación: descargue la nueva versión allí y actívela. Pero sería simple si el proyecto se basara en un DBMS no relacional, en el que no hay un esquema de datos rígido. En este caso, sí, solo la nueva versión capta el tráfico y listo. Pero con un DBMS relacional, todo es algo más complicado.

Los principales factores que nos impiden implementar la implementación continua de nuestra pila de tecnología son los siguientes:

  • La versión anterior de la aplicación no puede funcionar con la nueva estructura de base de datos.
  • La actualización de la estructura de la base de datos puede llevar un tiempo considerable y no siempre es posible usar la aplicación en sí a través del mecanismo de migración automática.

Te lo explicaré. Supongamos que implementa una nueva versión en una ranura paralela o en un centro de datos de respaldo y comienza la aplicación de migraciones. Supongamos que tenemos tres migraciones y, horror, dos han rodado, y la tercera ha caído. En este momento, no pasará nada a los servidores en funcionamiento, Entity Framework no verifica la versión de cada solicitud, pero lo más probable es que no resuelva rápidamente el problema. En este momento, la carga en la aplicación puede aumentar, y la plataforma lanzará una instancia adicional de la aplicación para usted, y ... naturalmente, no se iniciará, ya que la estructura de la base de datos ha cambiado. Una parte importante de los usuarios comenzará a recibir errores. Por lo tanto, el riesgo de la aplicación automática de migraciones es grande.



En cuanto al segundo punto, su migración puede contener algún tipo de comandos cuyo tiempo de ejecución exceda los 30 segundos y el procedimiento estándar expirará. Bueno, además de estos puntos, personalmente no me gusta el hecho de que durante las migraciones automáticas se ve obligado a actualizar parte de la infraestructura a una nueva versión. Y si para un modo con ranuras en Azure esto no da tanto miedo, entonces para un modo con un centro de datos de respaldo, obtienes una parte de la infraestructura con una aplicación que no funciona. Todo es peligroso, disparará en el momento más inoportuno.

Que hacer


Comencemos con lo más difícil: con la base de datos. Por lo tanto, sería bueno actualizar automáticamente la estructura de la base de datos para que las versiones antiguas de la aplicación sigan funcionando. Además, sería bueno tener en cuenta el hecho de que existen tales actualizaciones en las que se puede ejecutar un comando separado durante un tiempo considerable, lo que significa que debemos actualizar la base de datos no utilizando los mecanismos integrados sino ejecutando un script SQL separado. Pregunta: ¿cómo prepararlo? Puedes hacer este manual de proceso. Si tiene un rol de administrador de versiones separado en el equipo, puede forzarlo a ejecutar el comando en Visual Studio:

update-database -script 

Ella generará un script, y esta persona colocará este script en una carpeta de proyecto específica. Pero debe admitir que esto sigue siendo un inconveniente, en primer lugar, el factor humano y, en segundo lugar, dificultades innecesarias si hubiera más de una migración entre versiones. O por alguna razón, se omitió una versión en el sistema de destino. Tendremos que crear una especie de jardín complicado con el seguimiento de las migraciones que ya están allí y las que deben iniciarse. Es difícil y, lo más importante, esta es la misma bicicleta que ya se ha hecho en el mecanismo de migración.

Y será correcto incorporar el proceso de generación y ejecución del script en el proceso de cálculo de la versión. Para generar un script de migración, puede usar la utilidad migrate.exe, que se incluye con Entity Framework. Le llamo la atención sobre el hecho de que necesita Entity Framework versión 6.2 o superior, ya que la opción de generación de script apareció en esta utilidad solo en abril de 2017. La llamada de utilidad se ve así:

 migrate.exe Context.dll /connectionString="Data Source=localhost;Initial Catalog=myDB;User Id=sa;Password=myPassword;" /connectionProviderName="System.Data.SqlClient" /sc /startUpDirectory="c:\projects\MyProject\bin\Release" /verbose 

El nombre del ensamblado se indica donde se encuentra su contexto, la cadena de conexión a la base de datos de destino, el proveedor y, lo más importante, el directorio de inicio, que contiene tanto el ensamblaje con el contexto como el ensamblaje de Entity Framework. No experimente con los nombres del directorio de trabajo, sea más simple. Nos topamos con el hecho de que migrate.exe no podía leer el directorio, en cuyo nombre había espacios y caracteres que no eran letras.

Aquí debemos hacer una digresión importante. El hecho es que después de ejecutar el comando anterior, se generará un solo script SQL que contiene todos los comandos para todas las migraciones que deben aplicarse a la base de datos de destino. Para Microsoft SQL Server, esto no es muy bueno. El hecho es que el servidor ejecuta comandos sin el separador GO como un solo paquete, y algunas operaciones no pueden realizarse juntas en un solo paquete.

Por ejemplo, en algunos casos, agregar un campo a una tabla e inmediatamente crear un índice en esta tabla con un nuevo campo no funciona. Pero esto no es suficiente, algunos comandos requieren ciertas configuraciones de entorno al ejecutar el script. Dichas configuraciones se habilitan de manera predeterminada cuando se conecta a SQL Server a través de SQL Server Management Studio, pero cuando el script se ejecuta a través de la utilidad de consola SQLCMD, deben configurarse manualmente. Para tener todo esto en cuenta, deberá modificar el archivo para generar el script de migración. Para hacer esto, cree una clase adicional al lado del contexto de la fecha, que hace todo lo que necesita:

  public class MigrationScriptBuilder : SqlServerMigrationSqlGenerator { public override IEnumerable<MigrationStatement> Generate(IEnumerable<MigrationOperation> migrationOperations, string providerManifestToken) { var statements = base.Generate(migrationOperations, providerManifestToken); var result = new List<MigrationStatement>(); result.Add(new MigrationStatement { Sql = "SET QUOTED_IDENTIFIER ON;" }); foreach (var item in statements) { item.BatchTerminator = "GO"; result.Add(item); } return result; } } 

Y para que Entity Framework pueda usarlo, regístrelo en la clase de Configuración, que generalmente se encuentra en la carpeta Migraciones:

  public Configuration() { SetSqlGenerator("System.Data.SqlClient", new MigrationScriptBuilder()); …. } 

Después de eso, la secuencia de comandos de migración resultante contendrá GO entre cada instrucción, y al comienzo del archivo contendrá SET QUOTED_IDENTIFIER ON;

Hurra, la preparación está hecha, queda por configurar el proceso en sí. En general, como parte del proceso de lanzamiento en Azure DevOps (VSTS / TFS), esto ya es bastante simple. Necesitamos crear un script de PowerShell como este:

 param ( [string] [Parameter(Mandatory=$true)] $dbserver, [string] [Parameter(Mandatory=$true)] $dbname, [string] [Parameter(Mandatory=$true)] $dbserverlogin, [string] [Parameter(Mandatory=$true)] $dbserverpassword, [string] [Parameter(Mandatory=$true)] $rootPath, [string] [Parameter(Mandatory=$true)] $buildAliasName, [string] [Parameter(Mandatory=$true)] $contextFilesLocation, ) Write-Host "Generating migration script..." $fullpath="$rootPath\$buildAliasName\$contextFilesLocation" Write-Host $fullpath & "$fullpath\migrate.exe" Context.dll /connectionProviderName="System.Data.SqlClient" /connectionString="Server=tcp:$dbserver.database.windows.net,1433;Initial Catalog=$dbname;Persist Security Info=False;User ID=$dbserverlogin;Password=$dbserverpassword;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;" /startUpDirectory=$fullpath /verbose /scriptFile=1.SQL Write-Host "Running migration script..." & "SQLCMD" -S "$dbserver.database.windows.net" -U $dbserverlogin@$dbserver -P $dbserverpassword -d $dbname -i 1.SQL Write-Host "====Finished with migration script====" 

Y agregue la unidad de ejecución de script de PowerShell al proceso de cálculo de lanzamiento. El bloque y su configuración pueden verse así:



La configuración de PowerShell se ve así:



Es importante no olvidar agregar el archivo migrate.exe al proyecto desde la carpeta <YourProject> /packages/EntityFramework.6.2.0/tools/ y configurar la propiedad Copiar siempre para que esta utilidad se copie en el directorio de salida cuando se construya el proyecto y pueda acceder a él en Lanzamiento de Azure DevOps.

El matiz Si su proyecto también usa WebJob cuando se implementa en Azure App Service, agregar Migrate.exe a su proyecto no es seguro. Nos enfrentamos al hecho de que en la carpeta donde se publica su WebJob, la plataforma Azure inicia estúpidamente el primer archivo exe que aparece. Y si su WebJob alfabéticamente cuesta más tarde migrate.exe (y nosotros lo hicimos), ¡entonces intenta ejecutar migrate.exe en lugar de su proyecto!

Entonces, aprendimos cómo actualizar la versión de la base de datos generando un script durante el proceso de lanzamiento, lo simple es: desactivar la verificación de la versión de migración, de modo que si hay fallas en el proceso de ejecución del script, la versión anterior de nuestro código continúa funcionando. Creo que no hay necesidad de decir que sus migraciones no deberían ser destructivas. Es decir Los cambios en la estructura de la base de datos no deberían interferir con el rendimiento de la versión anterior, sino mejor que las dos anteriores. Para deshabilitar la verificación, solo necesita agregar el siguiente bloque a Web.config:

  <entityFramework> <contexts> <context type="<full namespace for your DataContext class>, MyAssembly" disableDatabaseInitialization="true"/> </contexts> </entityFramework> 

Donde full namespace for your DataContext class es el espacio de nombres completo para su descendiente de DbContext, y MyAssembly es el nombre del ensamblado donde reside su contexto.

Y finalmente, es altamente deseable que nos aseguremos de que la aplicación se esté calentando antes de cambiar a los usuarios a la nueva versión. Para hacer esto, agregue un bloque especial a web.config con enlaces que su aplicación se cierra automáticamente durante el proceso de inicialización:

  <system.webServer> <applicationInitialization doAppInitAfterRestart="true"> <add initializationPage="/" hostName="" /> </applicationInitialization> </system.webServer> 

Puede agregar varios enlaces simplemente agregando /> Se argumenta que en Azure, al cambiar de ranura, la plataforma espera a que la aplicación se inicialice y solo luego cambia el tráfico a la nueva versión.

Pero, ¿qué pasa con un proyecto en .NET Core?


Todo es mucho más simple y al mismo tiempo diferente. Se puede generar una secuencia de comandos de migración utilizando herramientas normales, pero no se basa en el ensamblaje terminado, sino en el archivo del proyecto. Por lo tanto, el script debe formarse como parte del proceso de ensamblaje del proyecto y debe incluirse como un artefacto de ensamblaje. En este caso, el script contendrá todos los comandos de todas las migraciones desde el principio de los tiempos. No hay problemas en esto, ya que el guión es idempotente, es decir se puede aplicar a la base objetivo repetidamente sin ninguna consecuencia. Esto tiene otra consecuencia útil: no necesitamos modificar el proceso de generación de scripts para separar los comandos en paquetes, todo ya se ha hecho para esto.

Bueno, específicamente, los pasos del proceso se ven así. En el proceso de compilación, agregue la tarea:



Lo configuramos para generar un archivo con migraciones:



No olvide agregar un script al proyecto de PowerShell que realizará la migración (descrita anteriormente) y el archivo de migración en sí. Como resultado, después de construir el proyecto, los artefactos pueden verse así (además del archivo real con el ensamblado, hay un script PS adicional y un script SQL con migraciones):



Solo queda en el paso de lanzamiento apropiado configurar la ejecución de este script de PowerShell de la misma manera que se describe anteriormente.

Sobre el autor


Pavel Kutakov es un experto en tecnologías en la nube, desarrollador y arquitecto de sistemas de software en diversos sectores comerciales, desde la banca IP que opera en todo el mundo desde EE. UU. Hasta Papua Nueva Guinea, hasta una solución en la nube para el operador de lotería nacional.

Source: https://habr.com/ru/post/es436670/


All Articles