UPD: a segunda parte do artigo está pronta .Olá Habr! Meu nome é Alexander Izmailov. No Badoo, lidero uma equipe de engenheiros de lançamento. Eu sei que em muitas empresas você pode enviar alterações de código para uma pessoa especialmente treinada, ele as observa e as adiciona onde deveriam (por exemplo, é exatamente isso que acontece com o código Git). E quero falar sobre como automatizamos esse processo conosco.
Minha história consistirá em duas partes. Nesta parte, falarei sobre o que queríamos alcançar com o novo sistema, como parecia em sua primeira versão e por que, no final, tivemos que refazê-lo. Na segunda parte, falaremos sobre o processo de refazer o sistema e sobre os bônus inesperados que ele nos trouxe.
Imagem:
fonteEra uma vez em nossa empresa que todos podiam fazer suas alterações diretamente na filial principal e distribuí-las com suas próprias mãos. Para fazer isso, escrevemos um utilitário especial do MSCP que funcionava de maneira bastante primitiva: copiava as alterações de uma máquina para outra e definia os direitos necessários.
Com o passar do tempo, a empresa cresceu - e tivemos que pensar na automação de processos, incluindo o processo de fazer pequenas alterações. Então, tivemos a ideia de criar um sistema de patches. Antes de tudo, queríamos que ele permitisse a qualquer desenvolvedor enviar as alterações para o Git e colocá-las em servidores. De nossa parte, exigimos que outro desenvolvedor analisasse as mudanças e que elas estivessem vinculadas à tarefa do nosso sistema de rastreamento de bugs (usamos o Jira).
Coletor de remendo 6000Devo dizer que esses requisitos não agradaram a todos os desenvolvedores. Pareceu-nos que gastar alguns minutos na criação de uma tarefa não é uma coisa, mas para nós isso significaria um uso mais deliberado do sistema. Mas os desenvolvedores começaram a resistir, argumentando que definir as mudanças leva várias vezes menos tempo do que criar um novo ticket. Como resultado, ainda temos tarefas "universais", às quais centenas de patches estão anexados.
Além disso, o sistema foi concebido com o objetivo de solucionar problemas urgentes, e pode ser difícil encontrar um revisor para um patch às três da manhã.
Do que precisamos?
Sim, apenas uma luz na janela ... Nosso problema pode ser condicionalmente dividido em duas partes: precisávamos de alguma maneira de aceitar alterações no repositório e de alguma maneira de colocar essas alterações em ordem.
Decidimos a primeira pergunta com bastante rapidez: criamos um formulário ao qual deveríamos anexar nossas alterações e indicar os detalhes (revisor e tarefa).
Na segunda página, você pode ver as alterações, rejeitá-las ou aceitá-las.

Após a confirmação, as alterações caíram no mestre.
Segunda pergunta: como essas alterações podem ser entregues aos servidores rapidamente? Hoje, muitos usam a integração contínua, e isso poderia funcionar bem se nossas construções e layouts “honestos” não levassem tanto tempo.
Montagem justa
Nossa montagem sempre foi bastante complicada. O princípio geral era o seguinte: em um diretório separado, organizamos os arquivos como deveriam estar nos servidores de destino; então, salvamos esse estado em um instantâneo (instantâneo do sistema de arquivos) e o expusemos.
Colocamos o código PHP no diretório, que retiramos do repositório como está, adicionamos os arquivos gerados (por exemplo, modelos e traduções) a ele. Estabelecemos a estática separadamente. Esse é um processo bastante complicado, e você pode dedicar um artigo inteiro a ele, mas, como resultado, tínhamos um mapa de versão para gerar links de arquivos para usuários que saíram junto com o código principal.
Em seguida, o estado do diretório precisava ser salvo em algum lugar. Para fazer isso, usamos um
dispositivo de bloco , que chamamos de loop. O diretório inteiro foi copiado para um dispositivo vazio, que foi arquivado e entregue em servidores "principais" separados. A partir desses servidores, pegamos arquivos no processo de layout. Cada arquivo tinha 200 MB de tamanho e, quando descompactado, os loops pesavam 1 GB. Demoramos cerca de cinco minutos para construir sem estática.
Layout justo
Primeiro, precisamos entregar o arquivo aos servidores de destino. Como temos milhares deles, a questão da entrega para nós sempre foi um grande problema: temos várias plataformas (data centers) e nos mil servidores "mais espessos" com um código. Na tentativa de obter melhor desempenho (tempo e recursos mínimos), tentamos diferentes métodos: de um simples SCP a torrents. No final, decidimos usar o UFTP. O método foi rápido (com bom tempo - um minuto), mas, infelizmente, não apresenta problemas. Periodicamente, algo acontecia e tivemos que correr para os administradores e os membros da rede.
Depois que o arquivo (de alguma forma) se encontrou nos servidores, ele deve ser descompactado, o que também não é gratuito. Esse procedimento parece especialmente caro se você se lembrar de que é realizado milhares de vezes, embora em paralelo em máquinas diferentes.
Nenhuma montagem
Portanto, postar honestamente as alterações levou muito tempo e a velocidade de entrega foi muito importante para o sistema de patches, porque supunha-se que eles a usariam quando algo não estivesse mais funcionando. Portanto, voltamos à idéia de usar o MSCP: rápido e fácil de implementar. Assim, depois que as alterações apareceram no assistente, em uma página separada, foi possível decompor os arquivos alterados por vez.

Está vivo
O sistema está funcionando. Apesar de alguma insatisfação com as pequenas coisas, os desenvolvedores poderiam fazer seu trabalho e, para isso, não precisavam ter acesso ao mestre ou aos servidores.
Mas, é claro, com esse método de layout, tivemos problemas. Alguns eram previsíveis, outros até decidimos de alguma forma. A maioria deles estava relacionada à edição de arquivos em paralelo.
Um patch para vários arquivos
Um exemplo de um problema previsível. Novos arquivos foram apresentados por sua vez. O que fazer se você precisar alterar vários arquivos e as alterações estiverem relacionadas a eles? Por exemplo, quero adicionar um novo método em um arquivo e usá-lo imediatamente em outros. Desde que não haja loopback usando métodos (consulte
recursão mútua ), basta lembrar a ordem correta do layout do arquivo.
Decisão honestaPara resolver o problema, precisávamos substituir vários arquivos atomicamente. No caso de um único arquivo, a solução é conhecida: você precisa usar a renomeação da operação de arquivo. Suponha que tenhamos um arquivo F e precisamos substituir seu conteúdo. Para fazer isso, crie um arquivo TMP, escreva as informações necessárias e depois renomeie o TMP F.
Vamos complicar a tarefa. Suponha que tenhamos um diretório D e precisamos substituir seu conteúdo. A operação de renomeação não nos ajudará, porque não pode substituir um diretório não vazio. No entanto, existe uma solução alternativa: você pode pré-substituir o diretório D por um chamado link simbólico (link simbólico). Em seguida, o próprio conteúdo estará em outro lugar, por exemplo, no diretório D_1, e D será um link para D_1. No momento em que a substituição é necessária, o novo conteúdo é gravado no diretório D_2, no qual um novo link TMP é criado. Agora, renomear TMP D funcionará porque esta operação pode ser aplicada aos links.
Esta solução parece apropriada: você pode alterar o diretório inteiro com o código, copiando arquivos antigos e escrevendo novos por cima. O problema é que copiar todo o código é longo e caro. Você pode substituir apenas o subdiretório em que os arquivos foram alterados, mas todos os subdiretórios com o código devem ser links, porque não podemos substituir o diretório preenchido por nada durante o processo de layout. Essa solução não só parece muito complicada - você deve se lembrar de adicionar algumas restrições para que os dois processos não possam alterar simultaneamente o mesmo diretório ou diretório e seus subdiretórios.
Como resultado, não conseguimos encontrar uma solução técnica, mas descobrimos como simplificar um pouco a vida: fizemos o layout de vários arquivos com uma ação na interface. O desenvolvedor especificou o layout dos arquivos e o sistema os entregou.
Vários patches por arquivo
É mais difícil se houver um arquivo e vários desenvolvedores quiserem alterá-lo. Aplicamos o primeiro patch, mas não o decompusemos. Nesse ponto, o segundo patch chega e é solicitado a se decompor. O que fazer Ainda mais interessante, se o segundo patch for aplicado e, neste momento, somos solicitados a decompor o primeiro.
Provavelmente, precisamos esclarecer que sempre disponibilizamos apenas a versão mais recente do assistente. Caso contrário, outros problemas podem surgir. Por exemplo, colocando a versão antiga em cima da nova.
Não encontramos uma solução realmente boa para esse problema. Mostramos aos desenvolvedores a diferença entre o que eles apresentam e o que está nas máquinas em um determinado momento, mas isso nem sempre funcionava. Por exemplo, pode haver muitas mudanças, e o desenvolvedor pode estar com pressa ou apenas com preguiça (tudo pode acontecer).
Muitos patches, e todos mudam os mesmos arquivos
Essa é a pior opção que você nem quer pensar. Se as alterações de vários desenvolvedores afetassem vários dos mesmos arquivos, nosso sistema de patches não poderia ajudar particularmente - continuava a depender da atenção dos desenvolvedores e de sua capacidade de se comunicar. Mas, em teoria, é bem possível obter "peixe" quando, em qualquer ordem de layout, em algum momento do servidor houver código parcialmente quebrado.
Imagem:
fonteProblemas de ferro
Outro problema surgiu quando, por algum motivo, um dos servidores ficou indisponível. Tínhamos um mecanismo para excluir esses servidores do layout, que funcionava muito bem; dificuldades surgiram após o retorno ao serviço. O fato é que as versões das configurações e do código nos servidores em funcionamento são verificadas conosco (existe todo um departamento de monitoramento!) E garantimos que todas as versões estejam atualizadas quando o servidor estiver novamente em operação. Mas não tínhamos versão para patches - apenas copiamos novos arquivos para o código atual.
Não criamos uma maneira precisa de versão dos patches decompostos, mas tentamos resolver o problema com soluções alternativas. Por exemplo, rsync de uma máquina vizinha no final do processo de layout. Mas de alguma forma não conseguimos verificar de alguma forma.
Examinamos várias soluções para esse problema, por exemplo, queríamos aplicar patches também nos servidores "principais" (é importante lembrar que estamos implantando a versão empacotada, ou seja, precisamos aplicar o patch e empacotar a versão de volta), mas foi bastante difícil de implementar.
Uma colher de mel
Mas, além dos problemas, houve aspectos positivos.
Em primeiro lugar, os desenvolvedores descobriram rapidamente que, além de consertar as coisas, com a ajuda do sistema de patches, às vezes você pode carregar novas funcionalidades, por exemplo, quando precisar urgentemente. Como em qualquer empresa, temos força maior. Porém, se antes tivéssemos que criar uma construção extraordinária, na qual os testadores e engenheiros de versão estavam distraídos, agora o desenvolvedor poderia decompor algumas alterações por conta própria.
Em segundo lugar, uma pessoa especial com direitos não era mais necessária para consertar algo. Qualquer desenvolvedor pode postar suas edições. Mas isso não é tudo: as compilações em geral se tornaram mais fáceis, agora os problemas foram divididos em críticos e os que podem ser corrigidos usando patches. Isso tornou possível reverter com menos frequência e tomar uma decisão mais rápida sobre se fomos bem-sucedidos.
Simplificando, gostamos do sistema e ganhamos popularidade. Continuamos a tentar melhorá-lo, mas com os problemas descritos, tivemos que viver mais alguns anos. E como nós os decidimos, como o sistema funciona agora e como quase matamos os feriados de Ano Novo durante o processo de atualização, vou contar na segunda parte do artigo.