# WordPress with Nginx Helm Chart このHelmチャートは、Nginx + WordPress (PHP-FPM) 構成をKubernetes上にデプロイします。 **bitnami/wordpress**のように、デプロイ後すぐに使用可能な状態で起動します。 ## 主な機能 ✅ **自動インストール**: 初回デプロイ時にWordPressを自動セットアップ ✅ **自動パスワード生成**: 管理者パスワードを指定しない場合は自動生成 ✅ **アップデート対応**: 既存のDBがある場合は初期化をスキップ ✅ **ads.txt対応**: values.yamlから ads.txt を配置可能 ✅ **セキュアな構成**: WordPress本体は使い捨て、wp-contentのみ永続化 ✅ **本番環境対応**: セキュアなSecret管理、HA構成 ## アーキテクチャとセキュリティ ### ストレージ構成 ``` /var/www/html/ ← emptyDir(使い捨て、Pod再起動で消える) ├── index.php ← WordPress本体ファイル ├── wp-admin/ ← 管理画面(使い捨て) ├── wp-includes/ ← WordPressコア(使い捨て) ├── wp-config.php ← Secretから毎回生成 ├── ads.txt ← values.yamlから生成 └── wp-content/ ← シンボリックリンク → PVC /var/www/html-persistent/ ← PVC(永続化) └── wp-content/ ← テーマ、プラグイン、アップロード ├── themes/ ├── plugins/ └── uploads/ ``` ### セキュリティ上の利点 1. **WordPress本体の改ざん防止**: 毎回クリーンな状態から起動 2. **脆弱性への迅速な対応**: イメージ更新のみでコアファイル更新 3. **設定の一元管理**: wp-config.phpはvalues.yamlとSecretから生成 4. **ユーザーデータの保護**: wp-contentのみ永続化で管理が容易 ## アーキテクチャ - **Nginx**: リバースプロキシおよび静的ファイル配信 - **WordPress (PHP-FPM)**: WordPressアプリケーション実行 - **共有ボリューム**: Nginx と WordPress 間でファイルを共有 ## 前提条件 - Kubernetes 1.19+ - Helm 3.0+ - PersistentVolume プロビジョナー(永続化を有効にする場合) - MySQL/MariaDB データベース(別途デプロイが必要) ## インストール方法 ### 1. チャートの準備 ```bash # チャートディレクトリの作成 mkdir -p wordpress-nginx/templates # 必要なファイルをコピー # Chart.yaml, values.yaml, templates/* ``` ### 2. MySQLのデプロイ(必要な場合) ```bash # Helm を使用して MySQL をデプロイ helm repo add bitnami https://charts.bitnami.com/bitnami helm install mysql bitnami/mysql \ --set auth.rootPassword=rootpassword \ --set auth.database=wordpress \ --set auth.username=wordpress \ --set auth.password=changeme ``` ### 3. WordPressのデプロイ ```bash # デフォルト値でインストール helm install my-wordpress ./wordpress-nginx # カスタム値でインストール helm install my-wordpress ./wordpress-nginx \ --set wordpress.dbPassword=your-secure-password \ --set service.type=LoadBalancer # values.yaml を使用 helm install my-wordpress ./wordpress-nginx -f custom-values.yaml ``` ## 設定パラメータ ### 基本設定 | パラメータ | 説明 | デフォルト値 | |-----------|------|-------------| | `replicaCount` | レプリカ数 | `2` | | `image.nginx.registry` | Nginxイメージレジストリ | `docker.io` | | `image.nginx.repository` | Nginxイメージリポジトリ | `nginx` | | `image.nginx.tag` | Nginxイメージタグ | `1.29.3-alpine-perl` | | `image.wordpress.registry` | WordPressイメージレジストリ | `docker.io` | | `image.wordpress.repository` | WordPressイメージリポジトリ | `wordpress` | | `image.wordpress.tag` | WordPressイメージタグ | `6.8.3-php8.4-fpm-alpine` | ### WordPress設定 | パラメータ | 説明 | デフォルト値 | |-----------|------|-------------| | `wordpress.dbHost` | データベースホスト | `mysql-service` | | `wordpress.dbName` | データベース名 | `wordpress` | | `wordpress.dbUser` | データベースユーザー | `wordpress` | | `wordpress.dbPassword` | データベースパスワード | `changeme` | | `wordpress.tablePrefix` | テーブルプレフィックス | `wp_` | | `wordpress.siteTitle` | サイトタイトル | `My WordPress Site` | | `wordpress.adminUser` | 管理者ユーザー名 | `admin` | | `wordpress.adminPassword` | 管理者パスワード(空=自動生成) | `""` | | `wordpress.adminEmail` | 管理者メール | `admin@example.com` | | `wordpress.adsTxt.enabled` | ads.txtを有効化 | `false` | | `wordpress.adsTxt.content` | ads.txtの内容 | `""` | ### SMTP設定(メール送信) WordPressコンテナ内にはメール送信を行うMTA(sendmail相当のプログラム)が存在しないため、デフォルトのままではパスワードリセットメールやコメント通知などの `wp_mail()` によるメール送信ができません。外部SMTPリレー(Gmail、SendGrid、自前のメールサーバー等)経由で送信する場合は以下を設定してください。 | パラメータ | 説明 | デフォルト値 | |-----------|------|-------------| | `smtp.enabled` | SMTP機能有効化 | `false` | | `smtp.host` | SMTPサーバーホスト名 | `smtp.example.com` | | `smtp.protocol` | プロトコル(auto/starttls/tls) | `auto` | | `smtp.port` | ポート番号 | `587` | | `smtp.auth.enabled` | 認証有効化 | `true` | | `smtp.auth.username` | SMTPユーザー名 | `smtp-user@example.com` | | `smtp.auth.password` | SMTPパスワード | `""`(Secretで指定) | | `smtp.from` | 送信元メールアドレス(固定) | `noreply@example.com` | | `smtp.tls.verify` | TLS証明書検証 | `true` | | `smtp.tls.allowSelfSigned` | 自己署名証明書を許可 | `false` | 有効化すると、WordPressコンテナ起動時に `msmtp` を導入し、PHPの `sendmail_path` を `msmtp` 経由に設定します。`wp_mail()` はコード変更なしにそのままSMTP経由で送信されるようになります。 **設定例**: ```yaml smtp: enabled: true host: "smtp.gmail.com" protocol: "starttls" port: 587 auth: enabled: true username: "your-email@gmail.com" password: "" # --set smtp.auth.password='your-app-password' で指定 from: "noreply@your-domain.com" ``` ```bash helm upgrade --install my-wordpress . \ -f values.yaml \ --set smtp.auth.password='your-app-password' ``` **よくある設定**: | プロバイダ | ホスト | ポート | プロトコル | |-----------|-------|--------|----------| | Gmail | smtp.gmail.com | 587 | starttls | | Office365 | smtp.office365.com | 587 | starttls | | Sendgrid | smtp.sendgrid.net | 587 | starttls | | Amazon SES | email-smtp.region.amazonaws.com | 587 | starttls | **注意**: `smtp.enabled: true` の場合、msmtpのインストール(`apk add`)のためWordPressコンテナはrootとして起動し、セットアップ完了後に `php-fpm` へexecします。PHP-FPMのワーカープロセス自体はイメージ標準の設定により引き続き `www-data`(非root)で動作するため、実際のPHPコード実行権限は変わりません。`smtp.enabled: false`(デフォルト)の場合は従来どおり非rootユーザー(uid82)でコンテナが起動します。 ### Service設定 | パラメータ | 説明 | デフォルト値 | |-----------|------|-------------| | `service.type` | Serviceタイプ | `LoadBalancer` | | `service.port` | Serviceポート | `80` | | `service.externalTrafficPolicy` | 外部トラフィックポリシー(`LoadBalancer`/`NodePort`のみ有効) | `Local` | **実クライアントIPの取得について(ベアメタル/MetalLB構成)**: このチャートのデフォルト構成(ルーター → MetalLB等のLoadBalancer Service → Pod)では、`service.externalTrafficPolicy` が `Cluster`(Kubernetesのデフォルト)だと、kube-proxyが送信元IPをノードの内部IPにSNATしてしまい、Nginx/WordPressが実際の訪問者IPを取得できません。本チャートではデフォルトで `externalTrafficPolicy: Local` を設定し、SNATを回避することで `$remote_addr` に実IPがそのまま渡るようにしています。 - **注意**: `Local` にすると、リクエストを受けたノードにPodが存在しない場合はそのノードでの接続が失敗します。MetalLB L2モードなど、Podが存在するノードにのみトラフィックが向く構成と組み合わせて使用してください(複数ノードにPodを分散させることを推奨)。 - `service.type: ClusterIP` の場合、`externalTrafficPolicy` は無視されます(Kubernetes仕様上ClusterIPには適用不可のため)。 - **kube-vip(ARP方式)でServiceに直接VIPを払い出している場合は要注意**: kube-vipはL2 ARP応答に加えて自前でTCP接続をプロキシする実装のため、`externalTrafficPolicy: Local` を設定してもkube-vipの時点で送信元IPが失われ、効果がありません(`type: ClusterIP` + kube-vipアノテーションによる手動VIP付与構成は特に該当します)。この場合は下記「kube-vip等でグローバルIP/VIPが限られている環境での実IP取得」の共有Ingress Controller構成を参照してください。 `nginx.forwardRealIP` は、Ingress ControllerやCDN(Cloudflareなど)、外部LBのように **X-Forwarded-For ヘッダーを付与するリバースプロキシを前段に置く場合にのみ**有効にしてください。本チャートのデフォルト構成(Service直下にPodがぶら下がる構成)ではX-Forwarded-Forを付与する層が存在しないため、`forwardRealIP` を有効にしても効果はなく、`service.externalTrafficPolicy: Local` のみで実IPが取得できます。 ### Ingress設定 | パラメータ | 説明 | デフォルト値 | |-----------|------|-------------| | `ingress.enabled` | Ingressを有効化 | `false` | | `ingress.className` | IngressClass名 | `nginx` | | `ingress.annotations` | Ingressアノテーション | `{}` | | `ingress.hostname` | プライマリホスト名 | `wordpress.example.com` | | `ingress.path` | パス | `/` | | `ingress.pathType` | パスタイプ | `Prefix` | | `ingress.tls` | TLS有効化(自動設定) | `false` | | `ingress.extraHosts` | 追加ホスト設定 | `[]` | | `ingress.extraTls` | 追加TLS設定 | `[]` | **TLS自動設定**: `tls: true` にすると、`hostname` を使用して自動的に以下が設定されます: - hosts: `[hostname]` - secretName: `{hostname-with-dash}-tls`(例: `example-com-tls`) ### 永続化設定 | パラメータ | 説明 | デフォルト値 | |-----------|------|-------------| | `persistence.enabled` | 永続化を有効化(wp-contentのみ) | `true` | | `persistence.storageClass` | StorageClass | `""` | | `persistence.accessMode` | アクセスモード | `ReadWriteOnce` | | `persistence.size` | ストレージサイズ(wp-content用) | `10Gi` | **注意**: WordPress本体ファイル(wp-admin、wp-includesなど)はemptyDirに配置され、Pod再起動時に破棄されます。ユーザーデータ(wp-content)のみがPVCに永続化されます。 ## 使用例 ### 基本的なインストール(パスワード自動生成) ```bash helm install my-wordpress ./wordpress-nginx \ --set wordpress.dbPassword=SecurePassword123 \ --set wordpress.siteTitle="My Blog" \ --set wordpress.adminEmail=admin@example.com # パスワードの取得 kubectl get secret my-wordpress-wordpress-nginx-secret \ -o jsonpath='{.data.admin-password}' | base64 -d echo ``` ### パスワードを指定してインストール ```bash helm install my-wordpress ./wordpress-nginx \ --set wordpress.dbPassword=SecurePassword123 \ --set wordpress.adminPassword=AdminPass456 \ --set wordpress.adminUser=myadmin ``` ### ads.txt を配置 values.yamlで設定: ```yaml wordpress: adsTxt: enabled: true content: | google.com, pub-1234567890, DIRECT, f08c47fec0942fa0 adserver.com, 9876, RESELLER ``` またはコマンドラインで: ```bash helm install my-wordpress ./wordpress-nginx \ --set wordpress.dbPassword=SecurePassword123 \ --set wordpress.adsTxt.enabled=true \ --set-string wordpress.adsTxt.content="google.com, pub-1234567890, DIRECT, f08c47fec0942fa0" ``` ads.txtはConfigMapとして管理され、Nginxコンテナに直接マウントされます。 ### LoadBalancerでの公開 ```bash helm install my-wordpress ./wordpress-nginx \ --set service.type=LoadBalancer \ --set wordpress.dbPassword=SecurePassword123 ``` ### Ingressでの公開 #### 基本的なIngress(HTTPのみ) ```bash helm install my-wordpress ./wordpress-nginx \ --set ingress.enabled=true \ --set ingress.hostname=wordpress.example.com \ --set service.type=ClusterIP ``` #### TLS有効化(自動設定) ```bash helm install my-wordpress ./wordpress-nginx \ --set ingress.enabled=true \ --set ingress.hostname=wordpress.example.com \ --set ingress.tls=true \ --set service.type=ClusterIP ``` これで自動的に以下が設定されます: - TLS Secret名: `wordpress-example-com-tls` - TLS対象ホスト: `wordpress.example.com` #### cert-managerと組み合わせ ```bash helm install my-wordpress ./wordpress-nginx \ --set ingress.enabled=true \ --set ingress.hostname=blog.example.com \ --set ingress.tls=true \ --set ingress.annotations."cert-manager\.io/cluster-issuer"=letsencrypt-issuer \ --set ingress.annotations."acme\.cert-manager\.io/http01-ingress-class"=nginx ``` または values.yaml で: ```yaml ingress: enabled: true hostname: blog.example.com tls: true annotations: cert-manager.io/cluster-issuer: "letsencrypt-issuer" acme.cert-manager.io/http01-ingress-class: "nginx" nginx.ingress.kubernetes.io/from-to-www-redirect: "true" nginx.ingress.kubernetes.io/proxy-body-size: "100m" ``` #### 複数ホスト設定 ```bash helm install my-wordpress ./wordpress-nginx -f - < kube-vip.io/loadbalancerIPs- kube-vip.io/vipHost-` 等で削除してください)。DNS(またはルーターの名前解決)側で各ホスト名をIngress ControllerのVIP 1個に向ける設定も必要です。 `nginx.forwardRealIP.trustedProxies` のデフォルトには `10.0.0.0/8` が含まれているため、Cilium/Flannel等のPod CIDRがこの範囲内であれば追加設定は不要です。異なるPod CIDRを使用している場合は values.yaml で調整してください。 ### リソース制限のカスタマイズ ```bash helm install my-wordpress ./wordpress-nginx \ --set resources.nginx.limits.memory=1Gi \ --set resources.wordpress.limits.memory=2Gi ``` ## 初期化の動作 ### 新規インストール時 1. WordPressコアファイル(wp-content以外)をemptyDirにコピー(使い捨て領域) 2. wp-contentディレクトリのPVCが空の場合のみ、イメージ同梱のデフォルトwp-content(デフォルトテーマ、Akismet、Hello Dolly等)で初期化(永続化領域) 3. wp-config.phpをSecretから生成 4. データベース接続を確認 5. テーブルが存在しない場合: - WP-CLIを使用してWordPressをインストール - 管理者アカウントを作成 - パスワードが未指定の場合は16文字のランダム生成 6. ads.txtが有効な場合は配置(使い捨て領域) ### アップデート/再起動時 1. WordPressコアファイル(wp-content以外)を新しくコピー(常にクリーン) 2. 既存のwp-content(PVC)はそのまま利用(**wp-content配下は一切上書きしない**。デフォルトプラグイン等を削除していればその状態を維持) 3. wp-config.phpを再生成 4. データベーステーブルの存在を確認 5. テーブルが存在する場合: - 初期化処理をスキップ - コアバージョンの更新確認 - 必要に応じてデータベーススキーマをアップデート 6. 既存データ(wp-content)を保持したまま起動 **注意(v7.1.0-aで修正)**: それ以前は、WordPressコアファイルのコピー処理が `cp -r /usr/src/wordpress/* /var/www/html/` という実装になっており、`wp-content` を除外していませんでした。このため、Pod再起動のたびにイメージ同梱のデフォルトwp-content(Hello Dolly、Akismet、デフォルトテーマ等)がPVC上の既存wp-contentへ**毎回上書きマージ**され、ユーザーが削除したはずのプラグインが復活する不具合がありました。v7.1.0-aでコアファイルのコピーからwp-contentを除外し、PVC上のwp-contentは新規インストール時(PVCが空の場合)以外は一切触らないよう修正しています。 ### セキュリティ更新の適用方法 ```bash # イメージタグを更新して再デプロイするだけ helm upgrade my-wordpress ./wordpress-nginx \ --set image.wordpress.tag=6.8.4-php8.4-fpm-alpine # Pod再起動で自動的にクリーンなWordPress本体に置き換わる kubectl rollout restart deployment/my-wordpress-wordpress-nginx ``` ### ファイル構成の確認 ```bash # WordPress本体(emptyDir - 使い捨て) kubectl exec -it -c wordpress -- ls -la /var/www/html/ # wp-content(PVC - 永続化) kubectl exec -it -c wordpress -- ls -la /var/www/html-persistent/wp-content/ # シンボリックリンクの確認 kubectl exec -it -c wordpress -- ls -la /var/www/html/ | grep wp-content ``` ### 管理者パスワードの確認方法 ```bash # Secretから取得 kubectl get secret -wordpress-nginx-secret \ -o jsonpath='{.data.admin-password}' | base64 -d # または initContainer のログから確認(初回のみ) kubectl logs -c wordpress-init ``` ```bash # 設定を変更してアップグレード helm upgrade my-wordpress ./wordpress-nginx \ --set wordpress.dbPassword=NewPassword # values.yamlを使用してアップグレード helm upgrade my-wordpress ./wordpress-nginx -f custom-values.yaml ``` ## アンインストール ```bash helm uninstall my-wordpress ``` ## トラブルシューティング ### 初期化ログの確認 ```bash # initContainer のログを確認 kubectl logs -c wordpress-init # メインコンテナのログ kubectl logs -c nginx kubectl logs -c wordpress ``` ### 管理者パスワードの再確認 ```bash # Secretから取得 kubectl get secret my-wordpress-wordpress-nginx-secret \ -o jsonpath='{.data.admin-password}' | base64 -d && echo ``` ### データベース接続の確認 ```bash # WordPress コンテナで WP-CLI を実行 kubectl exec -it -c wordpress -- wp db check kubectl exec -it -c wordpress -- wp db tables ``` ### 再初期化が必要な場合 ```bash # wp-contentのみを保持して再インストール # (データベースをクリアすれば再初期化される) kubectl exec -it -c wordpress -- wp db reset --yes # 完全なクリーンインストール(PVCごと削除) kubectl delete pvc helm uninstall my-wordpress helm install my-wordpress ./wordpress-nginx ``` ### ストレージの確認 ```bash # PVCの確認(wp-contentのみ) kubectl get pvc kubectl exec -it -c wordpress -- du -sh /var/www/html-persistent/wp-content/ # emptyDirの使用量確認 kubectl exec -it -c wordpress -- du -sh /var/www/html/ ``` ### ads.txt の確認 ```bash # Pod内でファイルを確認 kubectl exec -it -c nginx -- cat /var/www/html/ads.txt # ブラウザまたはcurlでアクセス curl http://your-site.com/ads.txt ``` ## セキュリティ考慮事項 本番環境では以下を必ず実施してください: 1. **データベースパスワードの保護**: Kubernetes Secretを使用 2. **HTTPS の有効化**: cert-manager等でTLS証明書を設定 3. **リソース制限の設定**: 適切なresources設定 4. **定期的なバックアップ**: wp-content(PVC)とデータベースのバックアップ 5. **セキュリティアップデート**: - WordPress本体: イメージタグ更新 → Pod再起動で自動適用 - プラグイン/テーマ: WordPress管理画面から更新 6. **wp-config.phpのセキュリティ**: Secretに保存され、Pod再起動時に再生成 7. **読み取り専用ファイルシステム**: WordPress本体は毎回クリーン、改ざん不可 ### この構成のセキュリティメリット - ✅ WordPress本体への不正な変更を防止(Pod再起動で復元) - ✅ wp-config.phpへの直接アクセス不可(Secretから生成) - ✅ 脆弱性対応が容易(イメージ更新のみ) - ✅ ユーザーデータのみを管理(wp-contentのみバックアップ) - ✅ 設定の一元管理(values.yaml + Secret)