Linux è variegato: come lavorare su qualsiasi distribuzione

Linux è variegato: come lavorare su qualsiasi distribuzione

Creare un'applicazione di backup che funzioni su qualsiasi distribuzione è un compito complesso. Per garantire il funzionamento di Veeam Agent for Linux su distribuzioni da Red Hat 6 e Debian 6 fino a OpenSUSE 15.1 e Ubuntu 19.04, è necessario affrontare una serie di problemi, soprattutto considerando che il prodotto software include un modulo del kernel.

L'articolo è stato redatto sulla base della presentazione alla conferenza LinuxPiter 2019.

Linux non è solo uno dei sistemi operativi più popolari. Fondamentalmente, è una piattaforma su cui è possibile costruire qualcosa di unico, qualcosa di nostro. Grazie a questo, Linux ha molte distribuzioni, che si distinguono per il set di componenti software. Qui sorge il problema: affinché il prodotto software funzioni su qualsiasi distribuzione, è necessario tener conto delle peculiarità di ciascuna.

Gestori di pacchetti: .deb vs .rpm

Iniziamo con il problema evidente della distribuzione del prodotto per diverse distribuzioni.
Il modo più tipico di distribuire prodotti software è caricare un pacchetto su un repository, affinché il gestore di pacchetti integrato nel sistema possa installarlo da lì.
Tuttavia, ci sono due formati di pacchetti popolari: rpm e deb. Significa che sarà necessario supportare entrambi.

Nel mondo dei pacchetti deb, il livello di compatibilità è sorprendente. Lo stesso pacchetto si installa e funziona altrettanto bene sia su Debian 6 sia su Ubuntu 19.04. Gli standard del processo di creazione dei pacchetti e la loro gestione, stabiliti nelle vecchie distribuzioni di Debian, rimangono attuali anche nelle moderne Linux Mint e elementary OS. Pertanto, nel caso di Veeam Agent for Linux, è sufficiente un pacchetto deb per ogni piattaforma hardware.

Nel mondo dei pacchetti rpm, invece, le differenze sono grandi. In primo luogo, a causa del fatto che ci sono due distributori completamente indipendenti, Red Hat e SUSE, per i quali la compatibilità non è affatto necessaria. In secondo luogo, questi distributori hanno distribuzioni con supporto tecnico e sperimentali. Tra di loro la compatibilità non è necessaria. Risultano pacchetti distinti per el6, el7 ed el8. Un pacchetto separato per Fedora. Pacchetti per SLES11 e 12 e uno separato per openSUSE. Il problema principale riguarda le dipendenze e i nomi dei pacchetti.

Il problema delle dipendenze

Purtroppo, gli stessi pacchetti spesso si presentano con nomi diversi in diverse distribuzioni. Di seguito è riportato un elenco incompleto delle dipendenze del pacchetto veeam.

Per EL7:
Per 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

Di conseguenza, l'elenco delle dipendenze si rivela unico per la distribuzione.

Peggio ancora quando una versione aggiornata inizia a nascondersi dietro il vecchio nome del pacchetto.

Esempio:

In Fedora 24 il pacchetto è stato aggiornato ncurses dalla versione 5 alla versione 6. Il nostro prodotto è stato assemblato proprio con la versione 5, per garantire la compatibilità con le vecchie distribuzioni. Per utilizzare la vecchia versione 5 della libreria su Fedora 24, è stato necessario utilizzare il pacchetto ncurses-compat-libs.

Di conseguenza, per Fedora appaiono due pacchetti, con dipendenze diverse.

Dopo diventa più interessante. Dopo un altro aggiornamento della distribuzione, il pacchetto ncurses-compat-libs con la versione 5 della libreria risulta non disponibile. Per il distributore è oneroso portare vecchie librerie in una nuova versione della distribuzione. Dopo un po' di tempo, il problema si è ripetuto anche nelle distribuzioni SUSE.

Di conseguenza, per alcune distribuzioni è stato necessario rinunciare a una dipendenza esplicita da ncurses-libs, e modificare il prodotto in modo che potesse funzionare con qualsiasi versione della libreria.

A proposito, nella versione 8 di Red Hat non c'è più il meta-pacchetto python, che faceva riferimento al buon vecchio python 2.7. C'è python2 e python3.

L'alternativa ai gestori di pacchetti

Il problema delle dipendenze è vecchio e da tempo è evidente. Basta ricordare il fenomeno della Dependency hell.
Unire le varie librerie e applicazioni in modo che tutte funzionino in modo stabile e non conflittuale — questo è esattamente ciò che cerca di risolvere qualsiasi distributore Linux.

Diversamente, il gestore di pacchetti Snappy di Canonical. L'idea di base è: l'applicazione viene eseguita in una sandbox isolata e protetta dal sistema principale. Se l'applicazione ha bisogno di librerie, queste vengono fornite insieme all'applicazione stessa.

Flatpak permette anche di eseguire applicazioni in sandbox, utilizzando i container Linux. L'idea della sandbox è condivisa anche da AppImage.

Queste soluzioni permettono di creare un singolo pacchetto per tutte le distribuzioni. Nel caso di Flatpak l'installazione e l'esecuzione dell'applicazione è possibile anche senza il consenso dell'amministratore.

Il problema principale è che non tutte le applicazioni possono funzionare in sandbox. Alcune hanno bisogno di un accesso diretto alla piattaforma. Non parlo nemmeno dei moduli del kernel, che dipendono strettamente dal kernel e non si adattano per niente al concetto di sandbox.

Un altro problema è che le distribuzioni popolari nel settore enterprise di Red Hat e SUSE non supportano ancora Snappy e Flatpak.

A causa di ciò, Veeam Agent for Linux non è disponibile né su snapcraft.io né su flathub.org.

Per concludere la questione dei gestori di pacchetti, osservo che è possibile rinunciare del tutto ai gestori di pacchetti, combinando in un solo pacchetto i file binari e lo script per la loro installazione.

Tale bundle consente di creare un unico pacchetto per diverse distribuzioni e piattaforme, realizzando un processo di installazione interattivo, effettuando la personalizzazione necessaria. Ho incontrato tali pacchetti per Linux solo da VMware.

Il problema degli aggiornamenti

Linux è variegato: come lavorare su qualsiasi distribuzione
Anche se tutti i problemi di dipendenze sono risolti, il programma può funzionare in modo diverso sulla stessa distribuzione. Il problema sono gli aggiornamenti.

Ci sono 3 strategie per l'aggiornamento:

  • La più semplice è non aggiornare mai. Hai configurato il server e ti sei dimenticato. Perché aggiornare se tutto funziona? I problemi iniziano al primo contatto con il supporto tecnico. Il creatore della distribuzione supporta solo la release aggiornata.
  • Puoi fidarti del distributore e impostare aggiornamenti automatici. In questo caso, una chiamata al supporto tecnico è probabile subito dopo un aggiornamento fallito.
  • L'opzione di aggiornamento manuale solo dopo averlo testato su un'infrastruttura di prova è la più sicura, ma costosa e laboriosa. Non tutti possono permetterselo.

Poiché diversi utenti applicano varie strategie di aggiornamento, è necessario supportare sia l'ultima release che tutte quelle precedenti. Questo complica sia il processo di sviluppo sia quello di test, aggiungendo mal di testa al supporto tecnico.

La varietà delle piattaforme hardware

Diverse piattaforme hardware sono un problema, in gran parte specifico per il codice nativo. Al minimo, bisogna compilare i binari per ogni piattaforma supportata.

Nel progetto Veeam Agent for Linux non riusciamo a supportare nulla di RISC.

Non mi soffermerò dettagliatamente su questo argomento. Indicherò solo i principali problemi: tipi dipendenti dalla piattaforma, come size_t, allineamento delle strutture e ordine dei byte.

Linkaggio statico e/o dinamico

Linux è variegato: come lavorare su qualsiasi distribuzione
E ora la questione "Come linkarsi alle librerie - dinamicamente o staticamente?" è da discutere.

In genere, le applicazioni C/C++ su Linux utilizzano il collegamento dinamico. Funziona perfettamente se l'applicazione è compilata specificamente per una distribuzione particolare.

Se l'obiettivo è coprire diverse distribuzioni con un singolo file binario, è necessario orientarsi verso la distribuzione più vecchia supportata. Per noi, questa è Red Hat 6. Essa include gcc 4.4, che non supporta nemmeno lo standard C++11. completamente.

Compiliamo il nostro progetto usando gcc 6.3, che supporta completamente C++14. Naturalmente, in questo caso, su Red Hat 6, è necessario portare con sé la libreria libstdc++ e boost. È più semplice collegarli staticamente.

Ma sfortunatamente, non tutte le librerie possono essere collegate staticamente.

In primo luogo, le librerie di sistema, come libfuse, libblkid devono essere collegate dinamicamente per garantire la compatibilità con il kernel e i suoi moduli.

In secondo luogo, ci sono delle sottigliezze con le licenze.

La licenza GPL in linea di principio consente di collegare le librerie solo con codice open-source. MIT e BSD consentono il collegamento statico e permettono di includere le librerie nel progetto. Ma LGPL, apparentemente, non contraddice il collegamento statico, ma richiede di rendere disponibili i file necessari per il collegamento.

In generale, l'uso del collegamento dinamico proteggerà dalla necessità di fornire qualcosa.

Compilazione delle applicazioni C/C++

Per compilare le applicazioni C/C++ per diverse piattaforme e distribuzioni, è sufficiente scegliere o compilare una versione adatta di gcc e utilizzare i cross-compilatori per architetture specifiche, raccogliere l'intero set di librerie. Questo lavoro è assolutamente realizzabile, ma piuttosto complicato. E non ci sono garanzie che il compilatore e le librerie scelti garantiranno una versione funzionante.

Il vantaggio ovvio: l'infrastruttura si semplifica notevolmente, poiché l'intero processo di compilazione può essere eseguito su una sola macchina. Inoltre, è sufficiente compilare un unico set di file binari per una architettura e si possono confezionare in pacchetti per diverse distribuzioni. È proprio così che vengono creati i pacchetti veeam per Veeam Agent per Linux.

In contrapposizione a questa opzione, si può semplicemente preparare una farm di build, ossia diverse macchine per la compilazione. Ogni macchina sarà responsabile della compilazione dell'applicazione e della creazione del pacchetto per una specifica distribuzione e architettura. In questo caso, la compilazione avviene con gli strumenti messi a disposizione dal distributore. Questo significa che il passo di preparazione del compilatore e la selezione delle librerie vengono a cadere. Inoltre, il processo di compilazione può essere facilmente parallelizzato.

Tuttavia, c'è uno svantaggio in questo approccio: per ogni distribuzione all'interno della stessa architettura, sarà necessario compilare un insieme specifico di file binari. Inoltre, un altro svantaggio è che è necessario gestire un certo numero di macchine, dedicando una grande quantità di spazio su disco e di memoria RAM.

Così vengono creati i pacchetti KMOD del modulo del kernel veeamsnap per le distribuzioni Red Hat.

Open Build Service

I colleghi di SUSE hanno cercato di realizzare una sorta di compromesso con un servizio speciale per la compilazione delle applicazioni e la creazione dei pacchetti — openbuildservice.

In sostanza, è un hypervisor che crea una macchina virtuale, installs in essa tutti i pacchetti necessari, esegue la compilazione dell'applicazione e la creazione del pacchetto in questo ambiente isolato, dopo di che la macchina virtuale viene liberata.

Linux è variegato: come lavorare su qualsiasi distribuzione

Il scheduler implementato in OpenBuildService determinerà autonomamente quante macchine virtuali può avviare per ottimizzare la velocità di compilazione dei pacchetti. Il meccanismo di firma integrato firmerà automaticamente i pacchetti e li caricherà nel repository integrato. Il sistema di controllo delle versioni incorporato salverà la cronologia delle modifiche e delle compilazioni. Resta solo da aggiungere a questo sistema i propri sorgenti. Non è nemmeno necessario sollevare un server, ma si può utilizzare quello aperto.

Qui, però, c'è un problema: questo combinato non si integra facilmente con l'infrastruttura esistente. Ad esempio, non abbiamo bisogno di un controllo delle versioni, abbiamo già il nostro per i sorgenti. Il meccanismo di firma è diverso da noi: viene utilizzato un server speciale. Anche il repository non è necessario.

Inoltre, il supporto per altre distribuzioni — ad esempio, Red Hat — è piuttosto scarso, cosa comprensibile.

Il punto di forza di questo servizio è il supporto rapido per la nuova versione della distribuzione SUSE. Prima dell'annuncio ufficiale del rilascio, i pacchetti necessari per la compilazione vengono resi disponibili nel repository pubblico. Nella lista delle distribuzioni disponibili su OpenBuildService appare un nuovo elemento. Selezioniamo la casella e viene aggiunto al piano di compilazione. Così, l'aggiunta di una nuova versione della distribuzione si compie praticamente con un clic.

Nella nostra infrastruttura, utilizzando OpenBuildService, viene assemblata tutta la varietà dei pacchetti KMP del modulo del kernel veeamsnap per le distribuzioni SUSE.

Ora vorrei soffermarmi sulle questioni specifiche dei moduli del kernel.

kernel ABI

Storicamente, i moduli del kernel Linux sono stati distribuiti come codice sorgente. Il fatto è che i creatori del kernel non si caricano dell'onere di mantenere un'API stabile per i moduli del kernel, tanto meno a livello binario, noto come kABI.

Per compilare un modulo per il kernel vanilla, sono necessari gli header di quel kernel specifico, e funzionerà solo su quel kernel.

DKMS consente di automatizzare il processo di compilazione dei moduli quando il kernel viene aggiornato. Di conseguenza, gli utenti del repository Debian (e dei suoi numerosi parenti) utilizzano i moduli del kernel o dal repository del distributore, o assemblati dai sorgenti tramite DKMS.

Tuttavia, tale situazione non soddisfa particolarmente il segmento Enterprise. I distributori di codice proprietario vogliono distribuire il prodotto sotto forma di binari assemblati.

Gli amministratori non vogliono mantenere strumenti di sviluppo sui server di produzione per motivi di sicurezza. I distributori di Enterprise Linux — come Red Hat e SUSE — hanno deciso di supportare un kABI stabile per i propri utenti. Di conseguenza, sono stati introdotti pacchetti KMOD per Red Hat e pacchetti KMP per SUSE.

L'essenza di tale soluzione è piuttosto semplice. Per una specifica versione della distribuzione, l'API del kernel viene congelata. Il distributore dichiara che utilizza un determinato kernel, ad esempio, 3.10, e apporta solo correzioni e miglioramenti che non influiscono in alcun modo sulle interfacce del kernel, e i moduli compilati per il primo kernel possono essere utilizzati per tutti i successivi senza ricompilazione.

Red Hat dichiara la compatibilità kABI per la distribuzione per tutto il ciclo di vita. Ciò significa che un modulo compilato per RHEL 6.0 (rilascio di novembre 2010) dovrebbe funzionare anche sulla versione 6.10 (rilascio di giugno 2018). E sono quasi 8 anni. Naturalmente, questo è un compito piuttosto complesso.
Abbiamo registrato alcuni casi in cui, a causa di problemi di compatibilità kABI, il modulo veeamsnap smetteva di funzionare.

Dopo che il modulo veeamsnap, compilato per RHEL 7.0, si è rivelato incompatibile con il kernel di RHEL 7.5, ma si caricava e causava inevitabilmente il crash del server, abbiamo abbandonato l'uso della compatibilità kABI per RHEL 7 del tutto.

Attualmente, il pacchetto KMOD per RHEL 7 contiene una build per ogni versione di rilascio e uno script che garantisce il caricamento del modulo.

SUSE si è avvicinata al compito della compatibilità kABI con maggiore cautela. Garantisce la compatibilità kABI solo all'interno di un singolo service pack.

Ad esempio, il rilascio di SLES 12 è avvenuto a settembre 2014. SLES 12 SP1 è uscito già a dicembre 2015, quindi è passato poco più di un anno. Nonostante entrambi i rilasci utilizzino il kernel 3.12, sono incompatibili con kABI. È evidente che mantenere la compatibilità kABI per solo un anno è notevolmente più semplice. Un ciclo annuale di aggiornamento del modulo del kernel non dovrebbe causare problemi agli sviluppatori di moduli.

Di conseguenza, a causa di questa politica di SUSE, non abbiamo registrato alcun problema di compatibilità kABI con il nostro modulo veeamsnap. Tuttavia, il numero di pacchetti per SUSE è quasi un ordine di grandezza maggiore.

Patch e backport

Nonostante i distributori cerchino di garantire la compatibilità kABI e la stabilità del kernel, cercano anche di migliorare le prestazioni e risolvere i difetti di questo kernel stabile.

Inoltre, oltre al proprio «lavoro sui bug», gli sviluppatori del kernel enterprise linux monitorano le modifiche al kernel vanilla e le trasferiscono nel loro «stabile».

A volte questo porta a nuovi errori.

Nell'ultimo rilascio di Red Hat 6, in uno dei minori aggiornamenti, è stato commesso un errore. Questo portava il modulo veeamsnap a far crashare inevitabilmente il sistema quando si liberava uno snapshot. Confrontando i sorgenti del kernel prima e dopo l'aggiornamento, abbiamo scoperto che la causa era un backport. Un fix analogo è stato effettuato nel kernel vanilla versione 4.19. Solo che nel kernel vanilla questo fix funzionava correttamente, mentre nel trasferirlo nel «stabile» 2.6.32 è emerso un problema di spin-locking.

Certo, gli errori si verificano in tutti e sempre, ma ne valeva la pena trascinare il codice da 4.19 a 2.6.32, a rischio della stabilità?.. Non ne sono sicuro…

La cosa peggiore è quando il marketing si unisce al tira e molla tra "stabilità" "modernizzazione". Il dipartimento marketing ha bisogno che il core del nuovo distributivo sia stabile da un lato, ma allo stesso tempo migliori in termini di prestazioni e abbia nuove funzionalità. Questo porta a compromessi strani.

Quando ho provato a compilare un modulo sul kernel 4.4 di SLES 12 SP3, ho scoperto con sorpresa che conteneva funzionalità del vanilla 4.8. A mio avviso, l'implementazione dell'I/O a blocchi del kernel 4.4 di SLES 12 SP3 assomiglia di più al kernel 4.8 che alla precedente versione stabile del 4.4 di SLES12 SP2. Non mi sento di giudicare quale fosse la percentuale di codice trasferito dal kernel 4.8 al 4.4 di SLES per SP3, ma non riesco a definire il kernel come lo stesso stabile 4.4.

La cosa più sgradevole è che, quando scrivi un modulo che deve funzionare bene su diversi kernel, non puoi più fare affidamento sulla versione del kernel. Devi anche considerare la distribuzione. È un bene che a volte puoi fare affidamento su un define, che appare insieme a nuove funzionalità, ma questa possibilità non si presenta sempre.

Di conseguenza, il codice si riempie di strane direttive di compilazione condizionale.

Ci sono anche patch che cambiano l'API del kernel documentata.
Ho trovato distribuzione KDE neon 5.16 e sono rimasto molto sorpreso di vedere che la chiamata lookup_bdev in questa versione del kernel aveva cambiato l'elenco dei parametri di ingresso.

Per compilarsi, ho dovuto aggiungere a makefile uno script che verifica se il parametro mask è presente nella funzione lookup_bdev.

Firma dei moduli del kernel

Ma torniamo alla questione della distribuzione dei pacchetti.

Uno dei vantaggi dello stabile kABI è che i moduli del kernel come file binari possono essere firmati. In questo caso, lo sviluppatore può essere certo che il modulo non è stato danneggiato accidentalmente o modificato intenzionalmente. È possibile verificarlo con il comando modinfo.

Le distribuzioni Red Hat e SUSE consentono di verificare la firma del modulo e caricarlo solo se il certificato corrispondente è registrato nel sistema. Il certificato è una chiave pubblica con cui viene firmato il modulo. Noi lo distribuiamo come un pacchetto separato.

Il problema qui è che i certificati possono essere incorporati nel firmware (utilizzati dai distributori) oppure devono essere scritti nella memoria non volatile EFI tramite un'utilità mokutil. L'utilità mokutil richiede di riavviare il sistema al momento dell'installazione del certificato e, anche prima del caricamento del kernel del sistema operativo, chiede all'amministratore di autorizzare il caricamento del nuovo certificato.

Pertanto, aggiungere un certificato richiede l'accesso fisico dell'amministratore al sistema. Se la macchina si trova in qualche luogo nel cloud o semplicemente in un server remoto e l'accesso è disponibile solo attraverso la rete (ad esempio, tramite ssh), non sarà possibile aggiungere il certificato.

EFI su macchine virtuali

Nonostante EFI sia supportato da tempo da quasi tutti i produttori di schede madri, durante l'installazione del sistema l'amministratore potrebbe non considerare la necessità di EFI, e potrebbe essere disattivato.

Non tutti i hypervisor supportano EFI. VMWare vSphere supporta EFI a partire dalla versione 5.
Microsoft Hyper-V ha anch'esso ottenuto supporto per EFI a partire da Hyper-V per Windows Server 2012R2.

Tuttavia, nella configurazione predefinita questa funzionalità per le macchine Linux è disabilitata, il che significa che non è possibile installare il certificato.

In vSphere 6.5, l'opzione Secure Boot può essere impostata solo nella vecchia versione dell'interfaccia web, che funziona tramite Flash. L'interfaccia web basata su HTML-5 è ancora molto indietro.

Distribuzioni sperimentali

Infine, esaminiamo la questione delle distribuzioni sperimentali e delle distribuzioni senza supporto ufficiale. Da un lato, è improbabile che tali distribuzioni vengano trovate su server di organizzazioni serie. Non c'è supporto ufficiale per tali distribuzioni. Pertanto, non è possibile fornire supporto tecnico per il prodotto su una tale distribuzione.

Tuttavia, tali distribuzioni diventano una piattaforma comoda per provare nuove soluzioni sperimentali. Ad esempio, Fedora, OpenSUSE Tumbleweed o le versioni instabili di Debian. Sono abbastanza stabili. Contengono sempre nuove versioni del software e un nuovo kernel. Dopo un anno, questa funzionalità sperimentale potrebbe apparire nella versione aggiornata di RHEL, SLES o Ubuntu.

Quindi, se qualcosa non funziona su una distribuzione sperimentale, è un motivo per analizzare il problema e risolverlo. Bisogna essere pronti al fatto che questa funzionalità apparirà presto sui server di produzione degli utenti.

L'attuale lista ufficiale dei sistemi operativi supportati per la versione 3.0 può essere esaminata. quiMa l'elenco reale dei sistemi operativi su cui il nostro prodotto può funzionare è molto più ampio.

Personalmente, sono stato interessato all'esperimento con il sistema operativo "Elbrus". Dopo aver aggiornato il pacchetto veeam, il nostro prodotto è stato installato e ha funzionato. Ho scritto di questo esperimento su Habr in abbiamo chiarito il corretto completamento dei programmi che utilizzano il mediastreamer..

La supporto per i nuovi sistemi operativi continua. Aspettiamo l'uscita della versione 4.0. A breve dovrebbe arrivare la beta, quindi seguite il whats-new!

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster