<!--META
{
"title": "wp-cli search-replace --dry-run を用いたWordPress DB更新の安全な自動化",
"primary_category": "DevOps",
"secondary_categories": ["WordPress", "Bash Scripting"],
"tags": ["wp-cli", "search-replace", "dry-run", "systemd", "jq", "curl", "idempotent"],
"summary": "wp-cliのsearch-replace --dry-runを活用し、安全なBashスクリプト、systemdタイマー、そしてjq/curlを組み合わせたWordPressデータベース更新の自動化手法を解説します。",
"mermaid": true,
"verify_level": "L0",
"tweet_hint": {"text":"wp-cliのsearch-replace --dry-runでWordPress DBを安全に更新!systemdと堅牢なBashスクリプトで自動化するDevOpsプラクティスを解説。#WordPress #DevOps #wpcli","hashtags":["#WordPress","#DevOps","#wpcli"]},
"link_hints": [
"https://developer.wordpress.org/cli/commands/search-replace/",
"http://redsymbol.net/articles/unofficial-bash-strict-mode/",
"https://wiki.archlinux.org/title/Systemd/Timers"
]
}
-->
本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
wp-cli search-replace –dry-run を用いたWordPress DB更新の安全な自動化
要件と前提
WordPressサイトの移行やURL変更、ドメイン変更時など、データベース内の文字列を一括置換する作業は頻繁に発生します。この際、手作業や不適切なツールを使用すると、データベースが破損するリスクがあります。wp-cli search-replaceコマンドは、この作業を安全かつ効率的に行うための強力なツールですが、特に自動化された環境で利用する際には、その安全性と冪等性を確保するDevOpsプラクティスが不可欠です。
、wp-cli search-replace --dry-runを活用し、安全なBashスクリプト、systemdタイマー、そしてjqやcurlといったツールを組み合わせることで、WordPressデータベースの更新を自動化し、高い信頼性をもって運用する手法を解説します。
前提ツール
wp-cli: WordPressのCLIツール。バージョン2.10.0 (2024年05月16日時点) を想定。
Bash: シェルスクリプト実行環境。
jq: JSONデータ処理ツール。
curl: HTTP通信ツール。
systemd: サービス管理デーモン。
設計原則
冪等性 (Idempotence): スクリプトを複数回実行しても、常に同じ結果が得られるように設計します。これにより、予期せぬ再実行や途中で中断された場合のリカバリが容易になります。
安全性:
wp-cli search-replace --dry-runで事前に変更内容を確認し、エラーハンドリングを徹底します。データベースのバックアップは必須です。自動化:
systemdタイマーを用いて、定期的な実行や特定イベント後の自動実行を可能にします。権限分離: スクリプトは最小限の権限で実行し、
root権限の使用は避けます。WordPressのファイルオーナー/グループで実行することが望ましいです。
実装
全体フロー
データベース更新プロセスの全体フローをMermaidで示します。これにより、各ステップの関連性とロジックが明確になります。
graph TD
A["開始"] --> B["一時ディレクトリ作成 & trap設定"];
B --> C["環境チェックと事前検証"];
C -- 成功 --> D["wp-cli search-replace --dry-run実行"];
D --> E{"jqでドライラン結果を解析"};
E -- 変更あり AND 基準内 |変更件数 > 0 & 安全な範囲| --> F["DBバックアップ"];
F --> G["wp-cli search-replace本番実行"];
G --> H["実行結果をログ/curlで通知"];
E -- 変更なし OR 基準外 |変更件数 = 0 OR 異常| --> H;
C -- 失敗 --> H;
H --> I["一時ディレクトリクリーンアップ"];
I --> J["終了"];
シェルスクリプトの作成
以下のBashスクリプト例は、wp-cli search-replaceを安全に実行するための基本的な枠組みを示します。
#!/bin/bash
# file: /usr/local/bin/update-wp-db.sh
# 厳格なエラーハンドリング設定
set -euo pipefail # -e: コマンド失敗時に即座に終了
# -u: 未定義変数参照時にエラー
# -o pipefail: パイプライン中のコマンド失敗を検出
IFS=$'\n\t' # IFSを改行とタブのみに設定し、予期せぬワード分割を防ぐ
# 一時ディレクトリの作成とクリーンアップ設定
TMP_DIR=$(mktemp -d -t wp-db-update-XXXXXX)
# [4] Aaron Maxwell (2024年01月09日)
# [5] LinuxConfig.org (2023年01月14日)
function cleanup {
echo "スクリプト終了。一時ディレクトリ ${TMP_DIR} を削除します。" >&2
rm -rf "${TMP_DIR}"
}
trap cleanup EXIT # スクリプト終了時にcleanup関数を実行
# 設定変数 (環境に合わせて変更)
WP_PATH="/var/www/wordpress" # WordPressインストールパス
OLD_STRING="http://old-example.com"
NEW_STRING="https://new-example.com"
WP_CLI_USER="www-data" # wp-cliを実行するユーザー (WordPressファイルオーナーが理想)
LOG_FILE="${TMP_DIR}/wp-db-update_$(date +%Y%m%d%H%M%S).log"
NOTIFICATION_URL="https://hooks.slack.com/services/TXXXXX/BXXXXX/XXXXXX" # Slack Webhook URLなど
# ロギング関数
log_message() {
local type="$1"
local message="$2"
echo "$(date +'%Y-%m-%d %H:%M:%S') [${type}] ${message}" | tee -a "${LOG_FILE}"
# エラー時は標準エラーにも出力
if [[ "${type}" == "ERROR" ]]; then
echo "$(date +'%Y-%m-%d %H:%M:%S') [${type}] ${message}" >&2
fi
}
send_notification() {
local status="$1" # SUCCESS or FAILED
local message="$2"
local payload
payload=$(jq -n \
--arg status "${status}" \
--arg message "${message}" \
'{text: "\($status) - WordPress DB Update: \($message)"}')
# [6] Gabriel B. (2023年08月16日)
# curlの再試行と指数バックオフ
local max_retries=5
local delay=1
for i in $(seq 1 "${max_retries}"); do
log_message "INFO" "通知を送信中 (試行 ${i}/${max_retries}): ${message}"
if curl -s -S -X POST -H "Content-type: application/json" \
--data "${payload}" "${NOTIFICATION_URL}" \
--connect-timeout 5 --max-time 10 \
--fail-with-body --show-error; then
log_message "INFO" "通知の送信に成功しました。"
return 0
else
log_message "WARN" "通知の送信に失敗しました (ステータス: ${status})。再試行します..."
sleep "${delay}"
delay=$((delay * 2)) # 指数バックオフ
fi
done
log_message "ERROR" "通知の送信に繰り返し失敗しました (ステータス: ${status})。"
return 1
}
# ----------------- メイン処理 -----------------
log_message "INFO" "WordPressデータベース更新スクリプトを開始します (実行ユーザー: $(whoami))。"
# 権限チェックとWP-CLIユーザーへの切り替え
if [[ "$(whoami)" != "${WP_CLI_USER}" ]]; then
log_message "INFO" "wp-cli実行ユーザーを ${WP_CLI_USER} に切り替えます。"
# sudo -u を使うことで、root権限を回避し、指定ユーザーでwp-cliを実行
# ただし、sudoersの設定が必要
exec sudo -u "${WP_CLI_USER}" bash "$0" "$@" # 指定ユーザーでスクリプトを再実行
fi
cd "${WP_PATH}" || { log_message "ERROR" "WordPressパス ${WP_PATH} に移動できません。終了します。"; send_notification "FAILED" "WordPressパスへの移動失敗"; exit 1; }
# ドライランの実行
log_message "INFO" "wp-cli search-replace --dry-run を実行します..."
DRY_RUN_OUTPUT=$(wp search-replace "${OLD_STRING}" "${NEW_STRING}" --dry-run --format=json --report-changed-only 2>&1)
EXIT_CODE=$?
if [[ ${EXIT_CODE} -ne 0 ]]; then
log_message "ERROR" "ドライランでエラーが発生しました。出力:\n${DRY_RUN_OUTPUT}"
send_notification "FAILED" "wp-cli search-replace ドライランでエラー: $(echo "${DRY_RUN_OUTPUT}" | head -n 10)"
exit 1
fi
# jqでドライラン結果を解析 [8] stedolan.github.io/jq/manual/ (2024年05月18日)
DRY_RUN_CHANGES=$(echo "${DRY_RUN_OUTPUT}" | jq 'length')
if [[ "${DRY_RUN_CHANGES}" -eq 0 ]]; then
log_message "INFO" "ドライラン結果:変更対象は見つかりませんでした。本番実行をスキップします。"
send_notification "SUCCESS" "wp-cli search-replace: 変更なし(スキップ)"
exit 0 # 冪等性: 変更がない場合は成功終了
else
log_message "INFO" "ドライラン結果:${DRY_RUN_CHANGES} 件の変更が検出されました。"
echo "${DRY_RUN_OUTPUT}" | jq . | tee -a "${LOG_FILE}"
fi
# 本番実行前の最終確認 (自動化のため、ここでは単純に実行)
# 実際の運用では、ここで手動承認ステップや、より詳細な自動チェックを挟む
log_message "INFO" "データベースのバックアップを開始します..."
if ! wp db export "${TMP_DIR}/db_backup_$(date +%Y%m%d%H%M%S).sql"; then
log_message "ERROR" "データベースのバックアップに失敗しました。本番実行を中断します。"
send_notification "FAILED" "DBバックアップ失敗"
exit 1
fi
log_message "INFO" "データベースのバックアップが完了しました。"
log_message "INFO" "wp-cli search-replace 本番実行を開始します..."
# [1] WordPress.org (2024年03月20日)
# --recurse-objects: シリアライズされたデータも処理
# --skip-columns, --skip-tables などで対象を絞ることも可能
REAL_RUN_OUTPUT=$(wp search-replace "${OLD_STRING}" "${NEW_STRING}" --recurse-objects --format=json 2>&1)
EXIT_CODE=$?
if [[ ${EXIT_CODE} -ne 0 ]]; then
log_message "ERROR" "本番実行でエラーが発生しました。出力:\n${REAL_RUN_OUTPUT}"
send_notification "FAILED" "wp-cli search-replace 本番実行でエラー: $(echo "${REAL_RUN_OUTPUT}" | head -n 10)"
exit 1
else
log_message "INFO" "本番実行が正常に完了しました。${REAL_RUN_OUTPUT}"
echo "${REAL_RUN_OUTPUT}" | jq . | tee -a "${LOG_FILE}"
send_notification "SUCCESS" "wp-cli search-replace 本番実行完了 (${DRY_RUN_CHANGES}件の変更)"
fi
log_message "INFO" "WordPressデータベース更新スクリプトを終了します。"
exit 0
権限分離とroot権限の扱い:
上記のスクリプトでは、WP_CLI_USER変数で指定されたユーザー(例: www-dataやWordPressのファイルオーナー)でwp-cliコマンドを実行するように工夫しています。これはexec sudo -u "${WP_CLI_USER}" bash "$0" "$@"の部分で実現しています。スクリプト自体はより広い権限(例: root)で起動されることもありますが、実際に機密性の高いwp-cliコマンドを実行する際には、sudo -uを使ってユーザーを切り替えることで、最小権限の原則を守ります。sudoersファイルに適切なエントリを追加し、このスクリプトが指定ユーザーとして実行できるよう許可する必要があります。これにより、誤操作や脆弱性によるシステム全体への影響を最小限に抑えられます。
systemdユニットとタイマーの定義
このスクリプトを定期的に実行するため、systemdのユニットとタイマーを定義します。
1. サービスユニットファイル (/etc/systemd/system/wp-db-update.service)
サービスが実行される環境とコマンドを定義します。
# file: /etc/systemd/system/wp-db-update.service [Unit] Description=WordPress Database Update Service Documentation=https://developer.wordpress.org/cli/commands/search-replace/ # [7] ArchWiki (2024年05月14日) After=network-online.target # ネットワークが利用可能になってから起動 Wants=network-online.target [Service] # wp-cliコマンドを実行するユーザー。WordPressのファイルオーナーと一致させるのが理想。 # これにより、sudoersの設定なしで安全に実行できる。 User=www-data Group=www-data WorkingDirectory=/var/www/wordpress # WordPressのルートディレクトリ ExecStart=/usr/local/bin/update-wp-db.sh # Type=oneshot は一度実行したら終了するサービスに最適 Type=oneshot # エラー発生時にサービスを再開しない RemainAfterExit=no # 実行失敗時の詳細なログ取得 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target
2. タイマーユニットファイル (/etc/systemd/system/wp-db-update.timer)
サービスユニットをスケジュールに従って起動するためのタイマーを定義します。ここでは、毎日午前3時に実行する例です。
# file: /etc/systemd/system/wp-db-update.timer [Unit] Description=Run WordPress Database Update Script Daily [Timer] # 毎日午前3時00分にサービスを起動 # OnCalendar=はsystemd.time(7)で詳細を確認可能 OnCalendar=*-*-* 03:00:00 # タイマーが失敗してもサービスは失敗とみなされない # Persistent=true にすると、スケジュールされた起動時刻が過ぎていた場合、 # systemdが起動した直後に実行される。 Persistent=true [Install] WantedBy=timers.target
systemdの有効化と起動:
ファイルの配置後、
systemdに設定をリロードさせます。sudo systemctl daemon-reload
タイマーを有効化し、起動します。
sudo systemctl enable wp-db-update.timer sudo systemctl start wp-db-update.timer
検証
ドライラン出力の確認: スクリプトを手動で実行し、
--dry-runの出力が意図通りであることを確認します。# WP_CLI_USERで指定したユーザーで実行(sudoers設定済みの場合) sudo -u www-data /usr/local/bin/update-wp-db.sh # または、スクリプトの`exec sudo`行をコメントアウトしてrootで実行し、ドライラン結果を見る # その後、必ず元に戻す
ログファイル (
${TMP_DIR}/wp-db-update_*.log) を確認し、jqによる解析結果が正しいか検証します。systemdサービスとタイマーの動作確認:タイマーが起動しているか確認:
systemctl list-timers | grep wp-db-update
サービスの状態を確認:
systemctl status wp-db-update.service
サービスログの確認:
journalctl -u wp-db-update.service --since "1 hour ago"
手動でサービスを実行して動作を確認することも可能です。
sudo systemctl start wp-db-update.service journalctl -u wp-db-update.service -f
テスト環境での実際のデータベース変更確認: 本番環境に適用する前に、必ずステージング環境や開発環境でスクリプトを実行し、データベースの変更が正しく適用され、サイトが正常に動作することを確認してください。
運用
ログ監視:
journalctl -u wp-db-update.serviceおよびスクリプトが出力するログファイルを定期的に監視し、異常がないか確認します。通知システム (curl) が正しく機能していることも重要です。設定変更時の手順:
OLD_STRINGやNEW_STRINGなど、スクリプトの設定を変更する際は、必ずテスト環境で十分な検証を行ってから本番に適用します。定期的なテスト: 定期的にドライランを実行し、予期せぬ変更が検出されないか確認するテストを組み込むことを推奨します。
トラブルシュート
set -euo pipefailによる早期終了: スクリプトが途中で停止した場合は、詳細なエラーメッセージがログ (journalctlやログファイル) に出力されているはずです。エラー箇所を特定し、修正します。journalctlでのエラー特定:sudo journalctl -u wp-db-update.service --pager-endコマンドでサービスログの最新部分を確認し、エラーメッセージやスタックトレースを探します。wp-cliログの活用:wp-cliコマンド自体に--log=<file>オプションを追加することで、より詳細なwp-cli固有のログを取得できます。一時ファイル残骸のクリーンアップ:
trap cleanup EXITが機能しなかった場合でも、TMP_DIRに指定されたパターン (wp-db-update-XXXXXX) を元に、定期的に一時ファイルをクリーンアップする仕組みを検討してください。
まとめ
wp-cli search-replace --dry-runを活用し、堅牢なBashスクリプトとsystemdタイマーを組み合わせることで、WordPressデータベースの更新を安全かつ自動的に実行する強力なDevOpsプラクティスが実現できます。
本記事で紹介したset -euo pipefailによる厳格なエラーハンドリング、trapによるクリーンアップ、jqを用いたドライラン結果の自動評価、curlによる通知、そしてsystemdによるスケジュール管理と権限分離は、自動化されたシステムの安定性と信頼性を高める上で不可欠な要素です。これらのベストプラクティスを導入することで、データベース更新作業のリスクを最小限に抑え、DevOpsワークフローをより効率的かつ安全に進めることができるでしょう。

コメント