O Linux tem muitas faces: como trabalhar em qualquer distribuição

O Linux tem muitas faces: como trabalhar em qualquer distribuição

Criar um aplicativo de backup que funcione em qualquer distribuição não é uma tarefa fácil. Para garantir que o Veeam Agent for Linux funcione em distribuições do Red Hat 6 e Debian 6 ao OpenSUSE 15.1 e Ubuntu 19.04, você precisa resolver uma série de problemas, especialmente considerando que o produto de software inclui um módulo de kernel.

O artigo foi criado com base em materiais de um discurso na conferência Linux Pedro 2019.

Linux não é apenas um dos sistemas operacionais mais populares. Essencialmente, esta é uma plataforma com base na qual você pode criar algo único, algo seu. Graças a isso, o Linux possui muitas distribuições que diferem em seu conjunto de componentes de software. E aqui surge um problema: para que um produto de software funcione em qualquer distribuição, é preciso levar em consideração as características de cada uma.

Gerenciadores de pacotes. .deb versus .rpm

Comecemos com o problema óbvio de distribuição do produto em diferentes distribuições.
A maneira mais comum de distribuir produtos de software é colocar o pacote em um repositório para que o gerenciador de pacotes embutido no sistema possa instalá-lo a partir daí.
No entanto, temos dois formatos de pacote populares: rpm и deb. Isso significa que todos terão que apoiar.

No mundo dos pacotes deb, o nível de compatibilidade é incrível. O mesmo pacote é instalado e funciona igualmente bem no Debian 6 e no Ubuntu 19.04. Os padrões para o processo de construção de pacotes e trabalho com eles, estabelecidos nas antigas distribuições Debian, permanecem relevantes no novo Linux Mint e no sistema operacional elementar. Portanto, no caso do Veeam Agent for Linux, um pacote deb para cada plataforma de hardware é suficiente.

Mas no mundo dos pacotes rpm as diferenças são grandes. Em primeiro lugar, pelo facto de existirem dois distribuidores totalmente independentes, Red Hat e SUSE, para os quais a compatibilidade é totalmente desnecessária. Em segundo lugar, esses distribuidores possuem kits de distribuição daqueles. suporte e experimental. Também não há necessidade de compatibilidade entre eles. Acontece que el6, el7 e el8 têm seus próprios pacotes. Pacote separado para Fedora. Pacotes para SLES11 e 12 e outro separado para openSUSE. O principal problema são as dependências e os nomes dos pacotes.

Problema de dependência

Infelizmente, os mesmos pacotes muitas vezes acabam com nomes diferentes em distribuições diferentes. Abaixo está uma lista parcial de dependências de pacotes Veeam.

Para EL7:
Para SLES 12:

  • libblkid
  • libgcc
  • libstdc ++
  • ncurses-libs
  • fuse-libs
  • bibliotecas de arquivos
  • 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 é exclusiva para a distribuição.

O que piora é quando uma versão atualizada começa a se esconder sob o nome do pacote antigo.

Exemplo:

O pacote foi atualizado no Fedora 24 maldições da versão 5 à versão 6. Nosso produto foi construído com a versão 5 para garantir compatibilidade com distribuições mais antigas. Para usar a 5ª versão antiga da biblioteca no Fedora 24, tive que usar o pacote ncurses-compat-libs.

Como resultado, existem dois pacotes para o Fedora, com dependências diferentes.

Ainda mais interessante. Após a próxima atualização de distribuição, o pacote ncurses-compat-libs com a versão 5 da biblioteca ela não está disponível. É caro para um distribuidor arrastar bibliotecas antigas para uma nova versão da distribuição. Depois de algum tempo, o problema se repetiu nas distribuições SUSE.

Como resultado, algumas distribuições tiveram que abandonar a sua dependência explícita de ncurses-libse corrija o produto para que ele funcione com qualquer versão da biblioteca.

A propósito, na versão 8 do Red Hat não existe mais um meta pacote python, que se referia ao bom e velho python-2.7. Existe python2 и python3.

Alternativa para gerenciadores de pacotes

O problema com dependências é antigo e já é óbvio há muito tempo. Basta lembrar o inferno da dependência.
Combinar várias bibliotecas e aplicativos para que todos funcionem de forma estável e não entrem em conflito - na verdade, essa é a tarefa que qualquer distribuidor Linux tenta resolver.

O gerenciador de pacotes tenta resolver esse problema de uma maneira completamente diferente. Mal-humorado da Canônica. A ideia principal: a aplicação roda em um sandbox isolado e protegido do sistema principal. Se um aplicativo exigir bibliotecas, elas serão fornecidas com o próprio aplicativo.

Flatpak também permite executar aplicativos em uma sandbox usando Linux Containers. A ideia de sandbox também é usada AppImage.

Essas soluções permitem criar um pacote para qualquer distribuição. No caso de Flatpak a instalação e o lançamento do aplicativo são possíveis mesmo sem o conhecimento do administrador.

O principal problema é que nem todos os aplicativos podem ser executados em uma sandbox. Algumas pessoas precisam de acesso direto à plataforma. Não estou nem falando de módulos do kernel, que são estritamente dependentes do kernel e não se enquadram no conceito de sandbox.

O segundo problema é que as distribuições populares no ambiente corporativo da Red Hat e SUSE ainda não contêm suporte para Snappy e Flatpak.

Nesse sentido, o Veeam Agent for Linux não está disponível snapcraft.io não em www.flathub.org.

Para concluir a pergunta sobre gerenciadores de pacotes, gostaria de observar que existe uma opção de abandonar completamente os gerenciadores de pacotes, combinando arquivos binários e um script para instalá-los em um pacote.

Tal pacote permite criar um pacote comum para diferentes distribuições e plataformas, realizar um processo de instalação interativo, realizando as customizações necessárias. Só encontrei esses pacotes para Linux da VMware.

Problema de atualização

O Linux tem muitas faces: como trabalhar em qualquer distribuição
Mesmo que todos os problemas de dependência sejam resolvidos, o programa poderá funcionar de forma diferente na mesma distribuição. É uma questão de atualizações.

Existem 3 estratégias de atualização:

  • O mais simples é nunca atualizar. Configurei o servidor e esqueci. Por que atualizar se tudo funciona? Os problemas começam na primeira vez que você entra em contato com o suporte. O criador da distribuição oferece suporte apenas à versão atualizada.
  • Você pode confiar no distribuidor e configurar atualizações automáticas. Nesse caso, é provável que uma chamada para o suporte seja feita imediatamente após uma atualização malsucedida.
  • A opção de atualização manual somente após execução em uma infraestrutura de teste é a mais confiável, porém cara e demorada. Nem todos podem pagar.

Como diferentes usuários usam diferentes estratégias de atualização, é necessário oferecer suporte tanto à versão mais recente quanto a todas as lançadas anteriormente. Isso complica o processo de desenvolvimento e teste e acrescenta dores de cabeça à equipe de suporte.

Variedade de plataformas de hardware

Diferentes plataformas de hardware são um problema em grande parte específico do código nativo. No mínimo, você deve coletar binários para cada plataforma suportada.

No projeto Veeam Agent for Linux, ainda não podemos oferecer suporte a nada parecido com este RISC.

Não vou me alongar sobre esse assunto em detalhes. Descreverei apenas os principais problemas: tipos dependentes de plataforma, como size_t, alinhamento de estrutura e ordem de bytes.

Vinculação estática e/ou dinâmica

O Linux tem muitas faces: como trabalhar em qualquer distribuição
Mas a questão é “Como vincular bibliotecas – dinamicamente ou estaticamente?” vale a pena discutir.

Como regra, os aplicativos C/C++ no Linux usam vinculação dinâmica. Isso funciona muito bem se o aplicativo for desenvolvido especificamente para uma distribuição específica.

Se a tarefa é cobrir várias distribuições com um arquivo binário, você deve se concentrar na distribuição mais antiga suportada. Para nós, este é o Red Hat 6. Ele contém o gcc 4.4, que nem mesmo o padrão C++ 11 suporta totalmente.

Construímos nosso projeto usando gcc 6.3, que oferece suporte total a C++14. Naturalmente, neste caso, no Red Hat 6 você deve carregar o libstdc++ e aumentar as bibliotecas com você. A maneira mais fácil é vinculá-los estaticamente.

Mas, infelizmente, nem todas as bibliotecas podem ser vinculadas estaticamente.

Em primeiro lugar, bibliotecas de sistema como libfuse, libblkid é necessário vincular dinamicamente para garantir sua compatibilidade com o kernel e seus módulos.

Em segundo lugar, há uma sutileza nas licenças.

A licença GPL basicamente permite vincular bibliotecas apenas com código-fonte aberto. O MIT e o BSD permitem links estáticos e permitem que bibliotecas sejam incluídas em um projeto. Mas a LGPL não parece contradizer a vinculação estática, mas exige que os arquivos necessários para a vinculação sejam compartilhados.

Em geral, o uso de links dinâmicos evitará que você forneça qualquer coisa.

Construindo aplicativos C/C++

Para construir aplicações C/C++ para diferentes plataformas e distribuições, basta selecionar ou construir uma versão adequada do gcc e usar compiladores cruzados para arquiteturas específicas e montar todo o conjunto de bibliotecas. Este trabalho é bastante viável, mas bastante problemático. E não há garantia de que o compilador e as bibliotecas selecionadas fornecerão uma versão viável.

Uma vantagem óbvia: a infraestrutura é bastante simplificada, já que todo o processo de construção pode ser concluído em uma máquina. Além disso, basta coletar um conjunto de binários para uma arquitetura e empacotá-los em pacotes para diferentes distribuições. É assim que os pacotes Veeam são criados para o Veeam Agent for Linux.

Ao contrário desta opção, você pode simplesmente preparar um build farm, ou seja, várias máquinas para montagem. Cada uma dessas máquinas fornecerá compilação de aplicativos e montagem de pacotes para uma distribuição e arquitetura específicas. Neste caso, a compilação é realizada através dos meios preparados pelo distribuidor. Ou seja, elimina-se a etapa de preparação do compilador e seleção de bibliotecas. Além disso, o processo de construção pode ser facilmente paralelizado.

Há, no entanto, uma desvantagem nesta abordagem: para cada distribuição dentro da mesma arquitetura, você terá que coletar seu próprio conjunto de arquivos binários. Outra desvantagem é que um grande número de máquinas precisa ser mantido e uma grande quantidade de espaço em disco e RAM deve ser alocada.

É assim que os pacotes KMOD do módulo do kernel veeamsnap são compilados para distribuições Red Hat.

Serviço de compilação aberta

Colegas da SUSE tentaram implementar algum meio-termo na forma de um serviço especial para compilar aplicativos e montar pacotes - openbuildservice.

Essencialmente, é um hipervisor que cria uma máquina virtual, instala nela todos os pacotes necessários, compila a aplicação e constrói o pacote neste ambiente isolado, após o qual a máquina virtual é liberada.

O Linux tem muitas faces: como trabalhar em qualquer distribuição

O agendador implementado no OpenBuildService determinará quantas máquinas virtuais ele pode iniciar para obter a velocidade ideal de construção de pacotes. O mecanismo de assinatura integrado assinará os pacotes e os carregará no repositório integrado. O sistema de controle de versão integrado salvará o histórico de alterações e compilações. Resta simplesmente adicionar suas fontes a este sistema. Você nem precisa configurar o servidor sozinho; você pode usar um servidor aberto.

Há, no entanto, um problema: é difícil encaixar tal colheitadeira na infra-estrutura existente. Por exemplo, o controle de versão não é necessário; já temos o nosso próprio para códigos-fonte. Nosso mecanismo de assinatura é diferente: usamos um servidor especial. Um 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 deficiente, o que é compreensível.

A vantagem desse serviço é o suporte rápido para a próxima versão da distribuição SUSE. Antes do anúncio oficial do lançamento, os pacotes necessários para a montagem são postados em um repositório público. Uma nova aparece na lista de distribuições disponíveis no OpenBuildService. Marcamos a caixa e ela é adicionada ao plano de construção. Assim, adicionar uma nova versão da distribuição é feito com quase um clique.

Em nossa infraestrutura, usando OpenBuildService, toda a variedade de pacotes KMP do módulo do kernel veeamsnap para distribuições SUSE é montada.

A seguir, gostaria de abordar questões específicas dos módulos do kernel.

ABI do kernel

Os módulos do kernel Linux têm sido historicamente distribuídos em formato fonte. O fato é que os criadores do kernel não se sobrecarregam com a preocupação de suportar uma API estável para módulos do kernel, e principalmente no nível binário, ainda denominado kABI.

Para construir um módulo para um kernel vanilla, você definitivamente precisa dos cabeçalhos deste kernel específico, e ele só funcionará neste kernel.

DKMS permite automatizar o processo de construção de módulos ao atualizar o kernel. Como resultado, os usuários do repositório Debian (e seus muitos parentes) usam módulos do kernel do repositório do distribuidor ou compilados a partir do código-fonte usando DKMS.

No entanto, esta situação não é particularmente adequada ao segmento Enterprise. Os distribuidores de código proprietário desejam distribuir o produto como binários compilados.

Os administradores não desejam manter ferramentas de desenvolvimento em servidores de produção por motivos de segurança. Distribuidores Enterprise Linux, como Red Hat e SUSE, decidiram que poderiam oferecer suporte a kABI estável para seus usuários. O resultado foram pacotes KMOD para Red Hat e pacotes KMP para SUSE.

A essência desta solução é bastante simples. Para uma versão específica da distribuição, a API do kernel está congelada. O distribuidor afirma que usa o kernel, por exemplo, 3.10, e faz apenas correções e melhorias que não afetam as interfaces do kernel, e os módulos coletados para o primeiro kernel podem ser usados ​​para todos os subsequentes sem recompilação.

A Red Hat afirma compatibilidade com kABI para a distribuição durante todo o seu ciclo de vida. Ou seja, o módulo montado 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). E isso já faz quase 8 anos. Naturalmente, esta tarefa é bastante difícil.
Registramos vários casos em que o módulo veeamsnap parou de funcionar devido a problemas de compatibilidade com kABI.

Depois que o módulo veeamsnap, compilado para RHEL 7.0, revelou-se incompatível com o kernel do RHEL 7.5, mas foi carregado e com certeza travaria o servidor, abandonamos completamente o uso da compatibilidade kABI para RHEL 7.

Atualmente, o pacote KMOD para RHEL 7 contém um assembly para cada versão de lançamento e um script que carrega o módulo.

A SUSE abordou a tarefa de compatibilidade do kABI com mais cuidado. Eles fornecem compatibilidade com kABI apenas dentro de um service pack.

Por exemplo, o lançamento do SLES 12 ocorreu em setembro de 2014. E o SLES 12 SP1 já foi em dezembro de 2015, ou seja, já se passou pouco mais de um ano. Embora ambas as versões usem o kernel 3.12, elas são incompatíveis com kABI. Obviamente, manter a compatibilidade do kABI por apenas um ano é muito mais fácil. O ciclo anual de atualização do módulo do kernel não deve causar problemas aos criadores do módulo.

Como resultado desta política do SUSE, não registramos um único problema com a compatibilidade do kABI em nosso módulo veeamsnap. É verdade que o número de pacotes do SUSE é quase uma ordem de grandeza maior.

Patches e backports

Embora os distribuidores tentem garantir a compatibilidade do kABI e a estabilidade do kernel, eles também tentam melhorar o desempenho e eliminar defeitos deste kernel estável.

Ao mesmo tempo, além de seu próprio “trabalho em erros”, os desenvolvedores do kernel Linux corporativo monitoram as alterações no kernel vanilla e as transferem para o kernel “estável”.

Às vezes isso leva a novos erros.

Na última versão do Red Hat 6, foi cometido um erro em uma das pequenas atualizações. Isso levou ao fato de que o módulo veeamsnap tinha a garantia de travar o sistema quando o snapshot fosse lançado. Depois de comparar as fontes do kernel antes e depois da atualização, descobrimos que a culpa era do backport. Uma correção semelhante foi feita no kernel vanilla versão 4.19. Só que essa correção funcionou bem no kernel vanilla, mas ao transferi-lo para o “estável” 2.6.32, surgiu um problema com o spinlock.

Claro que todo mundo sempre tem erros, mas valeu a pena arrastar o código da 4.19 para a 2.6.32, arriscando a estabilidade?.. Não tenho certeza...

O pior é quando o marketing se envolve no cabo de guerra entre “estabilidade” e “modernização”. O departamento de marketing precisa que o núcleo da distribuição atualizada seja estável, por um lado, e ao mesmo tempo tenha melhor desempenho e tenha novos recursos. Isto leva a compromissos estranhos.

Quando tentei construir um módulo no kernel 4.4 do SLES 12 SP3, fiquei surpreso ao encontrar funcionalidades do vanilla 4.8 nele. Na minha opinião, a implementação de E/S de bloco do kernel 4.4 do SLES 12 SP3 é mais semelhante ao kernel 4.8 do que a versão anterior do kernel 4.4 estável do SLES12 SP2. Não consigo avaliar qual porcentagem de código foi transferida do kernel 4.8 para o SLES 4.4 para SP3, mas não consigo nem chamar o kernel de 4.4 estável.

A coisa mais desagradável sobre isso é que ao escrever um módulo que funcionaria igualmente bem em diferentes kernels, você não pode mais confiar na versão do kernel. Você também deve levar em consideração a distribuição. É bom que às vezes você possa se envolver em uma definição que aparece junto com novas funcionalidades, mas nem sempre essa oportunidade aparece.

Como resultado, o código fica repleto de diretivas estranhas de compilação condicional.

Existem também patches que alteram a API do kernel documentada.
Me deparei com a distribuição Néon do KDE 5.16 e fiquei muito surpreso ao ver que a chamada lookup_bdev nesta versão do kernel alterou a lista de parâmetros de entrada.

Para fazer isso, tive que adicionar um script ao makefile que verifica se a função lookup_bdev tem um parâmetro de máscara.

Assinando módulos do kernel

Mas voltemos à questão da distribuição de pacotes.

Uma das vantagens do kABI estável é que os módulos do kernel podem ser assinados como um arquivo binário. Nesse caso, o desenvolvedor pode ter certeza de que o módulo não foi danificado acidentalmente ou modificado intencionalmente. Você pode verificar isso com o comando modinfo.

As distribuições Red Hat e SUSE permitem verificar a assinatura do módulo e carregá-lo somente se o certificado correspondente estiver registrado 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 kernel (os distribuidores os utilizam) ou devem ser gravados na memória não volátil EFI usando um utilitário mokutil. Utilitário mokutil Ao instalar um certificado, é necessário reinicializar o sistema e, mesmo antes de carregar o kernel do sistema operacional, solicita ao administrador que permita o carregamento de um novo certificado.

Assim, adicionar um certificado requer acesso físico de administrador ao sistema. Se a máquina estiver localizada em algum lugar da nuvem ou simplesmente em uma sala de servidores remotos e o acesso for apenas via rede (por exemplo, via ssh), então será impossível adicionar um certificado.

EFI em máquinas virtuais

Apesar de o EFI ser suportado há muito tempo por quase todos os fabricantes de placas-mãe, ao instalar um sistema, o administrador pode não pensar na necessidade do EFI e ele pode estar desabilitado.

Nem todos os hipervisores oferecem suporte a EFI. VMWare vSphere oferece suporte a EFI a partir da versão 5.
O Microsoft Hyper-V também ganhou suporte EFI começando com o Hyper-V para Windows Server 2012R2.

Porém, na configuração padrão esta funcionalidade está desabilitada para máquinas Linux, o que significa que o certificado não pode ser instalado.

No vSphere 6.5, defina a opção seguro Bota só é possível na versão antiga da interface web, que roda via Flash. A UI da Web em HTML-5 ainda está muito atrasada.

Distribuições experimentais

E finalmente, vamos considerar a questão das distribuições experimentais e das distribuições sem suporte oficial. Por um lado, é improvável que tais distribuições sejam encontradas nos servidores de organizações sérias. Não há suporte oficial para tais distribuições. Portanto, forneça-os. O produto não pode ser suportado em tal distribuição.

No entanto, tais distribuições tornam-se uma plataforma conveniente para experimentar novas soluções experimentais. Por exemplo, Fedora, OpenSUSE Tumbleweed ou versões instáveis ​​do Debian. Eles são bastante estáveis. Eles sempre têm novas versões de programas e sempre um novo kernel. Em um ano, essa funcionalidade experimental pode acabar em um RHEL, SLES ou Ubuntu atualizado.

Portanto, se algo não funcionar em uma distribuição experimental, esse é um motivo para descobrir o problema e resolvê-lo. Você precisa estar preparado para o fato de que essa funcionalidade aparecerá em breve nos servidores de produção dos usuários.

Você pode estudar a lista atual de distribuições com suporte oficial para a versão 3.0 aqui. Mas a lista real de distribuições nas quais nosso produto pode funcionar é muito mais ampla.

Pessoalmente, fiquei interessado em experimentar o sistema operacional Elbrus. Após finalizar o pacote Veeam, nosso produto foi instalado e funcionando. Escrevi sobre esse experimento em Habré em статье.

Bem, o suporte para novas distribuições continua. Estamos aguardando o lançamento da versão 4.0. Beta está prestes a aparecer, então fique de olho o que há de novo!

Fonte: habr.com

Adicionar um comentário