Drupal CI‑vođeno upravljanje konfiguracijom korišćenjem Jenkins‑a i GitLab CI‑ja
1. Zašto je CI‑vođeno upravljanje konfiguracijom važno
Drupalov sistem konfiguracije je jedna od najvećih prednosti platforme — i jedan od njenih najpouzdanijih izvora problema. Mogućnost izvoza i uvoza svakog dela konfiguracije sajta kao YAML fajlova je moćna, ali samo ako se svi slažu oko toga ko je odgovoran za prenos tih fajlova između okruženja. U većini timova, taj dogovor zapravo nikada ne postoji.
Klasični problemi su dobro poznati svakome ko je isporučio Drupal sajt:
- Odstupanje konfiguracije (config drift) — staging se razlikuje od produkcije, produkcija od lokalnog okruženja, i niko nije siguran koje okruženje je kanonsko.
- "Radi na stagingu ali ne i na produkciji" — jer je neko izmenio prikaz ili formatter polja na stagingu i nikada ga nije izvezao.
- Ručni
drush cimkoji kvari sadržaj — brzinski import u 23h koji je obrisao polje tipa sadržaja na koje se i dalje pozivaju aktivni čvorovi.
Osnovni uzrok svakog od ovih scenarija je isti: čovek odlučuje kada i da li će konfiguracija biti promovisana. Ljudi zaboravljaju. Preskaču korake pod pritiskom. Donose procene koje se kasnije pokažu kao pogrešne.
CI ne zaboravlja. Pipeline ili prolazi ili ne prolazi. Nema standup na koji mora da stigne. Ne zna da je izdanje za dvadeset minuta. Ta determinističnost je upravo ono što je potrebno upravljanju konfiguracijom.
Obećanja koja ovaj članak ispunjava:
- Svaka promena konfiguracije je komitovana u Git pre nego što dotakne bilo koje zajedničko okruženje.
- Pipeline, a ne developer, je odgovoran za validaciju i import konfiguracije.
- Promocija između okruženja ne zahteva nijedan ručni korak.
- Config drift je pad build‑a, a ne Slack poruka.
2. Osnovni principi koje smo naučili iz stvarnih projekata
Konfiguracija je kod
Ako menja ponašanje sajta, pripada u Git. Tačka. Prikaz, tip sadržaja, podešavanje performansi, stil slike — sve je to kod. Tretirajte config fajlove sa istom disciplinom kao PHP fajlove: pregledajte ih, verzionišite ih, nikada ih ne menjajte direktno u zajedničkom okruženju.
Nema ručnog drush cim u zajedničkim okruženjima
Pipeline uvozi konfiguraciju. Developeri ne. Ovo pravilo zvuči ekstremno dok prvi put ne doživite da neko pokrene drush cim na produkciji sa nekomitovanim lokalnim izmenama u svom radnom direktorijumu.
Pipeline mora brzo da padne pri config drift‑u
Import konfiguracije, a zatim njen trenutni export, ne bi trebalo da proizvede nikakav diff. Ako proizvede, build pada. Ovo jedno pravilo hvata više bagova nego bilo koja druga provera koju smo uveli.
core.extension.yml je bio isključen iz izvoza jer "module ionako upravlja Composer." Tri meseca kasnije, hotfix je ponovo omogućio modul koji je namerno bio onemogućen u produkciji. Hotfix je bio ispravan — ali je naredni config import tiho ponovo onemogućio modul. Zbog tog incidenta sada core.extension.yml nazivamo najopasnijim fajlom koji možete ignorisati.3. Arhitektura na visokom nivou
drush cex
config/sync
validacija + import
Konfiguracija teče samo u jednom smeru: od lokalnog export‑a developera, kroz Git, kroz CI pipeline i u svako okruženje.
Ključni uvid je razdvajanje odgovornosti:
| Akter | Odgovornost | Nikada nije odgovoran za |
|---|---|---|
| Developer | Export konfiguracije, commit u Git, otvaranje MR/PR | Import konfiguracije u bilo kom zajedničkom okruženju |
| CI pipeline | Validacija, import, verifikacija, promocija | Generisanje ili izmena config fajlova |
| Okruženje | Pokretanje sajta | Generisanje, čuvanje ili export konfiguracije |
Okruženja su potrošači konfiguracije, nikada proizvođači. Onog trenutka kada okruženje postane izvor istine za konfiguraciju, odstupanje postaje neizbežno.
4. Struktura repozitorijuma koja se skalira
project-root/
├── composer.json
├── composer.lock
├── Jenkinsfile
├── .gitlab-ci.yml
│
├── ci/
│ ├── drupal-config-check.sh # Skripta za višekratnu validaciju
│ ├── drupal-deploy.sh
│ └── drupal-install.sh
│
├── config/
│ ├── sync/ # Config se nalazi ovde — komitovan u Git
│ │ ├── core.extension.yml
│ │ ├── system.site.yml
│ │ └── ...
│ └── splits/ # config_split override‑i po okruženju
│ ├── development/
│ ├── staging/
│ └── production/
│
└── web/
├── sites/
│ └── default/
│ ├── settings.php # Komitovan, bez tajni
│ ├── settings.local.php # Ignorisan u Git‑u, lokalni override‑i
│ └── settings.env.php # Učitava CI iz env varijabli
└── ...Šta se nalazi u config/sync
Svi YAML fajlovi konfiguracije generisani pomoću drush cex. Ovaj direktorijum je jedini izvor istine za konfiguraciju sajta. Komituje se, pregleda i deploy‑uje kao i bilo koji drugi kod.
Šta nikada ne ide u config
- Hostname‑ovi specifični za okruženje, API ključevi ili akreditivi — koristite environment varijable i
settings.env.php. - Tajne bilo koje vrste — ubacite ih preko CI skladišta tajni (Jenkins Credentials ili GitLab CI Variables).
- Sadržaj — ne koristite Default Content modul kao prečicu za ono što bi trebalo da bude config.
// settings.php — komitovan u Git, nezavisan od okruženja
$settings['config_sync_directory'] = DRUPAL_ROOT . '/../config/sync';
// Učitaj vrednosti specifične za okruženje koje ubacuje CI ili host.
if (file_exists($app_root . '/' . $site_path . '/settings.env.php')) {
include $app_root . '/' . $site_path . '/settings.env.php';
}
// Učitaj opcione lokalne override‑e (gitignored).
if (file_exists($app_root . '/' . $site_path . '/settings.local.php')) {
include $app_root . '/' . $site_path . '/settings.local.php';
}// Generiše CI iz environment varijabli — nikada se ne komituje.
$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');
// Kaže config_split‑u u kom se okruženju nalazimo.
$config['config_split.config_split.production']['status'] =
(getenv('APP_ENV') === 'production');5. Drupal konfiguracioni workflow
5.1 Lokalni razvoj
Lokalni workflow je jedino mesto gde petlja povratne sprege između izmena u UI‑ju i export‑a konfiguracije mora biti brza i rutinska. Svaki developer na projektu prati ista pravila:
- Napravite UI ili kod izmene lokalno.
- Pokrenite
drush cexodmah — ne na kraju dana. - Pregledajte diff pomoću
git diff config/sync. - Ili komitujte izmene ili ih odbacite pomoću
git checkout -- config/sync. - Ne postoji međustanje.
# Nakon izmena konfiguracije putem Drupal UI‑ja ili install hook‑ova:
drush cex --yes
# Pregledajte šta je promenjeno — tretirajte ovo kao pregled bilo koje izmene koda:
git diff config/sync
# Stage‑ujte i komitujte zajedno sa feature kodom:
git add config/sync
git commit -m "feat(search): add fulltext search API index config"
# Ili odbacite ako je promena bila eksperimentalna i nije spremna:
git checkout -- config/syncOvo sprovodimo pomoću laganog Git pre‑commit hook‑a koji upozorava (ali ne blokira) kada su PHP ili template fajlovi stage‑ovani bez odgovarajuće izmene u config/sync:
#!/bin/bash
# Upozori ako je modul/tema kod promenjen, ali config/sync nije.
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 "⚠ Upozorenje: PHP/module fajlovi su stage‑ovani, ali nema promena u config/sync."
echo " Da li ste zaboravili da pokrenete: drush cex ?"
echo " Nastavljamo dalje — ali proverite još jednom."
fi
exit 05.2 Feature grane
Konfiguracija se nalazi uz kod koji je zahteva u istoj grani i u istom pull/merge request‑u. Feature koji dodaje novi tip sadržaja treba da uključi i PHP install hook (ako postoji) i YAML fajlove za taj tip sadržaja u istom commitu.
6. Odgovornosti CI‑ja: Šta pipeline mora da sprovede
Svaki pipeline koji dodiruje zajedničko Drupal okruženje mora redom da izvrši sledeće korake:
- Instalacija ili vraćanje baze — čista instalacija ili sanitizovani produkcioni snapshot.
- Pokretanje ažuriranja baze —
drush updb. - Import konfiguracije —
drush cim --yes. - Ponovni export konfiguracije —
drush cex --yes. - Provera da nema diff‑a — ako se bilo koji YAML fajl promenio, build pada.
Koraci 4 i 5 zajedno su najvažniji. Import, a zatim trenutni ponovni export, moraju proizvesti prazan diff. Ako ne:
- Modul generiše konfiguraciju pri import‑u (često bag u modulu).
- UUID config entiteta se ne poklapa sa onim u bazi.
- Config schema je nepotpuna, zbog čega Drupal normalizuje vrednosti drugačije nego prilikom export‑a.
- Developer je ručno menjao config fajlove i uneo nedoslednosti.
#!/bin/bash
set -euo pipefail
echo "=== Pokretanje Drupal ažuriranja baze ==="
drush updb --yes
echo "=== Import konfiguracije iz config/sync ==="
drush cim --yes
echo "=== Ponovni export radi verifikacije da nema čekajućih izmena ==="
drush cex --yes
echo "=== Provera config drift‑a ==="
if ! git diff --exit-code config/sync; then
echo ""
echo "❌ FAIL: Detektovano odstupanje konfiguracije!"
echo " Config u Git‑u se ne poklapa sa onim što je Drupal proizveo nakon import+export."
echo " Diff je prikazan iznad. Ispravite lokalno pomoću 'drush cex' i komitujte."
exit 1
fi
echo "✅ Konfiguracija je čista — nema odstupanja."7. Jenkins implementacija (proverena u praksi)
7.1 Struktura Jenkinsfile‑a
Koristimo isključivo deklarativne pipeline‑e. Scripted pipeline‑i nude veću fleksibilnost, ali su deklarativni pipeline‑i lakši za čitanje, lakši za lintovanje pomoću jenkins-cli declarative-linter, i lakši za razumevanje novim članovima tima.
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 tajna
}
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 {
// Generiše settings.env.php iz Jenkins kredencijala/env varijabli
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: "Validacija konfiguracije nije uspela. Pogledajte: ${BUILD_URL}",
recipientProviders: [[$class: 'DevelopersRecipientProvider']]
)
}
}
}7.2 Specifičnosti Jenkins‑a
Shared Libraries
Kada upravljate sa više od dva ili tri Drupal sajta, izdvojite logiku Drupal pipeline‑a u Jenkins Shared Library. Jenkinsfile svakog projekta tada postaje tanak omotač:
@Library('drupal-pipeline-lib@v2') _
drupalPipeline(
phpVersion: '8.2',
deployBranch: 'main',
dbCredentials: 'drupal-ci-db',
slackChannel: '#deployments'
)Čišćenje radnog prostora
Uvek pozovite cleanWs() u post { always } bloku. Jenkins agenti akumuliraju stanje iz prethodnih build‑ova — settings.env.php ili vendor direktorijum iz prethodnog build‑a mogu tiho uticati na trenutni build. Budite nemilosrdni: očistite radni prostor.
8. GitLab CI implementacija
8.1 Struktura .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 Prednosti GitLab CI‑ja
GitLab CI ima nekoliko karakteristika koje ga čine posebno pogodnim za ovaj workflow:
- First‑class artifacts — neuspešni config export‑i se automatski čuvaju i dostupni su za pregled u MR UI‑ju bez dodatne konfiguracije.
- Nativni cache vezan za
composer.lock— Composer instalacije su jeftine nakon prvog pokretanja. - Vidljivost Merge Request pipeline‑a — developeri vide status validacije konfiguracije direktno na MR‑u pre merge‑a.
- Services blok — MySQL se pokreće kao sidecar bez dodatnog infrastrukturnog opterećenja.
- Okruženja + manuelne kapije — pravilo
when: manualna production deploy‑u omogućava promociju jednim klikom uz ljudsku proveru, bez potrebe za posebnim approval workflow‑om.
#!/bin/bash
set -euo pipefail
TARGET_ENV="${1:-staging}"
echo "=== Deploy na: ${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 "=== Pokretanje post-deploy komandi na ${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 "❌ Config drift na targetu!"; exit 1)
drush cr
echo "✅ Deploy završen."
REMOTE9. Konfiguracija specifična za okruženje bez varanja
Nije svaka konfiguraciona vrednost ista u svim okruženjima. Search backend‑i, nivo logovanja, caching slojevi i endpoint‑i za spoljne API‑je se legitimno razlikuju. Pitanje je kako upravljati tim razlikama bez kompromitovanja integriteta vašeg config pipeline‑a.
Pravi alati: config_split i config_ignore
config_split omogućava definisanje skupova konfiguracije koji su aktivni samo u određenim okruženjima. Svaki split se nalazi u sopstvenom direktorijumu i aktivira se putem settings.php ili settings.env.php na osnovu environment varijable.
langcode: en
status: true
id: development
label: Development
description: 'Config aktivan samo u lokalnim/dev okruženjima'
folder: '../config/splits/development'
module:
devel: 0
kint: 0
dblog: 0
theme: { }
blacklist: { }
graylist: { }$app_env = getenv('APP_ENV') ?: 'production';
// Aktiviraj ispravan config_split na osnovu okruženja.
$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');Primeri split‑ova iz prakse
| Config stavka | Dev split | Stage split | Prod split |
|---|---|---|---|
| Search API backend | Database backend | Solr (mali) | Solr (prod klaster) |
| Logovanje grešaka | dblog, verbose | syslog | syslog + eksterni APM |
| Performance moduli | Onemogućeni | Omogućeni | Omogućeni + CDN konfiguracija |
| Mail transport | Mailhog / null mailer | Mailpit | SMTP / SendGrid |
Neispravni pristupi — nemojte raditi sledeće:
- Direktno menjanje konfiguracije u produkcionoj bazi.
- Korišćenje
if ($settings['environment'] === 'prod')uslova unutarsettings.phpza zamenu config vrednosti — ovo potpuno zaobilazi config sistem i stvara nevidljivo runtime odstupanje. - Održavanje posebnih Git grana po okruženju sa različitim config fajlovima.
10. Promocija između okruženja
Promocija nije deployment. Deployment premešta kod i konfiguraciju u okruženje. Promocija premešta validirani artefakt — nešto što je već prošlo CI — u sledeće okruženje u lancu.
U praksi, to znači da isti Git SHA koji je validiran na dev‑u stiže i do produkcije. Nema commit‑a u poslednjem trenutku. Nema hotfix‑eva koji preskaču CI. Nema cherry‑pick‑ova koji zaobilaze config proveru.
na feature grani / main
auto‑deploy nakon merge‑a
auto‑deploy iz develop
manuelna kapija na main
Isti validirani artefakt prolazi kroz okruženja. CI se izvršava jednom; rezultat se propagira.
Filozofija nepromenljivih build‑ova znači: ono što je CI validirao je ono što se deploy‑uje. Ako je hotfix zaista neophodan, prolazi kroz fast‑track granu sa sopstvenim CI pokretanjem — ne zaobilazi validaciju.
main granu obaveznom proverom statusa pipeline‑a. GitLab "protected branches" i Jenkins "GitHub Branch Source" plugin to podržavaju. Ako pipeline nije prošao, grana ne može biti deploy‑ovana na produkciju.11. Rukovanje specifičnim slučajevima (naići ćete na ovo)
UUID nepodudaranja
Config entiteti imaju UUID‑eve. Ako instalirate svež sajt i zatim pokušate import konfiguracije iz druge instalacije, Drupal će odbiti zbog UUID nepodudaranja. Rešenje je da uvek instalirate sa --existing-config ili da postavite UUID sajta nakon instalacije:
# Pročitaj UUID iz komitovanog config‑a i primeni ga na novu instalaciju:
SITE_UUID=$(grep "^uuid:" config/sync/system.site.yml | awk '{print $2}')
drush config-set "system.site" uuid "$SITE_UUID" --yes
drush cim --yesRedosled omogućavanja/onemogućavanja modula
Kada omogućavate novi modul putem config‑a, Drupal automatski poštuje redosled zavisnosti tokom drush cim. Međutim, ako install hook modula generiše podrazumevanu konfiguraciju koja je u konfliktu sa onim u config/sync, možda ćete morati da obrišete tu podrazumevanu konfiguraciju i ponovo je importujete. Pipeline će ovo detektovati kao drift.
Konfiguracija zavisna od sadržaja
Neka konfiguracija referencira sadržaj — na primer blok koji referencira stavku menija po ID‑ju, ili view koji filtrira po taksonomskom terminu. Ove reference se mogu razlikovati između okruženja ako je sadržaj kreiran drugačije. Rešenje je upravljanje tim sadržajem putem migracija ili default content modula, a ne kroz config.
Multisite specifičnosti
U multisite postavci, svaki sajt ima sopstveni config sync direktorijum. CI pipeline mora pokrenuti config proveru za svaki sajt, ne samo za primarni. Koristite petlju:
#!/bin/bash
set -euo pipefail
for SITE_DIR in web/sites/*/; do
SITE=$(basename "$SITE_DIR")
[[ "$SITE" == "default" ]] && continue
[[ "$SITE" == "simpletest" ]] && continue
echo "--- Provera config‑a za sajt: $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 na sajtu: $SITE"
exit 1
fi
done
echo "✅ Svi sajtovi su čisti."Nadogradnja Drupal Core‑a uz izmene konfiguracije
Core ažuriranja ponekad dolaze sa izmenama u schema‑i koje utiču na postojeći config. Uvek pokrenite drush updb pre drush cim, i pokrenite drush cex nakon toga kako biste zabeležili sve schema‑normalizovane izmene. Komitujte te izmene kao deo grane za core nadogradnju — nemojte da vas iznenade u pipeline‑u.
12. Operativne sigurnosne mreže
Config Read‑Only u produkciji
Razmotrite omogućavanje Config Readonly modula u produkciji. On sprečava svaku izmenu konfiguracije putem admin UI‑ja — čvrsta blokada koja na nivou aplikacije pojačava pravilo "nema ručnih izmena".
if (getenv('APP_ENV') === 'production') {
$settings['config_readonly'] = TRUE;
}Post‑deploy verifikacija
# Proveri da nema zaostalih config izmena nakon deploy‑a:
drush config-status 2>&1 | grep -v 'No differences' && { \
echo "❌ Neočekivane config razlike nakon deploy‑a"; exit 1; \
} || echo "✅ Config status je čist"
# Proveri da su cache‑evi zagrejani:
drush cr
drush php-eval "echo \Drupal::state()->get('system.cron_last');"Rollback strategija
Rollback je Git operacija, ne operacija nad bazom. Ako deployment izazove problem:
- Identifikujte poslednji ispravan commit SHA.
- Pokrenite pipeline za taj SHA.
- Pipeline ponovo deploy‑uje prethodno validirani artefakt.
Ručna operacija nad bazom da bi se poništio config import nikada nije rešenje — zaobilazi sve sigurnosne provere i obično pravi više odstupanja nego što ih rešava.
Audit: Ko je menjao config, kada i zašto
Pošto svaka izmena konfiguracije ide kroz Git, vaš audit trag je Git istorija. Koristite smislene commit poruke i sprovedite ih pomoću commit‑msg hook‑a ili CI lint koraka:
$ 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. Uobičajene greške koje više ne pravimo
| Greška | Zašto boli | Rešenje |
|---|---|---|
Ručno pokretanje drush cim na staging‑u ili produkciji | Importuje nepoznato stanje — moguće uključujući nekomitovane lokalne izmene | Samo pipeline importuje config u zajedničkim okruženjima |
| Dozvoljavanje UI izmena na staging‑u | Staging postaje izvor istine, stvarajući odstupanje od Git‑a | Omogućite config_readonly u svim zajedničkim okruženjima |
| Oslanjanje na developere da se "sete da export‑uju" | Neće, pod pritiskom | Pre‑commit hook‑ovi upozoravaju; CI failure nameće |
| Ignorisanje config drift‑a | Tiho odstupanje se akumulira dok se nešto spektakularno ne pokvari | Trojka drush cim → cex → git diff --exit-code je obavezna |
| Posebne grane po okruženju sa različitim config‑om | Merge postaje noćna mora konflikata; nema jedinstvenog izvora istine | Jedna config grana, podeljena pomoću config_split |
Preskakanje drush updb pre drush cim | Schema ažuriranja mogu uzrokovati import greške ili tihi gubitak podataka | Uvek: updb → cim → cex → diff |
14. Rezultati nakon primene ovog pristupa
Nakon implementacije ovog pristupa kroz portfolio Drupal sajtova — od jednog malog sajta do agencije koja upravlja desetinama instalacija — rezultati su bili dosledni:
drush cim pokretanja na produkciji tokom 18 meseciOsim brojki, kulturna promena je najvažnija. Diskusije o konfiguraciji prelaze sa Slack‑a u Git istoriju. Umesto "da li je neko menjao stilove slika na staging‑u?", pitanje postaje "da li za to postoji commit?" — i uvek postoji, ili se izmena nije desila.
Novi developeri više ne moraju da uče nepisana pravila o tome "koje okruženje je trenutno kanonsko." Odgovor je uvek Git. Pipeline to sprovodi.
15. Završne preporuke
- Počnite striktno, opuštajte samo sa razlogom. Lakše je ublažiti pravilo koje se pokaže kao nepotrebno nego pooštriti ono koje nikada nije postojalo.
- Tretirajte svaki config diff kao pad build‑a. Bez izuzetaka, bez "popravićemo nakon release‑a."
- CI je izvor istine za ono što se deploy‑uje — ne laptop developera, ne staging okruženje, ne Slack odluka.
- Sekvenca
updb → cim → cex → diffje nepovrediva. Ako preskočite neki korak, letite naslepo. - Koristite
config_splitza stvarne razlike među okruženjima. Ne koristite uslove usettings.phpkao prečicu. - Omogućite config_readonly u svim zajedničkim okruženjima. Učinite slučajne UI izmene nemogućim, a ne samo nepoželjnim.
- Arhivirajte neuspele config export‑e kao CI artefakte. Developeri moraju videti tačno šta je odstupilo, ne samo da diff postoji.
- Rollback radite putem Git‑a, nikada putem operacija nad bazom. Pipeline je najsigurniji put u oba smera.
I Jenkins i GitLab CI su jednako sposobni da sprovedu ovu disciplinu. GitLab CI zahteva manje infrastrukture i ima bolju nativnu podršku za artefakte; Jenkins nudi veću fleksibilnost za kompleksna enterprise okruženja i odličnu podršku za shared library‑je za standardizaciju pipeline‑a kroz mnoge projekte. Izaberite alat koji odgovara vašoj organizaciji — principi iz ovog članka važe bez obzira na izbor.
Cilj nikada nije bio da upravljanje konfiguracijom učinimo komplikovanim. Cilj je da ga učinimo dosadnim: rutinskim korakom koji uvek radi, o kome niko ne mora da razmišlja, koji nikada ne izaziva incident u 23h. Sa pravilno implementiranim CI pipeline‑om, upravo to dobijate.
Ivan Abramenko, Principal Drupal Architect
ivan.abramenko@drupalbook.org
projects@drupalbook.org