移行しようと思ったきっかけと背景
個人的な話になりますが、2018年頃からXサーバーにて使い始めたWordPressの個人ブログをCloud Runに移行してみました(閉鎖するので移行してみたものの本運用はしません)
注:本題とは関係ない自分語りが冒頭にありますので、ご興味ない方は飛ばしてください
閉鎖する個人ブログですが、色々研究と工夫をして大体250記事近く書いてみた結果、特定の記事で検索結果の上位をとれたり、月間PVが最大で1万5千~2万ほどあったり、GoogleAdsenseの広告収入等も少々入っていたり。ほぼ素人だった者が一生懸命頑張ったなりにそこそこ成果は出ていたのですが。
2020年頃からエンジニア転職して、業務で扱う技術のキャッチアップに追われて全く更新できなくなりました。今後も更新するつもりはないので閉鎖しようと決意。ただせっかくなので、閉鎖する前に前職でよく使っていたCloudRunでWordPress運用できそうだなと考え、個人開発的なノリで実験的に移行してみようと思ったのが今回移行した背景です。
ちなみに放置している今でも僅かに広告収入自体は発生しているようですが、固定費のレンタルサーバー代 - 広告収入 = 良い方向に見積もっても月々マイナス500円くらいです。もしうまくコストを抑えたら、放置していても問題ないかもという願望もありましたが、調査の結果トントンにするのは難しく断念することに..
今回の記事で書くこと・書かないこと
<書くこと>
- XサーバーからCloud Runへの移行の流れ
- Google Cloud構築用のterraformコード
- 簡単な構成、概要説明など
<書かないこと>
- Cloud RunなどのGoogle Cloudリソースの基本的な使い方等
- 細かい移行手順
大まかな流れ
- Xサーバーから保存しているWordPressのデータをエクスポートする
- Google Cloudで使うインフラリソースを構築する
- Cloud SQLにDBをインポートする
- Cloud Storageにデータをインポートする
以下の記事等を参考に進めました
https://zenn.dev/google_cloud_jp/articles/cloudrun-wordpress
https://cloud.google.com/blog/ja/products/serverless/introducing-cloud-run-volume-mounts
https://cloud-ace.jp/column/detail356/
基本構成
以下のGoogle Cloudリソースを構築します
- Cloud Run
- Cloud SQL
- Cloud Storage
- Artifact Registry
- Secret Manager
- IAM
Xサーバーから保存しているWordPressのデータをエクスポートする
以下のファイルをバックアップしてローカル環境に保存します
- WordPressサイトファイル
- データベース (phpMyAdmin)
Google Cloudで使うインフラリソースを構築する
ここからは実際にterraformを使ってインフラリソースを作成します
terraformの構成・コード
以下のように記述しました
ディレクトリ構成
└── wordpress
├── tf
│ ├── db.tf
│ ├── gcp-credential.json
│ ├── provider.tf
│ ├── run.tf
│ ├── terraform.tfstate
│ ├── terraform.tfstate.backup
│ ├── variables.tf
│ └── wp.tfvars
└── wp
├── 000-default.conf
├── Dockerfile
├── apache2.conf
└── public_html
Google Cloudリソース側のterraformコード
Cloud SQL, Cloud Strage
resource "google_sql_database_instance" "wp_db" {
name = "wp-db"
database_version = "MYSQL_8_0"
region = var.region
settings {
tier = "db-f1-micro"
}
lifecycle {
ignore_changes = [
settings[0].version
]
}
deletion_protection = false
}
resource "google_sql_database" "default" {
name = "***com_wp1"
instance = google_sql_database_instance.wp_db.name
}
resource "google_sql_user" "root" {
name = "root"
instance = google_sql_database_instance.wp_db.name
password = var.db_password
}
resource "google_sql_user" "wp_user" {
name = var.db_user
instance = google_sql_database_instance.wp_db.name
password = var.db_password
}
resource "google_storage_bucket" "wp_media" {
name = "${var.project_id}-wp-media"
location = var.region
force_destroy = true
}
Cloud Run, Artifact Registry, Secret Manger, IAM
resource "google_artifact_registry_repository" "repo" {
location = var.region
repository_id = "***-repo"
format = "DOCKER"
description = "Docker repository"
}
resource "google_project_service" "artifact_registry" {
project = var.project_id
service = "artifactregistry.googleapis.com"
}
data "google_project" "project" {
project_id = var.project_id
}
resource "google_secret_manager_secret" "wp_password" {
secret_id = "wp-db-pass"
replication {
auto {}
}
}
resource "google_secret_manager_secret_version" "wp_password" {
secret = google_secret_manager_secret.wp_password.name
secret_data = var.db_password
}
resource "google_project_service" "secretmanager" {
project = var.project_id
service = "secretmanager.googleapis.com"
}
resource "google_service_account" "run_service_account" {
account_id = "run-sa"
display_name = "Cloud Run Service Account"
}
resource "google_storage_bucket_iam_member" "bucket_viewer" {
bucket = "***-wordpress-media"
role = "roles/storage.objectAdmin"
member = "serviceAccount:${google_service_account.run_service_account.email}"
}
resource "google_project_iam_member" "secret_accessor" {
project = var.project_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.run_service_account.email}"
}
resource "google_secret_manager_secret_iam_member" "secret-access" {
secret_id = google_secret_manager_secret.wp_password.id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.run_service_account.email}"
depends_on = [google_secret_manager_secret.wp_password]
}
resource "google_cloud_run_v2_service" "wp_***" {
name = "wp-***"
location = var.region
ingress = "INGRESS_TRAFFIC_ALL"
template {
service_account = google_service_account.run_service_account.email
vpc_access {
egress = "PRIVATE_RANGES_ONLY"
network_interfaces {
network = "default"
subnetwork = "default"
}
}
scaling {
max_instance_count = 1
min_instance_count = 0
}
containers {
image = "${var.region}-docker.pkg.dev/${var.project_id}/***-repo/***-com"
name = "***-com-1"
ports {
container_port = 80
}
env {
name = "DB_HOST"
value = "**.**.**.**"
}
env {
name = "DB_USER"
value = var.db_user
}
env {
name = "DB_PASSWORD"
value_source {
secret_key_ref {
secret = google_secret_manager_secret.wp_password.secret_id
version = "latest"
}
}
}
env {
name = "DB_NAME"
value = var.db_name
}
resources {
cpu_idle = true
limits = {
cpu = "1000m"
memory = "512Mi"
}
}
volume_mounts {
name = "wp-media"
mount_path = "/var/www/html"
}
volume_mounts {
name = "cloudsql"
mount_path = "/cloudsql"
}
}
volumes {
name = "wp-media"
gcs {
bucket = "***-wordpress-media"
read_only = false
}
}
volumes {
name = "cloudsql"
cloud_sql_instance {
instances = [google_sql_database_instance.wp_db.connection_name]
}
}
}
lifecycle {
ignore_changes = [
annotations,
labels,
client,
etag,
generation,
last_modifier,
latest_created_revision,
latest_ready_revision,
observed_generation,
terminal_condition,
update_time,
template[0].containers
]
}
depends_on = [
google_sql_database_instance.wp_db,
google_artifact_registry_repository.***_repo,
google_project_service.secretmanager
]
}
data "google_iam_policy" "noauth" {
binding {
role = "roles/run.invoker"
members = ["allUsers"]
}
}
resource "google_cloud_run_service_iam_policy" "noauth" {
location = google_cloud_run_v2_service.wp_***.location
project = google_cloud_run_v2_service.wp_***.project
service = google_cloud_run_v2_service.wp_***.name
policy_data = data.google_iam_policy.noauth.policy_data
}
resource "google_project_service" "run" {
project = var.project_id
service = "run.googleapis.com"
}
環境変数
variable "project_id" {
type = string
}
variable "region" {
type = string
default = "asia-east1"
}
variable "db_password" {
type = string
}
variable "db_name" {
type = string
}
variable "db_user" {
type = string
}
WP側のコード
Dockerfile
FROM wordpress:latest
COPY ./000-default.conf /etc/apache2/sites-available/000-default.conf
COPY ./apache2.conf /etc/apache2/apache2.conf
COPY ./public_html /var/www/html
WORKDIR /var/www/html
RUN a2enmod rewrite
EXPOSE 80
- 000-default.conf
- apache2.conf
- public_html (WordPressのファイル群)
Cloud Runの設定解説
ボリュームのマウント
2024年春頃にGAしたCloudRunのボリュームマウントがCloud Storageの静的ウェブサイト ホスティング機能を利用できたので採用しました。
公式の記事
https://cloud.google.com/blog/ja/products/serverless/introducing-cloud-run-volume-mounts
yaml
volumeMounts:
- name: wp-media
mountPath: /var/www/html
startupProbe:
timeoutSeconds: 240
periodSeconds: 240
failureThreshold: 1
tcpSocket:
port: 80
volumes:
- name: wp-media
csi:
driver: gcsfuse.run.googleapis.com
volumeAttributes:
bucketName: wp-media
ドメインマッピング
CloudSQLにDBをインポートする
1️️. 移行用のCloud SQLを作成する
2. MySQLのDBをインポートする
<構成>
極力課金を抑える構成として、以下のようなスペックにしました
リージョン:asia-east1 (台湾)
ゾーン:シングルゾーン
マシン構成: 共有コア・1vCPU, 0.614GB
ストレージ:10GB
接続:パブリックIP
料金:$0.01/
※Cloud SQLがGoogle Cloudのリソースでランニングコストが高く、最安の構成でも1日約50円(月額1,500円)ほどかかります。この料金がレンタルサーバーを借りるより高くなってしまったので、移行できても維持せず閉鎖を決めました。
Cloud SQLに接続してMySQLのWordPress用SQLをインポートします
# cloud_sql_proxyを使ってコネクション作成
./cloud_sql_proxy -instances=****:asia-east1:wp-db=tcp:3306
2024/08/12 11:06:03 current FDs rlimit set to 61440, wanted limit is 8500. Nothing to do here.
2024/08/12 11:06:07 Listening on 127.0.0.1:3306 for learngcp-335008:asia-east1:wp-db
2024/08/12 11:06:07 Ready for new connections
2024/08/12 11:06:08 Generated RSA key in 242.316875ms
2024/08/12 11:06:44 New connection for "****:asia-east1:wp-db"
2024/08/12 11:06:44 refreshing ephemeral certificate for instance ****:asia-east1:wp-db
2024/08/12 11:06:45 Scheduling refresh of ephemeral certificate in 55m0s
2024/08/12 11:06:46 New connection for "****:asia-east1:wp-db"
MySQLの操作 (DBをインポート)
mysql -u ***_wp1 -p -h 127.0.0.1
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 602859
Server version: 8.0.31-google (Google)
Copyright (c) 2000, 2024, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| ***_wp1 |
| mysql |
| performance_schema |
| sys |
+--------------------+
mysql -u[ユーザー名] -p[パスワード] [インポートするデータベース名] < [インポートするファイル名]
移行した個人ブログ
ぼかしをかけさせてもらいますが、無事移行できた状態です
CloudRunに移行した欠点としては、やはりコールドスタートによる初回アクセス時のレイテンシーが遅い点です。(数秒レベル) Allways on CPUなどの設定次第で30ms以下にはできそうですが、ランニングコストがかなり高くなるので、許容金額を確認して設定することをお勧めします。
やってみた感想
Cloud Runを使って十分にWordPrssを安価・手軽に利用できるようになり、さらに使い勝手が良いサービスになった印象です。しかしながら、レンタルサーバーでの運用で問題なければCloud Runに移行せずとも良いと感じました。(コールドスタートのレイテンシー、Cloud SQLの運用コストなどがネック)
手順を調べて休日の空き時間等で進めてみたものの、所々でエラーが出ては解消しての繰り返し対応がそこそこ発生して想定より手こずりました・・ ChatGPTやClaudeなどの生成AIのおかげで、IaCのterraformコード作成はかなり時短できた印象です。
普段WordPressを利用していて、コンテナ化したサーバレス環境で運用してみたい方はぜひお試しください。