Para atualizar?
Tudo começou com uma chamada de PagerDuty - um switch com falha em um de nossos datacenters. Um tíquete de suporte depois, e nosso fornecedor diz que é hora de fazer uma atualização do firmware. Em todos os nossos switches. Nós nos encolhemos. Fizemos isso muitas vezes antes e, de alguma forma, sempre houve casos extremos, estados estranhos e outras finalidades soltas. Atualizando um único switch? era um aborrecimento a que estávamos acostumados, que felizmente não acontecia com muita frequência. Francamente, não é grande coisa. Mas atualizando uma frota de 200? uh…
Para acelerar, nosso ambiente de rede é baseado na malha Clos , ou mais especificamente, em uma topologia de coluna vertebral . O BGP é o protocolo de roteamento dinâmico que cola tudo junto.
Estamos usando o Cumulus Linux como o sistema operacional de nossos comutadores, o Chef para gerenciar sua configuração e o Jenkins para automatizar o provisionamento.
Então, vamos pular direto para ele. Como atualizamos firmwares de switch no Avance Network? Simples. Nós não. Nós os provisionamos novamente. Caso você não tenha certeza do que quero dizer, vou esclarecer.
Processo de re-provisionamento de switches
Cumulus Linux é uma distribuição Linux projetada para switches. Como tal, você pode gerenciá-lo usando ferramentas padrão do Linux e também pode instalá-lo usando metodologias padrão do Linux, como a inicialização do PXE. Exceto no caso de comutadores, a inicialização do PXE é substituída pelo Open Network Install Environment ( ONIE ).
Depois que um switch é instruído a fornecer novamente, na próxima inicialização, as seguintes coisas acontecerão:
- O switch solicita um endereço IP via DHCP
- O servidor DHCP confirma e responde com a opção DHCP 114 para o local da imagem de instalação
- O switch usa o ONIE para baixar a imagem de disco Cumulus Linux, instala e reinicializa
- Sucesso! Agora você está executando o Cumulus Linux
No final desse processo, temos uma versão atualizada do firmware (SO) em execução no comutador sem nenhuma configuração especial. Assim como um novo switch.
"Mas e a configuração?" você pode perguntar. Excelente pergunta! Em uma palavra, ZTP . Em três palavras, "Zero Touch Provisioning":
- O switch inicializa para uma nova versão do Cumulus Linux (consulte a etapa 4 do ONIE acima)
- A interface de gerenciamento (eth0) é configurada por padrão para o DHCP
- eth0 envia uma solicitação DHCP
- O servidor DHCP retorna uma URL para um script ZTP
- O script ZTP instala o cliente Chef, configura e executa
- Chef-client executa nossa run_list caseira
Depois que a execução do cliente Chef é concluída com êxito, o switch é totalmente configurado com toda a configuração relevante personalizada para ele. As sessões do BGP são estabelecidas e o switch volta à produção.
Agora que você tem uma noção do processo, eis o que é realmente necessário para provisionar novamente um comutador:
- Onie-select " seleciona o modo ONIE para usar durante a próxima inicialização
- “ -I ” significa “Install Boot”: na próxima reinicialização, carregue o ONIE no modo 'install'. Isso instala um novo sistema operacional, substituindo o atual
- " -F " significa "Forçar a operação": assume "sim" a qualquer pergunta que o script possa fazer
- " reboot " reinicializará o switch somente se o comando onie-select anterior tiver êxito
15 minutos depois, e o switch está de volta ao jogo, executando um firmware atualizado e sua própria configuração personalizada. Muito legal, certo? Agora, basta fazer isso com 74 switches, que é o que temos em um datacenter.
15 minutos vezes 74 é…
… muito. Especialmente se você adicionar algumas validações antes e depois do "re-provisionamento", o que leva a 30 minutos por switch. Multiplicado por 74? 2220 minutos, que são 37 horas. É muito tempo para cuidar de interruptores. Sem mencionar falhas, casos extremos, falta de um switch ou dois ...
E esse é apenas um datacenter. Multiplicado por 3, são 6660 minutos, ou seja, 111 horas. Portanto, se você passar 8 horas por dia fazendo exatamente isso, um interruptor de cada vez, sem nada falhar, levaria cerca de duas semanas de trabalho repetitivo para fazer a atualização completa.
Deve haver uma maneira melhor. Uma maneira mais inteligente. Uma maneira mais rápida. Uma maneira que é:
- Automatizado
- Robusto
- Mãos livres
- Auditável
Uma maneira melhor
3 dias depois e temos algo para mostrar. Permitam-me apresentar-lhe nosso processo de "Switch Upgrade Automation" (observe que "tem um nome atraente" não fazia parte dos requisitos).
O processo de "automação de atualização de switch" é um programa baseado em Ruby, executado dentro de um contêiner de docker. Ele é construído como um trabalho Jenkins, executado periodicamente. O programa abrange todas as validações necessárias para escolher uma opção candidata válida, re-provisioná-la e garantir que a opção esteja pronta para ser colocada novamente em produção.
Mergulhando no processo, cada compilação Jenkins gerará um contêiner de encaixe, obtendo os seguintes argumentos (na verdade existem mais, mas escolhemos os principais):
- Nome do datacenter
- Tipo de interruptor: folha ou coluna
- Alterne a versão do SO para atualizar de
- A primeira ou a segunda chave no rack
Automatizado e Robusto
Primeiro, verificamos se já existe uma compilação em execução, pois não queremos o reaprovisionamento de dois comutadores ao mesmo tempo. Adotamos a abordagem conservadora e decidimos executar o processo em série. Além disso, não queremos passar para a próxima opção se a anterior falhar, pois pode haver um problema com o processo, o ambiente ou a própria opção.
Em seguida, criamos uma consulta dinâmica do Prometheus que retorna uma lista de todos os comutadores executando a versão do SO da qual estamos atualizando. Usando esta lista, escolhemos uma opção aleatória e acionamos mais uma consulta do Prometheus. A consulta adicional nos permite recuperar as seguintes informações sobre a opção de candidato:
- Status de integridade global - uma métrica criada a partir de muitos testes locais em execução no comutador, incluindo sensores de hardware, ztp_exit_status, status de serviços e PTM
- Status do par BGP - número de pares estabelecidos do BGP
- Número de links ativos
Usando esses valores, podemos construir uma estrutura de dados que contém o nome do comutador, os valores das métricas e a fase de validação: o status de pré-atualização. Lembre-se dessa estrutura, voltaremos a ela mais tarde.
switch_list.each do |switch| puts "Candidate switch for upgrade: #{switch}" switch_data_structure = { switch = { 'pre' = {}, 'post' = {} } } switch_data = fetch_switch_data(switch_data_structure, switch, switch_type, 'pre') ...
Agora temos o status de pré-atualização do switch candidato, no entanto, isso não é suficiente. Também precisamos validar a funcionalidade do comutador irmão do candidato (o outro comutador no rack) antes de executar qualquer ação. Executamos a mesma consulta do Prometheus novamente para recuperar as métricas mencionadas acima, mas desta vez para a troca de irmãos. O nome do comutador irmão e seus valores métricos são inseridos no status de pré-atualização.
sibling_switches = get_sibling_switches(switch, switch_type, switch_list) sibling_switches.each do |sibling| sibling_data_structure = { sibling = { 'pre' = {}, 'post' = {} } } sibling_data = fetch_switch_data(sibling_data_structure, sibling, switch_type, 'pre') switch_data.merge!(sibling_data) end ...
Compare o switch candidato com o irmão: se ele corresponder, ou se o candidato ao upgrade tiver menos links ativos / vizinhos BGP do que o irmão (entre outras métricas), estamos prontos - sabemos que não perderemos a conectividade com nenhum dos as máquinas no rack. Isso nos dá confiança para executar o processo de reprovisionamento enquanto houver cargas de trabalho ativas no rack.
Cross-checking switch metrics with sibling switch: leaf-r32-p1-pod1 leaf-r32-p2-pod1leaf-r32-p1-pod1: ---- {"bgp_peer_status"="32","interface_link_status"="37","health_global_status"="0"}leaf-r32-p2-pod1: ---- {"bgp_peer_status"="32","interface_link_status"="37","health_global_status"="0"}Status: Pre upgrade validation passed!Sending changelog for switch leaf-r32-p1-pod1Sending leaf-r32-p1-pod1 to Re-provisioning, command is '/root/run_switch_provision.sh -F'
E se a validação falhar? o processo ignoraria essa opção de candidato e elegeria um novo candidato.
Mãos-livres e auditáveis
Pouco antes de reiniciar nosso switch candidato, silenciamos alertas, para não interromper nossa chamada enquanto o processo é executado em segundo plano. Além disso, para garantir que sabemos “o que aconteceu, quando e por quê”, usamos nosso mecanismo interno de registro de alterações para disparar um evento de registro de máquina para futuras auditorias. Quais são essas coisas do changelog e machinelog? Eles são outro post de blog esperando para serem escritos.
Com todas as validações de pré-provisionamento, mascaramento de alerta e eventos de auditoria fora do caminho, é hora do show! Nós acionamos o Processo de Reprovisionamento de Comutador para nosso comutador candidato eleito e o enviamos para uma reinicialização.
Depois de reiniciado, monitoramos se o switch aceita conexões na porta TCP 9100, que é a porta exportadora do nó Prometheus. Por quê? Como estamos usando o Chef para instalar o exportador de nó do Prometheus, o que nos dá uma boa indicação de que o Chef (que é a etapa final de provisionamento) foi concluído com êxito. Além disso, o exportador de nós é responsável por expor as métricas do comutador, nas quais confiamos na validação pós-provisionamento. Depois que conseguimos estabelecer uma conexão, sabemos que o switch está novamente ativo, executando o novo sistema operacional.
Por último, mas não menos importante, precisamos validar que nossa opção está no mesmo estado que estava antes do re-provisionamento. Queremos ter certeza de que não perdemos nenhum link, colegas, etc. Como fazemos isso?
Lembra do status de pré-atualização que salvamos quando iniciamos o processo de validação? É hora de obter o status pós-atualização e comparar os dois. Basicamente, isso significa executar a mesma consulta de antes, criar a estrutura de dados e executar um diff. Se tudo estiver bem, o processo foi concluído com êxito e podemos passar para o próximo candidato. Caso contrário, abortamos e notificamos um membro da equipe que algo deu errado.
Retrieving switch metrics: leaf-r32-p1-pod1 Upgrade_phase: postPrometheus query: count(fabric_switch_bgp_peer_status{instance=~"leaf-r32-p1-pod1"}==0) - Prometheus response: 2Prometheus query: count(fabric_switch_bgp_peer_status{instance=~"leaf-r32-p2-pod1"}==0) - Prometheus response: 2Prometheus query: count(fabric_switch_interface_link_status{instance=~"leaf-r32-p1-pod1"}==2) - Prometheus response: 7Prometheus query: count(fabric_switch_interface_link_status{instance=~"leaf-r32-p2-pod1"}==2) - Prometheus response: 7Prometheus query: fabric_switch_health_global_status{instance=~"leaf-r32-p1-pod1"}==0 - Prometheus response: 0Prometheus query: fabric_switch_health_global_status{instance=~"leaf-r32-p2-pod1"}==0 - Prometheus response: 0Data for switch: leaf-r32-p1-pod1.nydc1 was validated SUCCESSFULY after upgrade
Então o que aconteceu?
Se você leu atentamente até agora, pode perceber que não economizamos tempo em todo esse processo. Optamos por não paralelizar o re-provisionamento no momento, para garantir que o raio de explosão, em caso de falha, seja limitado a um único rack. No entanto, removemos o fator humano do processo, o que significa que ele pode executar as 2 semanas completas em segundo plano e informar quando as coisas são feitas (ou não estão funcionando como o esperado). Isso, por sua vez, nos libera para lidar com os aspectos mais importantes da execução de um sistema de produção em escala.