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



Criar um aplicativo de backup que seja executado em qualquer distribuição não é uma tarefa fácil. Para garantir que o Veeam Agent for Linux funcione nas distribuições do RHEL 6 e Debian 6, até o openSUSE Leap 15.1 e Ubuntu 19.04, você deve resolver uma série de problemas, principalmente quando considerar que o módulo do kernel faz parte do produto de software.

Este artigo é baseado em uma apresentação na conferência LinuxPiter 2019 .

O Linux não é apenas um dos sistemas operacionais mais populares. De fato, esta é uma plataforma com base na qual você pode fazer algo único, algo seu. Por esse motivo, o Linux possui muitas distribuições que diferem em um conjunto de componentes de software. E aqui surge o problema: para que o produto de software funcione em qualquer distribuição, é necessário levar em consideração as características de cada uma.

Gerenciadores de pacotes. .deb vs .rpm


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

No mundo dos pacotes deb, o nível de compatibilidade é incrível. O mesmo pacote instala-se igualmente bem e funciona no Debian 6 e no Ubuntu 19.04. Os padrões para o processo de compilação de pacotes e seu trabalho, estabelecidos nas antigas distribuições Debian, permanecem relevantes no novo Linux Mint e no SO 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, devido ao fato de haver dois distribuidores completamente independentes do Red Hat e do SUSE, para os quais a compatibilidade não é absolutamente necessária. Em segundo lugar, esses distribuidores têm distribuições desses. suporte e experimental. Entre eles, a compatibilidade também não é necessária. Descobriu-se que para el6, el7 e el8 seus próprios pacotes. Pacote separado para o Fedora. Pacotes para SLES11 e 12 e separados para openSUSE. O principal problema são dependências e nomes de pacotes.

Problema de dependência


Infelizmente, os mesmos pacotes geralmente acabam com nomes diferentes em diferentes distribuições. Abaixo está uma lista parcial das dependências do pacote veeam.
Para EL7:Para o SLES 12:
  • libblkid
  • libgcc
  • libstdc ++
  • ncurses-libs
  • bibliotecas de fusíveis
  • libs de arquivo
  • 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.

Fica pior quando uma versão atualizada começa a se esconder sob o nome do pacote antigo.

Um exemplo:

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

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

Mais interessante. Após a próxima atualização do pacote de distribuição, o pacote ncurses-compat-libs com a 5ª versão da biblioteca fica indisponível. Não é rentável para um distribuidor atrair bibliotecas antigas para uma nova versão de distribuição. Depois de algum tempo, o problema foi repetido nas distribuições do SUSE.

Como resultado, em algumas distribuições, tive que abandonar a dependência explícita de ncurses-libs e corrigir o produto para que ele pudesse funcionar com qualquer versão da biblioteca.

A propósito, na 8ª versão do Red Hat, não há mais um meta-pacote python que faça referência ao bom e velho python 2.7 . Existem python2 e python 3.

Alternativa aos gerenciadores de pacotes


O problema com dependências é antigo e há muito óbvio. Apenas lembre-se do inferno da Dependência.
Combine várias bibliotecas e aplicativos para que todos funcionem de maneira estável e não entrem em conflito - de fato, qualquer distribuidor Linux está tentando resolver esse problema.

O gerenciador de pacotes Canonical Snappy está tentando resolver esse problema de maneira bastante diferente. A idéia principal: o aplicativo é executado em uma caixa de proteção isolada e protegida do sistema principal. Se o aplicativo precisar de bibliotecas, elas serão entregues com o próprio aplicativo.

O Flatpak também permite executar aplicativos na sandbox usando o Linux Containers. Há também o AppImage , que permite criar imagens portáteis de programas.

Essas soluções permitem criar um pacote para qualquer distribuição. No caso do Flatpak e AppImage, 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 na sandbox e sem privilégios de root . Alguns precisam de acesso direto à plataforma. Não estou falando de módulos do kernel, que são altamente dependentes do kernel e não se encaixam no conceito de sandbox.

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

Nesse sentido, o Veeam Agent for Linux não está no snapcraft.io nem no flathub.org .

No final da pergunta sobre gerenciadores de pacotes, observo que há uma opção para abandonar completamente os gerenciadores de pacotes combinando arquivos binários e um script para instalá-los em um pacote.

Esse pacote permite criar um pacote comum para diferentes distribuições e plataformas, para executar um processo de instalação interativa, executando a customização necessária. Me deparei com esses pacotes apenas para Linux da VMware.

Problema de atualização



Mesmo se todos os problemas de dependência forem resolvidos, o programa poderá funcionar de maneira diferente na mesma distribuição. O ponto está nas atualizações.

Existem três estratégias de atualização:

  • O mais fácil é nunca atualizar. Configurou o servidor e esqueceu. Por que atualizações se tudo funciona? Os problemas começam na primeira vez em que você entra em contato com o suporte. O criador da distribuição suporta apenas uma versão atualizada.
  • Você pode confiar no distribuidor e configurar atualizações automáticas. Nesse caso, é provável que haja uma chamada para o suporte imediatamente após uma atualização sem êxito.
  • A opção de atualização manual somente após executá-la na infraestrutura de teste é a mais fiel, mas cara e demorada. Nem todo mundo é capaz de pagar.

Como usuários diferentes usam estratégias de atualização diferentes, é necessário oferecer suporte à versão mais recente e a todas as versões anteriores. Isso complica o processo de desenvolvimento e o processo de teste, adiciona uma dor de cabeça ao serviço de suporte.

Variedade de plataformas de hardware


Várias plataformas de hardware são um problema amplamente específico ao 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 pelo menos algo semelhante ao RISC.

Não vou me debruçar sobre essa questão em detalhes. Vou descrever apenas os principais problemas: tipos dependentes da plataforma, como size_t , alinhamento de estruturas e ordem de bytes.

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



E aqui está a pergunta "Como vincular às bibliotecas - dinâmica ou estaticamente?" vale a pena discutir.

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

Se a tarefa é abranger uma variedade de distribuições com um arquivo binário, você deve se concentrar na distribuição suportada mais antiga. Para nós, esse é o Red Hat 6. Ele contém o gcc 4.4, que nem o padrão C ++ 11 oferece suporte completo .

Estamos construindo nosso projeto usando o gcc 6.3, que suporta totalmente o C ++ 14. Naturalmente, neste caso no Red Hat 6, a biblioteca libstdc ++ e boost precisam ser arrastadas. A maneira mais fácil de vincular a eles é estaticamente.

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

Primeiro, as bibliotecas do sistema, como libfuse , libblkid, precisam ser vinculadas dinamicamente para garantir que sejam compatíveis com o kernel e seus módulos.

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

O licenciamento GPL basicamente permite vincular bibliotecas apenas ao código de código-fonte aberto. O MIT e o BSD permitem a vinculação estática e permitem a inclusão de bibliotecas no projeto. Mas a LGPL não parece contradizer a vinculação estática, mas exige o compartilhamento dos arquivos necessários para a vinculação.

Em geral, o uso de links dinâmicos protegerá contra a necessidade de fornecer algo.

Criando aplicativos C / C ++


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

Uma vantagem óbvia: a infraestrutura é bastante simplificada, pois todo o processo de montagem pode ser realizado em uma máquina. Além disso, é suficiente coletar um conjunto de arquivos binários para uma arquitetura e você pode empacotá-los em pacotes para diferentes distribuições. É assim que os pacotes veeam para o Veeam Agent for Linux são criados.

Em contraste com essa opção, você pode simplesmente preparar o farm de construção, ou seja, várias máquinas para montagem. Cada uma dessas máquinas fornecerá a compilação da aplicação e montagem do pacote para uma distribuição específica e uma arquitetura específica. Nesse caso, a compilação é realizada pelos meios que o distribuidor preparou. Ou seja, o estágio de preparação do compilador e a seleção de bibliotecas não são mais necessários. Além disso, o processo de montagem pode ser facilmente paralelizado.

No entanto, há um ponto negativo nessa abordagem: para cada distribuição dentro da mesma arquitetura, você precisará montar seu próprio conjunto de arquivos binários. Um ponto negativo é que muitas máquinas precisam ser mantidas, para alocar uma grande quantidade de espaço em disco e RAM.

Dessa maneira, os pacotes KMOD do módulo do kernel veeamsnap para distribuições do Red Hat são montados.

Serviço de compilação aberto


Os colegas do SUSE tentaram implementar um meio termo como um serviço especial para compilar aplicativos e criar pacotes - openbuildservice .

De fato, é um hipervisor que cria uma máquina virtual, instala todos os pacotes necessários, compila o aplicativo e compila o pacote nesse ambiente isolado, após o qual a máquina virtual é liberada.



O planejador implementado no OpenBuildService determinará quantas máquinas virtuais ele pode executar para obter a velocidade ideal de compilação de pacotes. O próprio mecanismo de assinatura interno assinará os pacotes e os colocará no repositório interno. O sistema de controle de versão embutido salvará o histórico de alterações e montagens. Resta simplesmente adicionar seu código-fonte a este sistema. Mesmo o servidor em si não é necessário para aumentar, mas você pode usar o aberto.

Aqui, no entanto, existe um problema: é difícil adaptar essa combinação à infraestrutura existente. Por exemplo, o controle de versão não é necessário, já temos o nosso próprio para as fontes. O mecanismo de assinatura é diferente: um servidor especial é usado. O repositório também não é necessário.

Além disso, o suporte a outras distribuições - por exemplo, Red Hat - é implementado pouco, o que é compreensível.

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

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

Além disso, gostaria de me concentrar em questões específicas dos módulos do kernel.

ABI do kernel


Os módulos do kernel do Linux historicamente foram distribuídos na forma de origem. O fato é que os criadores do kernel não se sobrecarregam com o cuidado de manter uma API estável para os módulos do kernel, e mais ainda no nível binário, do que o kABI.

Para criar um módulo para o kernel vanilla, são necessários cabeçalhos desse kernel específico, e ele funcionará apenas nesse núcleo.

O DKMS permite automatizar o processo de montagem 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 no repositório do distribuidor ou montados na fonte usando o DKMS.

No entanto, essa situação não é particularmente confortável com o segmento corporativo. Os distribuidores de códigos proprietários desejam distribuir o produto na forma de binários compilados.

Os administradores não desejam manter ferramentas de desenvolvimento nos servidores de produção por motivos de segurança. Os distribuidores Enterprise Linux - como Red Hat e SUSE - decidiram que podem manter o kABI estável para seus usuários. Como resultado, os pacotes KMOD para Red Hat e KMP para SUSE apareceram.

A essência desta solução é bastante simples. A API do kernel é congelada para uma versão específica da distribuição. O distribuidor declara que ele 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 montados para o primeiro kernel podem ser usados ​​para todos os subseqüentes sem recompilação.

A Red Hat anuncia a compatibilidade do kABI para a distribuição ao longo do ciclo de vida. Ou seja, o módulo montado para o rhel 6.0 (versão de novembro de 2010) também deve funcionar na versão 6.10 (versão de junho de 2018). E isso é quase 8 anos. Naturalmente, a tarefa é bastante complicada.
Registramos vários casos em que, devido a problemas de compatibilidade com o kABI, o módulo veeamsnap parou de funcionar.

Após o módulo veeamsnap compilado para o RHEL 7.0 ser incompatível com o kernel do RHEL 7.5, mas ele carregou e garantiu a queda do servidor, recusamos o uso da compatibilidade do kABI para o RHEL 7 em geral.

Atualmente, o pacote KMOD para RHEL 7 contém um assembly para cada versão do release e um script que fornece o carregamento do módulo.

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

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

Como resultado dessa política do SUSE, não corrigimos nenhum problema com a compatibilidade do kABI em nosso módulo veeamsnap. É verdade que o número de pacotes para o SUSE é quase uma ordem de magnitude maior.

Patches e backports


Apesar do fato de os distribuidores estarem tentando garantir a compatibilidade com o kABI e a estabilidade do kernel, eles também estão tentando melhorar o desempenho e eliminar defeitos nesse kernel estável.

Além disso, além do seu próprio "trabalho sobre erros", os desenvolvedores do kernel linux corporativo rastreiam as alterações no kernel vanilla e as transferem para o "estável".

Às vezes, isso leva a novos erros .

A versão mais recente do Red Hat 6 cometeu um erro em uma das pequenas atualizações. Isso levou ao fato de que o módulo veeamsnap estava garantido para travar o sistema quando o instantâneo foi lançado. Comparando as fontes do kernel antes e depois da atualização, descobrimos que o backport era o culpado. Uma correção semelhante foi feita no kernel vanilla versão 4.19. Mas apenas no núcleo da baunilha, essa correção funcionou bem e, ao transferi-la para o "estável" 2.6.32, houve um problema com o bloqueio de rotação.

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

O pior de tudo é que quando o marketing está atrelado ao cabo-de-guerra "estabilidade" <-> "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. Isso leva a compromissos estranhos.

Quando tentei criar um módulo no kernel 4.4 do SLES 12 SP3, fiquei surpreso ao encontrar a funcionalidade do vanilla 4.8 nele. Na minha opinião, a implementação do bloco de E / S do kernel 4.4 do SLES 12 SP3 é mais como um kernel 4.8 do que a versão anterior do kernel 4.4 estável do SLES12 SP2. Não posso julgar qual porcentagem de código foi transferida do kernel 4.8 para o SLES 4.4 para SP3, mas ainda não tenho chance de chamar o kernel como o mesmo 4.4 estável.

A coisa mais desagradável é que, ao escrever um módulo que funciona igualmente bem em núcleos diferentes, você não pode mais confiar na versão do kernel. Também temos que levar em conta a distribuição. É bom que, às vezes, você possa se envolver em uma definição que aparece junto com a nova funcionalidade, mas esse recurso nem sempre aparece.

Como resultado, o código é cercado por diretivas sofisticadas para compilação condicional.

Também há patches que alteram a API do kernel documentada.
Encontrei um kit de distribuição do KDE neon 5.16 e fiquei muito surpreso ao ver que a chamada lookup_bdev nesta versão do kernel mudou a lista de parâmetros de entrada.

Para nos reunirmos, tivemos que adicionar um script no makefile que verifica se a função lookup_bdev possui um parâmetro de máscara.

Assinatura de módulos do kernel


Mas voltando à 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 acidentalmente danificado ou intencionalmente alterado. Você pode verificar isso com o comando modinfo.

As distribuições da Red Hat e do SUSE permitem verificar a assinatura de um módulo e fazer o download apenas se o certificado apropriado estiver registrado no sistema. O certificado é a chave pública pela 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 (são usados ​​pelos distribuidores) ou devem ser gravados na memória EFI não volátil usando o utilitário mokutil . Ao instalar o certificado, o utilitário mokutil requer uma reinicialização do sistema e, mesmo antes do carregamento do kernel do sistema operacional, ele oferece ao administrador a permissão para baixar o novo certificado.

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

EFI em máquinas virtuais


Apesar do fato de a EFI ter sido suportada por quase todos os criadores da placa-mãe, ao instalar o sistema, o administrador pode não pensar na necessidade da EFI e pode ser desativada.

Nem todos os hipervisores suportam EFI. O VMWare vSphere suporta EFI desde a versão 5.
O Microsoft Hyper-V também recebeu suporte de EFI, começando com o Hyper-V para Windows Server 2012R2.

No entanto, na configuração padrão, essa funcionalidade está desabilitada para máquinas Linux, o que significa que o certificado não pode ser instalado.

No vSphere 6.5, você pode definir a opção Inicialização segura apenas na versão antiga da interface da web que funciona via Flash. A interface da Web da Web no HTML-5 está muito atrasada.

Distribuições Experimentais


E, finalmente, considere a questão das distribuições experimentais e distribuições sem suporte oficial. Por um lado, é improvável que essas distribuições sejam encontradas nos servidores de organizações sérias. Não há suporte oficial para essas distribuições. Portanto, para fornecer aqueles. o suporte ao produto em tal distribuição não é possível.

No entanto, essas distribuições se tornam uma plataforma conveniente para testar novas soluções experimentais. Por exemplo, Fedora, OpenSUSE Tumbleweed ou a versão Instável do Debian. Eles são bem estáveis. Eles sempre têm novas versões de programas e sempre um novo kernel. Após um ano, essa funcionalidade experimental pode estar no RHEL, SLES ou Ubuntu atualizado.

Portanto, se algo não funcionar no kit de distribuição experimental, esta é uma ocasião para resolver 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.

A lista atual de distribuições oficialmente suportadas para a versão 3.0 pode ser encontrada aqui . Mas a lista real de distribuições nas quais nosso produto pode trabalhar é muito mais ampla.

Pessoalmente, eu estava interessado em um experimento com o Elbrus OS. Após a atualização do pacote veeam, nosso produto foi instalado e adquirido. Sobre esse experimento, escrevi sobre Habré no artigo .

Bem, o suporte para novas distribuições continua. Estamos aguardando o lançamento da versão 4.0. A versão beta está prestes a aparecer, portanto, fique atento às novidades !

Source: https://habr.com/ru/post/pt471226/


All Articles