Indice dei contenuti dell'articolo:
In una migrazione Linux enterprise, il problema raramente è il semplice avanzamento di versione del sistema operativo. Il vero problema è tutto ciò che negli anni è stato costruito sopra quel sistema: pacchetti legacy, dipendenze non più disponibili, script di init storici, configurazioni applicative, assunzioni implicite e componenti che, pur essendo datati, continuano a svolgere un ruolo critico in produzione.
È il caso di un cliente che aveva la necessità di migrare 43 sistemi da CentOS 7 a RHEL 9, mantenendo però un requisito tecnico molto vincolante: continuare a utilizzare Varnish Cache 3.0.7.
Il motivo non era una preferenza arbitraria per software legacy. L’infrastruttura utilizzava numerose VCL 3.x personalizzate, sviluppate nel corso degli anni, spesso lunghe migliaia di righe e profondamente integrate con la logica applicativa. Quelle VCL gestivano cache, bypass, backend, header, normalizzazione delle richieste, eccezioni, comportamenti speciali e logiche specifiche di business.
Riscriverle per Varnish 4, Varnish 6 o versioni successive non significava cambiare qualche direttiva. Significava avviare un progetto separato di analisi, porting, test funzionali, test di carico, validazione applicativa e confronto puntuale tra vecchio e nuovo comportamento.
Per questo motivo abbiamo scelto un approccio pragmatico: migrare il sistema operativo a RHEL 9 senza modificare il comportamento di Varnish 3. Per riuscirci, però, non bastava installare i vecchi RPM EL7. Su EL9 quei pacchetti non funzionavano più correttamente. La strada obbligata era una sola: recuperare il sorgente ufficiale e ricostruire il pacchetto in modo pulito per RHEL 9 e derivate.
Il repository GitHub del rebuild Varnish 3 per EL9
Il risultato di questo lavoro è stato pubblicato in un repository GitHub dedicato:
Repository: https://github.com/MarcoMarcoaldi/VARNISH3-EL9
Il repository contiene il rebuild dei pacchetti RPM ufficiali di Varnish Cache 3.0.7 per RHEL 9, AlmaLinux 9 e Rocky Linux 9, basato sul Source RPM originale varnish-3.0.7-1.el7.centos.src.rpm pubblicato da Varnish Software.
Non si tratta di un fork funzionale di Varnish, né di una versione modernizzata del software. È un rebuild mirato, con patch e modifiche minime allo spec RPM, pensato per un caso d’uso preciso: mantenere la compatibilità con configurazioni VCL 3.x legacy durante una migrazione verso EL9.
Questa distinzione è fondamentale. Varnish 3 è software End of Life dal 2015, non riceve fix di sicurezza upstream e non dovrebbe essere utilizzato per nuovi deployment. Per nuovi progetti è corretto usare Varnish 6.0 LTS o versioni successive. In questo caso, però, il requisito non era progettare una nuova architettura di caching, ma consentire a 43 sistemi esistenti di uscire da CentOS 7 senza rompere migliaia di righe di VCL già in produzione.
Perché Varnish 4 ha rotto la compatibilità con Varnish 3
Il nodo tecnico principale è la rottura di compatibilità introdotta a partire da Varnish 4. Il linguaggio VCL non è rimasto invariato: è stato ristrutturato in modo sostanziale, con modifiche incompatibili rispetto alla sintassi e al modello logico di Varnish 3.
Alcuni esempi rendono immediatamente chiara la portata del cambiamento:
| Varnish 3 | Varnish 4 e successivi | Impatto |
|---|---|---|
vcl_fetch | vcl_backend_response | Cambia il punto del flusso in cui viene gestita la risposta dal backend |
req.request | req.method | Cambia la variabile usata per leggere il metodo HTTP |
error | synth | Cambia la logica di generazione delle risposte sintetiche |
Azioni return VCL 3 | Azioni return riorganizzate | Cambia il comportamento del flusso decisionale |
In una configurazione semplice, questi cambiamenti possono essere affrontati con un porting manuale relativamente contenuto. In una configurazione enterprise, con migliaia di righe di VCL e anni di logiche stratificate, la situazione è diversa.
Una VCL di produzione non è solo un file di configurazione. Spesso è parte integrante del comportamento applicativo. Decide quando una richiesta deve essere messa in cache, quando deve essere passata al backend, quando un cookie deve causare un bypass, quando un header deve essere rimosso, quando un URL deve essere normalizzato, quando una risposta deve avere TTL differenziati o quando un errore deve essere trasformato in una risposta sintetica.
Tradurre male anche una sola di queste logiche può generare problemi gravi: caching di contenuti privati, bypass mancati, overload dei backend, errori su API, perdita di header applicativi, variazioni inattese nei TTL o differenze difficili da diagnosticare nel comportamento lato client.
Per questo, in questo progetto, il porting a VCL 4 o successive è stato volutamente escluso dalla migrazione del sistema operativo. Sarebbe stato tecnicamente possibile, ma non ragionevole all’interno della stessa finestra progettuale.
Perché i vecchi RPM EL7 non potevano essere riutilizzati su EL9
Il primo tentativo naturale, quando si deve mantenere una versione legacy, è verificare se i pacchetti esistenti possano essere installati sul nuovo sistema operativo. Nel nostro caso il pacchetto storico era varnish-3.0.7-1.el7.centos.
Su EL8 alcune dipendenze erano ancora disponibili tramite pacchetti di compatibilità. Su EL9, invece, il vecchio binario rompeva per motivi strutturali. Le dipendenze runtime richieste dal pacchetto EL7 non erano più presenti o non erano più risolvibili nello stesso modo.
| Dipendenza del runtime EL7 | Situazione su EL8 | Situazione su EL9 |
|---|---|---|
libncurses.so.5 / libtinfo.so.5 | Disponibili tramite ncurses-compat-libs | Rimosse |
libnsl.so.1 | Disponibile tramite libnsl | Rimossa, disponibile solo libnsl.so.2 |
/sbin/chkconfig e /sbin/service | Dipendenze ancora risolvibili | Dipendenze non risolvibili a causa di UsrMove |
Questo escludeva un adattamento superficiale. Non era sufficiente creare symlink, forzare l’installazione o ignorare le dipendenze. Il problema era più profondo: il binario era stato compilato per un ecosistema runtime diverso.
La conclusione è stata netta: serviva ricompilare Varnish 3.0.7 dal sorgente, generando RPM coerenti con RHEL 9, AlmaLinux 9 e Rocky Linux 9.
Recupero del Source RPM ufficiale
Il lavoro è partito dal Source RPM originale dichiarato nei metadati dei pacchetti storici: varnish-3.0.7-1.el7.centos.src.rpm.
Il vecchio repository storico repo.varnish-cache.org non era più disponibile, ma il Source RPM era ancora reperibile nel repository varnishcache/varnish30 su packagecloud. Una volta scaricato ed estratto, il pacchetto conteneva i due elementi fondamentali per procedere:
varnish-3.0.7.tar.gz, cioè il tarball sorgente upstream;varnish.spec, cioè lo spec file RPM originale per EL7.
Da qui è iniziato il lavoro di adattamento. L’obiettivo non era alterare il comportamento di Varnish, ma applicare il minimo set di modifiche necessario per ottenere un pacchetto che compilasse e, soprattutto, funzionasse realmente su EL9.
Release 3.0.7-2: primo adattamento dello spec RPM a EL9
La prima release del rebuild, 3.0.7-2, ha riguardato l’adattamento iniziale dello spec e del sorgente all’ambiente di build EL9.
Il primo problema era nel codice sorgente incluso nel tarball. La versione storica di jemalloc incorporata da Varnish 3 includeva l’header <sys/sysctl.h>, rimosso nelle versioni moderne di glibc. Su EL9, basato su glibc moderna, quel riferimento impediva la compilazione.
È stata quindi applicata una patch minimale per eliminare l’include non più disponibile. Non una modifica funzionale, ma una correzione necessaria per permettere al sorgente del 2015 di essere compilato in un ambiente del 2020+.
Lo spec file ha poi richiesto diversi adeguamenti:
pythonè stato sostituito conpython3neiBuildRequires;groffè stato sostituito congroff-base;libedit-develè stato gestito tramite repository CRB;- le dipendenze scriptlet sono state espresse per nome pacchetto, non più tramite path
/sbin/*; - LTO è stato disabilitato;
- la test suite è stata resa opt-in tramite
--with check; - il check rpath di rpmdevtools è stato disabilitato, perché l’RPATH verso le librerie private in
/usr/lib64/varnishè necessario.
Un punto importante riguarda proprio gli scriptlet. I vecchi pacchetti facevano affidamento su file dependency come /sbin/chkconfig e /sbin/service. Su EL9, con UsrMove, questo modello non è più affidabile. La soluzione corretta è stata dichiarare dipendenze verso pacchetti come chkconfig, initscripts e initscripts-service.
Gli script SysV sono stati mantenuti per compatibilità con EL7. Questo ha consentito di preservare il comportamento operativo esistente, compreso l’uso di /etc/sysconfig/varnish.
SysV init su EL9: scelta legacy, ma utile per compatibilità
Varnish 3 nasce in un’epoca in cui l’integrazione con systemd non era il modello dominante. Per mantenere la massima compatibilità con l’ambiente precedente, gli script SysV sono stati lasciati sostanzialmente invariati.
Su EL9 il servizio può comunque essere gestito tramite systemd grazie a systemd-sysv-generator, che genera dinamicamente unit systemd a partire dagli script SysV legacy.
systemctl enable --now varnish systemctl status varnish
Questo permette di mantenere una gestione familiare del servizio senza riscrivere subito tutto il layer di init. È una soluzione coerente con l’obiettivo del progetto: non introdurre cambiamenti non necessari mentre si sta migrando il sistema operativo.
Resta però un limite noto. Questa scelta è adatta a EL9, ma non deve essere considerata una strategia futura indefinita. In scenari successivi, soprattutto pensando a RHEL 10 e oltre, sarà necessario prevedere unit systemd native.
Release 3.0.7-3: il crash SIGSEGV causato da jemalloc
Dopo il primo rebuild, il pacchetto veniva generato e installato. Il problema successivo è emerso solo a runtime: varnishd crashava immediatamente all’avvio con un SIGSEGV.
Questo è stato uno dei passaggi più interessanti del troubleshooting, perché la build era formalmente corretta. Il problema non compariva durante la compilazione, ma solo quando il demone veniva eseguito su EL9.
Il debug con gdb, supportato dai pacchetti -debuginfo prodotti durante la build, ha mostrato la catena reale:
varnishd
-> getgrnam("varnish")
-> nss-systemd
-> malloc_usable_size(NULL)
-> jemalloc legacy
-> SIGSEGV
In fase di startup, varnishd risolve il gruppo varnish tramite getgrnam("varnish"). Su EL9 entra in gioco nss-systemd, che può chiamare malloc_usable_size(NULL). Per glibc questo comportamento è lecito.
Il problema è che Varnish 3 incorporava una versione molto vecchia di jemalloc, risalente al 2008, che intercettava le funzioni di allocazione a livello di processo. La sua implementazione di malloc_usable_size() non gestiva correttamente il puntatore NULL. Il risultato era un segfault immediato, prima ancora che varnishd riuscisse a stampare un messaggio utile.
La correzione applicata nella release 3.0.7-3 è stata compilare Varnish con:
--without-jemalloc
In questo modo varnishd utilizza il malloc moderno di glibc, evitando l’interposizione del vecchio jemalloc embedded. Questo ha risolto il crash all’avvio su EL9.
Release 3.0.7-4: VCL che non compila sugli host EL9 puliti
Risolto il crash, è emerso un secondo problema runtime, ancora più subdolo. Il servizio partiva correttamente sulla macchina di build, ma falliva su un host EL9 minimale e pulito quando doveva compilare la VCL.
L’errore era il seguente:
gcc: fatal error: cannot read spec file '/usr/lib/rpm/redhat/redhat-hardened-cc1'
Per comprendere il problema bisogna ricordare un dettaglio architetturale di Varnish: Varnish compila le VCL a runtime in codice C. Questo significa che gcc non è solo una dipendenza di build, ma anche una dipendenza runtime. Ogni VCL viene trasformata in codice C, compilata e caricata dal demone.
Durante la fase di build RPM su EL9, il comando cc registrato da Varnish incorporava flag derivati dall’ambiente RPM, tra cui il riferimento a:
-specs=/usr/lib/rpm/redhat/redhat-hardened-cc1
Quel file è presente sulla macchina di build, dove è installato redhat-rpm-config. Ma non è normalmente presente su un server di produzione minimale. Quindi il pacchetto sembrava funzionare sulla build machine, ma falliva appena distribuito su un sistema pulito.
La correzione applicata nella release 3.0.7-4 è stata forzare un comando VCC_CC pulito e portabile in fase di configure:
export VCC_CC='exec gcc -std=gnu99 -O2 -g -fpic -shared -Wl,-x -o %o %s'
In questo modo varnishd registra un comando di compilazione runtime indipendente dai file presenti solo sulla macchina di build. La VCL può quindi essere compilata anche su host EL9 minimali, purché sia installato gcc, richiesto per design.
Installazione dei pacchetti RPM
Una volta prodotti o scaricati gli RPM dalla pagina Releases del repository, l’installazione avviene tramite dnf, lasciando al package manager la risoluzione automatica delle dipendenze:
cd /path/to/downloaded/rpms/ dnf install ./varnish*.rpm
Dopo l’installazione, il servizio può essere abilitato e avviato normalmente:
systemctl enable --now varnish curl -I http://localhost:6081/
La configurazione rimane nei percorsi storici usati su EL7:
/etc/varnish/default.vcl /etc/sysconfig/varnish
Questo è un aspetto pratico molto importante in una migrazione di 43 sistemi. Mantenere gli stessi path riduce le differenze operative, semplifica il rollout e permette di riutilizzare procedure, template e automazioni già esistenti.
Build da sorgente su AlmaLinux 9 o Rocky Linux 9
Il repository permette anche di ricostruire i pacchetti localmente. Su una macchina AlmaLinux 9 o Rocky Linux 9, il flusso è quello classico RPM:
dnf install -y rpm-build rpmdevtools gcc make ncurses-devel libxslt groff-base pcre-devel pkgconf-pkg-config python3 dnf config-manager --set-enabled crb dnf install -y libedit-devel rpmdev-setuptree cp SOURCES/* ~/rpmbuild/SOURCES/ cp SPECS/varnish.spec ~/rpmbuild/SPECS/ rpmbuild -ba ~/rpmbuild/SPECS/varnish.spec
Gli RPM vengono generati in:
~/rpmbuild/RPMS/x86_64/
Il Source RPM ricostruito viene invece prodotto in:
~/rpmbuild/SRPMS/
Questo consente di avere un processo ripetibile, versionato e verificabile, invece di affidarsi a installazioni manuali o workaround non documentati.
Collaudo su EL9: non basta che il pacchetto compili
Uno degli insegnamenti principali di questo intervento è che, in un rebuild legacy, la compilazione è solo il primo passo. Un RPM può essere formalmente corretto, installarsi senza errori e comunque non essere utilizzabile in produzione.
Nel nostro caso i problemi più importanti sono emersi solo dopo:
- il riferimento a
<sys/sysctl.h>è emerso in fase di compilazione; - il bug di jemalloc è emerso solo all’avvio di
varnishdsu EL9; - il problema di
VCC_CCè emerso solo su un host pulito diverso dalla build machine.
Per questo il collaudo è stato eseguito su sistemi EL9 minimali, verificando non solo lo start del servizio ma anche il caricamento delle VCL, gli header HTTP, i cache HIT, il comportamento sotto carico e la visibilità tramite strumenti come varnishstat.
Un controllo basilare dopo lo startup può essere eseguito con:
curl -I http://localhost:6081/
Gli header attesi includono elementi come:
Via: 1.1 varnish X-Varnish: ...
In un ambiente reale, naturalmente, questo non è sufficiente. Occorre validare le VCL applicative, i backend, le regole di bypass, la gestione dei cookie, i TTL, le purge, gli error path e tutti i casi specifici che negli anni sono stati codificati dentro la logica VCL 3.x.
Limiti noti della soluzione
Questa soluzione non trasforma Varnish 3 in un software moderno. È e rimane un componente legacy, mantenuto qui solo per esigenze di compatibilità.
I limiti principali sono chiari:
- Varnish 3 è End of Life e non riceve aggiornamenti di sicurezza upstream;
- non deve essere utilizzato per nuovi deployment;
- non supporta TLS nativo;
- non supporta HTTP/2;
- usa ancora script SysV, gestiti su EL9 tramite
systemd-sysv-generator; - richiede
gcca runtime, perché la VCL viene compilata al volo in codice C.
In produzione, TLS deve essere terminato davanti a Varnish, ad esempio tramite nginx, HAProxy o hitch. Per ambienti futuri sarà inoltre opportuno valutare una migrazione verso versioni moderne di Varnish e una riscrittura completa delle VCL.
Perché questa era la scelta corretta per il cliente
Dal punto di vista teorico, la risposta più semplice sarebbe stata: “aggiorniamo anche Varnish”. Dal punto di vista operativo, però, sarebbe stata la scelta più rischiosa.
Il cliente non aveva solo bisogno di un nuovo sistema operativo. Aveva bisogno di mantenere continuità applicativa su 43 sistemi, senza introdurre contemporaneamente quattro cambiamenti critici:
- cambio da CentOS 7 a RHEL 9;
- cambio di runtime e librerie di sistema;
- cambio di major version di Varnish;
- riscrittura completa delle VCL da versione 3.x a versione 4 o successiva.
Separare questi problemi è stata la scelta più prudente. Prima è stato aggiornato il sistema operativo, mantenendo il comportamento di cache esistente. Solo in una fase successiva, con un progetto dedicato, si potrà valutare il porting delle VCL verso Varnish 6 LTS o versioni successive.
Questo è un approccio che in Managed Server S.r.l. adottiamo spesso nei contesti complessi: evitare migrazioni “big bang” quando i rischi sono troppo elevati e costruire invece percorsi progressivi, verificabili e reversibili.
Conclusioni
Il rebuild di Varnish Cache 3.0.7 per RHEL 9, AlmaLinux 9 e Rocky Linux 9 è nato da una necessità concreta: migrare 43 sistemi CentOS 7 verso RHEL 9 senza riscrivere immediatamente VCL 3.x complesse e già validate in produzione.
Il lavoro non si è limitato a rilanciare un rpmbuild. È stato necessario recuperare il Source RPM ufficiale, adattare lo spec a EL9, correggere il problema di <sys/sysctl.h>, eliminare il crash causato da jemalloc, rendere portabile il comando di compilazione runtime delle VCL e verificare il comportamento su host puliti.
La parte più semplice era compilare. La parte difficile era ottenere un pacchetto che si installasse, partisse, compilasse le VCL e funzionasse davvero su EL9, non solo sulla macchina di build.
Il repository pubblico è disponibile qui:
https://github.com/MarcoMarcoaldi/VARNISH3-EL9
È una soluzione di compatibilità, non una raccomandazione per nuovi progetti. Ma in un contesto reale, con sistemi legacy, VCL critiche e una migrazione infrastrutturale da completare senza interrompere la produzione, era esattamente il tipo di intervento necessario.
Perché nella sistemistica Linux reale, a volte, il lavoro più importante non è installare l’ultima versione disponibile. È costruire il ponte tecnico che permette di arrivarci senza rompere ciò che oggi tiene in piedi il servizio.