Vai al contenuto
EuroVPSEuroVPS

Provare un upgrade di PHP prima del passaggio, con un rollback pronto

Spostare un'applicazione su un nuovo ramo PHP in sicurezza: installate in parallelo, fate lint e scansione del codice, tracciate ciò che il traffico carica davvero, poi cambiate un solo socket.

Di EuroVPS Engineering

5 min di lettura
Provare un upgrade di PHP prima del passaggio, con un rollback pronto

La maggior parte degli upgrade di PHP fallisce allo stesso modo: la nuova versione viene installata sopra la vecchia, la home page si carica, tutti si rilassano, e il primo amministratore che il mattino dopo apre una schermata usata di rado trova una pagina bianca. Ecco il metodo che usiamo per trovare quei problemi sul banco, prima del cutover, con una via di ritorno che richiede secondi.

Perché gli upgrade continuano a rompere le cose

Ogni ramo di PHP rimuove funzioni, irrigidisce la gestione dei tipi e trasforma vecchi warning in errori fatali. Un'applicazione cresciuta negli anni porta con sé percorsi di codice che nessuno ha esercitato da molto tempo: un'esportazione, un modulo di pagamento legacy, un report amministrativo. Uno smoke test delle pagine pubbliche non li raggiungerà mai.

Le applicazioni commerciali aggiungono un secondo livello. I file protetti da un encoder girano solo se il loader per il nuovo ramo PHP supporta il formato con cui sono stati codificati, e questa è una proprietà di ogni file, non della pagina marketing dell'applicazione.

Passo uno: installate in parallelo, mai sopra

Installate il PHP di destinazione accanto a quello attuale. Su RHEL e sulle sue ricostruzioni, le software collections di Remi lo rendono semplice: ogni ramo vive nel proprio prefisso, con il proprio servizio FPM e il proprio socket. Create un pool per il sito sulla nuova versione, identico al vecchio pool tranne che per il binario e il percorso del socket.

Per i visitatori non è ancora cambiato nulla. Entrambe le versioni girano, e il web server punta ancora al vecchio socket.

Passo due: lint di ogni file con il nuovo binario

Il test più economico è chiedere al nuovo PHP di analizzare ogni file:

NEW_PHP=/path/to/new/php
find /var/www/app -name '*.php' -print0 \
  | xargs -0 -n1 -P8 "$NEW_PHP" -l 2>&1 \
  | grep -v '^No syntax errors'

Questo cattura la sintassi che il nuovo ramo non accetta più. Non cattura le funzioni rimosse o i comportamenti cambiati, perché quelli falliscono solo a runtime. Per quelli, eseguite una scansione statica di compatibilità, come PHP_CodeSniffer con lo standard PHPCompatibility, impostato sul vostro ramo di destinazione:

phpcs -p --standard=PHPCompatibility --runtime-set testVersion <target> \
      --extensions=php /var/www/app

Aspettatevi rumore dalle librerie vendorizzate e dal codice morto. L'elenco è una mappa, non un verdetto.

Passo tre: scoprite quali file il traffico carica davvero

Un'applicazione può contenere decine di migliaia di file PHP, di cui solo una frazione viene mai eseguita. Volete che lo sforzo di compatibilità sia speso su quella frazione, e volete essere sicuri che includa i percorsi degli utenti autenticati e degli amministratori.

OPcache lo sa già. Dopo una normale giornata lavorativa sulla versione attuale, incluse le persone che accedono e gli amministratori che fanno il loro lavoro, chiedetegli quali script ha compilato:

<?php
// protect this file: admin network only, and delete it afterwards
$s = opcache_get_status(true);
foreach (array_keys($s['scripts']) as $f) { echo $f, "\n"; }

Salvate l'elenco, confrontatelo con i risultati di lint e scansione, e i problemi che verranno davvero colpiti salgono in cima. I file che compaiono nella scansione ma mai nella traccia sono candidati alla rimozione, non alla correzione.

Passo quattro: leggete le intestazioni dei file codificati

Per il software commerciale codificato, controllate ogni file invece di fidarvi di un banner di compatibilità. I file codificati portano un'intestazione in testo semplice che nomina l'encoder e il formato:

grep -rl --include='*.php' 'ionCube Loader' /var/www/app \
  | while read -r f; do head -c 400 "$f" | strings | head -3; done | sort | uniq -c

Raggruppate i file per formato di encoder, poi confermate che il loader per il vostro ramo PHP di destinazione esegue quel formato. Un modulo codificato anni fa per un vecchio ramo è esattamente il file che si rifiuterà di caricarsi sul nuovo, ed è meglio scoprirlo da un'intestazione che da un cliente.

Passo cinque: provate sul nuovo pool

Puntate un hostname di test, o il vostro IP tramite una regola sugli header, al nuovo socket mentre la produzione resta sul vecchio. Percorrete i percorsi della traccia OPcache, con particolare attenzione a quelli raggiunti solo da utenti autenticati e amministratori, e osservate il log degli errori del nuovo pool mentre lo fate:

tail -F /var/log/php-fpm/app-new-error.log | grep -E 'Fatal|Uncaught|Deprecated'

Gli avvisi di deprecazione non sono fallimenti oggi, ma sono l'elenco per il prossimo upgrade. Annotateli.

Passo sei: cambiate una riga, tenete il vecchio pool in esecuzione

Il cutover è una sola modifica nel web server: il socket FastCGI.

# fastcgi_pass unix:/run/php-fpm/app-old.sock;
fastcgi_pass unix:/run/php-fpm/app-new.sock;
nginx -t && systemctl reload nginx

Il rollback è la stessa modifica al contrario, applicata in secondi, senza cambi di pacchetti e senza riavviare nulla tranne la configurazione del web server. Concordate l'orario con il proprietario del sito, osservate tassi di errore e tempi di risposta per la prima ora, e tenete il vecchio pool in esecuzione per un periodo di burn-in abbastanza lungo da coprire i job settimanali e mensili. Solo allora ritiratelo.

Su uno stack multi-nodo, fatelo un nodo web alla volta dietro il load balancer, così anche una brutta sorpresa raggiunge una frazione del traffico per pochi minuti.

Passo sette: tenete cron e CLI al passo

Il web server non è l'unica cosa che esegue PHP. I job pianificati, i worker delle code e gli strumenti da riga di comando chiamano spesso un percorso di binario fisso nel codice. Trovateli:

grep -rE 'php[0-9]*( |$)|/bin/php' /etc/cron* /var/spool/cron /etc/systemd/system 2>/dev/null

Spostateli al nuovo binario nella stessa finestra di cambiamento, o deliberatamente dopo, ma mai per caso.

La vostra checklist

  • Il nuovo PHP installato accanto al vecchio, con il proprio pool e il proprio socket.
  • Ogni file passato al lint con il nuovo binario, e una scansione di compatibilità per il ramo di destinazione.
  • Una traccia OPcache di ciò che il traffico reale carica, inclusi i percorsi autenticati e di amministrazione.
  • I file codificati raggruppati per formato e confrontati con il nuovo loader.
  • Una prova sul nuovo pool con il suo log degli errori aperto.
  • Un cutover di una riga, il vecchio pool conservato fino alla fine del burn-in, e cron spostato di proposito.

Se avete un upgrade che state rimandando, siamo lieti di pianificarlo con voi.

cta: Parla con un ingegnere

Related

  • /what-we-do/data-web/php-upgrades-security-patching/
  • /what-we-do/data-web/performance-audits/
  • /what-we-do/data-web/high-availability-web-tiers/

Pubblicato il 15 settembre 2026.

Tutte le note dal campo