Introduzindo Pipelines no Processo de Implantação do Airbnb

Unindo-se à última linha de defesa contra o “código incorreto”, desenvolvendo um método de impor nossos procedimentos de implantação e lições aprendidas sobre como a teoria e a prática diferem no desenvolvimento de software do mundo real.

Alexander Katz Blocked Unblock Seguir Seguindo 18 de dezembro de 2018 O pipeline de implantação é usado para serviços que alimentam essas Experiências exclusivas no Airbnb.com .

Na Airbnb, o tempo de inatividade não é apenas uma inconveniência – muitas vezes há um impacto substancial no mundo real sobre nossos usuários. Como um lembrete constante desse fato incrivelmente importante, criamos um pôster destacado de um único tweet de muitas luas atrás. Parafraseando aproximadamente, lê-se:

@AirbnbAjuda Ajuda! Estou trancado do lado de fora e não posso entrar em contato com meu anfitrião enquanto o Airbnb está fora do ar.

Naturalmente, um site pode ser desativado devido a vários motivos. Talvez um teste esquisito tenha agido e nos permitido empurrar código ruim. Talvez uma caixa tenha falhado inesperadamente. Talvez uma correção de vulnerabilidade em grande escala afete o desempenho do kernel . Felizmente, cada equipe do Airbnb desempenha seu papel em garantir que esse cenário não ocorra, tornando o tempo de inatividade extremamente raro . No DeployInfra, nos concentramos na caixa de implantação no ciclo de vida de desenvolvimento de software – basicamente, quando o código é escrito e está pronto para uso, somos responsáveis por colocá-lo nos servidores apropriados (o que abrange muito território!). Quase todos os muitos aplicativos / serviços do Airbnb são enviados usando o nosso serviço de implantação interno, o Deployboard, que em muitos aspectos nos torna a última linha de defesa contra “códigos ruins” (depois de nós, o código é – por definição – ao vivo!).

Com esse grande poder, vem uma responsabilidade ainda maior. Embora quase todos os engenheiros da Airbnb interajam com o Deployboard regularmente, essas interações costumam ser curtas e mais do que um meio para um fim. Como resultado, não podemos confiar na familiaridade como uma muleta; Precisamos garantir que o processo de implantação seja cuidadosamente projetado e que o possível uso incorreto seja minimizado . Em particular, mantemos nossa interface com padrões extremamente altos para facilidade de uso e intuição. Qualquer coisa menos e erros correm desenfreados.

Um procedimento de implantação desnecessariamente flexível

É claro que não somos perfeitos e, muitas vezes, o que parece intuitivo para nós – como desenvolvedores do sistema – é menos do que intuitivo para usuários pouco frequentes. Em particular, a flexibilidade é uma bênção para os experientes, mas muitas vezes também fornece a corda para se enforcar. Neste verão, notamos que esse é o caso de uma parte bastante importante do Deployboard, que indiretamente estava causando um bom número de incidentes.

Primeiro, um pouco de fundo necessário. O Airbnb tem cerca de mil serviços diferentes, um número que só continuará crescendo à medida que avançamos em direção à SOA . Cada serviço tem seu próprio conjunto de “ambientes” – clusters de máquinas que cumprem uma determinada função – e um procedimento de implantação que especifica a ordem na qual se deve implantar alterações de código. Por exemplo, uma alteração típica de código pode ser implantada primeiro em um “staging”. ambiente, em seguida, para um ambiente "canário" e, finalmente, para a produção.

Até agora, tudo normal. Mas na Airbnb, nos orgulhamos de dar aos nossos engenheiros uma grande liberdade e flexibilidade, o que, nesse caso, significa que qualquer engenheiro pode implantar em qualquer ambiente com o mínimo de esforço (lembre-se de que queríamos que as coisas fossem o mais simples possível!). No entanto, neste contexto particular, essa liberdade levanta duas questões:

  1. Não há nada que impeça os engenheiros de implantar na ordem errada , por exemplo, implantando diretamente para a produção antes de implantar em teste ou canário. Na verdade, isso não é incomum; antes do meu projeto, cerca de 10% das alterações de código foram diretamente para a produção. Observe que a maioria delas são apenas de teste ou comentário, portanto, esse número não é tão assustadoramente alto quanto parece, mas ainda assim é preocupante.
  2. Se um engenheiro quiser contribuir para um serviço com o qual não trabalha regularmente (o que é muito comum no Airbnb!), Não é fácil para ele descobrir qual é o procedimento de implantação do serviço – a documentação em torno disso raramente é feita. -encontro.

Apenas alguns dos 24 (!) Ambientes para um dos nossos muitos serviços. Tente adivinhar a ordem correta!

Claro, esses problemas alimentam um ao outro. Ao se deparar com a exibição acima, um engenheiro novo no serviço pode decidir que a mudança deve acabar na produção, então a solução “mais simples” é simplesmente implantá-la lá.

Como mencionei anteriormente, quase todos os engenheiros do Airbnb interagem regularmente com o Deployboard e, embora essas interações sejam curtas, quase todas envolvem precisamente essa exibição em algum momento (afinal de contas, essa exibição mostra como o código é realmente implementado!). Como resultado, resolver esse problema tornou-se uma prioridade imediata. Assim, embora tenhamos avaliado várias soluções externas e provavelmente chegaremos a uma nos próximos anos, no curto prazo optamos por implementar um sistema de pipeline interno no próprio Deployboard.

Este trabalho precisava ser feito com muito cuidado. Como o Airbnb cresceu ao longo da última meia década, o Deployboard está bem ali, agora lidando com milhares de implantações diariamente. Basta dizer que o Deployboard é bastante essencial para as operações do Airbnb e, portanto, fazer alterações substanciais nele, especialmente em suas seções críticas, requer uma mão bastante firme. Assim começou uma colaboração de várias equipes para desenvolver uma proposta de solução, um processo que era meticuloso, extremamente cauteloso e muito detalhista; Eu me senti quase mais como um PM do que como engenheiro nas primeiras semanas do meu estágio!

Finalmente, depois de muitas iterações de maquete e centenas de comentários de documentos, o objetivo final tomou forma:

Os mesmos 24 alvos, mas a) dispostos corretamente eb) separados em alvos “obrigatórios” / “opcionais”

Desenvolvendo o pipeline

Agora sabemos como é o ponto A e o ponto B, então “tudo” que resta é conectar os pontos! Como sempre, o passo zero não escrito é primeiro dividir o trabalho em várias etapas relativamente distintas:

  1. Desenvolva uma especificação para um arquivo de configuração que os proprietários do serviço possam usar para definir seus procedimentos de implantação (por exemplo, pipeline).

Como tratamos a configuração como código , isso torna o desenvolvimento e a atualização posterior de uma configuração de pipeline tão simples quanto a implantação!

Como as implementações são tratadas por pipelines, essa abordagem gera uma importante sutileza: é possível que um pipeline seja chamado para atualizar sua própria configuração. Em princípio, isso não é um problema, mas significa que, se um pipeline de alguma forma obtiver uma configuração quebrada, ele não poderá ser corrigido por meio de métodos diretos. Como resultado, essa etapa exige uma programação altamente defensiva para evitar esse cenário, bem como o desenvolvimento de ferramentas para “resgatar” um canal caso algo escorregue.

2. Salvar configurações de pipeline em um banco de dados.

Isso gera muitas questões de design de banco de dados. Quais tabelas precisamos? Como eles interagem? Como representamos dependências entre alvos? Os dois primeiros são relativamente simples; podemos ter uma tabela de pipelines, referenciando uma tabela de etapas de pipeline, etc. A última, no entanto, requer um pouco mais de esforço em uma tabela de links . Um exemplo rápido:

Aqui, atores e filmes são mapeados em um relacionamento muitos-para-muitos por meio de uma tabela separada, cujas linhas contêm um ID de ator e um ID de filme. Da mesma forma, podemos mapear alvos para alvos. Essencialmente, esta é uma maneira amigável de banco de dados de representar um tipo de lista de adjacências .

3. Dê ao frontend um método de acessar esses dados .

Felizmente este é realmente simples; podemos simplesmente expor pipelines como parte do apresentador do Rails de um serviço; em outras palavras, produzimos um equivalente JSON das informações que queremos reter sobre um pipeline. Como essa informação inclui os estágios e as dependências envolvidas no pipeline, isso exige recursivamente os apresentadores de pipelines, estágios, links, etc.

4. Separe e ordene os destinos corretamente na exibição da interface do usuário .

Ignorando alguns detalhes do caso de borda em torno da igualdade de estágio, isso se resume a ordenar corretamente um DAG , o que é mais fácil de fazer com a classificação topológica (uma aplicação típica do DFS ). Essencialmente, esta é uma maneira de ordenar nossos estágios, para que os estágios posteriores dependam apenas de estágios anteriores, o que nos dá uma ordem garantida que podemos processá-los sem incorrer em problemas de dependência. Um bom benefício deste algoritmo é a detecção implícita do ciclo , o que significa que ele pode ser reutilizado como parte da validação da configuração (obviamente, devemos rejeitar configurações que contenham ciclos).

5. Desabilite os alvos que não devem ser implantados (isto é, tem dependências que ainda não foram implantadas).

Este soou provavelmente o mais simples, já que já temos uma tabela de banco de dados para implantar, mas acabou sendo de longe a mudança mais difícil. Acontece que temos muitas implantações que recuperam as implantações associadas a uma mudança na maneira ingênua é proibitivamente lenta. Esse problema persistiu mesmo depois de adicionar os índices apropriados, o que foi bastante surpreendente.

No final, a solução era um pouco mais complicada e exigia a exposição de um novo endpoint da API (essencialmente permitindo que as informações fossem acessadas sob demanda, em vez de pré-carregadas, distribuindo o padrão de acesso ao banco de dados para ser aceitavelmente rápido), o que acabou sendo uma bênção disfarçada, já que alguns outros projetos exigiam essa funcionalidade básica também.

6. Finalmente, implemente a interface do usuário .

Não há muito o que dizer aqui – este é um trabalho de frontend razoavelmente padronizado envolvendo CSS consertando e alguns componentes de React . Uma nota interessante, no entanto, é a atenção acima mencionada dada ao design intuitivo – em um ponto, passamos vários dias discutindo qual ícone usar: um círculo azul ou uma estrela dourada! Embora isso pareça desnecessário, na verdade foi de suma importância – esse ícone era, essencialmente, representar "o que fazer a seguir", que é talvez a informação mais importante que a IU deve transmitir.

(Se você está curioso: no final, escolhemos o círculo azul para consistência com o restante da interface do usuário)

É claro que concluir o trabalho técnico não foi o fim do projeto – não há valor em desenvolver uma solução se ninguém a usar! Assim, as últimas semanas do meu estágio foram dedicadas a promover o projeto para várias equipes do Airbnb, exigindo uma enxurrada de demonstrações, apresentações e muitos, muitos e-mails. No final, uma série de serviços-chave – incluindo o próprio Deployboard! – Acabei adotando o deploy de pipelines, melhorando substancialmente a experiência do desenvolvedor.

Palavras finais

No geral, o meu estágio na Airbnb foi uma incrível experiência de aprendizado, tanto tecnicamente quanto de outra maneira. Da organização de palestras relâmpago a dar demos para escrever este post, havia muito o que fazer aqui além de sentar no meu próprio cantinho do mundo produzindo código, o que tem sido bastante incomum para estágios na minha experiência. Isso não é coincidência – a cultura aqui é singularmente acolhedora e encorajadora de comunicação (não é surpresa, considerando a ênfase na contratação de valores essenciais ). Estou definitivamente ansioso para voltar no futuro!

Texto original em inglês.