
Criar um aplicativo de backup que funcione em qualquer distribuição é uma tarefa desafiadora. Para garantir o funcionamento do Veeam Agent for Linux em distribuições do Red Hat 6 ao Debian 6, passando pelo OpenSUSE 15.1 e Ubuntu 19.04, é preciso resolver uma variedade de problemas, especialmente considerando que o produto inclui um módulo do núcleo.
Este artigo foi criado com base na apresentação conferida na .
Linux não é apenas um dos sistemas operacionais mais populares. Na verdade, é uma plataforma na qual é possível criar algo único, algo pessoal. Por causa disso, existem muitas distribuições Linux, que diferem em conjunto de componentes de software. E aqui surge o problema: para que o produto funcione em qualquer distribuição, é necessário levar em conta as particularidades de cada uma.
Gerenciadores de pacotes: .deb vs .rpm
Começaremos pelo problema evidente de distribuir o produto para diferentes distribuições.
A maneira mais típica de distribuir produtos de software é disponibilizar um pacote em um repositório, para que o gerenciador de pacotes integrado ao sistema possa instalá-lo a partir daí.
No entanto, temos dois formatos de pacotes populares: rpm e deb. Portanto, será necessário dar suporte a cada um deles.
No mundo dos pacotes deb, o nível de compatibilidade é impressionante. O mesmo pacote é instalado e funciona igualmente bem tanto no Debian 6 quanto no Ubuntu 19.04. Os padrões do processo de construção de pacotes e seu uso, estabelecidos nas antigas distribuições Debian, permanecem relevantes nas modernas Linux Mint e elementary OS. Portanto, no caso do Veeam Agent for Linux, basta um único pacote deb para cada plataforma de hardware.
Por outro lado, no mundo dos pacotes rpm, as diferenças são grandes. Em primeiro lugar, porque existem dois distribuidores completamente independentes, Red Hat e SUSE, para os quais a compatibilidade não é necessária. Em segundo lugar, esses distribuidores têm distribuições com suporte técnico e experimentais. Entre elas, a compatibilidade também não é necessária. Assim, temos pacotes específicos para el6, el7 e el8. Um pacote separado para Fedora. Pacotes para SLES11 e 12 e outro separado para openSUSE. O principal problema está nas dependências e nomes dos pacotes.
Problema de dependências
Infelizmente, os mesmos pacotes frequentemente têm nomes diferentes em diferentes distribuições. Abaixo, uma lista incompleta de dependências do pacote veeam.
Para EL7:
Para SLES 12:
- libblkid
- libgcc
- libstdc++
- ncurses-libs
- fuse-libs
- file-libs
- veeamsnap = 3.0.2.1185
- libblkid1
- libgcc_s1
- libstdc++6
- libmagic1
- libfuse2
- veeamsnap-kmp = 3.0.2.1185
Como resultado, a lista de dependências se torna única para a distribuição.
Pior é quando uma versão atualizada começa a se esconder sob o antigo nome do pacote.
Exemplo:
No Fedora 24, o pacote foi atualizado ncurses da versão 5 para a versão 6. Nosso produto foi construído especificamente com a versão 5, para garantir compatibilidade com distribuições antigas. Para utilizar a antiga versão 5 da biblioteca no Fedora 24, foi necessário usar o pacote ncurses-compat-libs.
Como resultado, o Fedora passa a ter dois pacotes, com dependências diferentes.
A seguir, as coisas ficam mais interessantes. Após outra atualização da distribuição, o pacote ncurses-compat-libs com a versão 5 da biblioteca se torna indisponível. Para o distribuidor, é custoso carregar bibliotecas antigas para a nova versão da distribuição. Depois de algum tempo, o problema ocorreu novamente em distribuições SUSE.
Como resultado, para algumas distribuições foi necessário abrir mão da dependência explícita de ncurses-libs, e corrigir o produto para que ele pudesse funcionar com qualquer versão da biblioteca.
Aliás, na versão 8 do Red Hat, não há mais o meta-pacote python, que referenciava o bom e velho python 2.7. Há python2 e python3.
A alternativa aos gerenciadores de pacotes
O problema com dependências é antigo e já é bem conhecido. Basta recordar o que chamam de "Dependency hell".
Combinar diversas bibliotecas e aplicações de modo que todas funcionem de forma estável e sem conflitos — essa é, na verdade, a tarefa que qualquer distribuidor de Linux tenta resolver.
Totalmente diferente tenta resolver esse problema o gerenciador de pacotes Snappy da Canonical. A ideia principal: o aplicativo executa em uma sandbox isolada e protegida do sistema principal. Se o aplicativo precisa de bibliotecas, elas são fornecidas junto com o próprio aplicativo.
Flatpak também permite executar aplicativos em uma sandbox, utilizando Linux Containers. A ideia da sandbox é aproveitada também por AppImage.
Essas soluções permitem criar um único pacote para quaisquer distribuições. No caso do Flatpak a instalação e execução do aplicativo é possível mesmo sem o conhecimento do administrador.
O principal problema é que nem todos os aplicativos podem funcionar na sandbox. Alguns precisam de acesso direto à plataforma. Não estou nem mencionando os módulos do núcleo, que dependem fortemente do núcleo e não se encaixam de forma alguma na concepção da sandbox.
O segundo problema é que as distribuições populares no ambiente corporativo da Red Hat e SUSE ainda não suportam Snappy e Flatpak.
Em relação a isso, o Veeam Agent for Linux não está disponível nem em nem em .
Para concluir a questão sobre gerenciadores de pacotes, vale a pena mencionar que existe a opção de abrir mão completamente dos gerenciadores de pacotes, juntando os arquivos binários e um script para sua instalação em um único pacote.
Esse bundle permite criar um único pacote para diferentes distribuições e plataformas, realizando um processo de instalação interativo e permitindo a personalização necessária. Eu só encontrei pacotes desse tipo para Linux da VMware.
O problema das atualizações

Mesmo que todos os problemas de dependências estejam resolvidos, o programa pode funcionar de maneira diferente na mesma distribuição. O problema está nas atualizações.
Existem 3 estratégias de atualização:
- A mais simples é não atualizar nunca. Configurou o servidor e esqueceu. Para que atualizações, se tudo funciona? Os problemas começam assim que se faz o primeiro contato com o suporte técnico. O criador da distribuição só oferece suporte à versão atualizada.
- É possível confiar no distribuidor e configurar atualizações automáticas. Nesse caso, uma ligação para o suporte técnico é provável logo após uma atualização malsucedida.
- A opção de atualização manual só após testá-la em uma infraestrutura de teste é a mais segura, mas cara e trabalhosa. Nem todos conseguem arcar com isso.
Como diferentes usuários aplicam diferentes estratégias de atualização, é necessário manter tanto a versão mais recente quanto todas as versões anteriores. Isso complica o processo de desenvolvimento e de testes, além de trazer dor de cabeça ao suporte técnico.
A diversidade de plataformas de hardware
Diversas plataformas de hardware são um problema que, em grande parte, é específico para código nativo. No mínimo, é preciso compilar binários para cada plataforma suportada.
No projeto Veeam Agent for Linux, ainda não conseguimos suportar nada que seja RISC.
Não vou me aprofundar mais nesse assunto. Vou apenas destacar os principais problemas: tipos dependentes da plataforma, como size_t, alinhamento de estruturas e ordem dos bytes.
Linkagem estática e/ou dinâmica

A questão "Como se linkar com bibliotecas - dinamicamente ou estaticamente?" deve ser discutida.
Normalmente, aplicativos C/C++ no Linux usam linkagem dinâmica. Isso funciona muito bem quando o aplicativo é construído especificamente para uma distribuição específica.
Se o objetivo é cobrir diversas distribuições com um único arquivo binário, é preciso se orientar pela distribuição mais antiga suportada. Para nós, isso é o Red Hat 6. Ele contém o gcc 4.4, que nem mesmo suporta totalmente o padrão C++11. .
Nós compilamos nosso projeto usando o gcc 6.3, que suporta totalmente C++14. Naturalmente, nesse caso, no Red Hat 6, é preciso incluir a biblioteca libstdc++ e boost. O mais fácil é linkar com elas estaticamente.
Mas, infelizmente, nem todas as bibliotecas podem ser linkadas estaticamente.
Primeiro, bibliotecas do sistema, como libfuse, libblkid , precisam ser linkadas dinamicamente para garantir a compatibilidade com o núcleo e seus módulos.
Em segundo lugar, há uma questão relacionada às licenças.
A licença GPL, em princípio, permite linkar bibliotecas apenas com código opensource. MIT e BSD permitem a linkagem estática e permitem incluir bibliotecas no projeto. Já a LGPL parece não ter objeções à linkagem estática, mas exige que os arquivos necessários para o linking sejam disponibilizados publicamente.
De um modo geral, o uso da linkagem dinâmica evitará a necessidade de fornecer algo.
Compilação de aplicativos C/C++
Para compilar aplicativos C/C++ para diferentes plataformas e distribuições, é suficiente escolher ou compilar a versão apropriada do gcc e utilizar compiladores cruzados para arquiteturas específicas, reunindo todo o conjunto de bibliotecas. Esse trabalho é bastante realizável, mas é bastante trabalhoso. E não há garantias de que o compilador e as bibliotecas escolhidas produzirão uma versão funcional.
Uma vantagem óbvia: a infraestrutura é simplificada, pois todo o processo de construção pode ser realizado em uma única máquina. Além disso, é suficiente compilar um único conjunto de arquivos binários para uma arquitetura e pode-se empacotá-los em pacotes para diferentes distribuições. É assim que os pacotes do veeam são construídos para o Veeam Agent for Linux.
Em contraste com essa opção, pode-se simplesmente preparar uma build farm, ou seja, várias máquinas para compilação. Cada uma dessas máquinas garantirá a compilação do aplicativo e a construção de pacotes para uma distribuição específica e uma arquitetura determinada. Nesse caso, a compilação é realizada com as ferramentas preparadas pelo distribuidor. Ou seja, a etapa de preparação do compilador e seleção de bibliotecas é dispensada. Além disso, o processo de construção pode ser facilmente paralelizado.
No entanto, há uma desvantagem nessa abordagem: para cada distribuição dentro de uma mesma arquitetura, será necessário construir seu próprio conjunto de arquivos binários. Outra desvantagem é que esse grande número de máquinas precisa ser mantido, exigindo uma quantidade considerável de espaço em disco e memória RAM.
Assim, são construídos pacotes KMOD do módulo do kernel veeamsnap para distribuições Red Hat.
Open Build Service
Os colegas da SUSE tentaram implementar um meio-termo por meio de um serviço especial para compilar aplicativos e construir pacotes — .
Na essência, trata-se de um hipervisor que cria uma máquina virtual, instala todos os pacotes necessários nela, realiza a compilação do aplicativo e constrói o pacote nesse ambiente isolado, após o que a máquina virtual é liberada.

O planejador implementado no OpenBuildService determinará quantas máquinas virtuais ele pode iniciar para otimizar a velocidade de construção dos pacotes. O mecanismo de assinatura embutido assinará os pacotes automaticamente e os disponibilizará no repositório integrado. O sistema de controle de versão integrado salvará o histórico de alterações e compilações. Resta apenas adicionar seus códigos-fonte a esse sistema. Mesmo o servidor não precisa ser levantado manualmente, podendo-se utilizar o aberto.
Aqui, de fato, existe um problema: tal combinador é difícil de se encaixar na infraestrutura existente. Por exemplo, o controle de versão não é necessário, já temos o nosso para os códigos-fonte. O mecanismo de assinatura que temos é diferente: utiliza um servidor especial. O repositório também não é necessário.
Além disso, o suporte para outras distribuições — por exemplo, Red Hat — é implementado de forma bastante limitada, o que é compreensível.
Uma das vantagens desse serviço é o suporte rápido à nova versão da distribuição SUSE. Antes do anúncio oficial do lançamento, os pacotes necessários para a compilação são disponibilizados no repositório público. Uma nova entrada aparece na lista de distribuições disponíveis no OpenBuildService. Marcamos a opção e ela é adicionada ao plano de compilação. Assim, a adição de uma nova versão da distribuição é feita praticamente com um clique.
Em nossa infraestrutura, utilizando o OpenBuildService, são compiladas todas as variedades de pacotes KMP do módulo do núcleo veeamsnap para distribuições SUSE.
A seguir, gostaria de focar em questões específicas relacionadas aos módulos do núcleo.
kernel ABI
Os módulos do núcleo Linux historicamente foram distribuídos na forma de códigos-fonte. Isso se deve ao fato de que os criadores do núcleo não se preocupam em manter uma API estável para os módulos, especialmente a nível binário, conhecido como kABI.
Para compilar um módulo para o núcleo vanilla, são exatamente os headers desse núcleo que são necessários, e ele funcionará apenas nesse núcleo.
O DKMS permite automatizar o processo de compilação de módulos durante a atualização do núcleo. Como resultado, os usuários do repositório Debian (e seus muitos parentes) utilizam módulos do núcleo ou do repositório do distribuidor, ou compilados a partir do código-fonte usando DKMS.
No entanto, essa situação não agrada muito ao segmento Enterprise. Os distribuidores de código proprietário desejam oferecer o produto na forma de binários compilados.
Os administradores não querem manter ferramentas de desenvolvimento em servidores de produção por motivos de segurança. Distribuidores de Enterprise Linux — como Red Hat e SUSE — decidiram que poderiam suportar uma kABI estável para seus usuários. Como resultado, surgiram pacotes KMOD para Red Hat e pacotes KMP para SUSE.
A essência dessa solução é bastante simples. Para uma versão específica da distribuição, a API do núcleo é congelada. O distribuidor afirma que utiliza, por exemplo, o núcleo 3.10, e faz apenas correções e melhorias que não afetam as interfaces do núcleo, permitindo que os módulos compilados para o núcleo original sejam utilizados em todos os subsequentes sem recompilação.
A Red Hat afirma que há compatibilidade kABI para sua distribuição ao longo de todo o ciclo de vida. Ou seja, um módulo compilado para RHEL 6.0 (lançamento de novembro de 2010) também deve funcionar na versão 6.10 (lançamento de junho de 2018). Isso representa quase 8 anos. Naturalmente, essa tarefa é bastante complexa.
Registramos vários casos em que, devido a problemas de compatibilidade kABI, o módulo veeamsnap parou de funcionar.
Após o módulo veeamsnap, compilado para RHEL 7.0, se mostrar incompatível com o núcleo do RHEL 7.5, embora carregasse e garantidamente derrubasse o servidor, desistimos de usar a compatibilidade kABI para RHEL 7 completamente.
Atualmente, o pacote KMOD para RHEL 7 contém uma compilação para cada versão de lançamento e um script que garante o carregamento do módulo.
A SUSE abordou a questão da compatibilidade kABI de maneira mais cautelosa. Eles garantem a compatibilidade kABI apenas dentro de um único service pack.
Por exemplo, o lançamento do SLES 12 ocorreu em setembro de 2014. E o SLES 12 SP1 já em dezembro de 2015, ou seja, passou pouco mais de um ano. Apesar de ambas as versões utilizarem o núcleo 3.12, elas são incompatíveis com kABI. É evidente que manter a compatibilidade kABI durante apenas um ano é significativamente mais fácil. Um ciclo de atualização do módulo do núcleo anual não deve apresentar problemas para os criadores de módulos.
Como resultado dessa política da SUSE, não registramos nenhum problema de compatibilidade kABI com nosso módulo veeamsnap. Na verdade, o número de pacotes para SUSE é quase uma ordem de magnitude maior.
Correções e backports
Embora os distribuidores se esforcem para garantir a compatibilidade kABI e a estabilidade do núcleo, também buscam melhorar o desempenho e corrigir defeitos desse núcleo estável.
Além de seu próprio "trabalho de correção de erros", os desenvolvedores do núcleo enterprise linux monitoram as mudanças no núcleo vanilla e as transferem para seu "estável".
Isso às vezes leva a novos .
Na última versão do Red Hat 6, em uma das atualizações menores, ocorreu um erro. Isso fazia com que o módulo veeamsnap derrubasse o sistema ao liberar o snapshot. Comparando os códigos-fonte do núcleo antes e depois da atualização, descobrimos que a causa era o backport. Uma correção semelhante foi feita no núcleo vanilla versão 4.19. No entanto, no núcleo vanilla, essa correção funcionou corretamente, mas ao ser transferida para o "estável" 2.6.32, surgiu um problema com a bloqueio spin.
Claro, todos cometem erros de vez em quando, mas valeu a pena arrastar o código de 4.19 para 2.6.32, em risco da estabilidade?.. Não tenho certeza...
O pior é quando o marketing entra na disputa entre "estabilidade" "modernização". O departamento de marketing precisa que o núcleo da nova distribuição seja estável, por um lado, e ao mesmo tempo, melhor em desempenho e com novas funções. Isso leva a compromissos estranhos.
Quando tentei compilar um módulo no núcleo 4.4 do SLES 12 SP3, fiquei surpreso ao descobrir recursos do 4.8 vanilla. Na minha opinião, a implementação de I/O em bloco do núcleo 4.4 do SLES 12 SP3 se parece mais com o núcleo 4.8 do que com a versão anterior estável 4.4 do núcleo do SLES 12 SP2. Não me atrevo a julgar qual foi a porcentagem de código transferido do núcleo 4.8 para o 4.4 do SLES para SP3, mas não consigo chamar o núcleo de 4.4 estável.
O mais desagradável é que, ao escrever um módulo que funcione igualmente bem em diferentes núcleos, não se pode mais confiar apenas na versão do núcleo. Também é necessário levar em conta a distribuição. É bom que às vezes se possa se basear em uma definição que aparece com novas funcionalidades, mas essa possibilidade nem sempre está disponível.
Como resultado, o código se acumula em diretrizes peculiares de compilação condicional.
Também existem patches que alteram a API documentada do núcleo.
Encontrei a distribuição 5.16 e fiquei muito surpreso ao ver que a chamada lookup_bdev nesta versão do núcleo alterou a lista de parâmetros de entrada.
Para compilar, tive que adicionar um script ao makefile que verifica se o parâmetro mask está na função lookup_bdev.
Assinatura dos módulos do núcleo
Mas voltemos à questão da distribuição de pacotes.
Uma das vantagens da kABI estável é que os módulos do núcleo podem ser assinados como arquivos binários. Nesse caso, o desenvolvedor pode ter certeza de que o módulo não foi acidentalmente corrompido ou alterado intencionalmente. Isso pode ser verificado com o comando modinfo.
As distribuições Red Hat e SUSE permitem verificar a assinatura do módulo e carregá-lo apenas se a chave correspondente estiver registrada no sistema. O certificado é a chave pública com a qual o módulo é assinado. Nós o distribuímos como um pacote separado.
O problema aqui é que os certificados podem ser incorporados ao núcleo (usados pelos distribuidores) ou devem ser gravados na memória não volátil EFI usando uma ferramenta. mokutil. A ferramenta mokutil ao instalar o certificado, exige que o sistema reinicie e, ainda antes de carregar o núcleo do sistema operacional, oferece ao administrador a opção de permitir a carga do novo certificado.
Assim, adicionar um certificado requer acesso físico do administrador ao sistema. Se a máquina estiver localizada em algum lugar na nuvem ou simplesmente em um servidor remoto com acesso apenas pela rede (por exemplo, via ssh), então não será possível adicionar o certificado.
EFI em máquinas virtuais
Apesar de já faz tempo que o EFI é suportado praticamente por todos os fabricantes de placas-mãe, ao instalar o sistema, o administrador pode não considerar a necessidade do EFI, e ele pode estar desativado.
Nem todos os hypervisores suportam EFI. O VMWare vSphere suporta EFI a partir da versão 5.
O Microsoft Hyper-V também ganhou suporte ao EFI, começando com o Hyper-V para Windows Server 2012R2.
Entretanto, na configuração padrão, essa funcionalidade para máquinas Linux está desativada, o que significa que não é possível instalar o certificado.
No vSphere 6.5, a opção Secure Boot só pode ser configurada na versão antiga da interface web, que funciona através do Flash. A interface web em HTML-5 ainda está bastante atrasada.
Distribuições experimentais
Por fim, vamos considerar a questão das distribuições experimentais e das distribuições sem suporte oficial. Por um lado, essas distribuições dificilmente serão encontradas em servidores de organizações sérias. Não há suporte oficial para essas distribuições. Portanto, não se pode garantir o suporte técnico do produto em uma dessas distribuições.
No entanto, essas distribuições se tornam uma plataforma conveniente para experimentar novas soluções. Por exemplo, Fedora, OpenSUSE Tumbleweed ou versões instáveis do Debian. Elas são bastante estáveis. Sempre apresentam novas versões de programas e um núcleo novo. Em um ano, essa funcionalidade experimental pode aparecer em uma versão atualizada do RHEL, SLES ou Ubuntu.
Assim, se algo não funcionar em uma distribuição experimental, é um bom motivo para investigar o problema e resolvê-lo. É preciso estar preparado para o fato de que essa funcionalidade em breve aparecerá em servidores de produção dos usuários.
A lista atual dos distribuições oficialmente suportados para a versão 3.0 pode ser consultada . Mas a lista real de distribuições em que nosso produto pode operar é muito mais extensa.
Pessoalmente, fiquei interessado na experiência com o sistema operacional "Elbrus". Após a atualização do pacote veeam, nosso produto foi instalado e funcionou. Escrevi sobre essa experiência no Habr em .
Apoio para novas distribuições continua. Estamos aguardando o lançamento da versão 4.0. A versão beta deve sair a qualquer momento, então fique atento ao !
Fonte: habr.com
