WordPressをKubernetes に構築する手順(GCP GKE)

feature-image

Kubernetes へ WordPress を設置したので作業経緯を記録する記事です。

この記事が指す Kubernetes は、GKE (Google Kubernetes Engine) です。とはいえ GKE 特有の機能に依存したものではないため、その他 Kubernetes クラスタでも概ね大丈夫だと思います。MySQL の部分だけ Cloud SQL を使うため、一部例外あり。

また、手順だけではなくどうしてそういう設定(構成にしているのか)という点も補足していきます。

今回の記事で作る構成

WordPredd コンテナを1つ構築する方法です。複数台構成ではないので可用性は低下するものの、土台となる Kubernetes は複数台の仮想マシン(ノード)で稼働しているケースが大半でしょうから、一定の可用性を有していることになります。また、SSLの終端にあたり Ingress と Let’s Encrypt を利用しています。

複数コンテナ構成にする場合の課題

想定アクセス数などの非機能要件による設計次第です。コンテナなので replica: 2 にすればいいんじゃないの?となりますが、コンテンツ用のボリュームの考慮が必要なのでそう簡単にはいきません。WordPress コンテンツの変化って何あるの?ということですが、例えば以下の行為によります。

  • 記事の投稿のために画像をアップロードした場合
  • 管理画面から任意のプラグインをインストールシた場合
  • 管理画面上のエディタでテーマを一部書き換えた場合
  • 管理画面上から WordPress そのものをアップグレードした場合
  • …などなど

プラグインやテーマ、アップグレード行為については一切やらない(やる場合は Docker Image でしっかり管理する)という運用もありますが、記事のための画像アップロードについては頻度を考えると現実的ではないでしょう。一切画像を使わないテキストコンテンツならば、まあ大丈夫だけど・・・という感じです。

複数ノード上のコンテナ(Pod)から単一のボリュームを読み書きしようとすると選択肢が非常に限られます。例えばNFSボリュームを使えば解決できますが、手間やコストとの相談ですね。

Persistent Volumes | Kubernetes ― https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes

他の案としては、 wp-content は Docker Image として固めてしまって、アップロード画像のみ外部ストレージで運用する手段も有用です。

この場合、プラグインの追加といったちょっとしたシーンでもビルドしなければならないので面倒ではありますが、コンテナ設計としてはより理想的です。オブジェクトストレージに画像をアップロードするプラグインも別の記事で紹介していますので参考まで。

WordPress メディアファイルをオブジェクトストレージに格納するプラグイン (WP-Stateless)
WordPress メディアファイルをオブジェクトストレージに格納するプラグイン (WP-Stateless)
https://fand.jp/technologies/2022/01/wp-stateless-vs-wp-offload-media-lite/
WordPress でアップロードしたメディアファイルを Cloud Storage へも格納するためのプラグインの説明

長くなりましたが、この記事ではシンプルな構成で構築していきます。

Kubernetes デプロイ設計の方針

Kubernetes 上では、とある「目的」を達成する「手段(設定方法)」が複数あるケースも多く、開発者や運用者の関係など様々な要素により最適解が変わってくる部分です。開発者と運用者が異なる場合は、一般的には運用者が理解できるように優しく(難易度を下げて)提供します。

今回は、開発者と運用者が同一の前提で、ほどほどに使いやすければいいくらいのスタンスです。

前提条件

Kubernetes では Docker の知識がそのまま流用可能ですので、まずは WordPress オフィシャル提供の Docker イメージをローカル環境で構築してみることをおすすめします。WordPress のオフィシャルイメージに関しては、開発環境づくりの記事で記載しました。

Docker でお手軽 WordPress 練習環境の作り方 - MacBook M1/M2版
Docker でお手軽 WordPress 練習環境の作り方 - MacBook M1/M2版
https://fand.jp/technologies/2022/01/how-to-build-a-wordpress-test-environment-with-docker-on-m1-mac-book/
MacBook M1チップセットで WordPress の動作テスト環境をお手軽に構築する方法を説明します。

今回の構成は以下の前提とします。

  • WordPress オフィシャルイメージを利用
  • コンテナ(Pod)構成はシングル
  • データベースはMySQLだが、GCPのマネージドサービス(Cloud SQL)を利用する
  • ロードバランサはマネージドを使う
  • Ingress Controller は Nginx を使う(別用途のサイトも収容しており複数サイトを終端するため)

事前準備

  • CloudSQL にユーザーを作っておく
  • CloudSQL 接続用の鍵(JSONフォーマット)を用意しておく
  • WordPress 接続用 ユーザ・パスワードは環境変数に用意しておく。変数名は何でも構いませんがこの記事では以下の通りとします
    • GCP_CLOUDSQL_INSTANCE_NAME
      • Cloud SQL インスタンス作成時に用意される project-name:region-name:cloudsql-instance-name:3306 のような書式の文字列
    • WORDPRESS_DB_NAME
    • WORDPRESS_DB_USER
    • WORDPRESS_DB_PASSWORD
      • 上記3点は、いわゆる通常の MySQL 接続情報
# 変数リスト
WORDPRESS_DB_NAME=
WORDPRESS_DB_USER=
WORDPRESS_DB_PASSWORD=
GCP_CLOUDSQL_INSTANCE_NAME=

設定値の管理方針

大きくわけると(1)平文で設定値を管理する、(2)暗号化して設定値を管理する、という2種類があります。使い分けが面倒であれば全て暗号化しておくのも手ですが、「今の設定値どうなってるんだっけ」というのをちょっとコマンドやコンフィグファイルで確認したい場合に手間が増えますので、個人で気には情報の質を踏まえて使い分けするのがいいと思います。

  • 秘匿情報ではない設定値は ConfigMap として準備しておく
  • 秘匿情報は、 Secret を作る

上記の通り、平文か暗号化とは別に、設置値をどのようにアプリケーションに適用するかも考える必要があります。環境変数で設定できる場合は原則は環境設定で統一してしまうのがよいでしょう。ただし Docker Image の設計に依りますので、環境変数では対応出来ない場合には設定ファイルを何らかの手段で配置することとします。

デプロイ作業(準備作業)

以降の説明では、 yourns という名前のネームスペースに構築しているものとします。

CloudSQL用のシークレットファイル

/path/to/鍵ファイルの場所 : 任意の場所に CloudSQL 接続用の鍵ファイル(JSON)を配置しておきます。そしてそのファイルを secret に格納します。

kubectl -n yourns create secret generic database-key --from-file=cloudsql.json=/path/to/鍵ファイルの場所

kubectl -n yourns describe secret database-key

環境変数

/path/to/secrets.env : 事前準備の内容に沿って、任意の場所に環境変数のリストを準備しておきます。そして以下のコマンドで secret に流し込みます。

やっていることは単純に、 --from-literal= の書式で Secret を設定しているだけです。こんな面倒くさそうなことをしなくてもいいのですが、ローカルの開発環境のファイルを手っ取り早く格納するのに都合がいいのでこのようにしました。

_secrets=$(cat ~/path/to/secrets.env | sed -e 's/^/--from-literal=/' | tr "\n" ' ')

# 必要に応じて中身を確認
echo $_secrets

# 内容が意図したものであれば、それらを Secret に登録する。
eval "kubectl -n yourns create secret generic env-production $_secrets"

登録に成功したら、以下のように設定されたことを確認できます。

kubectl -n yourns describe secret env-production
Name:         env-production
Namespace:    yourns
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
GCP_CLOUDSQL_INSTANCE_NAME:  XX bytes
WORDPRESS_DB_NAME:           XX bytes
WORDPRESS_DB_PASSWORD:       XX bytes
WORDPRESS_DB_USER:           XX bytes

configMap 作成

configmap.yml

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: wordpress-config
  namespace: yourns
data:
  WORDPRESS_DB_HOST: "127.0.0.1"
  #WORDPRESS_CONFIG_EXTRA: |
  #  define('WP_追加設定したいものがあればここに書ける', true);

書いたら配置。 apply としていますが、初回なら create でも構いません。

kubectl -n yourns apply -f configmap.yml

PHP.ini ファイルセット

PHPの設定をするため、 php.ini を ConfigMap にセットします。ここでは2つのファイルを配置しています。

  • origin.ini : もともと WordPress の Docker Image に用意されている .ini ファイル群をまとめたファイルです。何故改て用意するのかは、この記事の末尾にエラーの経緯を記載しています。
  • upload_max_filesize.ini : WordPress 管理画面での画像アップロードサイズが標準では 2Mbyte しかありません。これでは少々少ないと思いますので拡張します。

upload_max_filesize.ini

upload_max_filesize = 8M

origin.ini

error_reporting = E_ERROR | E_WARNING | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING | E_RECO
VERABLE_ERROR
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /dev/stderr
log_errors_max_len = 1024
ignore_repeated_errors = On
ignore_repeated_source = Off
html_errors = Off
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=2
opcache.fast_shutdown=1
zend_extension=opcache
extension=bcmath.so
extension=gd.so
extension=mysqli.so
extension=zip.so
extension=imagick
extension=exif.so
extension=sodium
Docker でお手軽 WordPress 練習環境の作り方 - MacBook M1/M2版
Docker でお手軽 WordPress 練習環境の作り方 - MacBook M1/M2版
https://fand.jp/technologies/2022/01/how-to-build-a-wordpress-test-environment-with-docker-on-m1-mac-book/
MacBook M1チップセットで WordPress の動作テスト環境をお手軽に構築する方法を説明します。

そしてコマンドを実行します。この例では、 .ini ファイルは conf/ ディレクトリに配置していますが、パス指定が正しければどこでも構いません。

kubectl -n yourns create configmap php-ini-files --from-file=conf/origin.ini --from-file=conf/upload_max_filesize.ini

PVC (Persistent Volume Claim) 作成

コンテナが必要とするディスクを要求するためのリソースです。この例では「1GByteのディスクが欲しい」というシンプルな要求を書いています。

このディスクは、追々 wp-content/ といった失いたくない情報(変化のある情報)を格納するディレクトリ用に使います。

pvc.yml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wordpress-content-disk
  namespace: yourns
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
kubectl -n yourns apply -f pvc.yml
Persistent Volume (永続ボリューム) の詳細

詳細な設定、例えばSSDがいいとか別のプロトコルがいいとか、、といったケースには、どのクラウドプロバイダーを利用しているかによって事情が異なります。例えば、AWSやGCPや、Azureでは・・・といった具合です。

Persitent Volume の詳細はオフィシャルドキュメントを参照してください。

永続ボリューム | Kubernetes ― https://kubernetes.io/ja/docs/concepts/storage/persistent-volumes/

中間チェックリスト

ここまでの設定で、以下の内容が正しく表示されることを確認します。

# cloudsql.json があること
kubectl -n yourns describe secret database-key

# 事前準備をしたDB接続用の情報4つがあること
kubectl -n yourns describe secret env-production

# WordPress 向けの設定情報があること(暗号化していないので中身が表示される)
kubectl -n yourns describe configmap wordpress-config

# PHP設定情報があること(暗号化していないので中身が表示される)
kubectl -n yourns describe configmap php-ini-files

# wordpress-content-disk があること
kubectl -n yourns get pvc

デプロイ作業(本番)

ここからは、いよいよ公開するための作業をしていきます。Kubernetes の基本として、以下のリソースについて予め概念を把握しておくことを推奨します。

  • Pod
  • Service
  • Deployment
  • Ingress

Service

service.yml

apiVersion: v1
kind: Service
metadata:
  name: wordpress
  namespace: yourns
  labels:
    name: wordpress
spec:
  type: NodePort
  ports:
    - name: http
      port: 80
      targetPort: 80
  selector:
    app: wordpress
kubectl -n yourns apply -f service.yml

Deployment

いきなりゴチャっとしますが、まずは完成形を貼り付けます。

  • この記事執筆時点で最新の WordPress 5.8 イメージを利用しています。
  • MySQL (Cloud SQL) への接続のために Proxy を利用しています。Google提供のものです。
  • 応答確認の livenessProbe を設定してはいますがとりあえず感満載です。

Deployment の中身でいくつか、古臭い点や暫定的な内容もあるため文末で説明します。必要に応じて参照してください。

deployment.yml

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
  namespace: yourns
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wordpress
  template:
    metadata:
      labels:
        app: wordpress
    spec:
      containers:
        # CONTAINER: 1
        - name: wordpress
          image: wordpress:5.8
          ports:
            - name: http-port
              containerPort: 80
          livenessProbe:
            httpGet:
              path: /readme.html
              port: http-port
            initialDelaySeconds: 30
            timeoutSeconds: 1
          envFrom:
            - secretRef:
                name: env-production
          env:
            - name: WORDPRESS_DB_HOST
              valueFrom:
                configMapKeyRef:
                  name: wordpress-config
                  key: WORDPRESS_DB_HOST
            - name: WORDPRESS_CONFIG_EXTRA
              valueFrom:
                configMapKeyRef:
                  name: wordpress-config
                  key: WORDPRESS_CONFIG_EXTRA
          volumeMounts:
            - mountPath: /usr/local/etc/php/conf.d
              name: php-ini-files
              readOnly: true
            - name: wordpress-disk
              mountPath: "/var/www/html"

        # CONTAINER: 2
        - name: cloudsql
          image: gcr.io/cloudsql-docker/gce-proxy:1.17
          imagePullPolicy: IfNotPresent
          command:
            - "/cloud_sql_proxy"
            - "-instances=$(GCP_CLOUDSQL_INSTANCE_NAME)"
            - "-credential_file=/credentials/cloudsql.json"
          securityContext:
            runAsUser: 2 # non-root user
            allowPrivilegeEscalation: false
          envFrom:
            - secretRef:
                name: env-production
          volumeMounts:
            - mountPath: /cloudsql
              name: cloudsql
            - mountPath: /credentials
              name: database-key

      volumes:
        - name: php-ini-files
          configMap:
            name: php-ini-files
        - name: wordpress-disk
          persistentVolumeClaim:
            claimName: wordpress-content-disk
        - name: cloudsql
          emptyDir: {}
        - name: database-key
          secret:
            secretName: database-key
注意

Deployment の最初の行にある apiVersion: apps/v1 について、古い記事では extensions/v1beta1 と書かれていますが、 Kubernetes のバージョン 1.16 あたりで廃止されています。利用している Kubernetes のバージョンに注意してください。

そしてリソースを適用します。

kubectl -n yourns apply -f deployment.yml

動作確認(公開前)

この時点ではまだインターネット上に公開していませんが、ポート転送によりローカル環境からのみ接続して確認することが可能な状態になりました。

初期構築ならば一通り完了してから動作確認でもよいと思いますが、マメに確認した方が後々動かなかった時の切り分けがしやすいので、確認しておきましょう。

まずはテストに用いる Pod の名前を確認します。以下の例では wordpress-1abcd2345e-x2k2g です。

注意

注:この時点で STATUS が Running になっていない場合は、そもそもコンテナの起動に失敗していますので何か間違っている状態です。先に進む前に正常化しましょう。

kubectl -n yourns get pod

NAME                         READY   STATUS    RESTARTS   AGE
wordpress-1abcd2345e-x2k2g   2/2     Running   0          XXd

そして、Pod名と動作確認用のポートを指定して以下のコマンド port-forward を実行します。この例では、 localhost:9999 で確認できるようになります。

# Podの名前を入れること
kubectl -n yourns port-forward wordpress-1abcd2345e-x2k2g 9999:80

ここでもしエラーが出た場合、Pod の内部に接続したり、ログを見て調査する必要があります。

  • exec -it を使うとコンテナ(Pod)内に接続できます。
  • -c app では、接続先のコンテナを指定しています。今回の例では Pod 内に WordPress コンテナと Cloud SQL Proxy コンテナの2つがありますので明示する必要があります。
  • 最後に bash と書くことでコンテナ内の bash でターミナル接続ができます。
kubectl -n yourns exec -it wordpress-1abcd2345e-x2k2g -c wordpress -- bash
参考

Pod と Command の間に -- をつけないと警告が出ます。

kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] – [COMMAND] instead.

デプロイ作業(公開:証明書と LB)

ここまでの手順で接続確認ができたらもう完成したようなもので、あとはインターネットに公開するのみです。Ingress などを使って公開しましょう。

最後の仕上げではありますが、ここからの記事は少々前提情報を割愛しています。

Ingress Controller (Nginx) や Let’s Encrypt 自動更新のための Cert Manager が使える前提での手順なのですが、 WordPress のデプロイというメインの目的に対して説明対象がズレてしまいますのでこの記事では省略しています。また、この記事で利用している Cert Manager はバージョンが古いため、最新情報は別途確認が必要です。

こういうやり方もあるのか、という一例として参考にしてください。

証明書作成

certificate.yml

以下のリソースをデプロイすると、自動で Let’s Encrypt を用いた SSL 証明書を作成してくれます。

apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: wordpress-cert
  namespace: yourns
spec:
  commonName: wordpress.example.com
  secretName: wordpress-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - wordpress.example.com
kubectl -n yourns apply -f certificate.yml

作成されたことを確認する。

kubectl -n yourns describe certificate wordpress-cert

確認結果はこんな感じか:

Events:
  Type    Reason        Age    From          Message
  ----    ------        ----   ----          -------
  Normal  GeneratedKey  2m31s  cert-manager  Generated a new private key
  Normal  Requested     2m31s  cert-manager  Created new CertificateRequest resource "wordpress-cert-2840638159"
  Normal  Issued        2m6s   cert-manager  Certificate issued successfully

あるいはこんな感じ:

Status:
  Conditions:
    Last Transition Time:  2022-01-18T05:19:14Z
    Message:               Certificate is up to date and has not expired
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Not After:               2022-04-18T04:19:12Z

Let’s Encrypt の証明書期限3ヶ月を意識することなく自動で更新してくれるので大変便利です。

ローロバランサー(Ingress)作成

ingress.yml

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: wordpress-ingress
  namespace: yourns
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: 8m
spec:
  tls:
    - secretName: wordpress-tls
      hosts:
        - wordpress.example.com
  rules:
    - host: wordpress.example.com
      http:
        paths:
          - backend:
              serviceName: wordpress
              servicePort: 80

これでインターネットへの公開が完了しました。

この例ではhttps://wordpress.example.com でアクセスできることを確認します。

まとめ

WordPress オフィシャルイメージを使って、Kubernetes に配置するための大きな流れを記載しました。Kubernetes へデプロイ方法はいくつかあるなかでの一例になればと思います。

(参考)Deployment に関する補足説明

本文中に記載した Deployment からいくつか抜粋して部分説明します。

spec:
  #... 省略
  template:
    #... 省略
    spec:
      containers:
        - name: wordpress
          image: wordpress:5.8
          ports:
            - name: http-port
              containerPort: 80
          livenessProbe:
            httpGet:
              path: /readme.html
              port: http-port
            initialDelaySeconds: 30
            timeoutSeconds: 1

livenessProbe は、いわゆる死活監視の設定です。死活監視先を謝ると、「死活監視では応答OKなのに実際にはアプリケーションが応答せずにダウンしている」ということもあるので、監視先は重要です。

WordPress の場合は index.php がいいような気はしますが、この例では readme.html とすることで、少なくとも HTTP 応答はできる(但しPHPが応答するかはわからない)くらいのレベル感にしています。

Liveness Probe、Readiness ProbeおよびStartup Probeを使用する | Kubernetes ― https://kubernetes.io/ja/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/

spec:
  #... 省略
  template:
    #... 省略
    spec:
      containers:
        - name: wordpress
          envFrom:
            - secretRef:
                name: env-production
          env:
            - name: WORDPRESS_DB_HOST
              valueFrom:
                configMapKeyRef:
                  name: wordpress-config
                  key: WORDPRESS_DB_HOST
            - name: WORDPRESS_CONFIG_EXTRA
              valueFrom:
                configMapKeyRef:
                  name: wordpress-config
                  key: WORDPRESS_CONFIG_EXTRA

環境変数の設定方法がいくつか混在しています。

  • envFromを利用して全てのConfigMapのデータをコンテナ環境変数として定義します。ConfigMapからのキーがPodの環境変数名になります。
  • valueFrom を使うと、1個1個正確に明示して環境変数を設定します。

一括投入という意味では envFrom でまとめて適用した方が楽ですし、 volueFrom を使うと Deployment コンフィグが長文になりますので見づらくなります。一方、何の設定をしているのかを明確に指示している点では、 envFrom よりも valueFrom での個別指定の方が意思が現れますので作業ミスなどのリスクが低減できます。

よって、チームなどの複数メンバーで運営する場合には vlueFrom を使うべきでしょう。

Podを構成してConfigMapを使用する | Kubernetes ― https://kubernetes.io/ja/docs/tasks/configure-pod-container/configure-pod-configmap/

spec:
  #... 省略
  template:
    #... 省略
    spec:
      containers:
        - name: wordpress
          #... 省略
          volumeMounts:
            - mountPath: /usr/local/etc/php/conf.d
              name: php-ini-files
              readOnly: true
            - name: wordpress-disk
              mountPath: "/var/www/html"

Persistent Volume (永続ディスク)に対して、 /var/www/html を割り当てています。これは WordPress オフィシャルイメージにおける、HTTPサーバーの公開用ディレクトリです。 /var/www/html/wp-content 部分を割り当てるでもいいような気はしますが、管理画面上から WordPress のバージョンアップをGUIで行う場合は WordPrses を構成する PHP ファイル全体への変更が入るはずですので、素直にHTTPのルートディレクトリを割り当てています。

spec:
  #... 省略
  template:
    #... 省略
    spec:
      containers:
        # CONTAINER: 2
        - name: cloudsql
          image: gcr.io/cloudsql-docker/gce-proxy:1.17
          imagePullPolicy: IfNotPresent
          command:
            - "/cloud_sql_proxy"
            - "-instances=$(GCP_CLOUDSQL_INSTANCE_NAME)"
            - "-credential_file=/credentials/cloudsql.json"
          securityContext:
            runAsUser: 2 # non-root user
            allowPrivilegeEscalation: false

この記事ではバージョン 1.17 を指定していますが、本記事執筆時点(2022/02/06)における最新バージョンは 1.28 です。

GoogleCloudPlatform/cloudsql-proxy: Cloud SQL proxy client and Go library ― https://github.com/GoogleCloudPlatform/cloudsql-proxy

自身の検証構成の都合で 1.17 から 1.18 にアップグレードした際に意図せぬエラーが出たことから、暫定処置として 1.17 を指定したのでこのようになっていますが、新しいバージョンを利用するようにしてください。

"-instances=$(GCP_CLOUDSQL_INSTANCE_NAME)" としている点はちょっとしたポイントです。

Deployment は Git 共有などの都合も踏まえて秘匿情報は記載したくありません。コマンド内の文字列も変数化できますので活用しましょう。 $() にて囲んだ部分が変数化できます。 ${} だとNGなので気をつけてください。

(参考)発生した問題と解決策

作業途中で以下のエラーが発生しました。このエラー文中の /var/www/html... などのパスは、WordPress オフィシャルの Docker コンテナ内のパス情報です。

Fatal error: Uncaught Error: Call to undefined function mysql_connect() in /var/www/html/wp-includes/wp-db.php:1688 Stack trace: #0 /var/www/html/wp-includes/wp-db.php(632): wpdb->db_connect() #1 /var/www/html/wp-includes/load.php(558): wpdb->__construct(‘wordpressf0’, ‘pence.kaput.dat…’, ‘wordpress_headl…’, ‘127.0.0.1’) #2 /var/www/html/wp-settings.php(124): require_wp_db() #3 /var/www/html/wp-config.php(133): require_once('/var/www/html/w…') #4 /var/www/html/wp-load.php(50): require_once('/var/www/html/w…') #5 /var/www/html/wp-blog-header.php(13): require_once('/var/www/html/w…') #6 /var/www/html/index.php(17): require('/var/www/html/w…') #7 {main} thrown in /var/www/html/wp-includes/wp-db.php on line 1688

このエラーそのものは、PHPの設定ファイル (php.ini)に以下の記載をすれば解決します。

extension=mysqli

ですが、この基本的なコンフィグが無効になっているわけはありません。原因は、アップロードサイズの上限を拡張するために追加設定をした部分にありました。

PHPのconfディレクトリを上書きしないように注意

まず、WordPress オフィシャルイメージの PHP ファイルは以下の通り。 $PHP_INI_DIR という変数が予め用意されています。そして、細かい設定単位で .ini ファイルが配置されています。mysqli もありますね。

$ find $PHP_INI_DIR/conf.d/
/usr/local/etc/php/conf.d/
/usr/local/etc/php/conf.d/error-logging.ini
/usr/local/etc/php/conf.d/opcache-recommended.ini
/usr/local/etc/php/conf.d/docker-php-ext-opcache.ini
/usr/local/etc/php/conf.d/docker-php-ext-bcmath.ini
/usr/local/etc/php/conf.d/docker-php-ext-gd.ini
/usr/local/etc/php/conf.d/docker-php-ext-mysqli.ini
/usr/local/etc/php/conf.d/docker-php-ext-zip.ini
/usr/local/etc/php/conf.d/docker-php-ext-imagick.ini
/usr/local/etc/php/conf.d/docker-php-ext-exif.ini
/usr/local/etc/php/conf.d/docker-php-ext-sodium.ini

これに対して、 upload_max_filesize を設定するために Kubernetes の ConfigMap を利用しましたが、どのようにマウントされるかというと以下の通りになります。 $PHP_INI_DIR/conf が上書きされてしまっていますね。

# コンテナ内部 with configmap volumes

/usr/local/etc/php
/usr/local/etc/php/conf.d
/usr/local/etc/php/conf.d/..data
/usr/local/etc/php/conf.d/..2022_01_18_08_55_07.759485203
/usr/local/etc/php/conf.d/..2022_01_18_08_55_07.759485203/upload_max_filesize.ini
/usr/local/etc/php/conf.d/upload_max_filesize.ini

注意: **/etc/config/**ディレクトリに何かファイルがある場合、それらは削除されます。

公式ドキュメントの説明でも明確に上書きと書かれています。

Podを構成してConfigMapを使用する | Kubernetes ― https://kubernetes.io/ja/docs/tasks/configure-pod-container/configure-pod-configmap/#configmapに保存されているデータをボリュームに入力する

既存ファイルを上書きせずに追加したいコンフィグのみを都合よく配置する方法が思いつかなかったので(Dockerfile を弄ることも今回はしたくなかったので)、力技ですが既存ファイルの設定を吐き出してマウントすることにします。

# 設定書き出し用のワンライナー
cd $PHP_INI_DIR
find conf.d/ -type f | xargs more | sed 's/:/;/g' | sed 's/conf.d/; conf.d/' > origin.txt

実際に吐き出したコンフィグは以下の通り。変更の頻度は高くないと思うので、あまり問題にならないはずです。

;;;;;;;;;;;;;;
; conf.d/error-logging.ini
;;;;;;;;;;;;;;
error_reporting = E_ERROR | E_WARNING | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING | E_RECO
VERABLE_ERROR
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /dev/stderr
log_errors_max_len = 1024
ignore_repeated_errors = On
ignore_repeated_source = Off
html_errors = Off
;;;;;;;;;;;;;;
; conf.d/opcache-recommended.ini
;;;;;;;;;;;;;;
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=2
opcache.fast_shutdown=1
;;;;;;;;;;;;;;
; conf.d/docker-php-ext-opcache.ini
;;;;;;;;;;;;;;
zend_extension=opcache
;;;;;;;;;;;;;;
; conf.d/docker-php-ext-bcmath.ini
;;;;;;;;;;;;;;
extension=bcmath.so
;;;;;;;;;;;;;;
; conf.d/docker-php-ext-gd.ini
;;;;;;;;;;;;;;
extension=gd.so
;;;;;;;;;;;;;;
; conf.d/docker-php-ext-mysqli.ini
;;;;;;;;;;;;;;
extension=mysqli.so
;;;;;;;;;;;;;;
; conf.d/docker-php-ext-zip.ini
;;;;;;;;;;;;;;
extension=zip.so
;;;;;;;;;;;;;;
; conf.d/docker-php-ext-imagick.ini
;;;;;;;;;;;;;;
extension=imagick
;;;;;;;;;;;;;;
; conf.d/docker-php-ext-exif.ini
;;;;;;;;;;;;;;
extension=exif.so
;;;;;;;;;;;;;;
; conf.d/docker-php-ext-sodium.ini
;;;;;;;;;;;;;;
extension=sodium