はじめに Link to heading

「データベースに接続できない」

この一言の裏には、ネットワーク、認証、SSL証明書、パスワード管理など、複数の階層にわたる設定が絡み合っています

GCP上でWordPress マルチテナント環境を構築する過程で、Cloud SQLへの接続で何度も壁にぶつかりました。この記事では、その経験から得たCloud SQL接続のベストプラクティスとトラブルシューティング手法を共有します。

この記事で扱う内容:

  1. Cloud SQLの接続方式(プライベートIP vs パブリックIP)
  2. SSL/TLS設定の理解と選択
  3. Secret Managerを使ったパスワード管理
  4. 接続エラーのデバッグ手法
  5. Terraformでの適切な設定方法

1. Cloud SQL接続方式の選択 Link to heading

プライベートIP vs パブリックIP Link to heading

Cloud SQLには2つの接続方式があります:

接続方式説明ユースケースSSL要件
プライベートIPVPC内部からのみアクセス可能本番環境、セキュアな接続不要(推奨)
パブリックIPインターネットから接続可能開発環境、外部ツール接続必須

今回の構成: プライベートIP接続

┌────────────────────────────────────────┐
│            VPC Network                 │
│                                        │
│  ┌──────────────┐    ┌─────────────┐ │
│  │ Compute VM   │───▶│ Cloud SQL   │ │
│  │ 10.0.1.x     │    │ 10.168.0.2  │ │
│  └──────────────┘    └─────────────┘ │
│                                        │
│  Private IP Connection                 │
│  No SSL Required                       │
└────────────────────────────────────────┘

プライベートIP接続のメリット Link to heading

  1. セキュリティ: インターネットに公開されない
  2. レイテンシ: VPC内部通信で低遅延
  3. コスト: エグレス料金が発生しない
  4. シンプルさ: SSL証明書の管理が不要

Terraformでの設定 Link to heading

 1# terraform/modules/database/main.tf
 2resource "google_sql_database_instance" "wordpress" {
 3  name             = "${var.env}-wordpress-db"
 4  database_version = "MYSQL_8_0"
 5  region           = var.region
 6
 7  settings {
 8    tier = "db-f1-micro"
 9
10    # プライベートIP接続設定
11    ip_configuration {
12      ipv4_enabled    = false               # パブリックIPを無効化
13      private_network = var.network_id      # VPCネットワークを指定
14      require_ssl     = false               # SSL要件を無効化
15    }
16
17    # バックアップ設定
18    backup_configuration {
19      enabled            = true
20      start_time         = "03:00"
21      binary_log_enabled = true
22    }
23  }
24}

重要なポイント Link to heading

ipv4_enabled = false

  • パブリックIPアドレスを割り当てない
  • インターネットからのアクセスを完全にブロック

private_network = var.network_id

  • VPCネットワークを指定
  • Private Service Connectionを使用

require_ssl = false

  • プライベートIP接続ではSSL不要
  • これを true にすると証明書管理が必要になる

2. SSL/TLS設定の理解 Link to heading

SSL証明書が必要なケース Link to heading

1必要:
2  □ パブリックIP接続
3  □ インターネット経由でのアクセス
4  □ コンプライアンス要件
5
6不要:
7  □ プライベートIP接続(VPC内部)
8  □ トラフィックが暗号化されたネットワーク

SSL証明書のライフサイクル Link to heading

Cloud SQLでSSL証明書を使う場合の手順:

1. サーバーCA証明書の取得 Link to heading

1# サーバーCA証明書をダウンロード
2gcloud sql ssl-certs describe server-ca \
3  --instance=prod-wordpress-db \
4  --format="value(cert)" > server-ca.pem

2. クライアント証明書の作成 Link to heading

 1# クライアント証明書を作成
 2gcloud sql ssl-certs create wordpress-client \
 3  --instance=prod-wordpress-db
 4
 5# クライアント証明書をダウンロード
 6gcloud sql ssl-certs describe wordpress-client \
 7  --instance=prod-wordpress-db \
 8  --format="value(cert)" > client-cert.pem
 9
10# 秘密鍵をダウンロード
11gcloud sql ssl-certs describe wordpress-client \
12  --instance=prod-wordpress-db \
13  --format="value(privateKey)" > client-key.pem

3. MySQLクライアントでの使用 Link to heading

1mysql \
2  --host=10.168.0.2 \
3  --user=wp_user_1 \
4  --password \
5  --ssl-ca=server-ca.pem \
6  --ssl-cert=client-cert.pem \
7  --ssl-key=client-key.pem

SSL証明書の有効期限管理 Link to heading

1# 証明書一覧と有効期限確認
2gcloud sql ssl-certs list \
3  --instance=prod-wordpress-db \
4  --format="table(commonName,expirationTime)"
5
6# 出力例
7# COMMON_NAME        EXPIRATION_TIME
8# wordpress-client   2025-01-20T12:00:00Z
9# server-ca          2035-01-18T12:00:00Z

注意点:

  • クライアント証明書: 有効期限10年
  • サーバーCA証明書: 有効期限10年
  • 期限切れ前に再発行が必要

SSL無効化の判断基準 Link to heading

SSL無効化を推奨するケース:

  • ✅ プライベートIP接続のみ
  • ✅ VPC内部の通信
  • ✅ 証明書管理コストを削減したい
  • ✅ パフォーマンスを優先したい

SSL有効化が必須のケース:

  • ❌ パブリックIP接続
  • ❌ インターネット経由のアクセス
  • ❌ コンプライアンス要件(PCI-DSS等)
  • ❌ 監査要件

3. Secret Managerを使ったパスワード管理 Link to heading

なぜSecret Managerを使うのか Link to heading

NGパターン:

1# ❌ 平文でパスワードを記述
2resource "google_sql_user" "wordpress_user" {
3  password = "my_super_secret_password"  # 絶対にダメ!
4}

問題点:

  • Terraformステートファイルに平文で保存される
  • Gitにコミットされるリスク
  • チームメンバー全員がパスワードを知ることになる

正しいアプローチ:

 1# ✅ Secret Managerでパスワード管理
 2resource "random_password" "db_passwords" {
 3  count   = 10
 4  length  = 20
 5  special = true
 6}
 7
 8resource "google_secret_manager_secret" "db_passwords" {
 9  count     = 10
10  secret_id = "${var.env}-wordpress-db-password-${count.index + 1}"
11
12  replication {
13    auto {}
14  }
15}
16
17resource "google_secret_manager_secret_version" "db_passwords" {
18  count       = 10
19  secret      = google_secret_manager_secret.db_passwords[count.index].id
20  secret_data = random_password.db_passwords[count.index].result
21}

パスワードのライフサイクル管理 Link to heading

1. パスワード生成(Terraform) Link to heading

1resource "random_password" "db_passwords" {
2  length  = 20
3  special = true
4
5  lifecycle {
6    ignore_changes = [length, special]  # 再生成を防ぐ
7  }
8}

2. Secret Managerに保存 Link to heading

1# 手動でパスワードを設定する場合
2echo -n "your_secure_password" | \
3  gcloud secrets versions add prod-wordpress-db-password-1 \
4  --data-file=-

3. Cloud SQLユーザーに設定 Link to heading

1# Secret Managerからパスワードを取得してCloud SQLに設定
2PASSWORD=$(gcloud secrets versions access latest \
3  --secret=prod-wordpress-db-password-1)
4
5gcloud sql users set-password wp_user_1 \
6  --instance=prod-wordpress-db \
7  --password="$PASSWORD"

4. アプリケーションからの取得 Link to heading

1# Ansibleでの取得例
2- name: Secret Managerからパスワード取得
3  command: >
4    gcloud secrets versions access latest
5    --secret={{ env }}-wordpress-db-password-{{ item }}
6    --project={{ gcp_project_id }}
7  register: db_password
8  no_log: true  # ログに出力しない
9  loop: "{{ range(1, 11) | list }}"

パスワード不一致のトラブルシューティング Link to heading

症状:

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

原因:

  • Secret ManagerとCloud SQLのパスワードが不一致
  • Terraform applyでパスワードが再生成された
  • 手動でパスワードを変更した

解決策: 全ユーザーのパスワードを同期

 1#!/bin/bash
 2# sync-db-passwords.sh
 3
 4PROJECT_ID="infra-ai-agent"
 5INSTANCE_NAME="prod-wordpress-db"
 6ENV="prod"
 7
 8for i in {1..10}; do
 9  echo "Syncing password for wp_user_$i..."
10
11  # Secret Managerから最新のパスワードを取得
12  PASSWORD=$(gcloud secrets versions access latest \
13    --secret="${ENV}-wordpress-db-password-${i}" \
14    --project="${PROJECT_ID}")
15
16  # Cloud SQLユーザーのパスワードを更新
17  gcloud sql users set-password "wp_user_${i}" \
18    --instance="${INSTANCE_NAME}" \
19    --password="${PASSWORD}" \
20    --project="${PROJECT_ID}"
21
22  echo "✅ wp_user_$i synchronized"
23done
24
25echo "🎉 All passwords synchronized!"

実行結果:

1chmod +x sync-db-passwords.sh
2./sync-db-passwords.sh
3
4# Syncing password for wp_user_1...
5# ✅ wp_user_1 synchronized
6# Syncing password for wp_user_2...
7# ✅ wp_user_2 synchronized
8# ...
9# 🎉 All passwords synchronized!

4. 接続エラーのデバッグ手法 Link to heading

デバッグの基本フロー Link to heading

┌─────────────────────────────────────────┐
│ Step 1: ネットワーク疎通確認            │
│  ping, nc, telnet                       │
└────────────┬────────────────────────────┘
             │
             ▼
┌─────────────────────────────────────────┐
│ Step 2: MySQLクライアントで接続テスト   │
│  mysql -h HOST -u USER -p               │
└────────────┬────────────────────────────┘
             │
             ▼
┌─────────────────────────────────────────┐
│ Step 3: Cloud Loggingでログ確認         │
│  gcloud logging read                    │
└────────────┬────────────────────────────┘
             │
             ▼
┌─────────────────────────────────────────┐
│ Step 4: 設定の検証                      │
│  Terraform, wp-config.php               │
└─────────────────────────────────────────┘

Step 1: ネットワーク疎通確認 Link to heading

 1# 1. Cloud SQLインスタンスのIPアドレス確認
 2gcloud sql instances describe prod-wordpress-db \
 3  --format="value(ipAddresses[0].ipAddress)"
 4# 出力: 10.168.0.2
 5
 6# 2. Compute VMからping
 7gcloud compute ssh prod-web-l0br \
 8  --zone=asia-northeast1-a \
 9  --tunnel-through-iap \
10  --command="ping -c 3 10.168.0.2"
11
12# 3. ポート疎通確認(3306)
13gcloud compute ssh prod-web-l0br \
14  --zone=asia-northeast1-a \
15  --tunnel-through-iap \
16  --command="nc -zv 10.168.0.2 3306"
17# 出力: Connection to 10.168.0.2 3306 port [tcp/mysql] succeeded!

Step 2: MySQLクライアントで接続テスト Link to heading

 1# Cloud SQL Proxyを使った接続テスト
 2cloud_sql_proxy -instances=infra-ai-agent:asia-northeast1:prod-wordpress-db=tcp:3306 &
 3
 4# ローカルから接続
 5mysql -h 127.0.0.1 -u wp_user_1 -p
 6
 7# 接続成功の確認
 8mysql> SELECT USER(), DATABASE();
 9+-------------------------+----------+
10| USER()                  | DATABASE()|
11+-------------------------+----------+
12| wp_user_1@10.0.1.21     | NULL     |
13+-------------------------+----------+

Step 3: Cloud Loggingでログ確認 Link to heading

 1# Cloud SQLの接続ログを確認
 2gcloud logging read \
 3  'resource.type="cloudsql_database"
 4   AND logName="projects/infra-ai-agent/logs/cloudsql.googleapis.com%2Fmysql.err"' \
 5  --limit 50 \
 6  --format json \
 7  --project=infra-ai-agent
 8
 9# 認証エラーの検索
10gcloud logging read \
11  'resource.type="cloudsql_database"
12   AND textPayload=~"Access denied"' \
13  --limit 10 \
14  --format json

Step 4: 設定ファイルの検証 Link to heading

wp-config.php の確認 Link to heading

 1# リモートサーバーでwp-config.phpを確認
 2gcloud compute ssh prod-web-l0br \
 3  --zone=asia-northeast1-a \
 4  --tunnel-through-iap \
 5  --command="sudo grep -E '^define.*DB_' /var/www/wordpress/site1/wp-config.php"
 6
 7# 出力例
 8# define('DB_NAME', 'wordpress_db_1');
 9# define('DB_USER', 'wp_user_1');
10# define('DB_PASSWORD', '***');
11# define('DB_HOST', '10.168.0.2');

データベース存在確認 Link to heading

1# データベース一覧を取得
2gcloud sql databases list \
3  --instance=prod-wordpress-db \
4  --format="table(name,charset,collation)"
5
6# ユーザー一覧を取得
7gcloud sql users list \
8  --instance=prod-wordpress-db \
9  --format="table(name,host)"

よくあるエラーと解決策 Link to heading

エラー1: Can't connect to MySQL server Link to heading

原因:

  • ネットワーク疎通がない
  • ファイアウォールルールが不足
  • Cloud SQLが起動していない

確認:

1# Cloud SQLの状態確認
2gcloud sql instances describe prod-wordpress-db \
3  --format="value(state)"
4# 期待値: RUNNABLE
5
6# ファイアウォールルール確認
7gcloud compute firewall-rules list \
8  --filter="network:prod-vpc" \
9  --format="table(name,direction,allowed[].ports)"

エラー2: Access denied for user Link to heading

原因:

  • パスワードが間違っている
  • ユーザーが存在しない
  • ホスト制限

確認:

1# ユーザーの存在確認
2gcloud sql users list --instance=prod-wordpress-db
3
4# パスワード同期
5PASSWORD=$(gcloud secrets versions access latest --secret=prod-wordpress-db-password-1)
6gcloud sql users set-password wp_user_1 \
7  --instance=prod-wordpress-db \
8  --password="$PASSWORD"

エラー3: SSL connection error Link to heading

原因:

  • require_ssl = true だが証明書がない
  • 証明書のパスが間違っている

解決:

1# Terraformで設定変更
2resource "google_sql_database_instance" "wordpress" {
3  settings {
4    ip_configuration {
5      require_ssl = false  # プライベートIP接続ならfalse
6    }
7  }
8}

5. ベストプラクティス Link to heading

セキュリティ Link to heading

1□ プライベートIP接続を使用
2□ パブリックIPは無効化(ipv4_enabled = false)
3□ Secret Managerでパスワード管理
4□ 最小権限の原則(データベース権限)
5□ IAMによるCloud SQLへのアクセス制御

可用性 Link to heading

1□ 自動バックアップ有効化
2□ バイナリログ有効化(ポイントインタイムリカバリ用)
3□ 高可用性構成(本番環境)
4□ メンテナンスウィンドウの設定

パフォーマンス Link to heading

1□ 適切なマシンタイプ選択
2□ ストレージタイプの選択(SSD推奨)
3□ 接続プーリングの使用
4□ スロークエリログの監視

運用 Link to heading

1□ Cloud Loggingでログ監視
2□ Cloud Monitoringでメトリクス監視
3□ アラートポリシーの設定
4□ 定期的なバックアップテスト

Terraformでの完全な設定例 Link to heading

 1# terraform/modules/database/main.tf
 2resource "google_sql_database_instance" "wordpress" {
 3  name             = "${var.env}-wordpress-db"
 4  database_version = "MYSQL_8_0"
 5  region           = var.region
 6
 7  settings {
 8    tier              = "db-custom-2-7680"  # 本番用
 9    availability_type = "REGIONAL"          # 高可用性
10
11    # IPアドレス設定
12    ip_configuration {
13      ipv4_enabled    = false
14      private_network = var.network_id
15      require_ssl     = false
16    }
17
18    # バックアップ設定
19    backup_configuration {
20      enabled                        = true
21      start_time                     = "03:00"
22      binary_log_enabled             = true
23      transaction_log_retention_days = 7
24      backup_retention_settings {
25        retained_backups = 30
26        retention_unit   = "COUNT"
27      }
28    }
29
30    # メンテナンス設定
31    maintenance_window {
32      day          = 7  # 日曜日
33      hour         = 3  # 午前3時
34      update_track = "stable"
35    }
36
37    # データベースフラグ
38    database_flags {
39      name  = "max_connections"
40      value = "200"
41    }
42
43    database_flags {
44      name  = "slow_query_log"
45      value = "on"
46    }
47
48    # ログ設定
49    insights_config {
50      query_insights_enabled  = true
51      query_plans_per_minute  = 5
52      query_string_length     = 1024
53      record_application_tags = true
54    }
55  }
56
57  deletion_protection = true  # 本番環境では必須
58
59  lifecycle {
60    prevent_destroy = true
61  }
62}
63
64# データベース作成
65resource "google_sql_database" "wordpress_dbs" {
66  count    = 10
67  name     = "wordpress_db_${count.index + 1}"
68  instance = google_sql_database_instance.wordpress.name
69  charset  = "utf8mb4"
70  collation = "utf8mb4_unicode_ci"
71}
72
73# ユーザー作成
74resource "google_sql_user" "wordpress_users" {
75  count    = 10
76  name     = "wp_user_${count.index + 1}"
77  instance = google_sql_database_instance.wordpress.name
78  password = random_password.db_passwords[count.index].result
79
80  lifecycle {
81    ignore_changes = [password]  # パスワード再生成を防ぐ
82  }
83}

6. トラブルシューティングチェックリスト Link to heading

接続できない時の確認順序 Link to heading

1. ネットワーク層
   □ Cloud SQLのIPアドレスは正しいか?
   □ VPCネットワークは同じか?
   □ Private Service Connectionは設定されているか?
   □ ファイアウォールルールは適切か?
   □ pingは通るか?
   □ ポート3306は開いているか?

2. 認証層
   □ ユーザー名は正しいか?
   □ パスワードは正しいか?
   □ Secret Managerと同期されているか?
   □ ユーザーは存在するか?
   □ ホスト制限は適切か?

3. SSL層
   □ require_ssl設定は正しいか?
   □ 証明書は有効期限内か?
   □ 証明書のパスは正しいか?
   □ プライベートIP接続ならSSL不要か確認

4. データベース層
   □ データベースは存在するか?
   □ ユーザーに権限はあるか?
   □ Cloud SQLは起動しているか?
   □ メンテナンス中ではないか?

5. アプリケーション層
   □ wp-config.phpの設定は正しいか?
   □ PHPのmysqli拡張は有効か?
   □ 接続プーリングは適切か?
   □ タイムアウト設定は十分か?

コマンド集 Link to heading

 1# === Cloud SQL情報取得 ===
 2# インスタンス詳細
 3gcloud sql instances describe INSTANCE_NAME
 4
 5# IPアドレス取得
 6gcloud sql instances describe INSTANCE_NAME \
 7  --format="value(ipAddresses[0].ipAddress)"
 8
 9# 状態確認
10gcloud sql instances describe INSTANCE_NAME \
11  --format="value(state)"
12
13# === データベース管理 ===
14# データベース一覧
15gcloud sql databases list --instance=INSTANCE_NAME
16
17# ユーザー一覧
18gcloud sql users list --instance=INSTANCE_NAME
19
20# パスワード変更
21gcloud sql users set-password USER_NAME \
22  --instance=INSTANCE_NAME \
23  --password="NEW_PASSWORD"
24
25# === 接続テスト ===
26# ポート疎通確認
27nc -zv DB_IP 3306
28
29# MySQLクライアント接続
30mysql -h DB_IP -u USER_NAME -p
31
32# === Secret Manager ===
33# シークレット一覧
34gcloud secrets list
35
36# シークレット取得
37gcloud secrets versions access latest --secret=SECRET_NAME
38
39# === ログ確認 ===
40# Cloud SQLエラーログ
41gcloud logging read \
42  'resource.type="cloudsql_database"
43   AND logName=~"mysql.err"' \
44  --limit 50
45
46# 認証エラー検索
47gcloud logging read \
48  'resource.type="cloudsql_database"
49   AND textPayload=~"Access denied"' \
50  --limit 10

まとめ Link to heading

Cloud SQL接続の3大ポイント Link to heading

  1. プライベートIP接続 + SSL無効化

    • セキュアかつシンプル
    • 証明書管理不要
    • パフォーマンス向上
  2. Secret Managerでパスワード管理

    • 平文でのパスワード保存を避ける
    • 定期的な同期スクリプト実行
    • lifecycle.ignore_changesで再生成防止
  3. 段階的なデバッグ

    • ネットワーク → 認証 → SSL → アプリケーション
    • Cloud Loggingを活用
    • 各層で確実に検証

よくあるミスと対策 Link to heading

ミス対策
パスワード不一致同期スクリプトの定期実行
SSL証明書エラープライベートIP接続ならSSL無効化
ネットワーク疎通なしPrivate Service Connection確認
権限不足IAMロールとデータベース権限の両方確認

次のステップ Link to heading

  • Cloud SQL Proxyの導入検討
  • 読み取りレプリカの設定
  • パフォーマンスチューニング
  • 監視とアラート強化

参考リンク Link to heading


この記事のコード Link to heading

GitHub: infra-ai-agent

関連ファイル:


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