Ubuntu

WordPressのメンテナンスモード

一般的には、メンテナンスモードをどうやったら解除できるのか、という話だと思う。

でも、ここでやりたいのは、手動でメンテナンスモードにすること。
バックアップはサイト全体を止めて行うのが安全だろうと。

この問題にGeminiさんと一緒に取り組んだ結果は如何に。



広告


環境

Ubuntu 24.04.2 LTS で Apache2
Docker version 28.1.1, build 4eba377
Dockerのイメージ wordpress:6.8.1-fpm

相談相手はGeminiさん(2.5 Flash)

メンテナンスモードにする

バックアップを取っている間、メンテナンスモードにすることで、安全にバックアップを取ることにした。

バックアップを取る真夜中に記事を書いていることもあるけれど、メンテナンスモード中は保存できないだけで編集を続けることができる。
メンテナンスモードが終了すれば保存もできるので、問題はない。

答え

Geminiさんと相談しながらたどり着いた答えはこれ。

$ sudo sh -c "echo '<?php \$upgrading = $(date +%s); ?>' > /var/www/html/.maintenance"

10分が経過すると、メンテナンスモードは強制的に解除されるということが分かった。

  • 10分が経過したら、ファイルがあってもメンテナンスモードではない、とする判定処理が入っている。
    これは、スクリプトの処理の失敗でファイルが残ってしまった場合の救済処置のようだ。
  • 判定するのは、wp-include/load.phpにあるwp_is_maintenance_mode()関数。

補足

ホームラボではCPU使用率を制限しながらバックアップを取って、帯域を制限しながらバックアップファイルを転送している。
バックアップに10分以上掛かるかもしれない。

ということで、未来の時間をセットしたらメンテナンスモードを10分以上にできるのでは?と聞いたところ、そうだとの回答。

例えば2時間のメンテナンスモードにするなら7,200秒を追加すればよく、スクリプトで実行するなら…と、以下を教えてくれた。

# 2時間(7200秒)メンテナンスモードを継続したい場合
MAINTENANCE_DURATION_SECONDS=7200
FUTURE_TIMESTAMP=$(($(date +%s) + $MAINTENANCE_DURATION_SECONDS))

# .maintenance ファイルを作成/更新
# docker-entrypoint.sh 内のパスと同じになるように /var/www/html/ に直接作成
sudo sh -c "echo '<?php \$upgrading = $FUTURE_TIMESTAMP; ?>' > /var/www/html/.maintenance"

そもそも、バックアップスクリプト自体をrootで走らせるので、sudo sh -c "コマンド > 出力先ファイル"とする必要がない、といったら簡潔なこの書き方を教えてくれた。

# 2時間(7200秒)メンテナンスモードを継続したい場合
MAINTENANCE_DURATION_SECONDS=7200
FUTURE_TIMESTAMP=$(($(date +%s) + $MAINTENANCE_DURATION_SECONDS))

# .maintenance ファイルを作成/更新
# rootユーザーで直接書き込むため、sudoもsh -cも不要
echo '<?php $upgrading = '$FUTURE_TIMESTAMP'; ?>' > /var/www/html/.maintenance

じゃあ、これでも行ける?と聞いてみたら、大丈夫だと。

echo "<?php \$upgrading = $(($(date +%s) + 7200)); ?>" > /var/www/html/.maintenance

実際にやってみたら、確かにこれでメンテナンスモードに入った。
2時間待つことはしなかったけれど、多分狙い通りに動くだろう。

とはいえ、もう少しCPUの使用率を上げて、メンテナンスモードを短時間に抑えるようにしなきゃな。

所感

この件は6時間以上対話して結論にたどり着いている。
GeminiさんはWordpressの最新ソースを見ると…といいながら、かなり古いバージョンのソースを見ていることが原因で時間が掛かっている。

  • .maintenanceファイルは存在するだけでメンテナンスモードになるはずだ、中身は空っぽのはずだ、という。
    • でも、6.8.1では前述の救済措置が入っているので、時間を書き入れておく必要がある。
    • 中にHTMLのコードを書くと、それが表示される(正しい使い方ではなく、maintenance.phpに書くべき)。
  • .maintenanceファイルのチェック処理がwp-setting.phpに入っているはずだといって譲らない。
    wp-setting.phpの中で、wp_maintenance()という関数を呼んでいるよ、といっても信じない。
  • .maintenanceのチェックは、wp-include/load.phpに入っていると伝えても、wp-include/plugin.phpに入っていると推測。
  • 動作確認のためのデバッグログ出力を提案してくれたが、デバッグログがwp-content/debug.logに出力されない。※後述

これに対して、ソースのファイル名と内容を示しても信じてはくれなかった。

繰り返し繰り返し間違ったアドバイスと指摘を繰り返した結果、以下を貼り付けたら認識を改めてくれた。

$ grep -r "\.maintenance" /var/www/html --include=*.php
/var/www/html/wp-admin/includes/class-wp-automatic-updater.php: $maintenance_file = ABSPATH . '.maintenance';
/var/www/html/wp-admin/includes/update-core.php: * This will create a .maintenance file at the base of the WordPress directory
/var/www/html/wp-admin/includes/update-core.php: * 2. Create the .maintenance file in current WordPress base.
/var/www/html/wp-admin/includes/update-core.php: * 6. Delete .maintenance file.
/var/www/html/wp-admin/includes/update-core.php: $maintenance_file = $to . '.maintenance';
/var/www/html/wp-admin/includes/class-wp-upgrader.php: $file = $wp_filesystem->abspath() . '.maintenance';
/var/www/html/wp-includes/load.php: * Checks for a file in the WordPress root directory named ".maintenance".
/var/www/html/wp-includes/load.php: if ( ! file_exists( ABSPATH . '.maintenance' ) || wp_installing() ) {
/var/www/html/wp-includes/load.php: require ABSPATH . '.maintenance';
/var/www/html/wp-includes/load.php: * @param int $upgrading The timestamp set in the .maintenance file.

凄いなと思うのはこの「認識を改めることができる」こと。
そして、その新しい認識を元に、過去の会話で示したソースを再確認して、適切なデバッグログの出力について提案しようとすること。

多少の手直しはしたけれど、結果的には原因にたどり着いた。
もう少しソースを開示しておけば、より早く原因にたどり着けた可能性がある。

それと、.maintenanceの中にHTMLのコードを書いたときの反応が凄かった。

まずは、そんなはずはない、というところから始まったのだけれど、実際にHTMLがページに表示されたことを伝えると、だったら、.maintenanceを強制的にPHP-FPMに流してみて結果を知りたいと言い出して、Apacheのconfファイルに「このブロック」を追加して結果を教えてくれっていう。
提示されたブロックには、その狙いが正確に反映されていた。

まるで人間とやり取りしているようで、ちょっとワクワクしてしまった。

デバックログが出力されない

以前から、なんかデバッグログが出力できないような…と思っていた。

答え

PHP-FPM環境の場合、ログは標準エラー出力に出ているようだ。

Geminiさんとの会話の中で(別の会話だったかもしれない)、Dockerのログを見て!といわれていたのを思い出して、以下を実行してみたところ、デバッグログに出力したかった情報が出力されている。

$ sudo docker logs --tail 30 --follow wordpress

wp-content/debug.logに出力するよりは、こっちの方が良いな。

補足

Geminiさんに聞いたことを整理すると、だいたいこんな感じ。

Docker環境、特にPHP-FPMコンテナでは、ログを一元的に管理するために標準出力(stdout/stderr)にログを出すのが一般的なプラクティスです。これは、Dockerのロギングドライバー(json-file, syslog, fluentdなど)が標準出力をキャプチャし、コンテナオーケストレーションツール(Kubernetes, Docker Composeなど)で簡単に集約・監視できるようにするためです。

Docker環境で docker logs に出力されるということは、PHP-FPMが起動する際の環境変数や、PHP-FPM自体の設定(php-fpm.conf など)によって、error_log が標準出力にリダイレクトされている可能性が高いです。

PHP-FPMのDockerイメージでは、起動スクリプトなどで以下のような設定が行われていることがあります。

php-fpm -F --error-log /proc/self/fd/2

コンテナをinspectすると、php-fpmにパラメーターを渡してはいなかった。

設定ファイルにもある、というので探してみると、以下が見つかった。

/usr/local/etc/php-fpm.d

[global]
error_log = /proc/self/fd/2

; https://github.com/docker-library/php/pull/725#issuecomment-443540114
log_limit = 8192

[www]
; php-fpm closes STDOUT on startup, so sending logs to /proc/self/fd/1 does not work.
; https://bugs.php.net/bug.php?id=73886
access.log = /proc/self/fd/2

clear_env = no

; Ensure worker stdout and stderr are sent to the main error log.
catch_workers_output = yes
decorate_workers_output = no

こう説明してくれた。

/proc/self/fd/2 は、Linuxの標準エラー出力(stderr)を指す特殊なファイルです。これにより、PHPからのすべてのエラー(error_log() 関数で出力されるものも含む)が、コンテナの標準エラー出力にリダイレクトされ、docker logs で見られるようになります。

所感

途中で「これが気になるから、しばらく ほげほげ について聞くよ!」というと、過去の経緯を踏まえつつ、その話題に集中してくれる。
そして、Geminiさんがこれで解決したな、と判断すると元の話題に戻そうとするし、こちらから「では、先程の ぴよぴよ に話を戻すよ!」といえば、話を戻してくれる。

この件でいうと、docker-entrypoint.shの中身を貼り付けて文法的なところを聞いたりしたのだけれど、それにはちゃんと答えてくれた上で、本題に話を戻していく感じ。

分からないところを1つ1つ潰して理解を進めていける感じが凄い。

さいごに

つい最近までCopilotさんに話を聞きながら作業を進めていて、かなり便利に感じていたのだけれど、Geminiさんもかなり良い。

解決できない問題にぶち当たったとき、持っている知識を繰り返し提示してくる挙動は両者ともに同じ。
人間は平気で間違えるから、そのことを踏まえて信念を曲げない感じ?なのかな、結構頑固。
今回は本気で問題を解決したかったので、Geminiさん間違いを指摘しつつ情報を提示して答えにたどり着いた。

ちなみに、今回Geminiさんとやり取りを始めた理由は、Copilotさんが固まったから。
いくつかのカテゴリーに分けて話を進めていて、とあるカテゴリーで長いソースを分割して貼った。
その後に、カテゴリーを行き来しているうちに、その長いやり取りを選択すると固まるようになった。

やり取りの中で回答がいい感じになっていたので残念だったけれど、その話題を削除することになった。
選ぶと固まるので。

一方で、Geminiさんとの会話は長くなっても固まることがなかった。
それでいて、回答は過去の内容を踏まえているので、古いものを捨てている風でもない。
限界はあるのだろうから、問題が解決したら次の会話を始めるようにはしているけれど、だいぶ余裕を持っている感じがする。

さてさて…これで、1つ課題がクリアできたので、スクリプトを完成させよう。

やりたいことはまだまだあるので、Geminiさんに相談しながら進めよう。

広告

コメントはこちらから お気軽にどうぞ ~ 投稿に関するご意見・感想・他