Quando os desenvolvedores implementam uma carga de trabalho em uma plataforma de computação em nuvem, geralmente não param para pensar no hardware subjacente em que seus serviços são executados. Na imagem idealizada da "nuvem", a manutenção do hardware e as limitações físicas são invisíveis. Infelizmente, o hardware precisa de manutenção ocasionalmente, o que pode causar tempo de inatividade. Para evitar que esse tempo de inatividade seja repassado aos nossos clientes e para cumprir a promessa da nuvem, a Linode implementa uma ferramenta chamada Live Migrations.
Live Migrations é uma tecnologia que permite que as instâncias da Linode se movimentem entre máquinas físicas sem interrupção de serviço. Quando um Linode é movido com a Live Migrations, a transição é invisível para os processos desse Linode. Se o hardware de um host precisar de manutenção, o Live Migrations pode ser usado para fazer a transição perfeita de todos os Linodes desse host para um novo host. Após esta migração estar concluída, o hardware físico pode ser reparado, e o tempo de inatividade não terá impacto sobre nossos clientes.
Para mim, o desenvolvimento desta tecnologia foi um momento decisivo e um ponto de inflexão entre as tecnologias de nuvens e as tecnologias sem nuvens. Tenho um ponto fraco para a tecnologia Live Migrations porque passei mais de um ano de minha vida trabalhando nela. Agora, eu posso compartilhar a história com todos vocês.
Como funcionam as migrações ao vivo
A Live Migrations na Linode começou como a maioria dos novos projetos; com muita pesquisa, uma série de protótipos e a ajuda de muitos colegas e gerentes. O primeiro movimento em frente foi investigar como a QEMU lida com a Live Migrations. QEMU é uma tecnologia de virtualização utilizada pela Linode, e Live Migrations é uma característica da QEMU. Como resultado, o foco de nossa equipe foi trazer esta tecnologia para a Linode, e não inventá-la.
Então, como funciona a tecnologia Live Migration da forma como a QEMU a implementou? A resposta é um processo de quatro etapas:
- A instância qemu de destino é fiada com exatamente os mesmos parâmetros que existem na instância qemu de origem.
- Os discos são Live Migrated over. Quaisquer mudanças no disco também são comunicadas enquanto esta transferência está em execução.
- A RAM é Live Migrated over. Qualquer mudança nas páginas da RAM também tem que ser comunicada. Se houver mudanças de dados em disco também durante esta fase, então essas mudanças também serão copiadas para o disco da instância QEMU de destino.
- O ponto de corte é executado. Quando a QEMU determina que há poucas páginas de RAM suficientes que ela pode cortar com confiança, as instâncias de QEMU de origem e destino são pausadas. A QEMU copia sobre as últimas páginas da RAM e o estado da máquina. O estado da máquina inclui o cache da CPU e a próxima instrução da CPU. Então, a QEMU diz ao destino para começar, e o destino pega exatamente onde a fonte parou.
Estas etapas explicam como realizar uma Migração ao Vivo com a QEMU a um nível elevado. Entretanto, especificar exatamente como você deseja que a instância QEMU de destino seja iniciada é um processo muito manual. Além disso, cada ação no processo precisa ser iniciada no momento certo.
Como as Migrações ao Vivo são Implementadas na Linode
Depois de ver o que os desenvolvedores da QEMU já criaram, como utilizamos isso na Linode? A resposta a esta pergunta é onde a maior parte do trabalho foi para nossa equipe.
De acordo com a etapa 1 do fluxo de trabalho da migração em tempo real, a instância do QEMU de destino é ativada para aceitar a migração em tempo real de entrada. Ao implementar essa etapa, o primeiro pensamento foi pegar o perfil de configuração do Linode atual e girá-lo em uma máquina de destino. Em teoria, isso seria simples, mas se pensarmos melhor, veremos cenários mais complicados. Em particular, o perfil de configuração informa como o Linode foi inicializado, mas não descreve necessariamente o estado completo do Linode após a inicialização. Por exemplo, um usuário poderia ter anexado um dispositivo Block Storage conectando-o ao Linode depois que ele foi inicializado, e isso não seria documentado no perfil de configuração.
A fim de criar a instância QEMU no anfitrião de destino, foi necessário traçar um perfil da instância QEMU atualmente em funcionamento. Nós traçamos o perfil desta instância QEMU em execução no momento, inspecionando a interface QMP. Esta interface nos dá informações sobre como a instância QEMU é configurada. Ela não fornece informações sobre o que está acontecendo dentro da instância, do ponto de vista do convidado. Ela nos informa onde os discos estão conectados e em qual slot PCI virtualizado os discos virtuais estão conectados, tanto para SSD local quanto para armazenamento em bloco. Após consultar o QMP e inspecionar e introspecção da instância QEMU, é construído um perfil que descreve exatamente como reproduzir esta máquina no destino.
Na máquina de destino, recebemos a descrição completa de como é a instância de origem. Podemos então recriar fielmente a instância aqui, com uma diferença. A diferença é que a instância de destino QEMU é inicializada com uma opção que diz à QEMU para aceitar uma migração de entrada.
Neste ponto, devemos fazer uma pausa na documentação das Migrações ao Vivo e mudar para explicar como a QEMU consegue estas proezas. A árvore de processos da QEMU é apresentada como um processo de controle e vários processos de trabalhadores. Um dos processos dos trabalhadores é responsável por coisas como retornar chamadas de QMP ou lidar com uma Migração ao Vivo. Os outros processos mapeiam um-a-um para as CPUs convidadas. O ambiente do hóspede é isolado deste lado da QEMU e se comporta como seu próprio sistema independente.
Neste sentido, existem 3 camadas com as quais estamos trabalhando:
- A camada 1 é nossa camada gerencial;
- A Camada 2 é a parte do processo de QEMU que trata de todas essas ações para nós; e
- A camada 3 é a verdadeira camada de convidados com a qual os usuários da Linode interagem.
Depois que o destino é inicializado e está pronto para aceitar a migração de entrada, o hardware de destino permite que o hardware de origem saiba que a fonte deve começar a enviar os dados. A fonte começa assim que recebe este sinal e dizemos à QEMU, em software, para iniciar a migração do disco. O software monitora o progresso do disco de forma autônoma para verificar quando ele for concluído. O software então muda automaticamente para a migração RAM quando o disco estiver completo. O software então monitora novamente de forma autônoma a migração para a RAM e então muda automaticamente para o modo de corte quando a migração para a RAM é concluída. Tudo isso acontece através da rede de 40Gbps da Linode, de modo que o lado da rede é bastante rápido.
Cutover: A Seção Crítica
A etapa de corte também é conhecida como a seção crítica de uma Migração ao Vivo, e entender esta etapa é a parte mais importante para entender as Migrações ao Vivo.
No ponto de corte, a QEMU determinou que está pronta para cortar novamente e começar a operar na máquina de destino. A instância QEMU de origem instrui ambos os lados a fazer uma pausa. Isto significa um par de coisas:
- O tempo pára de acordo com o hóspede. Se o hóspede estiver executando um serviço de sincronização de tempo como o Network Time Protocol(NTP), então o NTP irá automaticamente sincronizar novamente o tempo depois que a Migração ao Vivo for concluída. Isto porque o relógio do sistema estará alguns segundos atrasado.
- Os pedidos da rede param. Se essas solicitações de rede são baseadas em TCP como SSH ou HTTP, não haverá nenhuma perda percebida na conectividade. Se essas solicitações de rede são baseadas em UDP como vídeo ao vivo, pode resultar em alguns quadros descartados.
Como o tempo e os pedidos de rede estão parados, queremos que o corte aconteça o mais rápido possível. Entretanto, há várias coisas que precisamos verificar primeiro para garantir o sucesso do corte:
- Certifique-se de que a Migração ao Vivo seja concluída sem erros. Se houvesse um erro, nós recuamos, despatizamos a fonte Linode, e não prosseguimos mais. Este ponto precisou especificamente de muita tentativa e erro para ser resolvido durante o desenvolvimento, e foi a fonte de muita dor, mas nossa equipe finalmente chegou ao fundo da questão.
- Assegurar que a rede vai se desligar na fonte e começar no destino corretamente.
- Deixe o resto de nossa infra-estrutura saber exatamente em que máquina física reside agora este Linode.
Como há um limite de tempo para o corte, queremos terminar estas etapas rapidamente. Após estes pontos serem abordados, completamos o corte. A fonte Linode recebe automaticamente o sinal completo e diz ao destino para começar. O Linode de destino pega onde parou. Quaisquer itens restantes na fonte e no destino são limpos. Se o Linode de destino precisar ser Migrado ao Vivo novamente em algum momento no futuro, o processo pode ser repetido.
Visão geral dos casos Edge
A maior parte deste processo foi simples de implementar, mas o desenvolvimento das Migrações Vivas foi ampliado por casos de borda. Muito crédito pela conclusão deste projeto vai para a equipe administrativa que viu a visão da ferramenta concluída e alocou os recursos para completar a tarefa, bem como para os funcionários que viram o projeto até a sua conclusão.
Aqui estão algumas das áreas onde foram encontrados casos de bordas:
- A ferramenta interna para orquestrar as Migrações ao Vivo para as equipes de suporte ao cliente e operações de hardware da Linode teve que ser construída. Isto era semelhante a outras ferramentas existentes que tínhamos e utilizávamos na época, mas diferente o suficiente para que fosse necessário um grande esforço de desenvolvimento para construí-la:
- Esta ferramenta tem que olhar automaticamente toda a frota de hardware em um datacenter e descobrir qual hospedeiro deve ser o destino de cada Linode Migrado Vivo. As especificações relevantes ao fazer esta seleção incluem espaço de armazenamento SSD disponível e alocações de RAM.
- O processador físico da máquina de destino tem que ser compatível com a Linode de entrada. Em particular, uma CPU pode ter características (também chamadas de bandeiras de CPU) que o software dos usuários pode aproveitar. Por exemplo, uma dessas características é aes, que fornece criptografia acelerada por hardware. A CPU do destino de uma Migração ao Vivo precisa suportar as bandeiras da CPU da máquina de origem. Este acabou sendo um caso muito complexo, e a próxima seção descreve uma solução para este problema.
- Tratar graciosamente casos de falhas, incluindo a intervenção do usuário final ou a perda de rede de contatos durante a Migração ao Vivo. Estes casos de falha são enumerados com mais detalhes em uma seção posterior deste post.
- Acompanhando as mudanças na plataforma Linode, que é um processo contínuo. Para cada recurso que suportamos no Linodes agora e no futuro, temos que nos certificar de que o recurso seja compatível com as Migrações ao Vivo. Este desafio é descrito no final deste post.
Bandeiras de CPU
QEMU tem diferentes opções de como apresentar uma CPU ao sistema operacional convidado. Uma dessas opções é passar o número do modelo e as características da CPU hospedeira (também chamadas de bandeiras de CPU) diretamente para o convidado. Ao escolher esta opção, o hóspede pode usar toda a potência livre de encargos que o sistema de virtualização KVM permite. Quando o KVM foi adotado pela primeira vez pela Linode (que precedeu a Live Migrations), esta opção foi selecionada para maximizar o desempenho. Entretanto, esta decisão posteriormente apresentou muitos desafios durante o desenvolvimento da Live Migrations.
No ambiente de testes para Live Migrations, a fonte e os anfitriões de destino eram duas máquinas idênticas. No mundo real nossa frota de hardware não é 100% a mesma, e há diferenças entre as máquinas que podem resultar na presença de diferentes bandeiras de CPU. Isto importa porque quando um programa é carregado dentro do sistema operacional da Linode, a Linode apresenta bandeiras de CPU a esse programa, e o programa carregará seções específicas do software na memória para tirar proveito dessas bandeiras. Se um Linode é Live Migrado para uma máquina de destino que não suporta essas bandeiras de CPU, o programa irá travar. Isto pode levar o sistema operacional convidado a travar e pode resultar na reinicialização do Linode.
Encontramos três fatores que influenciam como as bandeiras de CPU de uma máquina são apresentadas aos convidados:
- Há pequenas diferenças entre as CPUs, dependendo de quando a CPU foi comprada. Uma CPU adquirida no final do ano pode ter bandeiras diferentes de uma adquirida no início do ano, dependendo de quando os fabricantes de CPU lançam novo hardware. A Linode está constantemente comprando novo hardware para adicionar capacidade, e mesmo que o modelo de CPU para dois pedidos de hardware diferentes seja o mesmo, as bandeiras da CPU podem ser diferentes.
- Diferentes kernels Linux podem passar bandeiras diferentes para a QEMU. Em particular, o kernel Linux para a máquina de origem de uma Migração ao Vivo pode passar bandeiras diferentes para a QEMU do que o kernel Linux da máquina de destino. A atualização do kernel Linux na máquina de origem requer uma reinicialização e, portanto, este descompasso não pode ser resolvido atualizando o kernel antes de prosseguir com a Migração ao Vivo, pois isso resultaria em tempo de inatividade para os Linodes naquela máquina.
- Da mesma forma, diferentes versões de QEMU podem afetar quais bandeiras de CPU são apresentadas. A atualização da QEMU também requer uma reinicialização da máquina.
Assim, a Live Migrations precisava ser implementada de forma a evitar que o programa travasse devido a desajustes da bandeira da CPU. Há duas opções disponíveis:
- Poderíamos dizer à QEMU para emular as bandeiras da CPU. Isto levaria a um software que costumava rodar rápido agora rodando devagar, sem nenhuma maneira de investigar o porquê.
- Podemos reunir uma lista de bandeiras de CPU na fonte e garantir que o destino tenha essas mesmas bandeiras antes de prosseguir. Isto é mais complicado, mas vai preservar a velocidade dos programas de nossos usuários. Esta é a opção que implementamos para as Migrações ao Vivo.
Depois que decidimos combinar as bandeiras da CPU de origem e destino, realizamos esta tarefa com uma abordagem de cinto e suspensórios que consistia em dois métodos diferentes:
- O primeiro método é o mais simples dos dois. Todas as bandeiras da CPU são enviadas da fonte para o hardware de destino. Quando o hardware de destino configura a nova instância qemu, ele verifica se tem pelo menos todas as bandeiras que estavam presentes na fonte Linode. Se não corresponderem, a Migração ao Vivo não prossegue.
- O segundo método é muito mais complicado, mas pode evitar migrações falhadas que resultam de desajustes de bandeiras de CPU. Antes de se iniciar uma migração ao vivo, criamos uma lista de hardware com flags de CPU compatíveis. Em seguida, é escolhida uma máquina de destino a partir desta lista.
Este segundo método precisa ser realizado rapidamente, e carrega muita complexidade. Precisamos verificar até 226 bandeiras de CPU em mais de 900 máquinas em alguns casos. Escrever todas essas 226 bandeiras de CPU seria muito difícil, e elas teriam que ser mantidas. Este problema acabou sendo resolvido por uma idéia surpreendente proposta pelo fundador da Linode, Chris Aker.
A idéia chave era fazer uma lista de todas as bandeiras da CPU e representá-la como uma seqüência binária. Então, o bitwise e a operação podem ser usados para comparar as cordas. Para demonstrar este algoritmo, vou começar com um exemplo simples como se segue. Considere este código Python que compara dois números usando bitwise e:
>>> 1 & 1
1
>>> 2 & 3
2
>>> 1 & 3
1
Para entender porque o bitwise e a operação tem estes resultados, é útil representar os números em binário. Vamos examinar o bitwise e a operação para os números 2 e 3, representados em binário:
>>> # 2: 00000010
>>> # &
>>> # 3: 00000011
>>> # =
>>> # 2: 00000010
A bitwise e a operação compara os dígitos binários, ou bits, dos dois números diferentes. Partindo do dígito mais à direita nos números acima e depois prosseguindo para a esquerda:
- Os bits mais à direita/primeiro de 2 e 3 são 0 e 1, respectivamente. O bitwise e o resultado para
0 & 1
é 0. - O segundo bit mais à direita de 2 e 3 é 1 para ambos os números. O bit mais à direita e o resultado para
1 & 1
é 1. - Todos os outros bits para estes números são 0, e o bitwise e o resultado para 0 & 0 é 0.
A representação binária para o resultado completo é então 00000010
que é igual a 2.
Para Live Migrations, a lista completa de bandeiras de CPU é representada como uma seqüência binária, onde cada bit representa uma única bandeira. Se o bit for 0, então a bandeira não está presente, e se o bit for 1, então a bandeira está presente. Por exemplo, um bit pode corresponder à bandeira aes, e outro bit pode corresponder à bandeira mmx. As posições específicas destas bandeiras na representação binária são mantidas, documentadas e compartilhadas pelas máquinas em nossos datacenters.
Manter esta representação de lista é muito mais simples e mais eficiente do que manter um conjunto de declarações que hipoteticamente verificariam a presença de uma bandeira de CPU. Por exemplo, suponha que houvesse 7 bandeiras de CPU que precisassem ser rastreadas e verificadas no total. Estas bandeiras poderiam ser armazenadas em um número de 8 bits (com um bit sobrando para expansão futura). Uma cadeia de exemplo poderia ser parecida com 00111011
A segunda ponta à direita mostra que mmx está habilitada, a terceira ponta à direita indica que outra bandeira está desabilitada, e assim por diante.
Como mostrado no próximo trecho de código, podemos então ver que hardware irá suportar esta combinação de bandeiras e devolver todas as partidas em um ciclo. Se tivéssemos usado um conjunto de se declarações para calcular estas correspondências, seria necessário um número muito maior de ciclos para alcançar este resultado. Para um exemplo de Migração ao Vivo onde 4 bandeiras de CPU estavam presentes na máquina de origem, seriam necessários 203.400 ciclos para encontrar o hardware correspondente.
O código de Migração ao Vivo executa um pouco de operação e operação nas cordas da bandeira da CPU nas máquinas de origem e destino. Se o resultado for igual à corda de bandeira da CPU da máquina de origem, então a máquina de destino é compatível. Considere este trecho de código Python :
>>> # The b'' syntax below represents a binary string
>>>
>>> # The s variable stores the example CPU flag
>>> # string for the source:
>>> s = b'00111011'
>>> # The source CPU flag string is equivalent to the number 59:
>>> int(s.decode(), 2)
59
>>>
>>> # The d variable stores the example CPU flag
>>> # string for the source:
>>> d = b'00111111'
>>> # The destination CPU flag string is equivalent to the number 63:
>>> int(d.decode(), 2)
63
>>>
>>> # The bitwise and operation compares these two numbers:
>>> int(s.decode(), 2) & int(d.decode(), 2) == int(s.decode(), 2)
True
>>> # The previous statement was equivalent to 59 & 63 == 59.
>>>
>>> # Because source & destination == source,
>>> # the machines are compatible
Observe que no trecho de código acima, o destino suportava mais bandeiras do que a fonte. As máquinas são consideradas compatíveis porque todas as bandeiras da CPU da fonte estão presentes no destino, que é o que o bitwise e a operação garantem.
Os resultados deste algoritmo são usados por nossas ferramentas internas para construir uma lista de hardware compatível. Esta lista é exibida para nossas equipes de Suporte ao Cliente e Operações de Hardware. Estas equipes podem usar o ferramental para orquestrar diferentes operações:
- As ferramentas podem ser usadas para selecionar o melhor hardware compatível para um determinado Linode.
- Podemos iniciar uma Migração ao Vivo para um Linode sem especificar um destino. O melhor hardware compatível no mesmo datacenter será automaticamente selecionado e a migração será iniciada.
- Podemos iniciar Migrações ao Vivo para todos os Linodes em um host como uma única tarefa. Esta funcionalidade é utilizada antes de realizar a manutenção em um host. A ferramenta selecionará automaticamente destinos para todos os Linodes e orquestrará as Migrações ao Vivo para cada Linode.
- Podemos especificar uma lista de várias máquinas que precisam de manutenção, e as ferramentas orquestrarão automaticamente as migrações ao vivo para todos os Linodes através dos anfitriões.
Casos de falha
Uma característica que não é muito falada em software é o tratamento gracioso de casos de falha. O software é suposto "apenas funcionar". Muito tempo de desenvolvimento vai para fazer o software "apenas funcionar", e este foi muito o caso de Live Migrations. Muito tempo foi gasto pensando em todas as formas em que esta ferramenta não poderia funcionar e tratando graciosamente esses casos. Aqui estão alguns desses cenários e como eles são tratados:
- O que acontece se um cliente quiser acessar uma característica de seu Linode a partir do Cloud Manager? Por exemplo, um usuário pode reiniciar o Linode ou anexar um volume Block Storage a ele.
- Resposta: O cliente tem o poder de fazer isso. A Migração ao Vivo é interrompida e não prossegue. Esta solução é apropriada porque a Migração ao Vivo pode ser tentada mais tarde.
- O que acontece se o Linode de destino não arrancar?
- Resposta: Deixe o hardware de origem saber, e projete as ferramentas internas para escolher automaticamente uma peça de hardware diferente no datacenter. Notifique também a equipe de operações para que possam investigar o hardware do destino original. Isto aconteceu na produção e foi tratado por nossa implementação da Live Migrations.
- O que acontece se você perder a rede em meados da migração?
- Resposta: Monitorar de forma autônoma o progresso da Migração ao Vivo, e se não tiver feito nenhum progresso no último minuto, cancelar a Migração ao Vivo e informar a equipe de operações. Isto não aconteceu fora de um ambiente de teste, mas nossa implementação está preparada para este cenário.
- O que acontece se o resto da Internet se desligar, mas o hardware de origem e destino ainda estiver funcionando e se comunicar, e a Linode de origem ou destino estiver funcionando normalmente?
- Resposta: Se a Migração ao Vivo não estiver na seção crítica, pare a Migração ao Vivo. Então, tente novamente mais tarde.
- Se você estiver na seção crítica, continue a Migração ao Vivo. Isto é importante porque a fonte Linode está em pausa, e o destino Linode precisa começar para que a operação seja retomada.
- Estes cenários foram modelados no ambiente de teste, e o comportamento prescrito foi considerado como o melhor curso de ação.
Acompanhando as mudanças
Após centenas de milhares de migrações ao vivo bem sucedidas, uma pergunta que às vezes é feita é "Quando as migrações ao vivo são feitas? A Live Migrations é uma tecnologia cujo uso se expande com o tempo e que é continuamente aperfeiçoada, portanto marcar o fim do projeto não é necessariamente simples. Uma maneira de responder a esta pergunta é considerar quando a maior parte do trabalho para este projeto estiver concluída. A resposta é: para um software confiável e confiável, o trabalho não é feito por um longo tempo.
Como novos recursos são desenvolvidos para Linodes ao longo do tempo, o trabalho deve ser feito para garantir a compatibilidade com as Migrações ao Vivo para esses recursos. Ao introduzir alguns recursos, não há nenhum novo trabalho de desenvolvimento sobre Live Migrations a ser feito, e só precisamos testar que o Live Migrations ainda funciona como esperado. Para outros, o trabalho de compatibilidade com o Live Migrations é marcado como uma tarefa no início do desenvolvimento dos novos recursos.
Como tudo em software, há sempre melhores métodos de implementação que são descobertos através da pesquisa. Por exemplo, pode ser que uma abordagem mais modular da integração do Live Migrations ofereça menos manutenção a longo prazo. Ou, é possível que a mistura da funcionalidade Live Migrations em código de nível inferior a ajude a habilitar fora da caixa para futuras funcionalidades do Linode. Nossas equipes consideram todas estas opções e as ferramentas que alimentam a plataforma Linode são entidades vivas que continuarão a evoluir.
Comentários