Como rolar atualizações na produção automaticamente

O lançamento da nova versão em operação de combate é sempre um evento nervoso. Especialmente se o processo envolver muitas operações manuais. O fator humano é uma coisa terrível. “Seria bom automatizar esse processo” - essa ideia é tão antiga quanto o mundo inteiro da TI. E existe um termo para isso - implantação contínua. Sim, o problema é que não há uma maneira única de configurar essa implantação contínua. Muito desse processo está atrelado à pilha tecnológica do projeto e de seu ambiente.

Neste artigo, quero compartilhar experiência prática na configuração de atualizações automáticas do sistema sem interromper sua operação para um ambiente tecnológico específico, a saber: aplicativo Web ASP.NET MVC + Azure SQL + Entity Framework no modo Code First, o aplicativo é implantado no Azure como Serviço de Aplicativo , montagem e implantação são feitas pelo Azure DevOps (anteriormente Visual Studio Team Services).



À primeira vista, tudo é muito simples, o Serviço de Aplicativo do Azure tem o conceito de slot de implantação - baixe a nova versão e ative-a. Mas seria simples se o projeto fosse baseado em um DBMS não relacional, no qual não haja um esquema de dados rígido. Nesse caso, sim - apenas a nova versão pega tráfego e pronto. Mas com um DBMS relacional, tudo é um pouco mais complicado.

Os principais fatores que nos impedem de implementar a implantação contínua de nossa pilha de tecnologias são os seguintes:

  • A versão antiga do aplicativo não pode funcionar com a nova estrutura de banco de dados
  • A atualização da estrutura do banco de dados pode levar um tempo considerável e nem sempre é possível usando o próprio aplicativo por meio do mecanismo de migração automática.

Eu vou explicar Suponha que você implantou uma nova versão em um slot paralelo ou em um datacenter de backup e iniciou o aplicativo de migrações. Suponha que temos três migrações e, horror, duas rolaram e a terceira caiu. Nesse momento, nada acontecerá com os servidores em funcionamento, o Entity Framework não verifica a versão de cada solicitação, mas você provavelmente não poderá resolver rapidamente o problema. Nesse momento, a carga no aplicativo pode aumentar e a plataforma iniciará uma instância adicional do aplicativo para você, e ele ... naturalmente não será iniciado, pois a estrutura do banco de dados mudou. Uma parcela significativa dos usuários começará a receber erros. Assim, o risco de aplicação automática de migrações é grande.



Quanto ao segundo ponto, sua migração pode conter algum tipo de comando cujo tempo de execução excede 30 segundos e o procedimento padrão expirará. Bem, além desses pontos, pessoalmente não gosto do fato de que durante as migrações automáticas você é forçado a atualizar parte da infraestrutura para uma nova versão. E se, para um modo com slots no Azure, isso não é tão assustador, para um modo com um data center de backup, você tem uma parte da infraestrutura com um aplicativo que não é conhecido como inoperante. É tudo perigoso, dispara no momento mais inoportuno.

O que fazer


Vamos começar com o mais difícil - com o banco de dados. Portanto, seria bom atualizar de alguma forma automaticamente a estrutura do banco de dados para que as versões antigas do aplicativo continuem funcionando. Além disso, seria bom levar em consideração o fato de que existem essas atualizações nas quais um comando separado pode ser executado por um tempo considerável, o que significa que precisamos atualizar o banco de dados não usando os mecanismos internos, mas executando um script SQL separado. Pergunta: como prepará-lo? Você pode fazer este processo manual. Se você tiver uma função de gerente de versão separada na equipe, poderá forçá-lo a executar o comando no Visual Studio:

update-database -script 

Ela irá gerar um script e essa pessoa o colocará em uma pasta específica do projeto. Mas você deve admitir que isso ainda é inconveniente, primeiro, o fator humano e, segundo, dificuldades desnecessárias se houver mais de uma migração entre as liberações. Ou, por algum motivo, uma versão foi ignorada no sistema de destino. Teremos que criar algum tipo de jardim complicado para rastrear quais migrações já existem e quais precisam ser lançadas. É difícil e, o mais importante, é a mesma bicicleta que já foi feita no mecanismo de migração.

E será correto criar o processo de geração e execução de script no processo de cálculo da liberação. Para gerar um script de migração, você pode usar o utilitário migrate.exe, incluído no Entity Framework. Chamo a atenção para o fato de que você precisa do Entity Framework versão 6.2 ou superior, pois a opção de geração de script apareceu neste utilitário somente em abril de 2017. A chamada de utilitário é assim:

 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 

O nome do assembly é indicado onde seu Contexto está localizado, a cadeia de conexão com o banco de dados de destino, o provedor e, muito importante, o diretório inicial, que contém o assembly com o contexto e o assembly do Entity Framework. Não experimente os nomes do diretório de trabalho, seja mais simples. Nos deparamos com o fato de que migrate.exe não pôde ler o diretório, em cujo nome havia espaços e caracteres que não são letras.

Aqui é necessário fazer uma digressão importante. O fato é que, após a execução do comando acima, será gerado um único script SQL contendo todos os comandos para todas as migrações que precisam ser aplicadas ao banco de dados de destino. Para o Microsoft SQL Server, isso não é muito bom. O fato é que o servidor executa comandos sem o separador GO como um único pacote, e algumas operações não podem ser executadas juntas em um único pacote.

Por exemplo, em alguns casos, adicionar um campo a uma tabela e criar imediatamente um índice nessa tabela com um novo campo não funciona. Mas isso não é suficiente, alguns comandos requerem determinadas configurações do ambiente ao executar o script. Essas configurações são ativadas por padrão quando você se conecta ao SQL Server através do SQL Server Management Studio, mas quando o script é executado por meio do utilitário SQLCMD do console, elas devem ser definidas manualmente. Para levar tudo isso em consideração, você precisará modificar o processo de geração do script de migração com um arquivo. Para fazer isso, crie uma classe adicional ao lado do contexto da data, que faz tudo o que você precisa:

  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; } } 

E para que o Entity Framework possa usá-lo, registre-o na classe Configuration, que geralmente está localizada na pasta Migrations:

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

Depois disso, o script de migração resultante conterá GO entre cada instrução e no início do arquivo conterá SET QUOTED_IDENTIFIER ON;

Viva, a preparação está concluída, resta configurar o próprio processo. Em geral, como parte do processo de lançamento no Azure DevOps (VSTS / TFS), isso já é bastante simples. Precisamos criar um script do 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====" 

E adicione a unidade de execução de script do PowerShell ao processo de cálculo da liberação. O bloco e suas configurações podem ser assim:



A instalação do PowerShell se parece com isso:



É importante não esquecer de adicionar o arquivo migrate.exe ao projeto da pasta <YourProject> /packages/EntityFramework.6.2.0/tools/ e defina a propriedade Copy Always para que este utilitário seja copiado no diretório de saída quando o projeto for criado e você poderá acessá-lo em Versão do Azure DevOps.

A nuance . Se o seu projeto também usar o WebJob ao implantar no Serviço de Aplicativo do Azure, a adição de Migrate.exe ao seu projeto não é segura. Somos confrontados com o fato de que, na pasta em que seu WebJob é publicado, a plataforma do Azure inicia estupidamente o primeiro arquivo exe que aparece. E se o seu WebJob custa em ordem alfabética o migrate.exe mais tarde (e o fizemos), ele tenta executar o migrate.exe em vez do seu projeto!

Então, aprendemos como atualizar a versão do banco de dados gerando um script durante o processo de lançamento, a coisa mais simples é: desative a verificação da versão da migração, para que, se houver alguma falha no processo de execução do script, a versão antiga do nosso código continue funcionando. Acho que não há necessidade de dizer que suas migrações devem ser não destrutivas. I.e. as alterações na estrutura do banco de dados não devem interferir no desempenho da versão anterior, mas melhor que as duas anteriores. Para desativar a verificação, você só precisa adicionar o seguinte bloco ao Web.config:

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

Onde full namespace for your DataContext class é o espaço para nome completo para o seu descendente do DbContext e MyAssembly é o nome do assembly em que o seu contexto está.

E, finalmente, é altamente desejável garantir que o aplicativo esteja aquecendo antes de mudar os usuários para a nova versão. Para fazer isso, adicione um bloco especial ao web.config com links que seu aplicativo fecha automaticamente durante o processo de inicialização:

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

Você pode adicionar vários links simplesmente adicionando /> Argumenta-se que, no Azure, ao alternar slots, a plataforma aguarda a inicialização do aplicativo e só então muda o tráfego para a nova versão.

Mas e um projeto no .NET Core?


Tudo é muito mais simples e ao mesmo tempo diferente. Um script de migração pode ser gerado usando ferramentas regulares, mas não se baseia na montagem final, mas no arquivo do projeto. Assim, o script deve ser formado como parte do processo de montagem do projeto e deve ser incluído como um artefato de montagem. Nesse caso, o script conterá todos os comandos de todas as migrações desde o início dos tempos. Não há problemas nisso, pois o script é idempotente, ou seja, pode ser aplicado à base de destino repetidamente, sem consequências. Isso tem outra conseqüência útil: não precisamos modificar o processo de geração de script para separar os comandos em pacotes, tudo já foi feito para isso.

Bem, especificamente, as etapas do processo são assim. No processo de construção, adicione a tarefa:



Nós o configuramos para gerar um arquivo com migrações:



Não se esqueça de adicionar um script ao projeto do PowerShell que executará a migração (descrita acima) e o próprio arquivo de migração. Como resultado, após a construção do projeto, os artefatos podem se parecer com isso (além do arquivo real com o assembly, há um script PS adicional e um script SQL com migrações):



Permanece apenas na etapa de versão apropriada para configurar a execução desse script do PowerShell da mesma maneira descrita acima.

Sobre o autor


Pavel Kutakov é especialista em tecnologias de nuvem, desenvolvedor e arquiteto de sistemas de software em vários setores de negócios - desde IP bancário em todo o mundo, operando nos EUA até Papua Nova Guiné, até uma solução em nuvem para o operador de loteria nacional.

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


All Articles