Capítulo 16 – Canarying releases

Por Alec Warner e Štěpán Davidovič com Alex Hidalgo, Betsy Beyer, Kyle Smith e Matt Duftler 

Engenharia de liberação/release é um termo que usamos para descrever todos os processos e artefatos relacionados à obtenção de código de um repositório para um sistema de produção em execução. A automação das liberações pode ajudar a evitar muitos dos problemas tradicionais associados à engenharia de liberação: o trabalho árduo de tarefas repetitivas e manuais, a inconsistência de um processo não automatizado, a incapacidade de saber o estado exato de uma implantação e a dificuldade de reverter. A automação da engenharia de liberação tem sido bem abordada em outras literaturas, por exemplo, livros sobre integração contínua e entrega contínua (CI/CD). 

Definimos “canarying” (canário) como uma implantação parcial e limitada no tempo de uma alteração em um serviço e sua avaliação. Essa avaliação nos ajuda a decidir se devemos ou não prosseguir com a implantação completa. A parte do serviço que recebe a alteração é “o canário”, e o restante do serviço é “o controle”. A lógica subjacente a essa abordagem é que geralmente a implantação do canário é realizada em um subconjunto muito menor da produção, ou afeta um subconjunto muito menor da base de usuários do que a porção de controle. O “canarying” é efetivamente um processo de teste A/B. 

Primeiro, abordaremos os conceitos básicos da engenharia de liberação e os benefícios da automação das liberações para estabelecer um vocabulário compartilhado. 

Princípios da Engenharia de Liberação

Os princípios básicos da engenharia de liberação são os seguintes: 

Construções reproduzíveis  

O sistema de construção deve ser capaz de receber os inputs de construção (código-fonte, recursos, etc.) e produzir artefatos repetíveis. O código construído a partir dos mesmos inputs na semana passada deve produzir a mesma saída nesta semana. 

Construções automatizadas  

Assim que o código é verificado, a automação deve produzir artefatos de construção e enviá-los para um sistema de armazenamento. 

Testes automatizados  

Uma vez que o sistema de construção automatizado construa os artefatos, uma suíte de testes de algum tipo deve garantir que eles funcionem. 

Implantações automatizadas  

As implantações devem ser realizadas por computadores, não por humanos. 

 Implantações pequenas  

Os artefatos de construção devem conter mudanças pequenas e autocontidas.  

Esses princípios oferecem benefícios específicos aos operadores: 

  • Redução da carga operacional sobre os engenheiros ao eliminar tarefas manuais e repetitivas. 
  • Reforço da revisão por pares e do controle de versão, uma vez que a automação geralmente é baseada em código. 
  • Estabelecimento de processos automatizados consistentes, repetíveis, resultando em menos erros. 
  • Possibilitando o monitoramento do pipeline de liberação, permitindo medição e melhoria contínua ao abordar questões como: 
      • Quanto tempo leva para uma liberação chegar à produção? 
      • Com que frequência as liberações são bem-sucedidas? Uma liberação bem-sucedida é uma liberação disponibilizada para os clientes sem defeitos graves ou violações de SLO (Service Level Objectives). 
      • Que mudanças podem ser feitas para detectar defeitos o mais cedo possível no pipeline? 
      • Quais etapas podem ser paralelizadas ou otimizadas ainda mais? 

 A integração contínua (CI) e a entrega contínua (CD) associadas à automação de liberação podem proporcionar melhorias contínuas ao ciclo de desenvolvimento, como mostrado na Figura 16-1. Quando as liberações são automatizadas, é possível fazer liberações com mais frequência. Para software com uma taxa de mudança significativa, fazer liberações mais frequentes significa que menos mudanças são agrupadas em um determinado artefato de liberação. Artefatos de liberação menores e autocontidos tornam mais barato e fácil reverter qualquer artefato de liberação em caso de bug. Frequências de liberação mais rápidas significam que correções de bugs chegam aos usuários mais rapidamente. 

  

Figura 16-1. O ciclo virtuoso da CI/CD 

Equilibrando velocidade de liberação e confiabilidade

 A velocidade de liberação (doravante chamada de “envio”) e a confiabilidade frequentemente são tratadas como objetivos opostos. O negócio deseja enviar novos recursos e melhorias de produtos o mais rápido possível com 100% de confiabilidade! Embora esse objetivo não seja alcançável (pois 100% nunca é o alvo correto para confiabilidade; consulte o Capítulo 2), é possível enviar o mais rápido possível enquanto atende a metas específicas de confiabilidade para um determinado produto. 

O primeiro passo para esse objetivo é entender o impacto do envio na confiabilidade do software. Na experiência do Google, a maioria dos incidentes é desencadeada por implantações binárias ou de configuração (consulte o Apêndice C). Muitos tipos de alterações de software podem resultar em falha do sistema, por exemplo, alterações no comportamento de um componente subjacente, alterações no comportamento de uma dependência (como uma API) ou uma alteração na configuração, como DNS. 

Apesar do risco inerente em fazer mudanças no software, essas alterações – correções de bugs, patches de segurança e novos recursos – são necessárias para o sucesso do negócio. Em vez de se opor às mudanças, você pode usar o conceito de SLOs (Service Level Objectives) e orçamentos de erros para medir o impacto das liberações em sua confiabilidade. Seu objetivo deve ser enviar o software o mais rápido possível, ao mesmo tempo em que atende às metas de confiabilidade que seus usuários esperam. A próxima seção discute como você pode usar um processo de canário para alcançar esses objetivos. 

Separando componentes que mudam em diferentes taxas

Seus serviços são compostos por múltiplos componentes com diferentes taxas de mudança: binários ou código, ambientes como JVM, kernel/SO, bibliotecas, configuração de serviço ou flags, configuração de recursos/experimentos e configuração de usuários. Se você tem apenas uma maneira de implantar mudanças, permitir que esses componentes mudem independentemente pode ser difícil. 

Frameworks de flags de recurso ou experimentos como Gertrude, Feature e PlanOut permitem separar os lançamentos de recursos das liberações binárias. Se uma liberação binária incluir vários recursos, você pode ativá-los um de cada vez alterando a configuração do experimento. Dessa forma, você não precisa agrupar todas essas mudanças em uma única alteração grande ou realizar uma liberação individual para cada recurso. Mais importante ainda, se apenas alguns dos novos recursos não se comportarem como esperado, você pode desativar seletivamente esses recursos até que o próximo ciclo de construção/liberação possa implantar uma nova binária. 

Você pode aplicar os princípios de flags de recurso/experimentos a qualquer tipo de alteração em seu serviço, não apenas a liberações de software. 

O que é canarying?

O termo “canarying” refere-se à prática de trazer canários para dentro de minas de carvão para determinar se a mina é segura para os humanos. Como os pássaros são menores e respiram mais rápido do que os humanos, eles são intoxicados por gases perigosos mais rapidamente do que seus manipuladores humanos. 

Mesmo que seu pipeline de liberação seja totalmente automatizado, você não será capaz de detectar todos os defeitos relacionados à liberação até que o tráfego real esteja atingindo o serviço. Quando uma liberação estiver pronta para ser implantada na produção, sua estratégia de teste deve instilar uma confiança razoável de que a liberação é segura e funciona conforme o esperado. No entanto, seus ambientes de teste não são 100% idênticos à produção, e seus testes provavelmente não cobrem 100% dos cenários possíveis. Alguns defeitos chegarão à produção. Se uma liberação for implantada instantaneamente em todos os lugares, qualquer defeito será implantado da mesma forma. 

Este cenário pode ser aceitável se você puder detectar e resolver os defeitos rapidamente. No entanto, você tem uma alternativa mais segura: inicialmente exponha apenas parte do tráfego de produção à nova liberação usando um canário. O “canarying” permite que o pipeline de implantação detecte defeitos o mais rápido possível com o menor impacto possível em seu serviço. 

Engenharia de liberação e canarying

Ao implantar uma nova versão de um sistema ou de seus componentes-chave (como configuração ou dados), agrupamos mudanças – mudanças que geralmente não foram expostas a entradas do mundo real, como tráfego voltado para o usuário ou processamento em lote de dados fornecidos pelo usuário. Mudanças trazem novos recursos e capacidades, mas também trazem riscos que são expostos no momento da implantação. Nosso objetivo é mitigar esse risco testando cada mudança em uma pequena parte do tráfego para ganhar confiança de que ela não tem efeitos negativos. Discutiremos os processos de avaliação mais tarde neste capítulo. 

O processo de canarying também nos permite ganhar confiança em nossa mudança à medida que a expomos a quantidades cada vez maiores de tráfego. Introduzir a mudança ao tráfego real de produção também nos permite identificar problemas que podem não ser visíveis em estruturas de teste como testes unitários ou testes de carga, que muitas vezes são mais artificiais. 

Vamos examinar o processo de canarying e sua avaliação usando um exemplo prático, enquanto evitamos uma análise estatística profunda. Em vez disso, focamos no processo como um todo e em considerações práticas típicas. Usaremos um aplicativo simples no App Engine para ilustrar vários aspectos da implantação. 

Requisitos de um processo de canarying

O Canarying para um determinado serviço requer capacidades específicas: 

  • Um método para implantar a mudança canarizada para um subconjunto da população do serviço. 
  • Um processo de avaliação para determinar se a mudança canarizada é “boa” ou “ruim”. 
  • Integração das avaliações do canário no processo de liberação. 

Em última análise, o processo de canarying demonstra valor quando os canários detectam candidatos a liberações ruins com alta confiança e identificam boas liberações sem falsos positivos. 

Nosso exemplo de configuração

Usaremos um aplicativo web frontend simples para ilustrar alguns conceitos de canarying. O aplicativo oferece uma API baseada em HTTP que os consumidores podem usar para manipular vários dados (informações simples como o preço de um produto). O aplicativo de exemplo possui alguns parâmetros ajustáveis que podemos usar para simular vários sintomas de produção, a serem avaliados pelo processo de canary. Por exemplo, podemos fazer com que o aplicativo retorne erros para 20% das solicitações, ou podemos estipular que 5% das solicitações levem pelo menos dois segundos. 

Ilustramos o processo de canary usando um aplicativo implantado no Google App Engine, mas os princípios se aplicam a qualquer ambiente. Embora o aplicativo de exemplo seja bastante artificial, em cenários do mundo real, aplicativos semelhantes compartilham sinais comuns com nosso exemplo que podem ser usados em um processo de canary. 

Nosso serviço de exemplo tem duas versões potenciais: ao vivo e candidata a lançamento. A versão ao vivo é a versão atualmente implantada na produção, e a candidata a lançamento é uma versão recém-criada. Usamos essas versões para ilustrar vários conceitos de implantação e como implementar canários para tornar o processo de implantação mais seguro. 

Um Deploy Roll Forward versus um Deploy Canary Simples

Vamos primeiro examinar um deployment sem processo canário, para que possamos mais tarde compará-lo a um deployment com canário em termos de economia de error budgetss e impacto geral quando ocorre uma falha. Nosso processo de deployment apresenta um ambiente de desenvolvimento. Quando sentimos que o código está funcionando no ambiente de desenvolvimento, implantamos essa versão no ambiente de produção. 

Pouco depois do nosso deployment, nosso monitoramento começa a reportar uma alta taxa de erros (veja a Figura 16-2, onde configuramos intencionalmente nossa aplicação de exemplo para falhar em 20% das solicitações para simular um defeito no serviço exemplo). Para o propósito deste exemplo, digamos que nosso processo de deployment não nos dá a opção de reverter para uma configuração anterior conhecida como boa. Nossa melhor opção para corrigir os erros é encontrar os defeitos na versão de produção, corrigi-los e implantar uma nova versão durante a interrupção. Essa ação quase certamente prolongará o impacto do bug para os usuários. 

 

Figura 16-2. Alta taxa de erros após o deployment. 

Para melhorar esse processo inicial de deployment, podemos utilizar canários em nossa estratégia de lançamento para reduzir o impacto de um push de código ruim. Em vez de implantar em produção de uma vez só, precisamos de uma maneira de criar um pequeno segmento de produção que execute nosso candidato a lançamento. Em seguida, podemos enviar uma pequena parte do tráfego para esse segmento de produção (o canário) e compará-lo com o outro segmento (o controle). Usando este método, podemos identificar defeitos no candidato a lançamento antes que toda a produção seja afetada. 

O simples deployment canário em nosso exemplo do App Engine divide o tráfego entre versões específicas rotuladas de nossa aplicação. Você pode dividir o tráfego usando o App Engine, ou qualquer outro método, como pesos de backend em um balanceador de carga, configurações de proxy, ou registros DNS de round-robin. 

A Figura 16-3 mostra que o impacto da mudança é grandemente reduzido quando usamos um canário; na verdade, os erros são quase invisíveis! Isso levanta uma questão interessante: a avaliação do canário é difícil de ver e acompanhar em comparação com a tendência geral do tráfego. 

 

Figura 16-3. Taxa de erro do deployment canário; como a população do canário é um pequeno subconjunto da produção, a taxa de erro geral é reduzida. 

Para obter uma visão mais clara dos erros que precisamos monitorar em uma escala razoável, podemos examinar nossa métrica chave (códigos de resposta HTTP) por versão da aplicação no App Engine, conforme mostrado na Figura 16-4. Quando olhamos a divisão por versão, podemos ver claramente os erros que a nova versão introduz. Também podemos observar na Figura 16-4 que a versão ativa está servindo muito poucos erros. 

Agora podemos ajustar nosso deployment para reagir automaticamente com base na taxa de erros HTTP por versão do App Engine. Se a taxa de erro da métrica do canário estiver muito distante da taxa de erro do controle, isso sinaliza que o deployment canário é “ruim”. Em resposta, devemos pausar e reverter o deployment, ou talvez contatar um humano para ajudar a solucionar o problema. Se as proporções de erro forem semelhantes, podemos prosseguir com o deployment normalmente. No caso da Figura 16-4, nosso deployment canário é claramente ruim e devemos revertê-lo. 

 

Figura 16-4. Códigos de resposta HTTP por versão do App Engine; o candidato a lançamento serve a grande maioria dos erros, enquanto a versão ativa produz um número baixo de erros em um estado estável (nota: o gráfico usa uma escala logarítmica de base 10). 

Implementação de canário

Agora que vimos uma implementação de deployment canário relativamente trivial, vamos nos aprofundar nos parâmetros que precisamos entender para um processo de canário bem-sucedido. 

Minimizar o Risco aos SLOs e ao Error Budget

O Capítulo 2 discute como os SLOs refletem os requisitos de negócios em torno da disponibilidade do serviço. Esses requisitos também se aplicam às implementações de canário. O processo de canário arrisca apenas um pequeno fragmento do nosso error budgets, que é limitado pelo tempo e pelo tamanho da população do canário. 

O deployment global pode colocar o SLO em risco rapidamente. Se implantarmos o candidato do nosso exemplo trivial, arriscaríamos falhar em 20% das solicitações. Se, em vez disso, usarmos uma população de canário de 5%, serviríamos 20% de erros para 5% do tráfego, resultando em uma taxa de erro geral de 1% (como visto anteriormente na Figura 16-3). Essa estratégia nos permite conservar nosso error budgets — o impacto no orçamento é diretamente proporcional à quantidade de tráfego exposto a defeitos. Podemos supor que a detecção e o rollback levam aproximadamente o mesmo tempo tanto para o deployment ingênuo quanto para o deployment canário, mas ao integrar um processo de canário em nosso deployment, aprendemos informações valiosas sobre nossa nova versão a um custo muito menor para o nosso sistema. 

Este é um modelo muito simples que assume carga uniforme. Também assume que podemos gastar todo o nosso error budget (além do que já incluímos na medição orgânica da disponibilidade atual) em canários. Em vez da disponibilidade real, aqui consideramos apenas a indisponibilidade introduzida por novas versões. Nosso modelo também assume uma taxa de falha de 100% porque este é o pior cenário possível. É provável que os defeitos no deployment canário não afetem 100% do uso do sistema. Também permitimos que a disponibilidade geral do sistema fique abaixo do SLO durante a duração do deployment canário. 

Este modelo tem falhas claras, mas é um ponto de partida sólido que você pode ajustar para atender às necessidades de negócios. Recomendamos usar o modelo mais simples que atenda aos seus objetivos técnicos e de negócios. Em nossa experiência, focar em tornar o modelo tecnicamente o mais correto possível muitas vezes leva a um investimento excessivo em modelagem. Para serviços com altas taxas de complexidade, modelos excessivamente complexos podem levar a ajustes incessantes do modelo sem benefício real. 

Escolhendo uma população e duração para o canário

Ao escolher uma duração apropriada para o canário, é necessário levar em conta a velocidade de desenvolvimento. Se você faz lançamentos diários, não pode deixar seu canário durar uma semana enquanto executa apenas um deployment canário por vez. Se você implanta semanalmente, tem tempo para realizar canários bastante longos. Se você implanta continuamente (por exemplo, 20 vezes em um dia), a duração do canário deve ser significativamente mais curta. Em uma nota relacionada, embora possamos executar vários deployments canários simultaneamente, isso adiciona um esforço mental significativo para acompanhar o estado do sistema. Isso pode se tornar problemático durante quaisquer circunstâncias não padrão, quando é importante raciocinar rapidamente sobre o estado do sistema. Executar canários simultâneos também aumenta o risco de contaminação de sinal se os canários se sobrepuserem. Recomendamos fortemente executar apenas um deployment canário por vez. 

Para uma avaliação básica, não precisamos de uma população de canário muito grande para detectar condições críticas chave. No entanto, um processo de canário representativo requer decisões em várias dimensões: 

Tamanho e duração 

Deve ser significativo e durar tempo suficiente para ser representativo do deployment geral. Encerrar um deployment canário após receber apenas algumas consultas não fornece um sinal útil para sistemas caracterizados por consultas diversas com funcionalidades variadas. Quanto maior a taxa de processamento, menos tempo é necessário para obter uma amostra representativa, a fim de garantir que o comportamento observado seja realmente atribuível à mudança do canário e não apenas um artefato aleatório. 

Volume de tráfego 

Precisamos receber tráfego suficiente no sistema para garantir que ele tenha tratado uma amostra representativa e que o sistema tenha a chance de reagir negativamente às entradas. Tipicamente, quanto mais homogêneas forem as solicitações, menor será o volume de tráfego necessário. 

Hora do dia 

Defeitos de desempenho geralmente se manifestam apenas sob carga pesada, portanto, implantar em um horário fora do pico provavelmente não desencadearia defeitos relacionados ao desempenho. 

Métricas para avaliação 

A representatividade de um canário está intimamente ligada às métricas que escolhemos para avaliar (que discutiremos mais adiante neste capítulo). Podemos avaliar métricas triviais como sucesso de consultas rapidamente, mas outras métricas (como profundidade de fila) podem precisar de mais tempo ou uma população de canário maior para fornecer um sinal claro. 

Frustrantemente, esses requisitos podem ser mutuamente conflitantes. Implementar canários é um ato de equilíbrio, informado tanto pela análise fria dos piores cenários quanto pelo histórico realista passado de um sistema. Uma vez que você tenha coletado métricas de canários anteriores, pode escolher os parâmetros do canário com base nas taxas típicas de falha na avaliação do canário, em vez de cenários hipotéticos de pior caso. 

Seleção e avaliação de métricas

Até agora, temos analisado a taxa de sucesso, uma métrica muito clara e óbvia para avaliação do canário. Mas intuitivamente, sabemos que essa única métrica não é suficiente para um processo de canário significativo. Se servirmos todas as solicitações com 10 vezes a latência, ou usar 10 vezes mais memória ao fazer isso, também podemos ter um problema. Nem todas as métricas são boas candidatas para avaliar um canário. Quais propriedades das métricas são mais adequadas para avaliar se um canário é bom ou ruim? 

As métricas devem indicar problemas

Em primeiro lugar, a métrica precisa ser capaz de indicar problemas no serviço. Isso é complicado porque o que constitui um “problema” nem sempre é objetivo. Provavelmente podemos considerar uma solicitação de usuário falhada como problemática. Mas e se uma solicitação levar 10% a mais, ou o sistema exigir 10% mais de memória? Normalmente, recomendamos usar SLIs como ponto de partida para pensar sobre métricas de canário. Bons SLIs tendem a ter forte atribuição à saúde do serviço. Se os SLIs já estiverem sendo medidos para garantir a conformidade com os SLOs, podemos reutilizar esse trabalho. 

Quase qualquer métrica pode ser problemática quando levada ao extremo, mas também há um custo em adicionar muitas métricas ao seu processo de canário. Precisamos definir corretamente uma noção de comportamento aceitável para cada uma das métricas. Se a ideia de comportamento aceitável for excessivamente rígida, teremos muitos falsos positivos; ou seja, pensaremos que um deployment canário está ruim, mesmo que não esteja. Por outro lado, se a definição de comportamento aceitável for muito frouxa, será mais provável deixar um deployment canário ruim passar despercebido. Escolher corretamente o que é comportamento aceitável pode ser um processo caro — é demorado e requer análise. Quando é feito de forma inadequada, no entanto, seus resultados podem completamente enganá-lo. Além disso, é necessário reavaliar as expectativas regularmente à medida que o serviço, seu conjunto de recursos e seu comportamento evoluem. 

Devemos classificar as métricas que queremos avaliar com base em nossa opinião sobre o quão bem elas indicam problemas reais percebidos pelo usuário no sistema. Selecione as principais métricas para usar em avaliações de canário (talvez não mais do que uma dúzia). Muitas métricas podem trazer retornos decrescentes, e em algum momento, os retornos são superados pelo custo de mantê-las, ou pelo impacto negativo na confiança no processo de lançamento se elas não forem mantidas. 

Para tornar esta diretriz mais tangível, vamos analisar nosso serviço de exemplo. Ele possui muitas métricas que podemos avaliar: uso da CPU, pegada de memória, códigos de retorno HTTP (200s, 300s, etc.), latência de resposta, correção, e assim por diante. Neste caso, nossas melhores métricas provavelmente são os códigos de retorno HTTP e a latência de resposta, porque sua degradação se mapeia mais de perto a um problema real que impacta os usuários. Nesse cenário, métricas para o uso da CPU não são tão úteis: um aumento no uso de recursos não necessariamente impacta um serviço e pode resultar em um processo de canário instável ou ruidoso. Isso pode resultar no desligamento ou ignorância do processo de canário pelos operadores, o que pode derrotar o propósito de ter um processo de canário em primeiro lugar. No caso de serviços de front-end, intuitivamente sabemos que ser mais lento ou falhar em responder são sinais típicos de problemas no serviço. 

Os códigos de retorno HTTP contêm casos interessantes e complicados, como o código 404, que nos informa que o recurso não foi encontrado. Isso pode acontecer porque os usuários obtêm a URL errada (imagine uma URL quebrada sendo compartilhada em um fórum de discussão popular), ou porque o servidor para de servir um recurso incorretamente. Frequentemente, podemos contornar problemas como este excluindo os códigos 400 da nossa avaliação de canário e adicionando monitoramento caixa-preta para testar a presença de uma URL específica. Podemos então incluir os dados de caixa-preta como parte de nossa análise de canário para ajudar a isolar nosso processo de canário de comportamentos estranhos dos usuários. 

As métricas devem ser representativas e atribuíveis

A fonte de mudanças nas métricas observadas deve ser claramente atribuível à mudança que estamos canarizando e não deve ser influenciada por fatores externos. 

Em uma grande população (por exemplo, muitos servidores ou muitos contêineres), é provável que tenhamos outliers — máquinas sobrecarregadas, máquinas executando kernels diferentes com características de desempenho diferentes, ou máquinas em um segmento de rede sobrecarregado. A diferença entre a população do canário e o controle é tão importante quanto a função da mudança que implementamos quanto a diferença entre as duas infraestruturas nas quais implementamos. 

Gerenciar canários é um ato de equilíbrio entre várias forças. Aumentar o tamanho da população do canário é uma maneira de diminuir o impacto desse problema (como discutido anteriormente). Quando alcançamos o que consideramos um tamanho razoável de população do canário para nosso sistema, precisamos considerar se as métricas que escolhemos podem mostrar alta variância. 

Também devemos estar cientes dos domínios de falha compartilhados entre nossos ambientes de canário e controle; um canário ruim poderia impactar negativamente o controle, enquanto um comportamento ruim no sistema poderia nos levar a avaliar incorretamente o canário. Da mesma forma, certifique-se de que suas métricas estão bem isoladas. Considere um sistema que executa nossa aplicação e outros processos. Um aumento dramático no uso da CPU do sistema como um todo seria uma métrica ruim, pois outros processos no sistema (carga do banco de dados, rotação de logs, etc.) podem estar causando esse aumento. Uma métrica melhor seria o tempo de CPU gasto enquanto o processo servia a solicitação. Uma métrica ainda melhor seria o tempo de CPU gasto enquanto o processo de serviço estava realmente agendado em uma CPU durante o intervalo de tempo. Embora uma máquina fortemente sobrecarregada co-localizada com nosso processo seja obviamente um problema (e o monitoramento deve detectá-lo!), não é causado pela mudança que estamos canarizando, portanto, não deve ser sinalizado como uma falha no deployment do canário. 

Os canários também precisam ser atribuíveis; ou seja, você também deve ser capaz de vincular a métrica do canário aos SLIs. Se uma métrica puder mudar drasticamente sem impacto no serviço, é improvável que seja uma boa métrica de canário. 

A avaliação antes/depois é arriscada

Um processo de canário antes/depois é uma extensão do problema de atribuição. Neste processo, o sistema antigo é totalmente substituído pelo novo sistema, e sua avaliação de canário compara o comportamento do sistema antes e depois da mudança ao longo de um período de tempo definido. Pode-se chamar este processo de “implantação do canário no espaço-tempo”, onde você escolhe os grupos A/B segmentando o tempo em vez de segmentar a população por máquinas, cookies ou outros meios. Porque o tempo é uma das maiores fontes de mudança nas métricas observadas, é difícil avaliar a degradação de desempenho com a avaliação antes/depois. 

Enquanto a implantação do canário pode ter causado a degradação, é bem possível que a degradação tenha ocorrido também no sistema de controle. Este cenário se torna ainda mais problemático se tentarmos executar uma implantação de canário ao longo de um período de tempo mais longo. Por exemplo, se realizarmos um lançamento na segunda-feira, podemos estar comparando o comportamento durante um dia útil ao comportamento durante um fim de semana, introduzindo uma grande quantidade de ruído. Neste exemplo, os usuários podem usar o serviço de maneira muito diferente durante o fim de semana, introduzindo ruído no processo de canário. 

O próprio processo antes/depois introduz a questão de se um grande pico de erro (como introduzido por uma avaliação antes/depois) é melhor do que uma taxa de erros pequena, mas possivelmente mais longa (como introduzido por um pequeno canário). Se o novo lançamento estiver completamente quebrado, quão rapidamente podemos detectar e reverter? Um canário antes/depois pode detectar o problema mais rapidamente, mas o tempo total de recuperação ainda pode ser bastante substancial e semelhante a um canário menor. Durante esse tempo, os usuários sofrem. 

Use um canário gradual para uma melhor seleção de métricas 

Métricas que não atendem às nossas propriedades ideais ainda podem trazer grande valor. Podemos introduzir essas métricas usando um processo de canário mais refinado. 

Em vez de simplesmente avaliar uma única etapa de canário, podemos usar um canário contendo várias etapas que reflitam nossa capacidade de raciocinar sobre as métricas. Na primeira etapa, não temos confiança ou conhecimento sobre o comportamento deste lançamento. Portanto, queremos usar uma etapa pequena para minimizar o impacto negativo. Em um canário pequeno, preferimos métricas que sejam a indicação mais clara de um problema — travamentos de aplicativos, falhas de solicitações e assim por diante. Uma vez que esta etapa passe com sucesso, a próxima etapa terá uma população de canário maior para aumentar a confiança em nossa análise do impacto das mudanças. 

Dependências e isolamento

O sistema sendo testado não funcionará em um vácuo completo. Por razões práticas, a população do canário e o controle podem compartilhar backends, frontends, redes, bancos de dados e outras infraestruturas. Pode até haver interações extremamente não óbvias com o cliente. Por exemplo, imagine duas solicitações consecutivas enviadas por um único cliente. A primeira solicitação pode ser tratada pela implantação do canário. A resposta do canário pode alterar o conteúdo da segunda solicitação, que pode chegar ao controle, alterando o comportamento do controle. 

O isolamento imperfeito tem várias consequências. Mais importante ainda, precisamos estar cientes de que, se o processo de canário fornecer resultados que indiquem que devemos interromper uma mudança de produção e investigar a situação, a implantação do canário não é necessariamente culpada. Este fato é verdadeiro para o canário em geral, mas na prática, é frequentemente reforçado por problemas de isolamento. 

Além disso, o isolamento imperfeito significa que o comportamento ruim da implantação do canário também pode afetar negativamente o controle. O canário é uma comparação A/B, e é possível que tanto A quanto B mudem em conjunto; isso pode causar confusão na avaliação do canário. É importante também usar medidas absolutas, como SLOs definidos, para garantir que o sistema esteja operando corretamente. 

Canário em sistemas não interativos

O capítulo focou em um sistema de solicitação/resposta interativo, que de muitas maneiras é o mais simples e mais discutido design de sistema. Outros sistemas, como pipelines de processamento assíncrono, são igualmente importantes, mas têm considerações de canário diferentes, que enumeraremos brevemente. Para mais informações sobre canarização relacionada a pipelines de processamento de dados, consulte o Capítulo 13. 

Em primeiro lugar, a duração e a implantação do canário dependem inerentemente da duração do processamento da unidade de trabalho. Ignoramos esse fator quando se trata de sistemas interativos, assumindo que o processamento da unidade de trabalho levará no máximo alguns segundos, o que é mais curto que a duração do canário. O processamento da unidade de trabalho em sistemas não interativos, como pipelines de renderização ou codificação de vídeo, pode levar muito mais tempo. Portanto, certifique-se de que a duração do canário abranja no mínimo a duração de uma única unidade de trabalho. 

O isolamento pode se tornar mais complexo para sistemas não interativos. Muitos sistemas de pipeline têm um único designador de trabalho e uma frota de trabalhadores com o código da aplicação. Em pipelines de múltiplos estágios, uma unidade de trabalho é processada por um trabalhador e depois devolvida ao pool para o mesmo ou outro trabalhador executar o próximo estágio de processamento. É útil para a análise do canário garantir que os trabalhadores que processam uma unidade de trabalho específica sejam sempre retirados do mesmo pool de trabalhadores — seja o pool do canário ou o pool de controle. Caso contrário, os sinais se tornam cada vez mais misturados (para mais sobre a necessidade de desembaraçar sinais, consulte “Requisitos de Dados de Monitoramento”). 

Finalmente, a seleção de métricas pode ser mais complicada. Podemos estar interessados no tempo de ponta a ponta para processar a unidade de trabalho (similar à latência em sistemas interativos), bem como na qualidade do processamento em si (que é, é claro, completamente específico da aplicação). 

Dadas essas ressalvas, o conceito geral de canarização permanece viável, e os mesmos princípios de alto nível se aplicam. 

Requisitos para dados de monitoramento

Ao conduzir a avaliação do canário, é necessário poder comparar os sinais do canário aos sinais de controle. Muitas vezes, isso requer algum cuidado na estruturação do sistema de monitoramento — comparações eficazes são diretas e produzem resultados significativos. 

Considere nosso exemplo anterior de uma implantação de canário para 5% da população que opera com uma taxa de erro de 20%. Como o monitoramento provavelmente examina o sistema como um todo, ele detectará uma taxa de erro geral de apenas 1%. Dependendo do sistema, este sinal pode ser indistinguível de outras fontes de erros (veja a Figura 16-3). 

Se desmembrarmos as métricas por população que atende à solicitação (o canário versus o controle), podemos observar as métricas separadas (veja a Figura 16-4). Podemos ver claramente a taxa de erro no controle versus o canário, uma ilustração marcante do que uma implantação completa traria. Aqui vemos que o monitoramento que raciocina bem sobre um serviço inteiro não é suficiente para analisar nosso canário. Ao coletar dados de monitoramento, é importante poder realizar desmembramentos detalhados que permitam diferenciar as métricas entre as populações de canário e controle. 

Outro desafio ao coletar métricas é que as implantações de canário são limitadas no tempo por design. Isso pode causar problemas quando as métricas são agregadas ao longo de períodos específicos. Considere a métrica de erros por hora. Podemos calcular essa métrica somando as solicitações ao longo da última hora. Se usarmos essa métrica para avaliar nosso canário, podemos encontrar problemas, conforme descrito na seguinte linha do tempo: 

  1. Um evento não relacionado causa alguns erros. 
  2. Um canário é implantado em 5% da população; a duração do canário é de 30 minutos. 
  3. O sistema de canário começa a monitorar a métrica de erros por hora para determinar se a implantação é boa ou ruim. 
  4. A implantação é detectada como ruim porque a métrica de erros por hora é significativamente diferente dos erros por hora da população de controle. 

Este cenário é resultado do uso de uma métrica que é calculada por hora para avaliar uma implantação que dura apenas 30 minutos. Como resultado, o processo de canário fornece um sinal muito confuso. Ao usar métricas para avaliar o sucesso do canário, certifique-se de que os intervalos de suas métricas sejam iguais ou menores que a duração do seu canário. 

Conceitos relacionados

Muitas vezes, nossas conversas com clientes abordam o uso de implantação azul/verde, geração de carga artificial e/ou divisão de tráfego na produção. Esses conceitos são semelhantes à canarização, então, embora não sejam estritamente processos de canário, podem ser usados como tal. 

Implantação Azul/Verde

A implantação azul/verde mantém duas instâncias de um sistema: uma que está servindo tráfego (verde) e outra que está pronta para servir tráfego (azul). Após implantar uma nova versão no ambiente azul, você pode então direcionar o tráfego para ele. A transição não requer tempo de inatividade, e o rollback é uma reversão trivial da alteração do roteador. Uma desvantagem é que essa configuração usa o dobro de recursos do que uma implantação mais “tradicional”. Nesta configuração, você está efetivamente realizando um processo de antes/depois do canário (discutido anteriormente). 

Você pode usar implantações azul/verde mais ou menos como canários normais, utilizando simultaneamente as implantações azul e verde (em vez de independentemente). Nessa estratégia, você pode implantar o canário na instância azul (em espera) e dividir lentamente o tráfego entre os ambientes verde e azul. Tanto suas avaliações quanto as métricas que comparam o ambiente azul com o ambiente verde devem estar ligadas ao controle de tráfego. Esta configuração se assemelha a um canário A/B, onde o ambiente verde é o controle, o ambiente azul é a implantação do canário e a população de canários é controlada pela quantidade de tráfego enviada para cada um. 

Geração de carga artificial

Em vez de expor tráfego de usuários reais a uma implantação de canário, pode ser tentador optar pela segurança e usar geração de carga artificial. Frequentemente, você pode executar testes de carga em vários estágios de implantação (QA, pré-produção e até mesmo em produção). Embora essas atividades não se enquadrem como canarização de acordo com nossa definição, elas ainda são abordagens viáveis para encontrar defeitos com algumas ressalvas. 

Testar com carga sintética faz um bom trabalho em maximizar a cobertura de código, mas não fornece uma boa cobertura de estado. Pode ser especialmente difícil simular artificialmente carga em sistemas mutáveis (sistemas com caches, cookies, afinidade de solicitações, etc.). A carga artificial também pode não modelar com precisão as mudanças de tráfego orgânico que ocorrem em um sistema real. Algumas regressões podem se manifestar apenas durante eventos não incluídos na carga artificial, levando a lacunas na cobertura. 

A carga artificial também funciona mal em sistemas mutáveis. Por exemplo, pode ser perigoso tentar gerar carga artificial em um sistema de cobrança: o sistema pode começar a enviar chamadas para provedores de cartão de crédito, que então começariam a cobrar ativamente os clientes. Embora possamos evitar testar caminhos de código perigosos, a falta de testes nesses caminhos reduz nossa cobertura de teste. 

Divisão de tráfego

Se a carga artificial não for representativa, podemos copiar o tráfego e enviá-lo tanto para o sistema de produção quanto para o sistema no ambiente de teste. Essa técnica é chamada de divisão de tráfego. Enquanto o sistema de produção atende ao tráfego real e entrega respostas aos usuários, a implantação de canário atende à cópia e descarta as respostas. Você pode até mesmo comparar as respostas do canário com as respostas reais e realizar análises adicionais. 

Essa estratégia pode fornecer tráfego representativo, mas muitas vezes é mais complicada de configurar do que um processo de canário mais direto. A divisão de tráfego também não identifica adequadamente o risco em sistemas baseados em estado; cópias de tráfego podem introduzir influências inesperadas entre as implantações aparentemente independentes. Por exemplo, se a implantação de canário e os sistemas de produção compartilharem um cache, uma taxa de acerto de cache artificialmente inflada invalidaria as medições de desempenho para as métricas de canário. 

Conclusão

Você pode usar várias ferramentas e abordagens para automatizar suas implantações e introduzir a canarização em seu pipeline. Nenhuma metodologia de teste única é uma panaceia, e as estratégias de teste devem ser informadas pelos requisitos e comportamento do sistema. A canarização pode ser uma maneira simples, robusta e facilmente integrável de complementar os testes. Quando você detecta defeitos no sistema precocemente, os usuários são minimamente impactados. A canarização também pode fornecer confiança em implantações frequentes e melhorar a velocidade de desenvolvimento. Assim como a metodologia de teste deve evoluir junto com os requisitos e o design do sistema, a canarização também deve evoluir. 

Rolar para cima