Jenkins と GitLab CI を用いた CI 主導の Drupal 設定管理
1. なぜ CI 主導の設定管理が重要なのか
Drupal の設定システムは、このプラットフォームの最大の強みの1つであり、同時に最も確実な痛みの源の1つです。サイト設定のあらゆる要素を YAML ファイルとしてエクスポート・インポートできる能力は強力です。しかしそれは、それらのファイルを環境間で移動させる責任者が誰なのかについて全員が合意している場合に限ります。ほとんどのチームでは、その合意は決してきちんと存在しません。
古典的な問題は、Drupal サイトをリリースしたことのある人なら誰でもよく知っています。
- 設定のドリフト — ステージングが本番と乖離し、本番がローカルと乖離し、どの環境が正典なのか誰も確信が持てなくなる。
- 「ステージングでは動くのに本番では動かない」 — 誰かがステージングでビューやフィールドフォーマッターを更新し、それをエクスポートしなかったから。
- 手動の
drush cimがコンテンツを壊す — 夜23時の慌ただしいインポートが、稼働中のノードから今なお参照されているコンテンツタイプのフィールドを削除してしまう。
これらのシナリオすべての根本原因は同じです。人間が、設定をいつ、そして反映するかどうかを決めているのです。人間は忘れます。プレッシャーの下で手順を飛ばします。結果的に誤りだった判断を下します。
CI は忘れません。 パイプラインは通るか、落ちるかのどちらかです。出席すべきスタンドアップもありません。リリースが20分後だということも知りません。その決定性こそ、設定管理が必要とするものです。
この記事が果たす約束はこうです。
- あらゆる設定変更は、共有環境に触れる前に Git にコミットされる。
- 設定の検証とインポートの責任は、開発者ではなくパイプラインにある。
- 環境間の反映に、手動の手順は一切不要。
- 設定のドリフトは、Slack のメッセージではなくビルドの失敗である。
2. 実際のプロジェクトから学んだ基本原則
設定はコードである
サイトの挙動を変えるものなら、それは Git に属します。以上。ビュー、コンテンツタイプ、パフォーマンス設定、画像スタイル — すべてコードです。設定ファイルを PHP ファイルと同じ規律で扱ってください。レビューし、バージョン管理し、共有環境で直接編集してはいけません。
共有環境で手動の drush cim をしない
設定をインポートするのはパイプラインです。開発者ではありません。このルールは、作業ディレクトリに未コミットのローカル変更を残したまま誰かが本番で drush cim を実行する瞬間を初めて経験するまでは、極端に聞こえます。
パイプラインは設定ドリフトで即座に失敗しなければならない
設定をインポートし、その直後にエクスポートしても、差分が生じてはなりません。差分が生じたら、ビルドは失敗します。この1つのルールは、私たちが追加した他のどのチェックよりも多くのバグを捕まえます。
core.extension.yml がエクスポートから除外されていました。「どうせモジュールは Composer で管理されているから」という理由です。3か月後、あるホットフィックスが、本番で意図的に無効化されていたモジュールを再び有効化しました。ホットフィックスは正しかったのですが、その後の設定インポートが、静かにそのモジュールを再び無効化しました。このインシデントこそ、私たちが今 core.extension.yml を「無視できる最も危険なファイル」と呼ぶ理由です。3. 高レベルのアーキテクチャ
drush cex
config/sync
検証 + インポート
設定は一方向にのみ流れます。開発者のローカルエクスポートから、Git を通り、CI パイプラインを通り、各環境へ。
鍵となる洞察は、関心の分離です。
| アクター | 責任 | 決して責任を負わないこと |
|---|---|---|
| 開発者 | 設定のエクスポート、Git へのコミット、MR/PR の作成 | いかなる共有環境での設定のインポート |
| CI パイプライン | 検証、インポート、確認、反映 | 設定ファイルの生成や編集 |
| 環境 | サイトの稼働 | 設定の生成、保存、エクスポート |
環境は設定の消費者であり、決して生産者ではありません。環境が設定の信頼できる情報源になった瞬間、ドリフトは避けられなくなります。
4. スケールするリポジトリ構造
project-root/
├── composer.json
├── composer.lock
├── Jenkinsfile
├── .gitlab-ci.yml
│
├── ci/
│ ├── drupal-config-check.sh # 再利用可能な検証スクリプト
│ ├── drupal-deploy.sh
│ └── drupal-install.sh
│
├── config/
│ ├── sync/ # 設定はここに置かれる — Git にコミット
│ │ ├── core.extension.yml
│ │ ├── system.site.yml
│ │ └── ...
│ └── splits/ # 環境ごとの config_split の上書き
│ ├── development/
│ ├── staging/
│ └── production/
│
└── web/
├── sites/
│ └── default/
│ ├── settings.php # コミットする、秘密情報なし
│ ├── settings.local.php # Git 無視、ローカルの上書き
│ └── settings.env.php # CI が環境変数から読み込む
└── ...config/sync に置かれるもの
drush cex で生成されたすべての設定 YAML ファイル。このディレクトリは、サイト設定の唯一の信頼できる情報源です。他のコードと同じように、コミットされ、レビューされ、デプロイされます。
設定に決して入れてはいけないもの
- 環境固有のホスト名、API キー、認証情報 — 環境変数と
settings.env.phpを使う。 - あらゆる種類の秘密情報 — CI の秘密情報ストア(Jenkins Credentials または GitLab CI Variables)を通じて注入する。
- コンテンツ — 設定であるべきものの間に合わせとして Default Content モジュールを使わない。
// settings.php — Git にコミット、環境非依存
$settings['config_sync_directory'] = DRUPAL_ROOT . '/../config/sync';
// CI やホストが注入する環境固有の値を読み込む。
if (file_exists($app_root . '/' . $site_path . '/settings.env.php')) {
include $app_root . '/' . $site_path . '/settings.env.php';
}
// 任意のローカル上書きを読み込む(Git 無視)。
if (file_exists($app_root . '/' . $site_path . '/settings.local.php')) {
include $app_root . '/' . $site_path . '/settings.local.php';
}// CI が環境変数から生成する — 決してコミットしない。
$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');
// config_split に、どの環境にいるかを伝える。
$config['config_split.config_split.production']['status'] =
(getenv('APP_ENV') === 'production');5. Drupal の設定ワークフロー
5.1 ローカル開発
ローカルワークフローは、UI 変更と設定エクスポートの間のフィードバックループが、速く、習慣的である必要がある唯一の場所です。プロジェクトのすべての開発者が、同じルールに従います。
- UI やコードの変更をローカルで行う。
drush cexをすぐに実行する — 一日の終わりにではなく。git diff config/syncで差分をレビューする。- 変更をコミットするか、
git checkout -- config/syncで破棄する。 - 中間の状態は存在しない。
# Drupal の UI または install hook で設定を変更した後:
drush cex --yes
# 何が変わったかレビューする — コード変更のレビューと同じように扱う:
git diff config/sync
# フィーチャーコードと一緒にステージングしてコミットする:
git add config/sync
git commit -m "feat(search): add fulltext search API index config"
# または、変更が試験的でまだ準備できていない場合は破棄する:
git checkout -- config/sync私たちはこれを、PHP やテンプレートファイルが config/sync の対応する変更なしにステージングされたときに警告する(ブロックはしない)軽量な Git pre-commit フックで補強しています。
#!/bin/bash
# モジュール/テーマのコードが変わったのに config/sync が変わっていなければ警告する。
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 "⚠ Warning: PHP/module files staged but no config/sync changes detected."
echo " Did you forget to run: drush cex ?"
echo " Proceeding anyway — but double-check."
fi
exit 05.2 フィーチャーブランチ
設定は、それを必要とするコードと同じブランチ、同じ pull/merge request の中に一緒に存在します。新しいコンテンツタイプを追加するフィーチャーは、PHP install hook(あれば)と、そのコンテンツタイプの YAML ファイルの両方を同じコミットに含めるべきです。
6. CI の責任:パイプラインが強制しなければならないこと
共有 Drupal 環境に触れるすべてのパイプラインは、次の手順を順番に実行しなければなりません。
- データベースをインストールまたは復元する — クリーンインストール、または匿名化された本番スナップショット。
- データベース更新を実行する —
drush updb。 - 設定をインポートする —
drush cim --yes。 - 設定を再エクスポートする —
drush cex --yes。 - 差分がないことを保証する — いずれかの YAML ファイルが変わったら、ビルドを失敗させる。
手順4と5が合わせて最も重要です。インポートし、その直後に再エクスポートしても、空の差分でなければなりません。そうでなければ、次のいずれかが真です。
- モジュールがインポート時に設定を生成している(しばしばモジュールのバグ)。
- 設定エンティティの UUID が、データベース内のものと一致しない。
- 設定スキーマが不完全で、Drupal がエクスポート時とは異なる形で値を正規化している。
- 開発者が設定ファイルを手作業で編集し、不整合を持ち込んだ。
#!/bin/bash
set -euo pipefail
echo "=== Running Drupal database updates ==="
drush updb --yes
echo "=== Importing configuration from config/sync ==="
drush cim --yes
echo "=== Re-exporting to verify no pending changes ==="
drush cex --yes
echo "=== Checking for config drift ==="
if ! git diff --exit-code config/sync; then
echo ""
echo "❌ FAIL: Configuration drift detected!"
echo " The config in Git does not match what Drupal produced after import+export."
echo " Diff shown above. Fix locally with 'drush cex' and commit."
exit 1
fi
echo "✅ Configuration is clean — no drift detected."7. Jenkins での実装(実戦で鍛えられたもの)
7.1 Jenkinsfile の構造
私たちは declarative pipeline のみを使います。scripted pipeline はより柔軟ですが、declarative pipeline は読みやすく、jenkins-cli declarative-linter で検証しやすく、新しいチームメンバーにとって理解しやすいです。
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 の秘密情報
}
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 {
// Jenkins の credentials/環境変数から settings.env.php を生成
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: "Config validation failed. See: ${BUILD_URL}",
recipientProviders: [[$class: 'DevelopersRecipientProvider']]
)
}
}
}7.2 Jenkins 固有の考慮事項
Shared Libraries
2〜3 以上の Drupal サイトを管理する場合は、Drupal パイプラインのロジックを Jenkins Shared Library に切り出します。すると各プロジェクトの Jenkinsfile は薄いラッパーになります。
@Library('drupal-pipeline-lib@v2') _
drupalPipeline(
phpVersion: '8.2',
deployBranch: 'main',
dbCredentials: 'drupal-ci-db',
slackChannel: '#deployments'
)ワークスペースのクリーンアップ
post { always } ブロックで必ず cleanWs() を呼び出してください。Jenkins のエージェントは以前のビルドの状態を蓄積します。前のビルドの settings.env.php や vendor ディレクトリが、現在のビルドに静かに影響を与えることがあります。容赦なく、ワークスペースをきれいにしてください。
8. GitLab CI での実装
8.1 .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 GitLab CI の利点
GitLab CI には、このワークフローを特にクリーンにする機能がいくつかあります。
- 一級のアーティファクト — 失敗した設定エクスポートが自動的に保存され、追加設定なしで MR の UI から閲覧できます。
composer.lockをキーにしたネイティブキャッシュ — 初回以降の Composer インストールが安価になります。- Merge Request でのパイプラインの可視化 — 開発者は、マージ前に MR で直接、設定検証のステータスを確認できます。
- Services ブロック — MySQL が、インフラのオーバーヘッドなしにサイドカーとして起動します。
- Environments + 手動ゲート — 本番デプロイの
when: manualルールが、別個の承認ワークフローを必要とせずに、人によるチェックポイント付きのワンクリック反映を提供します。
#!/bin/bash
set -euo pipefail
TARGET_ENV="${1:-staging}"
echo "=== Deploying to: ${TARGET_ENV} ==="
# ビルド済みアーティファクトを対象環境へ同期(rsync、SSH、S3 — ホストに合わせて調整)
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 "=== Running post-deploy commands on ${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 on target!"; exit 1)
drush cr
echo "✅ Deploy complete."
REMOTE9. ごまかさない、環境固有の設定
すべての設定値が全環境で同じであるべきではありません。検索バックエンド、ログの詳細度、キャッシュ層、サードパーティの API エンドポイントは、いずれも正当に異なります。問題は、設定パイプラインの整合性を損なわずに、その違いをどう扱うかです。
正しいツール:config_split と config_ignore
config_split は、特定の環境でのみアクティブになる設定のセットを定義できるようにします。各 split は独自のディレクトリに存在し、環境変数に基づいて settings.php または settings.env.php を通じて有効化されます。
langcode: en
status: true
id: development
label: Development
description: 'ローカル/dev 環境でのみアクティブな設定'
folder: '../config/splits/development'
module:
devel: 0
kint: 0
dblog: 0
theme: { }
blacklist: { }
graylist: { }$app_env = getenv('APP_ENV') ?: 'production';
// 環境に応じて正しい config_split を有効化する。
$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');実プロジェクトの split の例
| 設定項目 | Dev split | Stage split | Prod split |
|---|---|---|---|
| Search API バックエンド | データベースバックエンド | Solr(小規模) | Solr(本番クラスター) |
| エラーログ | dblog、詳細モード | syslog | syslog + 外部 APM |
| パフォーマンスモジュール | 無効 | 有効 | 有効 + CDN 設定 |
| メール転送 | Mailhog / null mailer | Mailpit | SMTP / SendGrid |
不適切なアプローチ — これらをしてはいけません:
- 本番データベースで設定を直接編集する。
settings.php内でif ($settings['environment'] === 'prod')の条件分岐を使って設定値を差し替える — これは設定システムを完全に回避し、目に見えない実行時の乖離を生みます。- 環境ごとに異なる設定ファイルを持つ、別々の Git ブランチを維持する。
10. 環境間の反映(プロモーション)
プロモーションはデプロイではありません。 デプロイはコードと設定を環境へ移します。プロモーションは、検証済みのアーティファクト(すでに CI を通過したもの)を、連鎖の次の環境へ移します。
実際には、これは、dev で検証されたのと同じ Git SHA が本番に到達することを意味します。土壇場のコミットはなし。CI を飛ばすホットフィックスはなし。設定チェックを回避する cherry-pick はなし。
フィーチャーブランチ / main で
マージ時に自動デプロイ
develop から自動デプロイ
main で手動ゲート
同じ検証済みアーティファクトが各環境を渡っていきます。CI は一度だけ実行され、その結果が伝播します。
不変ビルドの哲学は、こう意味します。CI が検証したものが、デプロイされるものである。ホットフィックスが本当に必要なら、それは独自の CI 実行を伴うファストトラックのブランチを通ります。検証を回避することはありません。
main ブランチを、必須のパイプラインステータスチェックで保護してください。GitLab の「protected branches」と Jenkins の「GitHub Branch Source」プラグインの両方がこれをサポートします。パイプラインが通っていなければ、そのブランチは本番へデプロイできません。11. エッジケースへの対処(必ず遭遇します)
UUID の不一致
設定エンティティは UUID を持ちます。新しいサイトをインストールしてから、別のインストールの設定をインポートしようとすると、Drupal は UUID の不一致エラーで拒否します。解決策は、常に --existing-config でインストールするか、あるいはインストール後にサイトの UUID を設定することです。
# コミットされた設定から UUID を読み取り、新しいインストールに適用する:
SITE_UUID=$(grep "^uuid:" config/sync/system.site.yml | awk '{print $2}')
drush config-set "system.site" uuid "$SITE_UUID" --yes
drush cim --yesモジュールの有効化・無効化の順序
設定を通じて新しいモジュールを有効化するとき、Drupal は drush cim の実行中に依存関係の順序を自動的に尊重します。ただし、モジュールの install hook が config/sync にあるものと競合するデフォルト設定を生成する場合、そのデフォルト設定を削除して再インポートする必要があるかもしれません。パイプラインはこれをドリフトとして捕まえます。
コンテンツに依存する設定
一部の設定はコンテンツを参照します。例えば、メニュー項目を ID で参照するブロックや、タクソノミーの用語でフィルタリングするビューです。これらの参照は、コンテンツが異なる形で作成された場合、環境間で異なり得ます。解決策は、そのコンテンツを設定ではなく、マイグレーションや default content モジュールを通じて管理することです。
マルチサイトの癖
マルチサイト構成では、各サイトが独自の設定同期ディレクトリを持ちます。CI パイプラインは、主サイトだけでなく、各サイトについて設定チェックを実行しなければなりません。ループを使います。
#!/bin/bash
set -euo pipefail
for SITE_DIR in web/sites/*/; do
SITE=$(basename "$SITE_DIR")
[[ "$SITE" == "default" ]] && continue
[[ "$SITE" == "simpletest" ]] && continue
echo "--- Checking config for 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 "✅ All sites clean."設定変更を伴う Drupal コアのアップグレード
コアの更新は、既存の設定に影響するスキーマ変更を伴うことがあります。常に drush cim の前に drush updb を実行し、その後 drush cex を実行して、スキーマによって正規化された変更を取り込みます。それらの変更を、コアアップグレードのブランチの一部としてコミットしてください。パイプラインで不意打ちを食らわないように。
12. 運用上のセーフティネット
本番での読み取り専用の設定
本番で Config Readonly モジュールを有効化することを検討してください。管理 UI を通じたいかなる設定変更も防ぎます。アプリケーションレベルで「手動変更なし」ルールを補強する、硬いブロックです。
if (getenv('APP_ENV') === 'production') {
$settings['config_readonly'] = TRUE;
}デプロイ後の検証
# デプロイ後に保留中の設定変更が残っていないか検証する:
drush config-status 2>&1 | grep -v 'No differences' && { \
echo "❌ Unexpected config differences after deploy"; exit 1; \
} || echo "✅ Config status clean"
# キャッシュがウォームか検証する:
drush cr
drush php-eval "echo \Drupal::state()->get('system.cron_last');"ロールバック戦略
ロールバックは Git の操作であって、データベースの操作ではありません。デプロイが問題を引き起こした場合は:
- 最後に正常だったコミット SHA を特定する。
- その SHA でパイプラインの実行をトリガーする。
- パイプラインが、以前に検証済みのアーティファクトを再デプロイする。
設定インポートを取り消すためにデータベースを手作業でいじることは、決して答えではありません。それはすべての安全チェックを回避し、たいてい直す以上のドリフトを生みます。
監査:誰が、いつ、なぜ設定を変えたか
あらゆる設定変更が Git を通るため、あなたの監査証跡は Git の履歴です。意味のあるコミットメッセージを使い、commit-msg フックや CI の lint ステップでそれを強制してください。
$ 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. もう犯さない、よくある間違い
| 間違い | なぜ痛いのか | 解決策 |
|---|---|---|
ステージングや本番で手動で drush cim を実行する | 未知の状態をインポートする — 未コミットのローカル変更を含む可能性 | 共有環境で設定をインポートするのはパイプラインのみ |
| ステージングでの UI 変更を許す | ステージングが信頼できる情報源になり、Git との乖離を生む | すべての共有環境で config_readonly を有効化する |
| 開発者が「エクスポートを忘れない」と信じる | プレッシャーの下で、彼らは忘れる | pre-commit フックが警告し、CI の失敗が強制する |
| 設定ドリフトで失敗させない | 静かな乖離が、派手に壊れるまで蓄積する | drush cim → cex → git diff --exit-code の三点セットは必須 |
| 環境ごとに異なる設定を持つ別々のブランチ | マージがコンフリクトの悪夢になり、単一の信頼できる情報源がなくなる | 1つの設定ブランチを config_split で分割する |
drush cim の前に drush updb を飛ばす | スキーマ更新がインポート失敗や静かなデータ損失を引き起こし得る | 常に:updb → cim → cex → diff |
14. このパターンを適用した後の結果
このアプローチを、1つの小さなサイトから数十のインストールを管理するエージェンシーまで、Drupal サイトのポートフォリオ全体に展開した結果、成果は一貫していました。
drush cim 実行回数数字を超えて、文化の変化が最も重要です。設定の議論が、Slack から Git の履歴へ移ります。「誰かステージングで画像スタイルを変えた?」の代わりに、問いは「それのコミットはある?」になります。そして、必ずあるか、変更が起きていないかのどちらかです。
新しい開発者は、「今どの環境が正典なのか」という暗黙のルールを学ぶ必要がもはやありません。答えは常に Git です。パイプラインがそれを強制します。
15. 最終的な推奨事項
- 厳格に始め、理由があるときにだけ緩める。 不要だと分かったルールを緩めるほうが、そもそも無かったルールを締めるより簡単です。
- あらゆる設定差分をビルドの失敗として扱う。 例外なし、「リリース後に直す」もなし。
- CI が、デプロイされるものの信頼できる情報源である — 開発者のノートパソコンでも、ステージング環境でも、Slack の決定でもありません。
updb → cim → cex → diffの順序は不可侵である。 どれか1つの手順でも飛ばせば、あなたは盲目で飛んでいます。- 真の環境差には
config_splitを使う。settings.phpの条件分岐を近道として使わないでください。 - すべての共有環境で config_readonly を有効化する。 偶発的な UI 変更を、単に非推奨にするのではなく、不可能にしてください。
- 失敗した設定エクスポートを CI アーティファクトとして保存する。 開発者は、差分があったという事実だけでなく、何が正確にドリフトしたのかを見る必要があります。
- ロールバックは Git 経由で、決してデータベースの手術で行わない。 パイプラインが、両方向で最も安全な道です。
Jenkins と GitLab CI は、この規律を強制するうえで同等に有能です。GitLab CI は必要なインフラが少なく、アーティファクトのネイティブサポートが優れています。Jenkins は複雑なエンタープライズ環境向けの柔軟性が高く、多くのプロジェクトでパイプラインを標準化するための shared library のサポートが優れています。あなたの組織に合ったツールを選んでください。この記事の原則は、いずれにせよ当てはまります。
目標は、設定管理を複雑にすることでは決してありませんでした。目標は、それを退屈にすることです。常に機能し、誰も考える必要がなく、夜23時のインシデントを決して引き起こさない、日常的な手順にすることです。正しく実装された CI パイプラインがあれば、退屈こそ、あなたが手にするものです。
projects@drupalbook.org