Avançar para o conteúdo principal
BlogLinodeMigrações ao vivo em Linode

Migrações ao vivo em Linode

Linode Live Migrations

Quando os desenvolvedores implantam 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 passar esse tempo de inatividade para nossos clientes e cumprir a promessa da nuvem, a Linode implementa uma ferramenta chamada Live Migrations.

Live Migrations é uma tecnologia que permite às instâncias Linode deslocarem-se entre máquinas físicas sem interrupção de serviço. Quando um Linode é movimentado com Live Migrations, a transição é invisível para os processos desse Linode. Se o hardware de um anfitrião precisar de manutenção, o Live Migrations pode ser utilizado para fazer uma transição sem problemas de todos os Linodes desse anfitrião para um novo anfitrião. Após esta migração estar concluída, o hardware físico pode ser reparado, e o tempo de inactividade não terá impacto nos nossos clientes.

Para mim, o desenvolvimento desta tecnologia foi um momento decisivo e um ponto de viragem entre as tecnologias de nuvens e as tecnologias não de nuvens. Tenho um ponto fraco para a tecnologia Live Migrations porque passei mais de um ano da minha vida a trabalhar nela. Agora, posso partilhar a história com todos vós.

Como funcionam as Migrações ao Vivo

Live Migrations na Linode começou como a maioria dos novos projectos; com muita investigação, uma série de protótipos, e ajuda de muitos colegas e gestores. O primeiro movimento em frente foi investigar como a QEMU lida com o Live Migrations. QEMU é uma tecnologia de virtualização utilizada pela Linode, e Live Migrations é uma característica da QEMU. Como resultado, o foco da nossa equipa foi trazer esta tecnologia à 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 em quatro etapas:

  1. A instância qemu de destino é fiada com exactamente os mesmos parâmetros que existem na instância qemu de origem.
  2. Os discos são Live Migrated over. Quaisquer alterações ao disco são também comunicadas enquanto esta transferência está a decorrer.
  3. A RAM é Live Migrated over. Quaisquer alterações às páginas da RAM também têm de ser comunicadas. Se também houver alterações de dados do disco durante esta fase, então essas alterações serão também copiadas para o disco da instância QEMU de destino.
  4. O ponto de corte é executado. Quando a QEMU determina que há poucas páginas de RAM suficientes que possa 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 a cache da CPU e a próxima instrução da CPU. Depois, a QEMU diz ao destino para começar, e o destino pega à direita onde a fonte parou.

Estes passos explicam como realizar uma Migração ao Vivo com a QEMU a um nível elevado. No entanto, especificar exactamente como deseja que a instância QEMU de destino seja iniciada é um processo muito manual. Além disso, cada acção no processo tem de ser iniciada na altura certa.

Como as Migrações ao Vivo são Implementadas em Linode

Depois de analisarmos o que os criadores da QEMU já criaram, como é que o utilizamos na Linode? A resposta a esta pergunta é onde a maior parte do trabalho foi para a nossa equipa. 

De acordo com a etapa 1 do fluxo de trabalho da migração em tempo real, a instância QEMU de destino é ativada para aceitar a migração em tempo real de entrada. Ao implementar esta etapa, o primeiro pensamento foi pegar o perfil de configuração do Linode atual e girá-lo em uma máquina de destino. Isso seria simples em teoria, mas pensar mais sobre isso revela 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, teve de ser tomado um perfil da instância QEMU actualmente em funcionamento. Perfis desta instância QEMU em execução no momento, através da inspecção da interface QMP. Esta interface dá-nos informações sobre como a instância QEMU é apresentada. Não fornece informação sobre o que se passa no interior da instância do ponto de vista do hóspede. Diz-nos onde os discos estão ligados e em que ranhura PCI virtualizada os discos virtuais estão ligados, tanto para SSD local como para armazenamento em bloco. Após consulta do QMP e inspecção e introspecção da instância QEMU, é construído um perfil que descreve exactamente 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.

Nesta altura, 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 controlo e vários processos de trabalhadores. Um dos processos dos trabalhadores é responsável por coisas como o retorno de chamadas QMP ou o tratamento de uma Migração em Directo. Os outros processos mapeiam um-a-um para as CPUs convidadas. O ambiente do hóspede é isolado deste lado da QEMU e comporta-se como o seu próprio sistema independente. 

Neste sentido, existem 3 camadas com as quais estamos a trabalhar: 

  • A camada 1 é a nossa camada de gestão;
  • A camada 2 é a parte do processo da QEMU que trata de todas estas acções para nós; e
  • A camada 3 é a verdadeira camada de convidados com a qual os utilizadores de Linode interagem.

Após o destino ser inicializado e estar pronto para aceitar a migração de entrada, o hardware de destino permite ao hardware de origem saber 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 monitoriza de forma autónoma o progresso do disco para verificar quando este estiver concluído. O software muda então automaticamente para a migração RAM quando o disco estiver completo. O software monitoriza então de novo autonomamente a migração RAM e depois muda automaticamente para o modo de corte quando a migração RAM estiver completa. Tudo isto acontece através da rede de 40Gbps da Linode, pelo que o lado da rede das coisas é bastante rápido.

Cutover: A Secção Crítica

A etapa de corte é também conhecida como a secção crítica de uma Migração em Directo, e a compreensão desta etapa é a parte mais importante da compreensão das Migrações em Directo. 

No ponto de corte, a QEMU determinou que está pronta para cortar e começar a funcionar 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:

  1. O tempo pára de acordo com o convidado. Se o hóspede estiver a executar um serviço de sincronização de tempo como o Network Time Protocol(NTP), então o NTP irá automaticamente sincronizar novamente o tempo após a Migração em Directo (Live Migration) estar concluída. Isto acontece porque o relógio do sistema estará alguns segundos atrasado.
  2. Os pedidos de rede param. Se esses pedidos de rede forem baseados em TCP como SSH ou HTTP, não haverá perda perceptível de conectividade. Se esses pedidos de rede forem baseados em UDP como o vídeo em directo, pode resultar em alguns quadros descartados.

Porque o tempo e os pedidos de rede são interrompidos, queremos que o corte aconteça o mais rapidamente possível. No entanto, há várias coisas que precisamos de verificar primeiro para garantir o sucesso do corte:

  • Certifique-se de que a Migração em directo foi concluída sem erros. Se houve um erro, retrocedemos, despatizamos a fonte Linode, e não avançamos mais. Este ponto levou especificamente muita tentativa e erro a resolver durante o desenvolvimento, e foi a fonte de muita dor, mas a nossa equipa finalmente chegou ao fundo da questão. 
  • Assegurar que a ligação em rede se desliga na fonte e arranca correctamente no destino. 
  • Deixe o resto das nossas infra-estruturas saber exactamente em que máquina física reside agora este Linode. 

Como há um limite de tempo para o corte, queremos terminar estes passos rapidamente. Depois destes pontos serem abordados, completamos o corte. A fonte Linode recebe automaticamente o sinal completado e diz ao destino para começar. O Linode de destino pega no ponto de partida. Quaisquer itens restantes na fonte e no destino são limpos. Se o Linode de destino precisar de 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 bordas. Muito do crédito pela conclusão deste projecto vai para a equipa de gestão que viu a visão da ferramenta concluída e atribuiu os recursos para completar a tarefa, bem como para os funcionários que viram o projecto até à sua conclusão.

Aqui estão algumas das áreas onde foram encontrados casos de bordas:

  • Foi necessário construir os instrumentos internos para orquestrar Live Migrations para as equipas de apoio ao cliente e operações de hardware da Linode. Isto era semelhante a outras ferramentas existentes que tínhamos e utilizávamos na altura, mas suficientemente diferente para que fosse necessário um grande esforço de desenvolvimento para a construir:
    • Esta ferramenta tem de olhar automaticamente para toda a frota de hardware num datacenter e descobrir qual o hospedeiro que deve ser o destino de cada Linode Migrado Vivo. As especificações relevantes ao fazer esta selecção incluem espaço de armazenamento SSD disponível e atribuições de RAM.
    • O processador físico da máquina de destino tem de ser compatível com o Linode de entrada. Em particular, uma CPU pode ter características (também referidas como bandeiras de CPU) que o software dos utilizadores pode aproveitar. Por exemplo, uma dessas características é aes, que fornece uma encriptação acelerada por hardware. A CPU do destino de uma Migração ao Vivo precisa de suportar as bandeiras da CPU da máquina de origem. Este acabou por ser um caso muito complexo, e a secção seguinte descreve uma solução para este problema.
  • Tratamento gracioso de casos de falhas, incluindo a intervenção do utilizador final ou a perda de redes durante a Migração em directo. Estes casos de falha são enumerados com mais detalhe numa secção posterior deste post.
  • Acompanhando as mudanças na plataforma Linode, que é um processo contínuo. Para cada funcionalidade que suportamos em Linodes agora e no futuro, temos de nos certificar que a funcionalidade é 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 um CPU ao sistema operativo convidado. Uma dessas opções é passar directamente ao convidado o número de modelo e características do CPU hospedeiro (também referido como bandeiras de CPU). Ao escolher esta opção, o convidado pode utilizar toda a potência livre de encargos que o sistema de virtualização KVM permite. Quando KVM foi adoptado pela primeira vez pela Linode (que precedeu as Migrações ao Vivo), esta opção foi seleccionada para maximizar o desempenho. Contudo, esta decisão apresentou mais tarde muitos desafios durante o desenvolvimento de Live Migrations.

No ambiente de testes para as Migrações Vivas, os hospedeiros de origem e de destino eram duas máquinas idênticas. No mundo real a nossa frota de hardware não é 100% a mesma, e existem diferenças entre as máquinas que podem resultar na presença de diferentes bandeiras de CPU. Isto é importante porque quando um programa é carregado dentro do sistema operacional da Linode, a Linode apresenta bandeiras de CPU a esse programa, e o programa irá carregar secções específicas do software na memória para tirar partido dessas bandeiras. Se um Linode for Live Migrado para uma máquina de destino que não suporta essas bandeiras de CPU, o programa irá falhar. Isto pode levar o sistema operacional convidado a falhar e pode resultar na reinicialização do Linode. 

Encontrámos três factores que influenciam a forma como as bandeiras de CPU de uma máquina são apresentadas aos convidados:

  • Existem pequenas diferenças entre 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 a comprar novo hardware para adicionar capacidade, e mesmo que o modelo de CPU para duas ordens 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 actualização do kernel Linux na máquina de origem requer uma reinicialização, pelo que este desfasamento não pode ser resolvido através da actualização do kernel antes de se proceder à Migração em Directo, porque isso resultaria em tempo de inactividade para os Linodes nessa máquina.
  • Da mesma forma, diferentes versões de QEMU podem afectar quais as bandeiras de CPU que são apresentadas. A actualização da QEMU também requer uma reinicialização da máquina.

Assim, as Migrações ao Vivo precisavam de ser implementadas de forma a evitar falhas de programas devido a desajustes de bandeiras de CPU. Há duas opções disponíveis:

  • Poderíamos dizer à QEMU para emular as bandeiras da CPU. Isto levaria a um software que costumava correr rápido agora a correr lentamente, sem forma de investigar o porquê.
  • Podemos reunir uma lista de bandeiras de CPU na fonte e certificarmo-nos de que o destino tem essas mesmas bandeiras antes de prosseguir. Isto é mais complicado, mas irá preservar a velocidade dos programas dos nossos utilizadores. Esta é a opção que implementámos para as Migrações ao Vivo.

Depois de termos decidido combinar as bandeiras de origem e destino da CPU, realizámos 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, verifica se tem pelo menos todas as bandeiras que estavam presentes no Linode de origem. Se não corresponderem, a Migração em Directo 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 bandeiras de CPU compatíveis. Depois, é escolhida uma máquina de destino a partir desta lista.

Este segundo método precisa de ser executado rapidamente, e carrega muita complexidade. Precisamos de verificar até 226 bandeiras de CPU em mais de 900 máquinas em alguns casos. A escrita de todas estas 226 bandeiras de CPU seria muito difícil, e teriam de ser mantidas. Este problema acabou por ser resolvido por uma ideia surpreendente proposta pelo fundador da Linode, Chris Aker. 

A ideia chave era fazer uma lista de todas as bandeiras da CPU e representá-la como uma cadeia binária. Depois, a bitwise e a operação podem ser usadas 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 bitwise:

>>> 1 & 1
1
>>> 2 & 3
2
>>> 1 & 3
1

Para compreender porque é que 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

O 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 referidos 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 00000010que é igual a 2.

Para Live Migrations, a lista completa de bandeiras de CPU é representada como uma string 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 de 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 partilhadas pelas máquinas nos 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 existiam 7 bandeiras de CPU que precisavam de ser seguidas e verificadas no total. Estas bandeiras poderiam ser armazenadas num número de 8 bits (sobrando um bit para expansão futura). Uma cadeia de exemplo poderia ser parecida com 00111011, onde o bit mais à direita mostra que aes está activado, o segundo bit mais à direita mostra que mmx está activado, o terceiro bit indica que outra bandeira está desactivada, 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 todos os jogos num único ciclo. Se tivéssemos utilizado 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 em Directo executa um pouco de operação nas cordas de bandeira da CPU nas máquinas de origem e destino. Se o resultado for igual à corda de bandeira 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

Note-se 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 de CPU da fonte estão presentes no destino, que é o que o bitwise e o funcionamento garantem.

Os resultados deste algoritmo são utilizados pelas nossas ferramentas internas para construir uma lista de hardware compatível. Esta lista é apresentada às nossas equipas de Apoio ao Cliente e Operações de Hardware. Estas equipas podem utilizar o ferramental para orquestrar diferentes operações:

  • As ferramentas podem ser utilizadas para seleccionar o melhor hardware compatível para um determinado Linode.
  • Podemos iniciar uma Migração em directo para um Linode sem especificar um destino. O melhor hardware compatível no mesmo datacenter será automaticamente seleccionado e a migração será iniciada.
  • Podemos iniciar Migrações ao Vivo para todos os Linodes num anfitrião como uma única tarefa. Esta funcionalidade é utilizada antes de se efectuar a manutenção num anfitrião. A ferramenta seleccionará 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 necessitam de manutenção, e as ferramentas orquestrarão automaticamente as Migrações ao Vivo para todos os Linodes através dos hospedeiros.

 Muito tempo de desenvolvimento vai para fazer o software "apenas funcionar...".

Casos de falha

Uma característica de que não se fala muito frequentemente no 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 a pensar em todas as formas em que esta ferramenta não podia funcionar e a tratar graciosamente esses casos. Aqui estão alguns desses cenários e como são tratados:

  • O que acontece se um cliente quiser aceder a uma funcionalidade do 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 para o fazer. A Migração ao Vivo é interrompida e não prossegue. Esta solução é apropriada porque a Migração em Directo pode ser tentada mais tarde.
  • O que acontece se o Linode de destino não arrancar? 
    • Resposta: Avisar o hardware de origem, e engendrar as ferramentas internas para escolher automaticamente uma peça de hardware diferente no datacenter. Além disso, notificar a equipa de operações para que possam investigar o hardware do destino original. Isto aconteceu na produção e foi tratado pela nossa implementação Live Migrations.
  • O que acontece se se perder o trabalho em rede a meio da migração?
    • Resposta: Monitorizar autonomamente o progresso da Migração em Directo, e se não tiver feito qualquer progresso no último minuto, cancelar a Migração em Directo e informar a equipa de operações. Isto não aconteceu fora de um ambiente de teste, mas a 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 a funcionar e a comunicar, e o Linode de origem ou destino estiver a funcionar normalmente?
    • Resposta: Se a Migração em Directo não estiver na secção crítica, parar a Migração em Directo. Depois, tente novamente mais tarde.
    • Se estiver na secção crítica, continue a Migração em directo. Isto é importante porque a fonte Linode está em pausa, e o destino Linode precisa de começar para que a operação seja retomada.
    • Estes cenários foram modelados no ambiente de teste, e verificou-se que o comportamento prescrito era o melhor curso de acção.

Acompanhar as mudanças

Após centenas de milhares de migrações ao vivo bem sucedidas, uma pergunta que por vezes se faz é "Quando é que as migrações ao vivo são feitas? Live Migrations é uma tecnologia cuja utilização se expande com o tempo e que é continuamente aperfeiçoada, pelo que marcar o fim do projecto não é necessariamente simples. Uma forma de responder a esta pergunta é considerar quando a maior parte do trabalho para este projecto estiver concluída. A resposta é: para um software fiável e fiável, o trabalho não é feito durante muito tempo.

À medida que novas funcionalidades são desenvolvidas para Linodes ao longo do tempo, deve ser feito trabalho para assegurar a compatibilidade com as Migrações ao Vivo para essas funcionalidades. Ao introduzir algumas funcionalidades, não há nenhum novo trabalho de desenvolvimento sobre Live Migrations a ser feito, e apenas precisamos de 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 das novas funcionalidades.

Como em tudo no software, há sempre melhores métodos de implementação que são descobertos através da investigação. Por exemplo, pode ser que uma abordagem mais modular da integração de 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 ajude a permitir a sua eliminação para futuras funcionalidades Linode. As nossas equipas consideram todas estas opções, e as ferramentas que alimentam a plataforma Linode são entidades vivas que irão continuar a evoluir.


Comentários

Deixe uma resposta

O seu endereço de correio electrónico não será publicado. Os campos obrigatórios estão marcados com *