WordPress運用をKubernetes(GKE)へ移行する流れ
Kubernetesの利用頻度が増えてきたので、個人VPSで運営しているWordPressを Google Kubernetes Engine (GKE)(cloud.google.com)に移行しようと思いました。新規で構築する手順は公式のチュートリアル(kubernetes.io)をはじめまとまっている情報を見かけるのですが、既存の運営データを引っ越す過程から記載している情報を目にしなかったので、それらも記載しています。
これまでのVPS運営に具体的な問題があるわけではないのですが、仕事とは別に個人で運用することで新たに気づく事も多いので、意味のあるトライと位置づけています。
WordPress を GKE で動かす方法として、別の記事を作成しました。本記事執筆当時よりは Kubernetes の扱いにも慣れていることから、あわせて参考にしてください。

本記事の前提
- Dockerに関する多少の理解があること(コンテナ、イメージ、タグなどの意味がわかること)
- GCPでGKEセットアップが完了していること(GKEセットアップ手順は記載していません)
kubectl get node
にてクラスタノードが確認できればOK
- データベースはCloud SQL (MySQL)(cloud.google.com)を利用します
- プライベート運用のお遊びリソースなので、コストはできるだけ安くしたい。そのため、以下の通りKubernetesベストプラクティス構成と比べて若干のイレギュラーがあります
- GKE(広義でのKubernetesという意味も含む)におけるサービスのパブリック公開・ロードバランスは通常、Ingressリソース(kubernetes.io)が使われますが本構成では利用していません。そのため、作成手順についても触れていません。
(GKEの
nodePort
に対して、always free(cloud.google.com)枠のインスタンスにNginxをセットアップしてReverse Proxyしています。この構成は別途記事にしたいと思っている) - 負荷の高くないWordPressサイトなので、シングルコンテナでの運用としています。トラフィックに対する可用性は非常に低いです。(但し、Kubernetesオーケストレーションによる障害復帰があるのでNode故障などでの手間はかからないはず)
- GKE(広義でのKubernetesという意味も含む)におけるサービスのパブリック公開・ロードバランスは通常、Ingressリソース(kubernetes.io)が使われますが本構成では利用していません。そのため、作成手順についても触れていません。
(GKEの
- WordPressコンテナイメージは Docker Official Imagesのwordpress(hub.docker.com) を利用します
- 最初はコンテナ構成にNginxを利用予定でしたが、不都合があり最終的にはApacheとしました。詳細は後述
- [要注意] GKEクラスタの作成時に
-enable-ip-alias
オプションを有効にしています。この有無による影響の正確な確認・検証はしていないのですが、ONとする必要があるかもしれません。(Ingressによるサービス提供ならば関係ありません)- IP Aliasについては、ドキュメント(cloud.google.com)に記載の通り、Podに対してVPCネットワーク内から疎通できるようになるオプションです。間もなくデフォルトになる旨がWebコンソール画面に記載されています(2019/01/05時点はまだ)。
記事の流れ
- WordPressイメージの特徴を把握する
- 既存WordPressのデータを移行する
- サイトのデータ(
.php
など)を移行する - MySQLのデータを移行する
- サイトのデータ(
- GKEにWordPressをデプロイする
- Nginx(別ホスト)経由で Reverse Proxy してインターネットに公開する
WordPressイメージの特徴を把握する
wordpressのレポジトリ(hub.docker.com)には多数のタグが並んでいますが、5.0.2-php7.1-apache
を例に見ていきます。多少バージョンが違っても考え方は同じなようです。
- Dockerfile: wordpress/Dockerfile at f63c0db6593494e8fee0e542af9413e22c1da15b · docker-library/wordpress(github.com)
- 序盤はApacheのセットアップなので、とりあえず気にする必要はないでしょう
- L49-55にて指定WordPressのソースを取得し、
/usr/src/wordpress
に配置しています - 最終行で、
apache2-foreground
のコマンドをENTRYPOINT
であるdocker-entrypoint.sh
に渡しています
- docker-entrypoint.sh(docker-entrypoint.sh): wordpress/docker-entrypoint.sh at f63c0db6593494e8fee0e542af9413e22c1da15b · docker-library/wordpress(github.com)
- L26で引数が
apache2*
かphp-fpm
かを判定しています。L29-41周辺にあるuser/group設定のためのようです。 - L48では、
index.php
およびwp-includes/version.php
が存在しない場合の処理が記載されています。これらは対象ディレクトリにWordPressが配置されているかどうか(≒初期セットアップが完了しているかどうか)をチェックしているようで、存在しない場合はDockerfileで記載のあった/usr/src/wordpress
からデータを配置しているようです。 - L87には TODO の字と共に、以下の記載があります。
- TODO handle WordPress upgrades magically in the same way, but only if wp-includes/version.php’s $wp_version is less than /usr/src/wordpress/wp-includes/version.php’s $wp_version
- upgradeに関する処理を実装しようとしているように見えますが、つまり現時点ではイメージとしてWordPressのupgradeを行う処理はしていないと言えます。これは逆に都合がよくて、WordPressをお使いの方ならお分かりの通りWordPressは優秀なアップグレード機構を持っています。管理画面からボタン1個でアップグレード操作を行うことができますので、現時点はWordPressアップグレードのためにDocker Imageタグを指定し直す必要はないと考えてもよいでしょう。
- L89以降は環境変数に関する処理が続きます。
wp-config.php
ファイルの位置を定義するなど気になる処理もありますが、今回要件では無視してもよさそうです。 - L148,L159周辺に記載されている通り、
wp-config.php
が存在しない場合はwp-config-sample.php
の内容をもとに生成しています。ここで、環境変数に設定した値で置き換える処理が入っていますので、移行にあたってはwp-config.php
を一度消しておくことにします。(移行元のをそのまま利用して値だけ手動で書き換えても問題ないはずです)
- L26で引数が
だいたい特徴がつかめたので、移行作業を進めます。
Step1 既存WordPressのデータを移行する
事前に準備しておくもの
- 既存WordPressサイトの公開コンテンツデータ(例:public_html):
public_html.tar.gz
とする - 既存WordPressサイトのMySQLデータ:
wordpress.dump.gz
とする
1-1 サイトのデータ(.phpなど)を移行する
1-1-1 永続ボリュームの作成
まず、Kubernetesクラスタに永続ディスクのボリュームを用意する必要があります。といってもGCPのコンソールからディスクを作成するのではなく、kubectl
コマンドを用いて作成することができます。
Kubernetesにおけるディスクボリュームを作成(要求)する処理が、PersistentVolumeClaim
と呼ばれるリソースです。以下のコンフィグ(yml
)を kubectl apply -f pvc.yml
として作成します(PVC = PersistentVolumeClaimの略)。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wordpress-disk
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
実行結果:
# WordPressデプロイ先のネームスペースを作成。`wordpress` として作る。
$ kubectl create namespace wordpress
namespace "wordpress" created
# PVCを作成。`-n` で操作対象のネームスペースを指定する。
$ kubectl -n wordpress apply -f pvc.yml
persistentvolumeclaim "wordpress-disk" created
# 作成されたPVCを確認する
$ kubectl -n wordpress get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
wordpress-disk Bound pvc-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX 1Gi RWO standard 1X
# PVCにより作成された永続ボリュームの実態(PersistentVolume = PV)を確認する
$ kubectl -n wordpress get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX 1Gi RWO Delete Bound wordpress/wordpress-disk standard 1X
ポイント:
ReadWriteOnce
で作成していますので、1つのコンテナからしかマウントできません。複数コンテナでスケールする必要がある場合は、ReadWriteMany
なボリューム(NFSサービスなど)を作成する必要があります。- GCEのディスクでは
ReadWriteMany
なボリュームは作成できません: Persistent Volumes - Kubernetes(kubernetes.io)
- GCEのディスクでは
kind: StorageClass
を作成しない場合は標準HDDで作成されます。SSDが必要な場合はStorageClass
から作成する必要がありますが、今回は扱いません。必要な場合はこちらを参照してください:Storage Classes - Kubernetes(kubernetes.io)- コンテンツ量が大きくないので最小のストレージサイズを要求しています。500Miで指定したところ作成された実態は1GiBボリュームでしたので、このコンフィグでも
1Gi
としています。大量のコンテンツを保有している場合は、適当なサイズに変更してください。 - PVの
RECLAIM POLICY
がDelete
となっています。**危ない!!**このままだとPVCを削除したときにボリュームが消えてしまいます=コンテンツが消失します。なかなか消すことはないと思いますが、念の為消えないように修正しておきます(下記参照)。
RECLAIM POLICY
の修正:
# RECLAIM POLICY を Delete から Retain に変更する
$ kubectl -n wordpress patch pv pvc-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX -p '{"spec": {"persistentVolumeReclaimPolicy":"Retain"}}'
persistentvolume "pvc-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" patched
patch
に続くサブコマンドはpv
です(pvc
ではありません!)ので注意- 改めて
get pv
することで変更されていることを確認できるはずです
- 改めて
1-1-2 移行用の一時的なコンテナの作成
さて、先のステップで作成したボリュームにデータをコピーします。このあと出てくるwordpresイメージのデプロイ前にはデータを用意しておきたいので、データ移行用の一時的なPodを作成して kubectl cp
コマンドにて配置することとします。(他に良いやり方があれば教えていただけると嬉しいです!)
データ移行用のPodはbusybox
コンテナにて作成します。busybox
はサイズが1MB程度の非常にコンパクトなイメージですので、このような用途にはもってこいです。コンフィグは以下の通り。
datacopy-pod.yml:
apiVersion: v1
kind: Pod
metadata:
name: data-copy
spec:
containers:
- name: sleep-pod
image: busybox
command:
- sleep
- "3600"
volumeMounts:
- name: disk
mountPath: "/tmp/wp-disk"
volumes:
- name: disk
persistentVolumeClaim:
claimName: wordpress-disk
- 見ての通り busybox を
sleep 3600
として起動しているのみです。1時間もあればデータ移行作業終わるでしょう、ということでの数字ですが、もっと時間がほしい場合は調整してください。 - 先程作成したPVC(ディスク)を
/tmp/wp-disk
のパスにマウントしています。つまり、持っていきたいデータは最終的にここに配置すればOKです。この後の wordpress イメージでは改めて別の path にマウントしますので、ここは適当で構いません。
# Podの作成
$ kubectl -n wordpress apply -f datacopy-pod.yml
pod "data-copy" created
$ kubectl -n wordpress get all
NAME READY STATUS RESTARTS AGE
pod/data-copy 1/1 Running 0 18s
# 持っていくコンテンツを public_html.tar.gz として、コンテナにコピー `cp` する
$ kubectl -n wordpress cp ./public_html.tar.gz data-copy:/tmp
# 転送完了後、レスポンスメッセージは特になし。
# データ移行用コンテナにINする
$ kubectl -n wordpress exec -it data-copy sh
/ #
# データがあることを確認しましょう。
$ cd /tmp/
$ ls -al
# ...その後、よしなにデータを配置します
# Ownerにも注意しましょう。Apacheの場合は www-data (id 33) です
# 前述の通り、`wp-config.php` は自動生成するので一旦削除(あるいはリネームで退避)しておきます!!
$ eixt
自動生成される wp-config.php
の情報源は wp-config-sample.php
ですので、ボリュームに無い場合は配置しておきましょう。
WordPress/wp-config-sample.php at master · WordPress/WordPress(github.com)
コピーが済んだらデータ移行用Podは削除します。
$ kubectl -n wordpress delete -f datacopy-pod.yml
pod "data-copy" deleted
$ kubectl -n wordpress get all
NAME READY STATUS RESTARTS AGE
pod/data-copy 1/1 Terminating 0 5m
# Terminatingの後しばらく放っておくと消えます
ここまでで wordpress コンテンツのデータ移行は完了しました。
1-2 MySQLのデータを移行する
続いて MySQL データを移行します。まず、CloudSQLに対してどのように接続するのか?を考える必要があります。手段はいくつか考えられますが、今回あまりシビアなデータ移行は求めていない & mysqldump サイズがさほど大きくない(100MB以内)ということで、ローカルの macOS から Cloud SQL Proxy 経由でインポートすることとします。(要は、ローカル環境からインターネット経由での mysql 接続となります)
Cloud SQL Proxy とは何ぞや?という方はこちらのドキュメントを参照ください。 Cloud SQL Proxy について | Cloud SQL for MySQL | Google Cloud(cloud.google.com)
Cloud SQL Proxy を使用して Cloud SQL インスタンスにアクセスする利点は以下のとおりです。
- 安全な接続: プロキシは、TLS 1.2 と 128 ビット AES 暗号を使用して、データベースとの間で送受信されるトラフィックを自動的に暗号化します。クライアントとサーバーの ID の確認には、SSL 証明書が使用されます。
- 簡単な接続管理: プロキシが Cloud SQL との認証を処理するので、静的な IP アドレスを提供する必要がなくなります。
MySQLサービスまでの安全な接続経路を担保してくれるもの、ということですね。
Cloud SQL を利用しない場合はVM上での作成あるいはコンテナでのMySQL作成になると思いますが、普通にmysql
コマンドで実施してもらえればよいので、Cloud SQL Proxyの手順は不要です。
1-2-1 Cloud SQL Proxy コンテナの起動
用意するもの:
INSTANCE_CONNECTION_NAME
: Cloud SQL に接続するための接続名。GCPプロジェクト名:リージョン:インスタンス名
という文字列ですが、Cloud SQL のWebコンソールから確認できます。- CloudSQLへの接続権限を持ったサービスアカウントのキーファイル(.json形式)。詳細は公式ドキュメントに丁寧に記載されています。
接続構成概要は以下の通り。
引用: Cloud SQL Proxy について | Cloud SQL for MySQL | Google Cloud(cloud.google.com)
準備ができたら mysqldump
ファイルを有する環境(今回はローカルマシン)にて、以下のコマンドで Cloud SQL Proxy を立ち上げます。
$ docker run -it \\
-v /path/to/keyfile.json:/config \\
-p 127.0.0.1:3306:3306 \\
gcr.io/cloudsql-docker/gce-proxy:1.12 /cloud_sql_proxy \\
-instances=<INSTANCE CONNECTION NAME>=tcp:0.0.0.0:3306 -credential_file=/config
...
2019/01/03 06:35:59 Listening on 0.0.0.0:3306 for <INSTANCE CONNECTION NAME>
2019/01/03 06:35:59 Ready for new connections
/path/to/keyfile.json
および <INSTANCE CONNECTION NAME>
の部分は正しい文字に置き換えてください。
Readyとなったあとは、127.0.0.1:3306
にバインドしていますので、mysql -u<USER NAME> -p -h127.0.0.1 -P3306
で接続ができるようになっているはずです。
1-2-2 MySQLユーザーの作成
先程立ち上げた Cloud SQL Proxy コンテナはそのままで、別のコンソールで作業を進めます。
続いてアカウントの作成ですが、root権限でよいか・権限を絞りたいかで作業が変わってきます。
- root権限で良い場合: Cloud SQLのWebコンソールからアカウントを作成すればよいので手っ取り早いです。
- 権限を狭めたい場合: 通常通りMySQLコマンドを用いて
CREATE USER
/GRANT
操作をする必要があります。今回、wordpress
データベースへのアクセス権限のみ設定したいのでこちらの手段をとりますが、内容そのものは一般的なものですので詳細説明は省きます。
MySQL ユーザー | Cloud SQL ドキュメント | Google Cloud(cloud.google.com)
$ mysql -uroot -p$ROOT_PASS -h127.0.0.1 -P3306
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \\g.
Your MySQL connection id is XXXXXX
Server version: 5.7.14-google-log (Google)
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
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.
mysql>
mysql> create database wordpress;
Query OK, 1 row affected (0.12 sec)
mysql> select User,Host from user;
+-----------+-----------+
| User | Host |
+-----------+-----------+
| root | % |
| mysql.sys | localhost |
+-----------+-----------+
2 rows in set (0.10 sec)
mysql> CREATE USER 'wordpress'@'%' IDENTIFIED BY 'XXXXXXXX';
Query OK, 0 rows affected (0.10 sec)
mysql> GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpress'@'%' WITH GRANT OPTION;
Query OK, 0 rows affected (0.10 sec)
mysql> SHOW GRANTS FOR 'wordpress'@'%';
+----------------------------------------------------------------------------+
| Grants for wordpress@% |
+----------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'wordpress'@'%' |
| GRANT ALL PRIVILEGES ON `wordpress`.* TO 'wordpress'@'%' WITH GRANT OPTION |
+----------------------------------------------------------------------------+
2 rows in set (0.10 sec)
mysql> select User,Host from user;
+-----------+-----------+
| User | Host |
+-----------+-----------+
| root | % |
| wordpress | % |
| mysql.sys | localhost |
+-----------+-----------+
3 rows in set (0.10 sec)
1-2-3 データのインポート
ここまでくれば、あとはMySQL知識範囲の操作です。
gzcat wordpress.dump.gz | mysql -uwordpress -p$WORDPRESS_PASS -h127.0.0.1 -P3306 wordpress
- 大量データで時間を要する場合は、コピー元の環境をよく考えましょう。特に、
mysqldump
してからインポートまでの間隔(データ欠損の許容時間)により変わってきます。- 今回、移行時間で失うデータは若干のアクセスログ程度なので普通に移行元サイトをAactiveのまま並行作業しています。
- CloudSQLと同じリージョンのGCEインスタンスからやればレイテンシは小さくなります。
- macOSからの操作なので、
zcact
ではなくgzcat
を利用しています- gzipファイルを展開せずに行数指定で中身を確認する方法 | DevelopersIO(dev.classmethod.jp)
インポート後の結果を確認:
mysql> show tables;
+------------------------+
| Tables_in_wordpress |
+------------------------+
| wp_ak_404_log |
| wp_commentmeta |
| wp_comments |
...
Step2 GKEにWordPressをデプロイする
ようやくデータが整いましたので、ここからサービスをデプロイしていきます。
先程は wordpress 5.0.2 のイメージを対象に内容を確認していましたが、アップグレードに関する一連の流れの動作確認をしておきたかったので、移行元サイトで利用していた wordpress 4.9.8 のイメージで作業を進めます。
2-1 wordpress イメージタグの確認
4.9.8 タグのバリエーションを確認します。registry.hub.docker.com
のAPIに対するGETリクエストで確認する方法が手軽そうです。
# `jq` はJSONフォーマットを操作する定番コマンドです。入ってない場合は `brew install jq` にて入れておきましょう。
$ curl -s <https://registry.hub.docker.com/v1/repositories/wordpress/tags> | jq -r '.[].name' | grep 4.9.8
4.9.8
4.9.8-apache
4.9.8-fpm
4.9.8-fpm-alpine
4.9.8-php5.6
4.9.8-php5.6-apache
4.9.8-php5.6-fpm
4.9.8-php5.6-fpm-alpine
4.9.8-php7.0
4.9.8-php7.0-apache
4.9.8-php7.0-fpm
4.9.8-php7.0-fpm-alpine
4.9.8-php7.1
4.9.8-php7.1-apache
4.9.8-php7.1-fpm
4.9.8-php7.1-fpm-alpine
4.9.8-php7.2
4.9.8-php7.2-apache
4.9.8-php7.2-fpm
4.9.8-php7.2-fpm-alpine
元々 Nginx + php-fpm 構成で運用していたので今回 4.9.8-php*.*-fpm-alpine
を利用するつもりだったのですが、VolueMountまわりで不都合あり一旦見送り、 *-apache
を採用しました。詳細はAppendixにて。
2-2 wordpress リソースコンフィグの作成
先に結論ですが、最終的には以下のコンフィグをKubernetesにデプロイします。
Serviceリソース:
apiVersion: v1
kind: Service
metadata:
name: wordpress
labels:
name: wordpress
spec:
type: NodePort
selector:
app: wordpress
ports:
- name: http
port: 80
targetPort: 80
# GKEクラスタ外からWordPressへ接続するためのポート番号 (範囲: 30000-32767 から指定)
nodePort: 30000
Deploymentリソース:
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress
labels:
app: wordpress
spec:
replicas: 1
selector:
matchLabels:
app: wordpress
strategy:
# kubectl apply 時の挙動。詳細後述 *1
type: RollingUpdate
rollingUpdate:
maxSurge: 0%
maxUnavailable: 100%
template:
metadata:
labels:
app: wordpress
spec:
containers:
# CONTAINER 1: WordPressコンテナ
- name: wordpress
image: wordpress:4.9.8-php7.1-apache
# このコンテナではどの程度の最低リソースを要求するかを記載 *2
resources:
requests:
cpu: 50m
memory: 200Mi
env:
# Cloud SQL に接続するための情報。同一Podなので 127.0.0.1 でアクセスできる
- name: WORDPRESS_DB_HOST
value: "127.0.0.1"
- name: WORDPRESS_DB_NAME
value: "wordpress"
# パスワードのような秘匿情報は Secret リソースから取得する(コンフィグに値を直接書かない)
- name: WORDPRESS_DB_USER
valueFrom:
secretKeyRef:
name: cloudsql-db-credentials
key: username
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: cloudsql-db-credentials
key: password
- name: WORDPRESS_DB_CHARSET
value: "utf8"
- name: WORDPRESS_DB_COLLATE
value: ""
- name: WORDPRESS_TABLE_PREFIX
value: "wp_"
# データ移行用コンテナで準備したボリュームを配置する場所
volumeMounts:
- name: disk
mountPath: "/var/www/html"
# CONTAINER 2: CLOUD SQLコンテナ
- name: cloudsql-proxy
image: gcr.io/cloudsql-docker/gce-proxy:1.11
command: ["/cloud_sql_proxy",
"-instances=<INSTANCE CONNECTION NAME>=tcp:3306",
"-credential_file=/secrets/cloudsql/credentials.json"]
securityContext:
runAsUser: 2 # non-root user
allowPrivilegeEscalation: false
# IAMサービスアカウントのキーファイルをマウントしている。ファイル作成は後述
volumeMounts:
- name: cloudsql-instance-credentials
mountPath: /secrets/cloudsql
readOnly: true
volumes:
- name: disk
persistentVolumeClaim:
claimName: wordpress-disk
- name: cloudsql-instance-credentials
secret:
secretName: cloudsql-instance-credentials
2-3 Secret(秘密情報)の準備
コンフィグ内に出てくる3つのキーを作成します。
- cloudsql-instance-credentials: CloudSQLに接続するためのキーファイル(JSON)
- cloudsql-db-credentials: MySQL接続に必要な以下の値を格納するSecret
- username
- password
KubernetesのSecret系情報を保管する領域に情報を預けるような操作をしていきます。
$ kubectl -n wordpress create secret generic cloudsql-instance-credentials \\
--from-file=credentials.json=/path/to/keyfile.json
# 先の手順で mysqldumpファイルをインポートした時にローカルで利用したものと同じキーファイルを指定する
$ kubectl -n wordpress create secret generic cloudsql-db-credentials \\
--from-literal=username=<string username> --from-literal=password=<string password>
# username / password それぞれの値を記載する。カッコ <> は不要です
作成したら、値が入っていることを確認します。
$ kubectl -n wordpress get secret
NAME TYPE DATA AGE
cloudsql-db-credentials Opaque 2 1d
cloudsql-instance-credentials Opaque 1 1d
2-4 Service / Deployment の作成
あとは、前述の service.yml
および deployment.yml
を kubectl apply
することで起動してくるはずです。順番はどちらが先でも構いません。
Podが Running
になっていることを確認しましょう。
(一瞬 Running
になっていてもしばらくして Crash
していることもあるので数分間確認しておきましょう。kubectl describe pod XXXX
の Events
欄に Warning
が出ていないことを確認するのも有用です。Crash
してしまった場合は、内容に応じて対処が変わってきます。)
$ kubectl -n wordpress get all
NAME READY STATUS RESTARTS AGE
pod/wordpress-XXXXXXXXXX-YYYYY 2/2 Running 0 13h
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/wordpress NodePort 10.X.Y.Z <none> 80:30000/TCP 1d
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deployment.apps/wordpress 1 1 1 1 1d
2-5 Nginx(別ホスト)経由で Reverse Proxy してインターネットに公開する
いよいよInternetへ公開となります。普通ならここでIngressリソース(kubernetes.io)によるロードバランサーを作成するところですが、今回は冒頭に記載の通りコスト成約のため、 always free(cloud.google.com)枠のGCEインスタンスに構築したNginxを経由します。
まず、KubernetesノードのIPを確認します。
$ kubectl describe node | grep IP
InternalIP: 10.0.0.4
ExternalIP: XX.XXX.XXX.C
InternalIP: 10.0.0.3
ExternalIP: XX.XXX.XXX.B
InternalIP: 10.0.0.2
ExternalIP: XX.XXX.XXX.A
# 動かしているKubernetesノードの数により返却数は異なる
ここで確認するのは InternalIP
の部分です。GCEインスタンス(Nginx)からアクセスするのはVPC内部通信になるため。
- [要注意] 冒頭で記載した
-enable-ip-alias
を有効化せずに GKE クラスタ作成をした場合、InternalIP
がどうなるのか未確認です。もしかすると、IP宛通信ができないかもしれません。 ExternalIP
を利用することもできるはずですが、ファイアウォールの設定調整が必要になってくると思われます(未検証)。
続いて、InternalIP:nodePort
に対して疎通確認してみましょう。どのIP宛でも問題ありません。
$ curl -I 10.0.0.4:30180
HTTP/1.1 301 Moved Permanently
Server: Apache/2.X.XX (Debian)
X-Powered-By: PHP/7.X.XX
Location: <http://10.0.0.4/>
Content-Type: text/html; charset=UTF-8
こんな感じでHTTPレスポンスがあればOKです。あとは、Nginxの設定です。
upstream k8snodes {
least_conn;
server 10.0.0.2:30000;
server 10.0.0.3:30000;
server 10.0.0.4:30000;
}
server {
server_name your.domai.n;
listen 443 ssl http2;
...中略...
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_pass <http://k8snodes>;
}
}
Nginxの設定はお使いの環境に応じて調整してください。
まとめ
Kubernetes上へのWordPressの移行作業が完了しました。正確には、移行元サイトで利用していた Let’s Encrypt 証明書の移行などの手順も入ってくるのですが本質ではない話が膨らんでいくので端折っています。
Version 4.9.8 のイメージで移行後、Web管理画面からバージョンアップを試みたところ、最新の 5.0.2 (2019/01/05現在) へのバージョンアップが正常にできることを確認しました。なので、しばらく wordpress イメージを apply
し直す必要はないでしょう。バージョンが不一致で気持ち悪い場合、latest
タグを利用するのも選択肢ですが、docker-entrypoint.sh
の処理がいつ変わるかも分かりませんので、把握できているものに固定化しておく方がよいかとは思います。
今回は求める可用性レベル・少ないKubernetesリソースの有効活用・および作業時間の都合でシングルコンテナ構成となってしまいましたが、いつか ReadWriteMany
ボリュームによる複数コンテナ構成も試したいところです。
Appendix
Nginx + php-fpm 構成を諦めた理由
php-fpm*
のコンテナはその名の通りphp-fpm
しか入っていないので、Webサーバー(Nginx)コンテナが別途必要になります。すると、WordPressのコンテンツファイルは Nginxコンテナ および php-fpmコンテナ 両方に対して必要になってくるのですが、同一ボリュームを複数コンテナからマウントしたらPodが起動できませんでした。NginxコンテナのボリュームをreadOnly: true
としましたが結果変わらずです。
Events:
...
Warning FailedAttachVolume 1m attachdetach-controller Multi-Attach error for volume "pvc-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" Volume is already used by pod(s) wordpress-XXXXXXXXX-XXXXX
Normal SuccessfulAttachVolume 47s attachdetach-controller AttachVolume.Attach succeeded for volume "pvc-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
となると、ReadWriteMany
なボリュームで再設計するか、それか他のうまいやり方を考えるかなのですが・・・。ApacheコンテナならばWebサーバー件PHP実行環境を単一コンテナで実現できるので、一旦 *-apache
で回避することにしました。
コンフィグを修正して apply
し直してもコンテナが入れ替わらない *1
検証中は何回も kubectl apply -f deployment.yml
することになりますが、なかなか新しいPodに切り替わってくれないことがありました。describe
でEvents:
欄を見ると以下の通り Warning
が出ています。
Warning FailedAttachVolume 11m attachdetach-controller Multi-Attach error for volume "pvc-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" Volume is already used by pod(s) wordpress-XXXXXXXXX-XXXXX
入れ替え前のPodがVolumeを掴んだまま新しいPodでVolumeマウントしようとしているため Multi-Attach error が出ているように見えます。デフォルトが RollingUpdate
であることがドキュメントに記載されています。
.spec.strategy specifies the strategy used to replace old Pods by new ones. .spec.strategy.type can be “Recreate” or “RollingUpdate”. “RollingUpdate” is the default value.
Deployments - Kubernetes(kubernetes.io)
というわけで Recreate
に設定して apply
し直したところ、別のエラーが発生。
The Deployment "wordpress" is invalid: spec.strategy.rollingUpdate: Forbidden: may not be specified when strategy `type` is 'Recreate'
既に RollingUpdate
で作成しているのに apply
で変更しようとしたために発生したErrorと思われます。一度 delete
のうえ改めて create
or apply
すればうまくいくのだと思いますが、今後スケールアウト設計にする時が来たら改めて RollingUpdate
にするわけなので、RollingUpdate
のままで Recreate
と同様の挙動になる設定としました。それが以下の値です。
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 0%
maxUnavailable: 100%
strategy
については以下のページが分かりやすいです:
Kubernetes道場 8日目 - ReplicaSet / Deploymentについて - Toku’s Blog(cstoku.io)
リソース要求について *2
Deploymentリソースで以下の記載をしています。
resources:
requests:
cpu: 50m
memory: 200Mi
これらを指定しないでデプロイすると、Kubernetesノードは以下のような内容になります(とある1ノード)。
$ kubectl describe node
Non-terminated Pods: (6 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits
--------- ---- ------------ ---------- --------------- -------------
kube-system fluentd-gcp-v3.1.0-7b4jr 100m (10%) 1 (106%) 200Mi (17%) 500Mi (43%)
kube-system heapster-v1.6.0-beta.1-155fb8fd97-pafmt 138m (14%) 138m (14%) 301856Ki (25%) 301856Ki (25%)
kube-system kube-dns-148976df6c-ra98z 260m (27%) 0 (0%) 110Mi (9%) 170Mi (14%)
kube-system kube-proxy-gke-default-pool-b76614f2-8agq 100m (10%) 0 (0%) 0 (0%) 0 (0%)
wordpress wordpress-XXXXXXXXX-XXXXX 0 (0%) 0 (0%) 0 (0%) 0 (0%)
最終行の wordpress
を見てみると、一律で 0
になっている状態です。指定しなくても動くときは動くのですが、Kubernetesリソースがギリギリの状況では不都合な挙動になると思いますので、不都合を理解のうえ設定しましょう。詳細は以下のページが詳しいです。
Kubernetesで実際のメモリを超えるコンテナアプリを動かすと、どうなるか? - あさのひとりごと(dr-asa.hatenablog.com)
それにしても、fluentdの要求するリソースがちょっと大きいですね・・・。ギリギリコストのインスタンススペックで運用しようと思うと存在感が大きいです。
_phpX.Y_ に関する選定基準
移行元サイトでPHP5.Xを利用していましたが、今回PHP7.Xのイメージに使うに当たり、簡単に(超雑に)ab
でテストしました。
実行環境は以下の通り。
- v1.11.5-gke.5
- Container-Optimized OS from Google
- instance-type=g1-small
- 負荷元: 日本
- 負荷先: 米国リージョン
今回 php7.1 の方が良かったのでこれを選びましたが、試行回数も同一条件で2回程度、同じ傾向なのを確認したうえですがサンプルが少なすぎるのであくまで雰囲気レベルで見てください。
wordpress:4.9.8-php7.1-apache
:
Concurrency Level: 4
Complete requests: 100
Failed requests: 0
Write errors: 0
Requests per second: 5.59 [#/sec] (mean)
Transfer rate: 328.74 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 287 291 4.6 290 321
Processing: 355 397 58.2 373 646
Waiting: 261 299 54.6 277 552
Total: 644 688 60.3 663 936
wordpress:4.9.8-php7.2-apache
:
Concurrency Level: 4
Complete requests: 100
Failed requests: 0
Write errors: 0
Requests per second: 3.76 [#/sec] (mean)
Transfer rate: 220.89 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 287 292 5.5 290 320
Processing: 364 714 925.1 551 7613
Waiting: 269 478 613.2 399 5724
Total: 653 1006 927.0 843 7920