Gestione della configurazione Drupal guidata dalla CI utilizzando Jenkins e GitLab CI
1. Perché la gestione della configurazione guidata dalla CI è importante
Il sistema di configurazione di Drupal è uno dei maggiori punti di forza della piattaforma — e una delle sue fonti più costanti di problemi. La possibilità di esportare e importare ogni elemento della configurazione del sito come file YAML è potente, ma solo se tutti concordano su chi sia responsabile dello spostamento di quei file tra gli ambienti. Nella maggior parte dei team, questo accordo non esiste mai realmente.
I problemi classici sono ben noti a chiunque abbia rilasciato un sito Drupal:
- Deriva della configurazione — lo staging diverge dalla produzione, la produzione diverge dall’ambiente locale e nessuno è sicuro di quale ambiente sia quello canonico.
- "Funziona in staging ma non in produzione" — perché qualcuno ha aggiornato una vista o un formatter di campo in staging e non l’ha mai esportato.
- Esecuzione manuale di
drush cimche rompe i contenuti — un'importazione frettolosa alle 23:00 che ha eliminato un campo di tipo di contenuto ancora referenziato da nodi attivi.
La causa principale di ognuno di questi scenari è la stessa: un essere umano decide quando e se la configurazione viene promossa. Gli esseri umani dimenticano. Saltano passaggi sotto pressione. Prendono decisioni che si rivelano errate.
La CI non dimentica. Una pipeline o passa o fallisce. Non ha uno standup a cui partecipare. Non sa che il rilascio è tra venti minuti. Questo determinismo è esattamente ciò di cui la gestione della configurazione ha bisogno.
La promessa che questo articolo mantiene:
- Ogni modifica alla configurazione viene salvata in Git prima di toccare qualsiasi ambiente condiviso.
- La pipeline, non uno sviluppatore, è responsabile della validazione e dell'importazione della configurazione.
- La promozione tra ambienti non richiede alcun passaggio manuale.
- La deriva della configurazione è un errore di build, non un messaggio su Slack.
2. Principi fondamentali che abbiamo imparato da progetti reali
La configurazione è codice
Se cambia il comportamento del sito, appartiene a Git. Punto. Una vista, un tipo di contenuto, un'impostazione delle prestazioni, uno stile di immagine — tutto è codice. Tratta i file di configurazione con la stessa disciplina dei file PHP: revisionali, versionali, non modificarli mai direttamente in un ambiente condiviso.
Nessun drush cim manuale negli ambienti condivisi
La pipeline importa la configurazione. Gli sviluppatori no. Questa regola può sembrare estrema finché non si verifica la prima volta che qualcuno esegue drush cim in produzione con modifiche locali non salvate ancora presenti nella directory di lavoro.
Le pipeline devono fallire rapidamente in caso di deriva della configurazione
Importare la configurazione e poi esportarla immediatamente non dovrebbe produrre alcuna differenza. Se accade, la build fallisce. Questa singola regola intercetta più bug di qualsiasi altro controllo che abbiamo aggiunto.
core.extension.yml è stato escluso dalle esportazioni perché "i moduli sono comunque gestiti da Composer". Tre mesi dopo, una hotfix ha riattivato un modulo che era stato deliberatamente disabilitato in produzione. La hotfix era corretta — ma una successiva importazione della configurazione ha disabilitato nuovamente il modulo in modo silenzioso. Questo incidente è il motivo per cui ora chiamiamo core.extension.yml il file più pericoloso che si possa ignorare.3. Architettura di alto livello
drush cex
config/sync
validazione + importazione
La configurazione scorre in una sola direzione: dall'esportazione locale dello sviluppatore, attraverso Git, tramite la pipeline CI, e all'interno di ciascun ambiente.
L'intuizione chiave è la separazione delle responsabilità:
| Attore | Responsabilità | Mai responsabile di |
|---|---|---|
| Sviluppatore | Esportare la configurazione, effettuare il commit su Git, aprire MR/PR | Importare la configurazione su qualsiasi ambiente condiviso |
| Pipeline CI | Validare, importare, verificare, promuovere | Generare o modificare file di configurazione |
| Ambiente | Eseguire il sito | Generare, archiviare o esportare la configurazione |
Gli ambienti sono consumatori di configurazione, mai produttori. Nel momento in cui un ambiente diventa una fonte di verità per la configurazione, la deriva diventa inevitabile.
4. Una struttura di repository che scala
project-root/
├── composer.json
├── composer.lock
├── Jenkinsfile
├── .gitlab-ci.yml
│
├── ci/
│ ├── drupal-config-check.sh # Script di validazione riutilizzabile
│ ├── drupal-deploy.sh
│ └── drupal-install.sh
│
├── config/
│ ├── sync/ # La configurazione vive qui — salvata su Git
│ │ ├── core.extension.yml
│ │ ├── system.site.yml
│ │ └── ...
│ └── splits/ # Override config_split per ambiente
│ ├── development/
│ ├── staging/
│ └── production/
│
└── web/
├── sites/
│ └── default/
│ ├── settings.php # Salvato su Git, senza segreti
│ ├── settings.local.php # Escluso da Git, override locali
│ └── settings.env.php # Caricato dalla CI tramite variabili d'ambiente
└── ...Cosa si trova in config/sync
Tutti i file YAML di configurazione generati da drush cex. Questa directory è l'unica fonte di verità per la configurazione del sito. Viene salvata, revisionata e distribuita come qualsiasi altro codice.
Cosa non deve mai entrare nella configurazione
- Hostname specifici per ambiente, chiavi API o credenziali — utilizzare variabili d'ambiente e
settings.env.php. - Segreti di qualsiasi tipo — iniettarli tramite il secret store della CI (Jenkins Credentials o GitLab CI Variables).
- Contenuto — non utilizzare il modulo Default Content come soluzione temporanea per ciò che dovrebbe essere configurazione.
// settings.php — salvato su Git, indipendente dall'ambiente
$settings['config_sync_directory'] = DRUPAL_ROOT . '/../config/sync';
// Carica i valori specifici dell'ambiente iniettati dalla CI o dall'host.
if (file_exists($app_root . '/' . $site_path . '/settings.env.php')) {
include $app_root . '/' . $site_path . '/settings.env.php';
}
// Carica eventuali override locali opzionali (esclusi da Git).
if (file_exists($app_root . '/' . $site_path . '/settings.local.php')) {
include $app_root . '/' . $site_path . '/settings.local.php';
}// Generato dalla CI a partire dalle variabili d'ambiente — mai salvato su Git.
$databases['default']['default'] = [
'driver' => 'mysql',
'host' => getenv('DB_HOST'),
'database' => getenv('DB_NAME'),
'username' => getenv('DB_USER'),
'password' => getenv('DB_PASS'),
'port' => getenv('DB_PORT') ?: 3306,
'prefix' => '',
];
$settings['hash_salt'] = getenv('DRUPAL_HASH_SALT');
// Indica a config_split in quale ambiente ci troviamo.
$config['config_split.config_split.production']['status'] =
(getenv('APP_ENV') === 'production');5. Workflow di configurazione Drupal
5.1 Sviluppo locale
Il workflow locale è l'unico contesto in cui il ciclo di feedback tra le modifiche tramite UI e l'esportazione della configurazione deve essere rapido e abituale. Ogni sviluppatore del progetto segue le stesse regole:
- Apportare modifiche tramite UI o codice in locale.
- Eseguire immediatamente
drush cex— non alla fine della giornata. - Revisionare le differenze con
git diff config/sync. - Effettuare il commit delle modifiche oppure scartarle con
git checkout -- config/sync. - Non esiste uno stato intermedio.
# Dopo aver apportato modifiche alla configurazione tramite la UI di Drupal o install hook:
drush cex --yes
# Revisionare cosa è cambiato — trattalo come qualsiasi modifica al codice:
git diff config/sync
# Effettuare lo stage e il commit insieme al codice della funzionalità:
git add config/sync
git commit -m "feat(search): aggiungi configurazione indice Search API fulltext"
# Oppure scartare se la modifica era esplorativa e non pronta:
git checkout -- config/syncApplichiamo questa regola con un leggero hook Git pre-commit che avvisa (ma non blocca) quando file PHP o template vengono messi in stage senza una corrispondente modifica in config/sync:
#!/bin/bash
# Avvisa se il codice del modulo/tema è cambiato ma config/sync no.
CHANGED_CODE=$(git diff --cached --name-only | grep -E '\.(php|module|theme|install)$')
CHANGED_CONFIG=$(git diff --cached --name-only | grep '^config/sync')
if [[ -n "$CHANGED_CODE" ]] && [[ -z "$CHANGED_CONFIG" ]]; then
echo "⚠ Attenzione: file PHP/modulo in stage ma nessuna modifica rilevata in config/sync."
echo " Hai dimenticato di eseguire: drush cex ?"
echo " Continuo comunque — ma ricontrolla."
fi
exit 05.2 Feature Branch
La configurazione vive insieme al codice che la richiede nello stesso branch e nella stessa pull/merge request. Una funzionalità che aggiunge un nuovo tipo di contenuto dovrebbe includere sia l'install hook PHP (se presente) sia i file YAML per quel tipo di contenuto nello stesso commit.
6. Responsabilità della CI: cosa deve far rispettare la Pipeline
Ogni pipeline che interagisce con un ambiente Drupal condiviso deve eseguire questi passaggi in ordine:
- Installare o ripristinare il database — un’installazione pulita o uno snapshot sanificato della produzione.
- Eseguire gli aggiornamenti del database —
drush updb. - Importare la configurazione —
drush cim --yes. - Riesportare la configurazione —
drush cex --yes. - Verificare l’assenza di differenze — se qualsiasi file YAML è cambiato, la build deve fallire.
I passaggi 4 e 5 insieme sono i più importanti. Importare e poi riesportare immediatamente deve produrre una diff vuota. Se non accade, una di queste condizioni è vera:
- Un modulo genera configurazione durante l'importazione (spesso un bug nel modulo).
- L’UUID di un’entità di configurazione non corrisponde a quello nel database.
- Uno schema di configurazione è incompleto, causando una normalizzazione dei valori da parte di Drupal diversa rispetto all’esportazione.
- Uno sviluppatore ha modificato manualmente i file di configurazione introducendo incoerenze.
#!/bin/bash
set -euo pipefail
echo "=== Esecuzione aggiornamenti database Drupal ==="
drush updb --yes
echo "=== Importazione configurazione da config/sync ==="
drush cim --yes
echo "=== Riesportazione per verificare l'assenza di modifiche pendenti ==="
drush cex --yes
echo "=== Verifica della deriva della configurazione ==="
if ! git diff --exit-code config/sync; then
echo ""
echo "❌ ERRORE: Rilevata deriva della configurazione!"
echo " La configurazione in Git non corrisponde a quanto prodotto da Drupal dopo importazione+esportazione."
echo " Diff mostrata sopra. Correggere localmente con 'drush cex' ed effettuare il commit."
exit 1
fi
echo "✅ La configurazione è pulita — nessuna deriva rilevata."7. Implementazione Jenkins (collaudata sul campo)
7.1 Struttura del Jenkinsfile
Utilizziamo esclusivamente pipeline dichiarative. Le pipeline scriptate offrono maggiore flessibilità, ma le pipeline dichiarative sono più facili da leggere, da verificare con jenkins-cli declarative-linter e da comprendere per i nuovi membri del team.
pipeline {
agent { label 'drupal-php82' }
options {
buildDiscarder(logRotator(numToKeepStr: '20'))
timeout(time: 30, unit: 'MINUTES')
disableConcurrentBuilds()
}
environment {
DRUPAL_ROOT = "${WORKSPACE}/web"
COMPOSER_HOME = "${WORKSPACE}/.composer"
APP_ENV = 'ci'
DB_CREDS = credentials('drupal-ci-db') // Segreto Jenkins
}
stages {
stage('Checkout') {
steps {
checkout scm
sh 'git log --oneline -5'
}
}
stage('Composer Install') {
steps {
sh '''
composer install \
--no-interaction \
--prefer-dist \
--optimize-autoloader
'''
}
}
stage('Write Settings') {
steps {
// Genera settings.env.php da credenziali/variabili Jenkins
sh '''
cat > web/sites/default/settings.env.php <<EOF
<?php
$databases['default']['default'] = [
'driver' => 'mysql',
'host' => '127.0.0.1',
'database' => 'drupal_ci',
'username' => '${DB_CREDS_USR}',
'password' => '${DB_CREDS_PSW}',
'port' => 3306,
'prefix' => '',
];
$settings['hash_salt'] = '${DRUPAL_HASH_SALT}';
EOF
'''
}
}
stage('Site Install') {
steps {
sh '''
drush site-install minimal \
--yes \
--existing-config \
--account-name=admin \
--account-pass="${DRUPAL_ADMIN_PASS}"
'''
}
}
stage('Validate Config') {
steps {
sh 'bash ci/drupal-config-check.sh'
}
post {
failure {
sh 'git diff config/sync || true'
archiveArtifacts artifacts: 'config/sync/**/*.yml', allowEmptyArchive: true
}
}
}
stage('Deploy') {
when {
anyOf {
branch 'main'
branch 'release/*'
}
}
steps {
sh 'bash ci/drupal-deploy.sh'
}
}
}
post {
always {
sh 'drush cr || true'
cleanWs()
}
failure {
emailext(
subject: "[FAIL] ${JOB_NAME} #${BUILD_NUMBER}",
body: "Validazione configurazione fallita. Vedi: ${BUILD_URL}",
recipientProviders: [[$class: 'DevelopersRecipientProvider']]
)
}
}
}7.2 Considerazioni specifiche per Jenkins
Shared Libraries
Quando si gestiscono più di due o tre siti Drupal, estrarre la logica della pipeline Drupal in una Jenkins Shared Library. Il Jenkinsfile di ciascun progetto diventa quindi un wrapper minimale:
@Library('drupal-pipeline-lib@v2') _
drupalPipeline(
phpVersion: '8.2',
deployBranch: 'main',
dbCredentials: 'drupal-ci-db',
slackChannel: '#deployments'
)Pulizia del Workspace
Chiamare sempre cleanWs() nel blocco post { always }. Gli agent Jenkins accumulano stato dalle build precedenti — un settings.env.php o la directory vendor da una build precedente possono influenzare silenziosamente quella corrente. Siate drastici: pulite il workspace.
8. Implementazione GitLab CI
8.1 Struttura di .gitlab-ci.yml
image: php:8.2-cli
stages:
- build
- test
- validate-config
- deploy
variables:
COMPOSER_CACHE_DIR: "$CI_PROJECT_DIR/.cache/composer"
APP_ENV: "ci"
MYSQL_DATABASE: "drupal_ci"
MYSQL_ROOT_PASSWORD: "root"
.cache-composer: &cache-composer
cache:
key:
files: [composer.lock]
paths:
- .cache/composer
- vendor/
policy: pull
composer:install:
stage: build
cache:
key:
files: [composer.lock]
paths:
- .cache/composer
- vendor/
policy: pull-push
script:
- composer install --no-interaction --prefer-dist --optimize-autoloader
artifacts:
paths:
- vendor/
- web/core/
- web/modules/contrib/
expire_in: 1 hour
phpunit:unit:
stage: test
<<: *cache-composer
script:
- ./vendor/bin/phpunit --testsuite=unit --log-junit=reports/phpunit.xml
artifacts:
reports:
junit: reports/phpunit.xml
drupal:validate-config:
stage: validate-config
<<: *cache-composer
services:
- name: mysql:8.0
alias: mysql
variables:
DB_HOST: mysql
DB_NAME: $MYSQL_DATABASE
DB_USER: root
DB_PASS: $MYSQL_ROOT_PASSWORD
before_script:
- bash ci/write-settings-env.sh
- drush site-install minimal --yes --existing-config
script:
- bash ci/drupal-config-check.sh
artifacts:
when: on_failure
paths:
- config/sync/
expire_in: 3 days
deploy:staging:
stage: deploy
environment:
name: staging
url: https://staging.example.com
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
script:
- bash ci/drupal-deploy.sh staging
deploy:production:
stage: deploy
environment:
name: production
url: https://example.com
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
script:
- bash ci/drupal-deploy.sh production8.2 Vantaggi di GitLab CI
GitLab CI dispone di diverse funzionalità che rendono questo workflow particolarmente pulito:
- Artifact di prima classe — le esportazioni di configurazione fallite vengono automaticamente salvate e consultabili nell'interfaccia MR senza configurazioni aggiuntive.
- Caching nativo basato su
composer.lock— le installazioni Composer sono rapide dopo la prima esecuzione. - Visibilità della pipeline nelle Merge Request — gli sviluppatori vedono lo stato della validazione della configurazione direttamente nella MR prima del merge.
- Blocco Services — MySQL viene avviato come sidecar senza overhead infrastrutturale.
- Ambienti + gate manuali — la regola
when: manualnel deploy in produzione consente la promozione con un clic mantenendo un controllo umano, senza richiedere un workflow di approvazione separato.
#!/bin/bash
set -euo pipefail
TARGET_ENV="${1:-staging}"
echo "=== Deploy su: ${TARGET_ENV} ==="
rsync -az --delete \
--exclude='.git' \
--exclude='web/sites/default/files' \
--exclude='web/sites/default/settings.env.php' \
./ "deploy@${TARGET_ENV}.example.com:/var/www/drupal/"
echo "=== Esecuzione comandi post-deploy su ${TARGET_ENV} ==="
ssh "deploy@${TARGET_ENV}.example.com" bash -s <<'REMOTE'
set -e
cd /var/www/drupal
drush updb --yes
drush cim --yes
drush cex --yes
git diff --exit-code config/sync || (echo "❌ Deriva configurazione sul target!"; exit 1)
drush cr
echo "✅ Deploy completato."
REMOTE9. Configurazione specifica per ambiente senza scorciatoie
Non tutti i valori di configurazione devono essere gli stessi in tutti gli ambienti. Backend di ricerca, livello di verbosità dei log, livelli di caching e endpoint di API di terze parti differiscono legittimamente. La domanda è come gestire questa differenza senza compromettere l'integrità della tua pipeline di configurazione.
Gli strumenti corretti: config_split e config_ignore
config_split consente di definire insiemi di configurazione attivi solo in ambienti specifici. Ogni split vive nella propria directory ed è attivato tramite settings.php o settings.env.php in base a una variabile di ambiente.
langcode: en
status: true
id: development
label: Development
description: 'Config attiva solo negli ambienti locale/dev'
folder: '../config/splits/development'
module:
devel: 0
kint: 0
dblog: 0
theme: { }
blacklist: { }
graylist: { }$app_env = getenv('APP_ENV') ?: 'production';
// Attiva il config_split corretto in base all'ambiente.
$config['config_split.config_split.development']['status'] = ($app_env === 'development');
$config['config_split.config_split.staging']['status'] = ($app_env === 'staging');
$config['config_split.config_split.production']['status'] = ($app_env === 'production');Esempi reali di split
| Elemento di configurazione | Split Dev | Split Stage | Split Prod |
|---|---|---|---|
| Backend Search API | Backend database | Solr (piccolo) | Solr (cluster produzione) |
| Logging errori | dblog, verboso | syslog | syslog + APM esterno |
| Moduli di performance | Disabilitati | Abilitati | Abilitati + configurazione CDN |
| Trasporto email | Mailhog / null mailer | Mailpit | SMTP / SendGrid |
Approcci impropri — da evitare:
- Modificare la configurazione direttamente sul database di produzione.
- Usare condizioni come
if ($settings['environment'] === 'prod')all'interno disettings.phpper sostituire valori di configurazione — questo aggira completamente il sistema di configurazione e crea divergenze runtime invisibili. - Mantenere branch Git separati per ambiente con file di configurazione differenti.
10. Promozione tra ambienti
La promozione non è il deployment. Il deployment sposta codice e configurazione in un ambiente. La promozione sposta un artifact validato — qualcosa che ha già superato la CI — verso il prossimo ambiente nella catena.
In pratica, questo significa che lo stesso Git SHA validato su dev è lo SHA che raggiunge la produzione. Nessun commit dell’ultimo minuto. Nessuna hotfix che salta la CI. Nessun cherry-pick che bypassa il controllo della configurazione.
su feature branch / main
auto-deploy dopo merge
auto-deploy da develop
gate manuale su main
Lo stesso artifact validato attraversa gli ambienti. La CI viene eseguita una sola volta; il risultato si propaga.
La filosofia delle build immutabili significa: ciò che la CI ha validato è ciò che viene distribuito. Se una hotfix è veramente necessaria, passa attraverso un branch a corsia preferenziale con la propria esecuzione CI — non aggira la validazione.
main con un controllo obbligatorio dello stato della pipeline. Le "protected branches" di GitLab e il plugin "GitHub Branch Source" di Jenkins supportano entrambi questa funzionalità. Se la pipeline non è passata, il branch non può essere distribuito in produzione.11. Gestire i casi limite (li incontrerai)
Mismatch UUID
Le entità di configurazione hanno UUID. Se installi un sito ex novo e poi provi a importare la configurazione da un’installazione diversa, Drupal rifiuterà l’operazione con un errore di mismatch UUID. La soluzione è installare sempre con --existing-config oppure impostare l’UUID del sito dopo l’installazione:
# Legge l'UUID dalla configurazione salvata e lo applica alla nuova installazione:
SITE_UUID=$(grep "^uuid:" config/sync/system.site.yml | awk '{print $2}')
drush config-set "system.site" uuid "$SITE_UUID" --yes
drush cim --yesOrdine di abilitazione/disabilitazione dei moduli
Quando si abilita un nuovo modulo tramite configurazione, Drupal rispetta automaticamente l'ordine delle dipendenze durante drush cim. Tuttavia, se l’install hook di un modulo genera una configurazione predefinita che è in conflitto con quanto presente in config/sync, potrebbe essere necessario eliminare quella configurazione predefinita e reimportare. La pipeline lo rileverà come deriva.
Configurazione dipendente dal contenuto
Alcune configurazioni fanno riferimento al contenuto — ad esempio, un blocco che fa riferimento a una voce di menu per ID, o una vista che filtra per un termine di tassonomia. Questi riferimenti possono differire tra ambienti se il contenuto è stato creato in modo diverso. La soluzione è gestire quel contenuto tramite migrazioni o moduli di default content, non tramite configurazione.
Particolarità Multisite
In una configurazione multisite, ogni sito ha la propria directory di sincronizzazione della configurazione. La pipeline CI deve eseguire il controllo della configurazione per ogni sito, non solo per quello principale. Utilizza un ciclo:
#!/bin/bash
set -euo pipefail
for SITE_DIR in web/sites/*/; do
SITE=$(basename "$SITE_DIR")
[[ "$SITE" == "default" ]] && continue
[[ "$SITE" == "simpletest" ]] && continue
echo "--- Controllo configurazione per il sito: $SITE ---"
drush --uri="${SITE}" updb --yes
drush --uri="${SITE}" cim --yes
drush --uri="${SITE}" cex --yes
CONFIG_DIR="config/${SITE}/sync"
if ! git diff --exit-code "${CONFIG_DIR}"; then
echo "❌ Deriva configurazione nel sito: $SITE"
exit 1
fi
done
echo "✅ Tutti i siti sono corretti."Aggiornamento di Drupal Core con modifiche alla configurazione
Gli aggiornamenti del core a volte includono modifiche allo schema che influenzano la configurazione esistente. Esegui sempre drush updb prima di drush cim, ed esegui poi drush cex per acquisire eventuali modifiche normalizzate dallo schema. Effettua il commit di queste modifiche come parte del branch di aggiornamento del core — non lasciarti sorprendere dalla pipeline.
12. Reti di sicurezza operative
Configurazione in sola lettura in produzione
Valuta di abilitare il modulo Config Readonly in produzione. Impedisce qualsiasi modifica della configurazione tramite l’interfaccia admin — un blocco rigido che rafforza la regola "nessuna modifica manuale" a livello applicativo.
if (getenv('APP_ENV') === 'production') {
$settings['config_readonly'] = TRUE;
}Verifiche post-deploy
# Verifica che non rimangano modifiche di configurazione dopo il deploy:
drush config-status 2>&1 | grep -v 'No differences' && { \
echo "❌ Differenze di configurazione inattese dopo il deploy"; exit 1; \
} || echo "✅ Stato configurazione corretto"
# Verifica che le cache siano attive:
drush cr
drush php-eval "echo \Drupal::state()->get('system.cron_last');"Strategia di rollback
Il rollback è un'operazione Git, non un'operazione sul database. Se un deployment causa un problema:
- Identifica l’ultimo commit SHA funzionante.
- Avvia una pipeline su quello SHA.
- La pipeline ridistribuisce l'artifact precedentemente validato.
La chirurgia manuale sul database per annullare un’importazione di configurazione non è mai la soluzione — aggira tutti i controlli di sicurezza e in genere crea più deriva di quanta ne risolva.
Audit: chi ha cambiato la configurazione, quando e perché
Poiché ogni modifica di configurazione passa attraverso Git, la tua traccia di audit è la cronologia Git. Utilizza messaggi di commit significativi e applicali con un hook commit-msg o un passaggio di lint nella CI:
$ git log --oneline config/sync/views.view.articles.yml
a3f8c12 feat(views): aggiungi filtro tassonomia alla vista articoli (PROJ-421)
9e1b307 fix(views): rimuovi ordinamento esposto che causava timeout della query (PROJ-389)
4d22a91 chore(config): esportazione dopo aggiornamento Search API Solr 4.3.013. Errori comuni che non commettiamo più
| Errore | Perché è dannoso | La soluzione |
|---|---|---|
Eseguire drush cim manualmente su staging o produzione | Importa uno stato sconosciuto — che potrebbe includere modifiche locali non salvate | Solo la pipeline importa la configurazione su ambienti condivisi |
| Consentire modifiche via UI su staging | Lo staging diventa una fonte di verità, creando divergenza da Git | Abilitare config_readonly su tutti gli ambienti condivisi |
| Fidarsi che gli sviluppatori "si ricordino di esportare" | Non lo faranno, sotto pressione | Gli hook pre-commit avvisano; il fallimento CI impone |
| Non fallire in caso di deriva della configurazione | La divergenza silenziosa si accumula finché qualcosa non si rompe in modo spettacolare | La sequenza drush cim → cex → git diff --exit-code è obbligatoria |
| Branch separati per ambiente con configurazioni diverse | Il merge diventa un incubo di conflitti; nessuna singola fonte di verità | Un solo branch di configurazione, suddiviso tramite config_split |
Saltare drush updb prima di drush cim | Gli aggiornamenti di schema possono causare errori di importazione o perdita silenziosa di dati | Sempre: updb → cim → cex → diff |
14. Risultati dopo l’applicazione di questo approccio
Dopo aver implementato questo approccio su un portafoglio di siti Drupal — da un singolo piccolo sito a un’agenzia che gestisce decine di installazioni — i risultati sono stati costanti:
drush cim in produzione in 18 mesiOltre ai numeri, il cambiamento culturale è ciò che conta di più. Le discussioni sulla configurazione passano da Slack alla cronologia di Git. Invece di "qualcuno ha modificato gli image styles su staging?", la domanda diventa "esiste un commit per questo?" — e c’è sempre, oppure la modifica non è mai avvenuta.
I nuovi sviluppatori non devono più imparare le regole non scritte su "quale ambiente è canonico in questo momento". La risposta è sempre Git. La pipeline lo impone.
15. Raccomandazioni finali
- Inizia con regole rigide, rilassa solo se necessario. È più facile allentare una regola inutile che rafforzarne una che non è mai esistita.
- Tratta ogni diff di configurazione come un fallimento della build. Nessuna eccezione.
- La CI è la fonte di verità per ciò che viene distribuito — non il laptop di uno sviluppatore, non un ambiente di staging, non una decisione su Slack.
- La sequenza
updb → cim → cex → diffè inviolabile. - Usa
config_splitper reali differenze tra ambienti. Non usare condizioni insettings.phpcome scorciatoia. - Abilita config_readonly su tutti gli ambienti condivisi.
- Archivia le esportazioni di configurazione fallite come artifact CI.
- Rollback tramite Git, mai tramite interventi sul database.
Sia Jenkins che GitLab CI sono perfettamente in grado di applicare questa disciplina. GitLab CI richiede meno infrastruttura e ha un miglior supporto nativo per gli artifact; Jenkins offre maggiore flessibilità per ambienti enterprise complessi e un eccellente supporto per le shared libraries per standardizzare le pipeline su molti progetti.
L’obiettivo non è mai stato rendere complessa la gestione della configurazione. L’obiettivo è renderla noiosa: un passaggio di routine che funziona sempre, che nessuno deve pensare, che non causa mai incidenti alle 23:00.
Ivan Abramenko, Principal Drupal Architect
ivan.abramenko@drupalbook.org
projects@drupalbook.org