Drupal CI-gestuurd configuratiebeheer met Jenkins en GitLab CI
1. Waarom CI-gestuurd configuratiebeheer belangrijk is
Het configuratiesysteem van Drupal is een van de grootste sterke punten van het platform β en een van de meest betrouwbare bronnen van problemen. De mogelijkheid om elk stukje siteconfiguratie te exporteren en te importeren als YAML-bestanden is krachtig, maar alleen als iedereen het eens is over wie verantwoordelijk is voor het verplaatsen van die bestanden tussen omgevingen. In de meeste teams bestaat die overeenstemming nooit echt.
De klassieke problemen zijn goed bekend bij iedereen die een Drupal-site heeft opgeleverd:
- Config drift β staging wijkt af van productie, productie wijkt af van lokaal, en niemand weet zeker welke omgeving canoniek is.
- "Werkt op staging maar niet op productie" β omdat iemand een view of een field formatter op staging heeft bijgewerkt en dit nooit heeft geΓ«xporteerd.
- Handmatige
drush cimbreekt content β een gehaaste import om 23:00 uur die een content type-veld verwijderde dat nog steeds werd gebruikt door live nodes.
De hoofdoorzaak van elk van deze scenario's is hetzelfde: een mens bepaalt wanneer en of configuratie wordt gepromoveerd. Mensen vergeten dingen. Ze slaan stappen over onder druk. Ze maken beoordelingskeuzes die achteraf verkeerd blijken te zijn.
CI vergeet niet. Een pipeline slaagt of faalt. Hij hoeft niet naar een stand-up. Hij weet niet dat de release over twintig minuten plaatsvindt. Die deterministische aanpak is precies wat configuratiebeheer nodig heeft.
De belofte die dit artikel waarmaakt:
- Elke configuratiewijziging wordt vastgelegd in Git voordat deze een gedeelde omgeving bereikt.
- De pipeline, niet een ontwikkelaar, is verantwoordelijk voor het valideren en importeren van configuratie.
- Promotie tussen omgevingen vereist nul handmatige stappen.
- Config drift is een buildfout, geen Slack-bericht.
2. Kernprincipes die we hebben geleerd uit echte projecten
Configuratie is code
Als het het gedrag van de site verandert, hoort het in Git. Punt. Een view, een content type, een performance-instelling, een image style β alles is code. Behandel configuratiebestanden met dezelfde discipline als PHP-bestanden: review ze, versieer ze, bewerk ze nooit direct in een gedeelde omgeving.
Geen handmatige drush cim in gedeelde omgevingen
De pipeline importeert configuratie. Ontwikkelaars niet. Deze regel klinkt extreem totdat je meemaakt dat iemand drush cim op productie uitvoert terwijl er nog niet-gecommitteerde lokale wijzigingen in hun werkdirectory staan.
Pipelines moeten snel falen bij config drift
Configuratie importeren en vervolgens onmiddellijk exporteren zou geen diff mogen opleveren. Als dat wel gebeurt, faalt de build. Deze ene regel vangt meer bugs af dan elke andere check die we hebben toegevoegd.
core.extension.yml uitgesloten van exports omdat "modules toch al door Composer worden beheerd." Drie maanden later schakelde een hotfix een module opnieuw in die bewust in productie was uitgeschakeld. De hotfix was correct β maar een daaropvolgende config-import schakelde de module stilletjes weer uit. Dat incident is de reden waarom we core.extension.yml nu het gevaarlijkste bestand noemen dat je kunt negeren.3. Architectuur op hoog niveau
drush cex
config/sync
valideren + importeren
Configuratie stroomt slechts in één richting: van de lokale export van de ontwikkelaar, via Git, door de CI-pipeline en naar elke omgeving.
Het belangrijkste inzicht is de scheiding van verantwoordelijkheden:
| Actor | Verantwoordelijkheid | Nooit verantwoordelijk voor |
|---|---|---|
| Ontwikkelaar | Config exporteren, committen naar Git, MR/PR openen | Config importeren op een gedeelde omgeving |
| CI-pipeline | Valideren, importeren, verifiΓ«ren, promoten | Configuratiebestanden genereren of bewerken |
| Omgeving | De site draaien | Config genereren, opslaan of exporteren |
Omgevingen zijn consumenten van configuratie, nooit producenten. Op het moment dat een omgeving een bron van waarheid voor configuratie wordt, is drift onvermijdelijk.
4. Repository-structuur die schaalt
project-root/
βββ composer.json
βββ composer.lock
βββ Jenkinsfile
βββ .gitlab-ci.yml
β
βββ ci/
β βββ drupal-config-check.sh # Herbruikbaar validatiescript
β βββ drupal-deploy.sh
β βββ drupal-install.sh
β
βββ config/
β βββ sync/ # Config staat hier β gecommit naar Git
β β βββ core.extension.yml
β β βββ system.site.yml
β β βββ ...
β βββ splits/ # config_split overrides per omgeving
β βββ development/
β βββ staging/
β βββ production/
β
βββ web/
βββ sites/
β βββ default/
β βββ settings.php # Gecommit, geen secrets
β βββ settings.local.php # Gitignored, lokale overrides
β βββ settings.env.php # Geladen door CI vanuit env-vars
βββ ...Wat hoort in config/sync
Alle configuratie-YAML-bestanden die zijn gegenereerd door drush cex. Deze directory is de enige bron van waarheid voor siteconfiguratie. Deze wordt gecommit, gereviewd en gedeployed zoals elke andere code.
Wat nooit in config thuishoort
- Omgevingsspecifieke hostnames, API-sleutels of credentials β gebruik omgevingsvariabelen en
settings.env.php. - Secrets van welke aard dan ook β injecteer via je CI secret store (Jenkins Credentials of GitLab CI-variabelen).
- Content β gebruik de Default Content-module niet als hulpmiddel voor wat configuratie zou moeten zijn.
// settings.php β gecommit naar Git, omgevingsonafhankelijk
$settings['config_sync_directory'] = DRUPAL_ROOT . '/../config/sync';
// Laad omgevingsspecifieke waarden die door CI of de host zijn geΓ―njecteerd.
if (file_exists($app_root . '/' . $site_path . '/settings.env.php')) {
include $app_root . '/' . $site_path . '/settings.env.php';
}
// Laad optionele lokale overrides (gitignored).
if (file_exists($app_root . '/' . $site_path . '/settings.local.php')) {
include $app_root . '/' . $site_path . '/settings.local.php';
}// Gegenereerd door CI vanuit omgevingsvariabelen β nooit committen.
$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');
// Laat config_split weten in welke omgeving we zitten.
$config['config_split.config_split.production']['status'] =
(getenv('APP_ENV') === 'production');5. Drupal-configuratieworkflow
5.1 Lokale ontwikkeling
De lokale workflow is de enige plek waar de feedbacklus tussen UI-wijzigingen en config-export snel en routinematig moet zijn. Elke ontwikkelaar op het project volgt dezelfde regels:
- Maak UI- of codewijzigingen lokaal.
- Voer onmiddellijk
drush cexuit β niet aan het einde van de dag. - Bekijk de diff met
git diff config/sync. - Commit de wijzigingen of verwerp ze met
git checkout -- config/sync. - Er bestaat geen tussenstatus.
# Na het aanbrengen van configuratiewijzigingen via de Drupal UI of install hooks:
drush cex --yes
# Bekijk wat er veranderd is β behandel dit zoals elke codewijziging:
git diff config/sync
# Stage en commit samen met de featurecode:
git add config/sync
git commit -m "feat(search): add fulltext search API index config"
# Of verwerp als de wijziging verkennend was en nog niet klaar:
git checkout -- config/syncWe handhaven dit met een lichte Git pre-commit hook die waarschuwt (maar niet blokkeert) wanneer PHP- of templatebestanden worden gestaged zonder een overeenkomstige wijziging in config/sync:
#!/bin/bash
# Waarschuw als module/theme-code gewijzigd is maar config/sync niet.
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 "β Waarschuwing: PHP/module-bestanden gestaged maar geen config/sync-wijzigingen gedetecteerd."
echo " Ben je vergeten: drush cex uit te voeren?"
echo " Ga toch door β maar controleer dit nog eens."
fi
exit 05.2 Feature branches
Configuratie bevindt zich naast de code die deze vereist in dezelfde branch en dezelfde pull/merge request. Een feature die een nieuw content type toevoegt, moet zowel de PHP install hook (indien aanwezig) als de YAML-bestanden voor dat content type in dezelfde commit bevatten.
6. CI-verantwoordelijkheden: Wat de pipeline moet afdwingen
Elke pipeline die een gedeelde Drupal-omgeving aanraakt, moet deze stappen in deze volgorde uitvoeren:
- Installeer of herstel de database β een schone installatie of een gesanitiseerde productiesnapshot.
- Voer database-updates uit β
drush updb. - Importeer configuratie β
drush cim --yes. - Exporteer configuratie opnieuw β
drush cex --yes. - Controleer op diff β als een YAML-bestand is gewijzigd, faalt de build.
Stap 4 en 5 samen zijn het belangrijkst. Configuratie importeren en vervolgens onmiddellijk opnieuw exporteren moet een lege diff opleveren. Als dat niet zo is, is één van deze dingen waar:
- Een module genereert config bij import (vaak een bug in de module).
- De UUID van een config-entiteit komt niet overeen met wat er in de database staat.
- Een config-schema is onvolledig, waardoor Drupal waarden anders normaliseert dan hoe ze zijn geΓ«xporteerd.
- Een ontwikkelaar heeft configbestanden handmatig bewerkt en inconsistenties geΓ―ntroduceerd.
#!/bin/bash
set -euo pipefail
echo "=== Drupal database-updates uitvoeren ==="
drush updb --yes
echo "=== Configuratie importeren vanuit config/sync ==="
drush cim --yes
echo "=== Opnieuw exporteren om te verifiΓ«ren dat er geen openstaande wijzigingen zijn ==="
drush cex --yes
echo "=== Controleren op config drift ==="
if ! git diff --exit-code config/sync; then
echo ""
echo "β FOUT: Config drift gedetecteerd!"
echo " De config in Git komt niet overeen met wat Drupal produceerde na import+export."
echo " Diff hierboven getoond. Herstel dit lokaal met 'drush cex' en commit."
exit 1
fi
echo "β
Configuratie is schoon β geen drift gedetecteerd."7. Jenkins-implementatie (Bewezen in de praktijk)
7.1 Jenkinsfile-structuur
We gebruiken uitsluitend declaratieve pipelines. Scripted pipelines bieden meer flexibiliteit, maar declaratieve pipelines zijn gemakkelijker te lezen, eenvoudiger te controleren met jenkins-cli declarative-linter en beter te begrijpen voor nieuwe teamleden.
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') // Jenkins-secret
}
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 {
// Genereer settings.env.php vanuit Jenkins credentials/env-vars
sh '''
cat > web/sites/default/settings.env.php <<EOF
$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: "Configvalidatie mislukt. Zie: ${BUILD_URL}",
recipientProviders: [[$class: 'DevelopersRecipientProvider']]
)
}
}
}7.2 Jenkins-specifieke overwegingen
Shared Libraries
Wanneer je meer dan twee of drie Drupal-sites beheert, haal dan de Drupal pipeline-logica uit naar een Jenkins Shared Library. De Jenkinsfile van elk project wordt dan een dunne wrapper:
@Library('drupal-pipeline-lib@v2') _
drupalPipeline(
phpVersion: '8.2',
deployBranch: 'main',
dbCredentials: 'drupal-ci-db',
slackChannel: '#deployments'
)Workspace-opruiming
Roep altijd cleanWs() aan in het post { always }-blok. Jenkins-agents verzamelen status van eerdere builds β een settings.env.php of vendor-directory van een vorige build kan de huidige build stilzwijgend beΓ―nvloeden. Wees rigoureus: maak de workspace schoon.
8. GitLab CI-implementatie
8.1 .gitlab-ci.yml-structuur
image: php:8.2-cli
stages:
- build
- test
- validate-config
- deploy
# ββ Gedeelde variabelen βββββββββββββββββββββββββββββββββββββββββββββ
variables:
COMPOSER_CACHE_DIR: "$CI_PROJECT_DIR/.cache/composer"
APP_ENV: "ci"
MYSQL_DATABASE: "drupal_ci"
MYSQL_ROOT_PASSWORD: "root"
# ββ Herbruikbare cache βββββββββββββββββββββββββββββββββββββββββββββββ
.cache-composer: &cache-composer
cache:
key:
files: [composer.lock]
paths:
- .cache/composer
- vendor/
policy: pull
# ββ Build-fase ββββββββββββββββββββββββββββββββββββββββββββββββββ
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
# ββ Testfase βββββββββββββββββββββββββββββββββββββββββββββββββββ
phpunit:unit:
stage: test
<<: *cache-composer
script:
- ./vendor/bin/phpunit --testsuite=unit --log-junit=reports/phpunit.xml
artifacts:
reports:
junit: reports/phpunit.xml
# ββ Configvalidatiefase ββββββββββββββββββββββββββββββββββββββ
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
# ββ Deployfase βββββββββββββββββββββββββββββββββββββββββββββββββ
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 # Promotie met één klik, menselijke goedkeuring vereist
script:
- bash ci/drupal-deploy.sh production8.2 GitLab CI-voordelen
GitLab CI heeft verschillende functies die deze workflow bijzonder overzichtelijk maken:
- First-class artifacts β Mislukte config-exports worden automatisch opgeslagen en kunnen in de MR UI worden bekeken zonder extra configuratie.
- Native caching op basis van
composer.lockβ Composer-installaties zijn na de eerste run goedkoop. - Zichtbaarheid van Merge Request pipelines β Ontwikkelaars zien de configvalidatiestatus direct op de MR vΓ³Γ³r het mergen.
- Services-blok β MySQL wordt als sidecar opgestart zonder infrastructuuroverhead.
- Omgevingen + handmatige gates β De
when: manual-regel op de productiedeploy geeft je promotie met één klik en een menselijke controle, zonder een aparte goedkeuringsworkflow.
#!/bin/bash
set -euo pipefail
TARGET_ENV="${1:-staging}"
echo "=== Deployen naar: ${TARGET_ENV} ==="
# Synchroniseer build-artifact naar doel (rsync, SSH, S3 β pas aan naar je host)
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 "=== Post-deploycommando's uitvoeren op ${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
# Laatste sanity check op de doelomgeving zelf
git diff --exit-code config/sync || (echo "β Config drift op doelomgeving!"; exit 1)
drush cr
echo "β
Deploy voltooid."
REMOTE9. Omgevingsspecifieke configuratie zonder valsspelen
Niet elke configuratiewaarde moet in alle omgevingen hetzelfde zijn. Zoekbackends, logging-niveaus, cachinglagen en API-endpoints van derden verschillen terecht. De vraag is hoe je dat verschil behandelt zonder de integriteit van je config-pipeline in gevaar te brengen.
De juiste tools: config_split en config_ignore
config_split stelt je in staat om configuratiesets te definiΓ«ren die enkel actief zijn in specifieke omgevingen. Elke split bevindt zich in een eigen directory en wordt geactiveerd via settings.php of settings.env.php op basis van een omgevingsvariabele.
langcode: en
status: true
id: development
label: Development
description: 'Config alleen actief in lokale/dev-omgevingen'
folder: '../config/splits/development'
module:
devel: 0
kint: 0
dblog: 0
theme: { }
blacklist: { }
graylist: { }$app_env = getenv('APP_ENV') ?: 'production';
// Activeer de juiste config_split op basis van de omgeving.
$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');Praktische voorbeelden van splits
| Config-item | Dev split | Stage split | Prod split |
|---|---|---|---|
| Search API-backend | Database-backend | Solr (klein) | Solr (prod-cluster) |
| Foutlogging | dblog, uitgebreid | syslog | syslog + externe APM |
| Performance-modules | Uitgeschakeld | Ingeschakeld | Ingeschakeld + CDN-config |
| Mailtransport | Mailhog / null mailer | Mailpit | SMTP / SendGrid |
Onjuiste benaderingen β doe dit niet:
- Configuratie rechtstreeks bewerken in de productiedatabase.
- Gebruik maken van
if ($settings['environment'] === 'prod')-voorwaarden insettings.phpom config-waarden te vervangen β dit omzeilt het configsysteem volledig en creΓ«ert onzichtbare runtime-afwijkingen. - Aparte Git-branches per omgeving bijhouden met verschillende configbestanden.
10. Promotie tussen omgevingen
Promotie is geen deployment. Deployment verplaatst code en config naar een omgeving. Promotie verplaatst een gevalideerd artefact β iets dat CI al heeft doorstaan β naar de volgende omgeving in de keten.
In de praktijk betekent dit dat dezelfde Git SHA die op dev is gevalideerd, ook de productieomgeving bereikt. Geen last-minute commits. Geen hotfixes die CI overslaan. Geen cherry-picks die de configcontrole omzeilen.
op feature branch / main
auto-deploy bij merge
auto-deploy vanuit develop
handmatige gate op main
Hetzelfde gevalideerde artefact gaat door de omgevingen. CI draait één keer; het resultaat verspreidt zich.
De filosofie van immutabele builds betekent: wat CI valideert, is wat wordt gedeployed. Als een hotfix echt noodzakelijk is, gaat deze via een fast-track branch met een eigen CI-run β validatie wordt niet omzeild.
main-branch met een verplichte pipeline-statuscheck. GitLab's "protected branches" en Jenkins' "GitHub Branch Source"-plugin ondersteunen dit beide. Als de pipeline niet is geslaagd, kan de branch niet naar productie worden gedeployed.11. Omgaan met randgevallen (je gaat deze tegenkomen)
UUID-mismatches
Config-entiteiten bevatten UUIDβs. Als je een nieuwe site installeert en vervolgens configuratie probeert te importeren vanuit een andere installatie, zal Drupal weigeren met een UUID-mismatchfout. De oplossing is om altijd te installeren met --existing-config of om na installatie de site-UUID in te stellen:
# Lees de UUID uit de gecommitte configuratie en pas deze toe op de nieuwe installatie:
SITE_UUID=$(grep "^uuid:" config/sync/system.site.yml | awk '{print $2}')
drush config-set "system.site" uuid "$SITE_UUID" --yes
drush cim --yesModule in-/uitschakelvolgorde
Bij het inschakelen van een nieuwe module via configuratie respecteert Drupal automatisch de afhankelijkheidsvolgorde tijdens drush cim. Als de install hook van een module echter standaardconfiguratie genereert die conflicteert met wat zich in config/sync bevindt, moet je mogelijk die standaardconfiguratie verwijderen en opnieuw importeren. De pipeline zal dit als drift detecteren.
Contentafhankelijke configuratie
Sommige configuratie verwijst naar content β bijvoorbeeld een block dat verwijst naar een menu-item op basis van ID, of een view die filtert op een taxonomieterm. Deze verwijzingen kunnen per omgeving verschillen als content anders is aangemaakt. De oplossing is om die content te beheren via migraties of default content-modules, niet via config.
Multisite-eigenaardigheden
In een multisite-opzet heeft elke site zijn eigen config sync-directory. De CI-pipeline moet de configcheck uitvoeren voor elke site, niet alleen voor de primaire. Gebruik een lus:
#!/bin/bash
set -euo pipefail
for SITE_DIR in web/sites/*/; do
SITE=$(basename "$SITE_DIR")
[[ "$SITE" == "default" ]] && continue
[[ "$SITE" == "simpletest" ]] && continue
echo "--- Config controleren voor site: $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 "β Config drift in site: $SITE"
exit 1
fi
done
echo "β
Alle sites zijn schoon."Drupal Core upgraden met configuratiewijzigingen
Core-updates bevatten soms schemawijzigingen die bestaande configuratie beΓ―nvloeden. Voer altijd drush updb uit vΓ³Γ³r drush cim, en daarna drush cex om eventuele door schema genormaliseerde wijzigingen vast te leggen. Commit deze wijzigingen als onderdeel van de core-upgrade-branch β laat je er niet door verrassen in de pipeline.
12. Operationele vangnetten
Read-only config in productie
Overweeg om de https://www.drupal.org/project/config_readonly Config Readonly-module in productie in te schakelen. Dit voorkomt configuratiewijzigingen via de admin UI β een harde blokkade die de regel "geen handmatige wijzigingen" op applicatieniveau afdwingt.
if (getenv('APP_ENV') === 'production') {
$settings['config_readonly'] = TRUE;
}Verificatie na deployment
# Controleer of er geen openstaande config-wijzigingen zijn na deployment:
drush config-status 2>&1 | grep -v 'No differences' && { \
echo "β Onverwachte configverschillen na deployment"; exit 1; \
} || echo "β
Configstatus schoon"
# Controleer of caches opgewarmd zijn:
drush cr
drush php-eval "echo \Drupal::state()->get('system.cron_last');"Rollback-strategie
Rollback is een Git-operatie, geen database-operatie. Als een deployment een probleem veroorzaakt:
- Identificeer de laatste goede commit SHA.
- Start een pipeline-run op die SHA.
- De pipeline deployt opnieuw het eerder gevalideerde artefact.
Handmatige database-ingrepen om een config-import ongedaan te maken zijn nooit de oplossing β het omzeilt alle veiligheidscontroles en creΓ«ert doorgaans meer drift dan het oplost.
Auditing: Wie heeft config gewijzigd, wanneer en waarom
Omdat elke configwijziging via Git gaat, is je audittrail je Git-geschiedenis. Gebruik betekenisvolle commitberichten en dwing dit af met een commit-msg hook of een CI-lintstap:
$ git log --oneline config/sync/views.view.articles.yml
a3f8c12 feat(views): add taxonomy filter to articles view (PROJ-421)
9e1b307 fix(views): remove exposed sort causing query timeout (PROJ-389)
4d22a91 chore(config): export after Search API Solr 4.3.0 upgrade13. Veelgemaakte fouten die we niet meer maken
| Fout | Waarom dit problematisch is | De oplossing |
|---|---|---|
Handmatig drush cim uitvoeren op staging of productie | Importeert een onbekende status β mogelijk inclusief niet-gecommitteerde lokale wijzigingen | Alleen de pipeline importeert configuratie op gedeelde omgevingen |
| UI-wijzigingen toestaan op staging | Staging wordt een bron van waarheid en veroorzaakt afwijkingen ten opzichte van Git | Schakel config_readonly in op alle gedeelde omgevingen |
| Vertrouwen dat ontwikkelaars "zullen onthouden om te exporteren" | Dat doen ze niet, onder druk | Pre-commit hooks waarschuwen; CI-fouten dwingen naleving af |
| Niet falen bij config drift | Stilzwijgende afwijkingen stapelen zich op tot iets spectaculair faalt | De drush cim β cex β git diff --exit-code-stappenreeks is verplicht |
| Aparte branches per omgeving met verschillende configuratie | Mergen wordt een conflictΒnachtmerrie; geen enkele bron van waarheid | EΓ©n configuratieΒbranch, gesplitst via config_split |
drush updb overslaan vΓ³Γ³r drush cim | Schema-updates kunnen importfouten of stil dataverlies veroorzaken | Altijd: updb β cim β cex β diff |
14. Resultaten na toepassing van deze aanpak
Na het uitrollen van deze aanpak over een portfolio van Drupal-sites β variΓ«rend van één kleine website tot een bureau dat tientallen installaties beheert β bleken de resultaten consistent:
drush cim-runs op productie in 18 maandenNaast de cijfers is vooral de culturele verandering van belang. Config-discussies verleggen zich van Slack naar Git-geschiedenis. In plaats van "heeft iemand de image styles op staging aangepast?", wordt de vraag "is daar een commit voor?" β en die is er altijd, anders heeft de wijziging niet plaatsgevonden.
Nieuwe ontwikkelaars hoeven niet langer de ongeschreven regels te leren over "welke omgeving op dit moment leidend is." Het antwoord is altijd Git. De pipeline handhaaft dit.
15. Eindaanbevelingen
- Begin strikt en versoepel alleen met goede redenen. Het is eenvoudiger om een regel te versoepelen die overbodig blijkt dan om er een in te voeren die er nooit was.
- Behandel elke config-diff als een buildfout. Geen uitzonderingen, geen "we lossen het na de release op."
- CI is de bron van waarheid voor wat wordt gedeployed β niet de laptop van een ontwikkelaar, niet een staging-omgeving, niet een Slack-beslissing.
- De
updb β cim β cex β diff-stappenreeks is onaantastbaar. Als je een stap overslaat, werk je blind. - Gebruik
config_splitvoor echte omgevingsverschillen. Gebruiksettings.php-voorwaarden niet als snelkoppeling. - Schakel config_readonly in op alle gedeelde omgevingen. Maak onbedoelde UI-wijzigingen onmogelijk, niet alleen ongewenst.
- Sla mislukte config-exports op als CI-artifacts. Ontwikkelaars moeten exact kunnen zien wat is afgeweken, niet alleen dat er een diff bestond.
- Rollback via Git, nooit via database-ingrepen. De pipeline is in beide richtingen de veiligste route.
Zowel Jenkins als GitLab CI zijn uitstekend geschikt om deze discipline af te dwingen. GitLab CI vereist minder infrastructuur en biedt betere native ondersteuning voor artifacts; Jenkins biedt meer flexibiliteit voor complexe enterprise-omgevingen en uitstekende shared library-ondersteuning om pipelines te standaardiseren over meerdere projecten heen. Kies de tool die het beste bij je organisatie past β de principes in dit artikel blijven hetzelfde.
Het doel was nooit om configuratiebeheer complex te maken. Het doel is om het saai te maken: een routinematige stap die altijd werkt, waar niemand over hoeft na te denken en die nooit een incident om 23:00 uur veroorzaakt. Met een correct ingerichte CI-pipeline is saai precies wat je krijgt.
Ivan Abramenko, Principal Drupal Architect
ivan.abramenko@drupalbook.org
projects@drupalbook.org