はじめに Link to heading

GCP上でWordPress マルチテナント環境(10サイト)を構築する過程で、予想外のエラーに何度も遭遇しました。

前回の記事「Cloud SQL SSL設定で2時間ハマった話」では判断ミスの分析に焦点を当てましたが、今回は具体的なトラブル事例と即効性のある解決策を共有します。

この記事で扱うハマりポイント:

  1. Ansibleのメタデータ取得で404エラー
  2. Terraform applyでインスタンス再作成
  3. データベースパスワード不一致
  4. WP-CLIの権限エラー
  5. カスタムdb.phpの残骸

同じエラーに遭遇した時、この記事がトラブルシューティングの時間短縮につながれば幸いです。


ハマりポイント1: Ansibleのメタデータ取得で404エラー Link to heading

症状 Link to heading

Ansibleでセットアップスクリプトを生成したところ、スクリプト内に HTML404エラーページ が埋め込まれていました。

1# /usr/local/bin/setup-wordpress-site.sh の中身
2DB_HOST="<!DOCTYPE html>
3<html lang=en>
4  <meta charset=utf-8>
5  <title>Error 404 (Not Found)!!1</title>
6  ...

スクリプトを実行すると:

1./setup-wordpress-site.sh: line 27: [: too many arguments

原因 Link to heading

GCPインスタンスのメタデータからdb_hostなどの値を取得しようとしましたが、メタデータが存在しないため404エラーが返されました。

Ansibleの該当コード:

 1- name: DB_HOST取得
 2  uri:
 3    url: "http://metadata.google.internal/computeMetadata/v1/instance/attributes/db_host"
 4    headers:
 5      Metadata-Flavor: "Google"
 6    return_content: yes
 7  register: db_host_metadata
 8  failed_when: false  # ← エラーでも続行
 9
10- name: メタデータから変数を設定
11  set_fact:
12    db_host: "{{ db_host_metadata.content }}"  # ← 404 HTMLがそのまま代入される

解決策 Link to heading

HTTPステータスコードをチェックして、200の場合のみ値を使用するように修正:

1- name: メタデータから変数を設定
2  set_fact:
3    db_host: "{{ db_host_metadata.content if db_host_metadata is defined
4                 and db_host_metadata.content is defined
5                 and db_host_metadata.status == 200
6                 else db_host | default('') }}"

学んだこと Link to heading

  • failed_when: false はエラーを無視するが、エラーレスポンスは残る
  • GCPメタデータが存在しない場合、404 HTMLが返される
  • ステータスコードの確認は必須

ハマりポイント2: Terraform applyでインスタンス再作成 Link to heading

症状 Link to heading

Cloud SQLの設定変更(require_ssl = false)のためにterraform applyを実行したところ、既存のComputeインスタンスが削除・再作成されました。

# module.compute.google_compute_region_instance_template.web must be replaced
-/+ resource "google_compute_region_instance_template" "web" {
      # ...
    }

# module.compute.google_compute_region_instance_group_manager.web will be updated
  ~ resource "google_compute_region_instance_group_manager" "web" {
      ~ version {
          ~ instance_template = "..." -> (known after apply)
        }
    }

原因 Link to heading

Instance Templateに関連する設定(今回はスタートアップスクリプト内のデータベース設定)が変更されたため、Terraformは:

  1. 新しいInstance Templateを作成
  2. Managed Instance Groupの設定を更新
  3. 古いインスタンスを削除し、新しいインスタンスを起動

これはTerraformの 正常な動作 です。

解決策(事前対策) Link to heading

対策1: create_before_destroy を使用 Link to heading

1resource "google_compute_region_instance_template" "web" {
2  lifecycle {
3    create_before_destroy = true
4  }
5}

これにより、新しいインスタンスを先に作成してから古いものを削除します。

対策2: ステートフルな構成要素をNFS/Filestoreに配置 Link to heading

今回の構成では、WordPressファイルをCloud Filestoreに配置していたため、インスタンス再作成後もデータは保持されました。

/var/www/wordpress  # ← NFSマウント(Cloud Filestore)
├── site1/
├── site2/
└── ...

対策3: Ansibleで再デプロイ Link to heading

新しいインスタンスには環境設定が含まれていないため、Ansibleで再デプロイが必要:

1cd ansible
2ansible-playbook -i inventory/gcp.yml playbooks/deploy-wordpress.yml \
3  -e "db_host=192.168.0.2" \
4  -e "nfs_ip=10.0.3.2" \
5  -e "nfs_path=/wordpress"

学んだこと Link to heading

  • Instance Template変更 = インスタンス再作成
  • ステートフルな構成はNFS/データベースに配置
  • create_before_destroy でダウンタイム最小化

ハマりポイント3: データベースパスワード不一致 Link to heading

症状 Link to heading

WordPressから以下のエラー:

ERROR 1045 (28000): Access denied for user 'wp_user_1'@'10.0.1.21' (using password: YES)

Secret Managerには正しいパスワードが保存されているのに、データベース接続ができません。

原因 Link to heading

Terraformで最初にCloud SQLユーザーを作成した後、SSL設定変更のためにterraform applyを実行しました。

この時、ユーザーのパスワードはリセットされませんでしたが、Secret Managerの値とCloud SQLのパスワードが不一致になっていました。

原因は、Terraformリソースのrandom_passwordが再生成されたためです:

 1resource "random_password" "db_passwords" {
 2  count   = 10
 3  length  = 20
 4  special = true
 5}
 6
 7resource "google_sql_user" "wordpress_users" {
 8  count    = 10
 9  name     = "wp_user_${count.index + 1}"
10  password = random_password.db_passwords[count.index].result  # ← 再生成される
11}

解決策 Link to heading

全ユーザーのパスワードをSecret Managerの値に同期:

 1for i in {1..10}; do
 2  PASS=$(gcloud secrets versions access latest \
 3    --secret=prod-wordpress-db-password-$i \
 4    --project=infra-ai-agent)
 5
 6  gcloud sql users set-password wp_user_$i \
 7    --instance=prod-wordpress-db \
 8    --password="$PASS" \
 9    --project=infra-ai-agent
10
11  echo "Updated wp_user_$i"
12done

学んだこと Link to heading

  • random_passwordは状態ファイルに保存されるが、再計画時に変更される場合がある
  • Secret Managerとデータベースのパスワード同期を確認
  • パスワード変更時は両方を更新する必要がある

改善案 Link to heading

パスワードをlifecycle { ignore_changes }で保護:

1resource "google_sql_user" "wordpress_users" {
2  lifecycle {
3    ignore_changes = [password]
4  }
5}

ハマりポイント4: WP-CLIの権限エラー Link to heading

症状 Link to heading

Ansibleで WP-CLI のバージョン確認を実行したところ、以下のエラー:

Error: YIKES! It looks like you're running this as root.
You probably meant to run this as the user that your WordPress installation exists under.

If you REALLY mean to run this as root, we won't stop you, but just bear in mind
that any code on this site will then have full control of your server, making it quite DANGEROUS.

If you'd like to continue as root, please run this again, adding this flag:  --allow-root

原因 Link to heading

AnsibleはデフォルトでSSH経由でrootユーザー(またはsudo権限を持つユーザー)として実行されます。

WP-CLIはセキュリティ上の理由でrootユーザーでの実行を推奨していません。

解決策 Link to heading

become_userでwww-dataユーザーに切り替え:

修正前:

1- name: WP-CLI バージョン確認
2  command: "{{ wpcli_bin_path }} --version"
3  register: wpcli_version
4  changed_when: false

修正後:

1- name: WP-CLI バージョン確認
2  command: "{{ wpcli_bin_path }} --version"
3  register: wpcli_version
4  changed_when: false
5  become_user: www-data  # ← 追加

WordPress セットアップスクリプトでも同様 Link to heading

1# 修正後
2sudo -u www-data /usr/local/bin/wp core download
3sudo -u www-data /usr/local/bin/wp config create ...
4sudo -u www-data /usr/local/bin/wp core install ...

学んだこと Link to heading

  • WP-CLIはwww-dataユーザーで実行するのがベストプラクティス
  • ファイルパーミッション問題を回避できる
  • セキュリティリスクを軽減

ハマりポイント5: カスタムdb.phpの残骸 Link to heading

症状 Link to heading

WordPress サイトにアクセスすると HTTP 500 エラー:

HTTP/2 500
server: nginx/1.22.1

エラーログを確認すると:

1Fatal error: Uncaught Error: Call to undefined function wp_kses()
2in /var/www/wordpress/site1/wp-includes/functions.php:6105

原因 Link to heading

前回の記事で作成した カスタムdb.php がNFSマウントされたディレクトリに残っていました。

/var/www/wordpress/site1/
├── wp-content/
│   └── db.php  # ← これが原因!
├── wp-admin/
└── wp-includes/

WordPressはwp-content/db.phpが存在すると、それをデータベースドライバとして読み込みます(Drop-in File機能)。

しかし、カスタムdb.phpは不完全な実装だったため、WordPressのコア関数が読み込まれず、wp_kses()などが未定義になっていました。

解決策 Link to heading

カスタムdb.phpを削除:

1# すべてのサイトから削除
2gcloud compute ssh prod-web-z11p --zone=asia-northeast1-a \
3  --tunnel-through-iap --project=infra-ai-agent \
4  --command="sudo find /var/www/wordpress -name 'db.php' -path '*/wp-content/db.php' -delete"

削除後、サイトは即座に正常に戻りました:

1curl -I https://ai-jisso.tech
2HTTP/2 200  # ← 成功!

学んだこと Link to heading

  • WordPressのDrop-in Fileは強力だが危険
  • NFSマウントされたファイルは複数サーバーで共有される
  • 手動で配置したファイルは必ず記録・削除

予防策 Link to heading

Ansibleでクリーンアップタスクを追加:

1- name: カスタムdb.phpの削除(もし存在すれば)
2  file:
3    path: "/var/www/wordpress/site{{ item }}/wp-content/db.php"
4    state: absent
5  loop: "{{ range(1, 11) | list }}"

トラブルシューティングの心得 Link to heading

1. エラーメッセージを正確に読む Link to heading

ERROR 1045 (28000): Access denied
  • ❌ 「パスワードが間違っている」
  • ✅ 「認証に失敗している(パスワード、ユーザー名、ホスト制限の可能性)」

2. ログを確認する順序 Link to heading

1. アプリケーションログ (/var/log/nginx/error.log)
2. ミドルウェアログ (/var/log/php8.2-fpm.log)
3. システムログ (journalctl -u service-name)
4. GCPコンソール (Cloud Logging)

3. 問題の切り分け Link to heading

□ ローカル環境で再現するか?
□ 他のサーバーでも発生するか?
□ 最近何を変更したか?
□ ロールバックすると解決するか?

4. 「動かない」時のチェックリスト Link to heading

 1network:
 2  □ ファイアウォールルール
 3  □ VPC接続
 4  □ プライベートIP vs パブリックIP
 5
 6authentication:
 7  □ パスワード
 8  □ ユーザー名
 9  □ ホスト制限
10
11permissions:
12  □ ファイルパーミッション (644/755)
13  □ オーナー (www-data:www-data)
14  □ SELinux/AppArmor
15
16configuration:
17  □ 設定ファイルの構文エラー
18  □ 環境変数
19  □ デフォルト設定の確認

まとめ Link to heading

各ハマりポイントの教訓 Link to heading

ハマりポイント根本原因教訓
Ansibleメタデータ404HTTPステータスコード未確認レスポンス検証は必須
インスタンス再作成Terraformの正常動作ステートレス設計
パスワード不一致Secret ManagerとDB不整合同期スクリプト必要
WP-CLI権限rootユーザー実行www-dataで実行
カスタムdb.php手動ファイル配置IaC原則の遵守

トラブルシューティングの時短テクニック Link to heading

  1. エラーログの優先度: アプリ → ミドルウェア → システム → クラウド
  2. 問題の切り分け: 最小構成で再現させる
  3. 変更履歴の確認: git log、Terraformステート、Ansibleログ
  4. 公式ドキュメント: エラーメッセージをそのまま検索
  5. コミュニティ: Stack Overflow、GitHub Issues

次回予告 Link to heading

次回は「Cloud SQL接続の落とし穴 - SSL設定とトラブルシューティング」で、Cloud SQL特有の設定とデバッグ手法を深掘りします。


参考リンク Link to heading


この記事のコード Link to heading

GitHub: infra-ai-agent

修正したAnsible Playbook:


この記事が役に立ったら: GitHub Starをいただけると嬉しいです! infra-ai-agent