Le lancement de la nouvelle version en opération de combat est toujours un événement nerveux. Surtout si le processus implique de nombreuses opérations manuelles. Le facteur humain est une chose terrible. «Ce serait bien d'automatiser ce processus» - cette idée est aussi ancienne que tout le monde informatique. Et il y a un terme pour cela - Déploiement continu. Oui, le problÚme est qu'il n'existe aucun moyen unique de configurer ce déploiement continu. Ce processus est en grande partie lié à la pile technologique du projet et de son environnement.
Dans cet article, je souhaite partager une expérience pratique de la configuration des mises à jour automatiques du systÚme sans interrompre son fonctionnement pour un environnement technologique spécifique, à savoir: Application Web ASP.NET MVC + Azure SQL + Entity Framework en mode Code First, l'application est déployée dans Azure en tant que App Service et l'assemblage et le déploiement sont effectués via Azure DevOps (anciennement Visual Studio Team Services).

à premiÚre vue, tout est trÚs simple, Azure App Service a le concept d'emplacement de déploiement - téléchargez-y la nouvelle version et allumez-la. Mais ce serait simple si le projet était basé sur un SGBD non relationnel, dans lequel il n'y a pas de schéma de données rigide. Dans ce cas, oui - seule la nouvelle version capte le trafic et le tour est joué. Mais avec un SGBD relationnel, tout est un peu plus compliqué.
Les principaux facteurs qui nous empĂȘchent de mettre en Ćuvre un dĂ©ploiement continu pour notre pile technologique sont les suivants:
- L'ancienne version de l'application ne peut pas fonctionner avec la nouvelle structure de base de données
- La mise Ă jour de la structure de la base de donnĂ©es peut prendre un temps considĂ©rable et n'est pas toujours possible en utilisant l'application elle-mĂȘme via le mĂ©canisme de migration automatique.
Je vais vous expliquer. Supposons que vous ayez dĂ©ployĂ© une nouvelle version dans un emplacement parallĂšle ou dans un centre de donnĂ©es de sauvegarde et dĂ©marrĂ© l'application des migrations. Supposons que nous ayons trois migrations et, horreur, deux ont roulĂ© et la troisiĂšme est tombĂ©e. Pour le moment, rien ne se passera sur les serveurs qui fonctionnent, Entity Framework ne vĂ©rifie pas la version de chaque requĂȘte, mais vous ne pourrez probablement pas rĂ©soudre rapidement le problĂšme. Ă ce moment, la charge sur l'application peut augmenter, et la plate-forme lancera une instance supplĂ©mentaire de l'application pour vous, et cela ... naturellement ne dĂ©marrera pas, car la structure de la base de donnĂ©es a changĂ©. Une partie importante des utilisateurs commencera Ă recevoir des erreurs. Ainsi, le risque d'application automatique des migrations est grand.

Quant au deuxiĂšme point, votre migration peut contenir une sorte de commandes dont le temps d'exĂ©cution dĂ©passe 30 secondes et la procĂ©dure standard expirera. Eh bien, en plus de ces points, personnellement, je n'aime pas le fait que lors des migrations automatiques, vous ĂȘtes obligĂ© de mettre Ă niveau une partie de l'infrastructure vers une nouvelle version. Et si pour un mode avec des emplacements dans Azure ce n'est pas si effrayant, alors pour un mode avec un centre de donnĂ©es de sauvegarde, vous obtenez une partie de l'infrastructure avec une application notoirement inopĂ©rante. Tout est dangereux, il tirera au moment le plus inopportun.
Que faire
Commençons par les plus difficiles - avec la base de donnĂ©es. Il serait donc agrĂ©able de mettre Ă jour automatiquement la structure de la base de donnĂ©es pour que les anciennes versions de l'application continuent de fonctionner. De plus, il serait intĂ©ressant de prendre en compte le fait qu'il existe de telles mises Ă jour dans lesquelles une commande distincte peut ĂȘtre exĂ©cutĂ©e pendant un temps considĂ©rable, ce qui signifie que nous devons mettre Ă jour la base de donnĂ©es non pas en utilisant les mĂ©canismes intĂ©grĂ©s mais en exĂ©cutant un script SQL sĂ©parĂ©. Question: comment le prĂ©parer? Vous pouvez crĂ©er ce manuel de processus. Si vous avez un rĂŽle de gestionnaire de versions distinct dans l'Ă©quipe, vous pouvez le forcer Ă exĂ©cuter la commande dans Visual Studio:
update-database -script
Elle gĂ©nĂ©rera un script et cette personne mettra ce script dans un dossier de projet spĂ©cifique. Mais vous devez admettre que cela est toujours gĂȘnant, d'une part, le facteur humain et, d'autre part, les difficultĂ©s inutiles s'il y avait plus d'une migration entre les versions. Ou pour une raison quelconque, une version a Ă©tĂ© ignorĂ©e sur le systĂšme cible. Nous devrons crĂ©er une sorte de jardin compliquĂ© avec le suivi des migrations qui sont dĂ©jĂ lĂ et qui doivent ĂȘtre lancĂ©es. C'est difficile et, surtout, c'est le mĂȘme vĂ©lo qui a dĂ©jĂ Ă©tĂ© fabriquĂ© dans le mĂ©canisme de migration.
Et il sera correct d'intégrer le processus de génération et d'exécution de script dans le processus de calcul de la version. Afin de générer un script de migration, vous pouvez utiliser l'utilitaire migrate.exe, qui est inclus avec Entity Framework. J'attire votre attention sur le fait que vous avez besoin d'Entity Framework version 6.2 ou supérieure, car l'option de génération de script n'est apparue dans cet utilitaire qu'en avril 2017. L'appel d'utilité ressemble à ceci:
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
Le nom de l'assembly est indiquĂ© oĂč se trouve votre contexte, la chaĂźne de connexion Ă la base de donnĂ©es cible, le fournisseur et, trĂšs important, le rĂ©pertoire de dĂ©marrage, qui contient Ă la fois l'assembly avec le contexte et l'assembly Entity Framework. N'expĂ©rimentez pas avec les noms du rĂ©pertoire de travail, soyez plus simple. Nous sommes tombĂ©s sur le fait que migrate.exe ne pouvait pas lire le rĂ©pertoire, au nom duquel il y avait des espaces et des caractĂšres non-lettre.
Ici, il est nĂ©cessaire de faire une digression importante. Le fait est qu'aprĂšs avoir exĂ©cutĂ© la commande ci-dessus, un seul script SQL sera gĂ©nĂ©rĂ© contenant toutes les commandes pour toutes les migrations qui doivent ĂȘtre appliquĂ©es Ă la base de donnĂ©es cible. Pour Microsoft SQL Server, ce n'est pas trĂšs bon. Le fait est que le serveur exĂ©cute les commandes sans le sĂ©parateur GO en tant que package unique et que certaines opĂ©rations ne peuvent pas ĂȘtre effectuĂ©es ensemble dans un package unique.
Par exemple, dans certains cas, l'ajout d'un champ Ă une table et la crĂ©ation immĂ©diate d'un index sur cette table avec un nouveau champ ne fonctionne pas. Mais cela ne suffit pas, certaines commandes nĂ©cessitent certains paramĂštres d'environnement lors de l'exĂ©cution du script. Ces paramĂštres sont activĂ©s par dĂ©faut lorsque vous vous connectez Ă SQL Server via SQL Server Management Studio, mais lorsque le script est exĂ©cutĂ© via l'utilitaire de console SQLCMD, ils doivent ĂȘtre dĂ©finis manuellement. Pour tenir compte de tout cela, vous devrez modifier le processus de gĂ©nĂ©ration du script de migration avec un fichier. Pour ce faire, crĂ©ez une classe supplĂ©mentaire Ă cĂŽtĂ© du contexte de date, qui fait tout ce dont vous avez besoin:
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; } }
Et pour que Entity Framework puisse l'utiliser, enregistrez-le dans la classe Configuration, qui se trouve généralement dans le dossier Migrations:
public Configuration() { SetSqlGenerator("System.Data.SqlClient", new MigrationScriptBuilder()); âŠ. }
AprÚs cela, le script de migration résultant contiendra GO entre chaque instruction, et au début du fichier contiendra SET QUOTED_IDENTIFIER ON;
Hourra, la prĂ©paration est faite, il reste Ă configurer le processus lui-mĂȘme. En gĂ©nĂ©ral, dans le cadre du processus de publication dans Azure DevOps (VSTS / TFS), cela est dĂ©jĂ assez simple. Nous devons crĂ©er un script PowerShell comme celui-ci:
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===="
Et ajoutez l'unité d'exécution de script PowerShell au processus de calcul de la version. Le bloc et ses paramÚtres peuvent ressembler à ceci:

La configuration de PowerShell ressemble Ă ceci:

Il est important de ne pas oublier d'ajouter le fichier migrate.exe au projet à partir du dossier <YourProject> /packages/EntityFramework.6.2.0/tools/ et de lui attribuer la propriété Copy Always afin que cet utilitaire soit copié dans le répertoire de sortie lors de la construction du projet et que vous puissiez y accéder dans Version Azure DevOps.
La nuance . Si votre projet utilise Ă©galement WebJob lors du dĂ©ploiement sur Azure App Service, l'ajout de Migrate.exe Ă votre projet n'est pas sĂ»r. Nous sommes confrontĂ©s au fait que dans le dossier oĂč votre WebJob est publiĂ©, la plateforme Azure dĂ©marre stupidement le premier fichier exe qui se prĂ©sente. Et si votre WebJob coĂ»te par ordre alphabĂ©tique plus tard migrate.exe (et nous l'avons fait), il essaie alors d'exĂ©cuter migrate.exe Ă la place de votre projet!
Nous avons donc appris Ă mettre Ă jour la version de la base de donnĂ©es en gĂ©nĂ©rant un script pendant le processus de publication, la chose simple est: dĂ©sactiver la vĂ©rification de la version de migration, de sorte que s'il y a des Ă©checs dans le processus d'exĂ©cution du script, l'ancienne version de notre code continue de fonctionner. Je pense qu'il n'est pas nĂ©cessaire de dire que vos migrations doivent ĂȘtre non destructives. C'est-Ă -dire les modifications apportĂ©es Ă la structure de la base de donnĂ©es ne devraient pas interfĂ©rer avec les performances de la version prĂ©cĂ©dente, mais mieux que les deux prĂ©cĂ©dentes. Pour dĂ©sactiver la vĂ©rification, il vous suffit d'ajouter le bloc suivant Ă Web.config:
<entityFramework> <contexts> <context type="<full namespace for your DataContext class>, MyAssembly" disableDatabaseInitialization="true"/> </contexts> </entityFramework>
OĂč l'
full namespace for your DataContext class
est l'espace de noms complet de votre descendant de DbContext et MyAssembly est le nom de l'assembly dans lequel se trouve votre contexte.
Et enfin, il est hautement souhaitable pour nous de nous assurer que l'application se réchauffe avant de basculer les utilisateurs vers la nouvelle version. Pour ce faire, ajoutez un bloc spécial à web.config avec des liens que votre application quitte automatiquement pendant le processus d'initialisation:
<system.webServer> <applicationInitialization doAppInitAfterRestart="true"> <add initializationPage="/" hostName="" /> </applicationInitialization> </system.webServer>
Vous pouvez ajouter plusieurs liens simplement en ajoutant
/>
Il est soutenu que dans Azure, lors du changement de créneaux, la plate-forme attend que l'application s'initialise et bascule ensuite le trafic vers la nouvelle version.
Mais qu'en est-il d'un projet sur .NET Core?
Tout est beaucoup plus simple et en mĂȘme temps diffĂ©rent. Un script de migration peut ĂȘtre gĂ©nĂ©rĂ© Ă l'aide d'outils ordinaires, mais il n'est pas basĂ© sur l'assemblage fini, mais sur le fichier de projet. Ainsi, le script doit ĂȘtre formĂ© dans le cadre du processus d'assemblage du projet et doit ĂȘtre inclus en tant qu'artefact d'assemblage. Dans ce cas, le script contiendra toutes les commandes de toutes les migrations depuis le dĂ©but du temps. Il n'y a aucun problĂšme Ă cela, car le script est idempotent, c'est-Ă -dire il peut ĂȘtre appliquĂ© Ă la base cible Ă plusieurs reprises sans aucune consĂ©quence. Cela a une autre consĂ©quence utile: nous n'avons pas besoin de modifier le processus de gĂ©nĂ©ration de script pour sĂ©parer les commandes en packages, tout a dĂ©jĂ Ă©tĂ© fait pour cela.
Eh bien, en particulier, les étapes du processus ressemblent à ceci. Dans le processus de génération, ajoutez la tùche:

Nous le configurons pour générer un fichier avec des migrations:

N'oubliez pas d'ajouter un script au projet PowerShell qui effectuera la migration (dĂ©crite ci-dessus) et le fichier de migration lui-mĂȘme. Par consĂ©quent, aprĂšs la construction du projet, les artefacts peuvent ressembler Ă ceci (en plus de l'archive rĂ©elle avec l'assembly, il existe un script PS supplĂ©mentaire et un script SQL avec migrations):

Il ne reste qu'Ă l'Ă©tape Release appropriĂ©e pour configurer l'exĂ©cution de ce script PowerShell de la mĂȘme maniĂšre que dĂ©crit ci-dessus.
Ă propos de l'auteur

Pavel Kutakov est un expert des technologies cloud, un dĂ©veloppeur et architecte de systĂšmes logiciels dans divers secteurs d'activitĂ© - de l'IP bancaire opĂ©rant dans le monde entier des Ătats-Unis Ă la Papouasie-Nouvelle-GuinĂ©e, en passant par une solution cloud pour l'opĂ©rateur de loterie national.