Files
phpfpm/README.md
T
claude f8aaa5944d
Helm Chart Release / release-chart (push) Successful in 6s
docs: nginx.customConfig の取り違えと client_max_body_size 重複を防ぐ記述に修正
あるプロジェクトから「serverSnippet が効かない」「snippet は古いキー」との
指摘があり調査した結果、指摘の前提は誤りだった。

- 実際に使われる templates/configmap.yaml が読むのは snippet で、values.yaml と
  README も snippet で一致している。
- serverSnippet / httpSnippet は、.Values.nginx.enabled が存在せず一度も出力
  されない configmap-nginx.yaml だけが読むキーで、values・README に載ったことが
  なく、指定しても黙って無視される。
- README 例6・例9 が snippet に client_max_body_size を書く手順になっており、
  v8.5.9-b 以降は uploadMaxSize の自動出力と重複して nginx が起動しない
  (2026-09-29 の障害の発生源)。例6 は location ~ \.php$ も重複定義していた。

挙動の変更はなくドキュメント・コメントのみ。
- values.yaml / README: 読まれるキーは snippet のみ、挿入先は server ブロック内、
  client_max_body_size は書かず uploadMaxSize を使うことを明記
- README 例6・例9 を uploadMaxSize 指定に修正
- configmap-nginx.yaml: 未使用であることを示す注記を追加

手動リリース 8.5.11-a。
2026-10-20 20:38:07 +09:00

1245 lines
46 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PHP-FPM Helm Chart
Alpine LinuxベースのNginx + PHP-FPM環境をKubernetesにデプロイするためのHelmチャートです。Composer、Selenium、外部データベース接続など、本格的なPHPアプリケーション実行に必要な機能を統合しています。
## 特徴
- 🚀 **軽量**: Alpine Linuxベースで高速起動
- 📦 **Composer対応**: 依存パッケージ管理を柔軟にカスタマイズ
- 🌐 **Nginx統合**: PHP-FPMとNginxのマルチコンテナ構成
- 🔧 **カスタマイズ可能**: Nginx設定、PHP拡張を自由に追加
- 🤖 **Selenium統合**: ブラウザ自動化・スクレイピングに対応
- 💾 **永続化ストレージ**: PVCによるデータ永続化
- 🔐 **外部DB接続**: MySQL/MariaDB接続機能を内蔵
- 📊 **本番対応**: Ingress、TLS、リソース管理に対応
## クイックスタート
### 前提条件
- Kubernetes 1.19+
- Helm 3.0+
- PV プロビジョナー(persistence有効時)
### 基本インストール
```bash
# Helmリポジトリを追加
helm repo add cafepieters https://git.cafepieters.com/api/packages/helmchart/helm
helm repo update
# デフォルト設定でインストール
helm install my-phpfpm cafepieters/phpfpm
# カスタム設定でインストール
helm install my-phpfpm cafepieters/phpfpm -f my-values.yaml
# 特定のバージョンをインストール
helm install my-phpfpm cafepieters/phpfpm --version 2.0.0
```
### ソースからのインストール
```bash
# リポジトリのクローン
git clone https://git.cafepieters.com/helmchart/phpfpm.git
cd phpfpm
# デフォルト設定でインストール
helm install my-phpfpm .
# カスタム設定でインストール
helm install my-phpfpm . -f my-values.yaml
```
### 動作確認
```bash
# Podの確認
kubectl get pods -l app.kubernetes.io/name=phpfpm
# サービスの確認
kubectl get svc -l app.kubernetes.io/name=phpfpm
# ログ確認
kubectl logs -l app.kubernetes.io/name=phpfpm -c php-fpm
kubectl logs -l app.kubernetes.io/name=phpfpm -c nginx
```
## アーキテクチャ
```
┌─────────────────────────────────────────────┐
│ Kubernetes Pod │
│ │
│ ┌──────────────┐ ┌─────────────────┐ │
│ │ Nginx │─────▶│ PHP-FPM │ │
│ │ :80 │ │ :9000 │ │
│ └──────────────┘ │ + Composer │ │
│ │ │ + Extensions │ │
│ │ └─────────────────┘ │
│ │ │ │
│ ┌──────▼──────────────────────▼────────┐ │
│ │ Persistent Volume (PVC) │ │
│ │ /var/www/html │ │
│ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
│ │
▼ ▼ SELENIUM_URL(自動注入)
┌─────────────────────────────┐ ┌──────────────────────────────┐
│ External Database (Optional)│ │ Selenium Deployment(Optional)│
│ MySQL/MariaDB │ │ 独立Pod・独立Service :4444 │
└─────────────────────────────┘ └──────────────────────────────┘
```
> **Selenium は独立した Deployment/Service(v8.5.10-b以降)**。以前は上の Pod 内の
> サイドカーコンテナだったが、Selenium 単体の不調(起動失敗・OOM等)で Pod 全体が
> Service の Endpoints から外れ、サイト全体が停止する問題があったため分離した
> (下記トラブルシューティング参照)。
## 主要な設定パラメータ
### 基本設定
| パラメータ | 説明 | デフォルト |
|-----------|------|-----------|
| `replicaCount` | Podのレプリカ数 | `1` |
| `image.php.tag` | PHPバージョン | `8.5.2-fpm-alpine3.23` |
| `image.nginx.tag` | Nginxバージョン | `1.29.4-alpine-perl` |
| `service.type` | Serviceタイプ | `LoadBalancer` |
| `service.port` | Serviceポート | `80` |
| `uploadMaxSize` | アップロード最大サイズ(nginx/PHP両方に反映) | `"100m"` |
> **📌 アップロードサイズ上限について**
> `uploadMaxSize` の値は、以下**すべて**に同時に反映されます。どれか一つでも小さいと、そこで上限に達して失敗するため、この1つの値で揃える設計です。
> - nginx(Podコンテナ内)の `client_max_body_size`
> - PHPの `upload_max_filesize` / `post_max_size`
> - `ingress.enabled: true` の場合、ingress-nginxの `nginx.ingress.kubernetes.io/proxy-body-size`(明示指定があればそちらが優先)
>
> 単位は `k` / `m` / `g`(大文字小文字問わず)が使用できます(例: `"200m"`, `"1g"`)。
>
> 大きなファイルをPHP側でメモリに読み込んで処理する場合は、PHPの `memory_limit`(デフォルト128M程度)が不足する可能性があります。その場合は `phpEnv` 等ではなくカスタムイメージ、または将来的な設定拡張で対応してください。
### 永続化ストレージ
| パラメータ | 説明 | デフォルト |
|-----------|------|-----------|
| `persistence.enabled` | PVC有効化 | `true` |
| `persistence.size` | ストレージサイズ | `1Gi` |
| `persistence.accessMode` | アクセスモード | `ReadWriteOnce` |
> **📁 PVCマウント構成(php-fpmコンテナ)**
> php-fpm コンテナには PVC の**ルート**が `/var/www` にマウントされます。
>
> - `/var/www/html` — **公開フォルダ**(nginxが配信するWebルート)
> - `/var/www/<任意のフォルダ>` — **非公開の格納先**(PHPからは読み書きできるが、Web経由では公開されない)
>
> nginx コンテナは `html` サブパスのみをマウントするため、`html/` 以外のディレクトリはWebサーバーのファイルシステムに存在せず、設定ミスでも公開されることはありません。設定ファイル・秘密鍵・アップロード受け皿・ログなど、公開したくないファイルの置き場として利用できます。
### Composer設定
| パラメータ | 説明 | デフォルト |
|-----------|------|-----------|
| `composer.enabled` | Composer有効化 | `false` |
| `composer.workingDir` | Composer導入先ディレクトリ(`{workingDir}/vendor/autoload.php`) | `"/composer"` |
| `composer.packages` | インストールパッケージ | `[]` |
| `composer.useComposerJson` | composer.json使用 | `false` |
| `composer.composerJson` | composer.json内容 | `""` |
| `composer.installOptions` | installオプション | `"--no-dev --optimize-autoloader"` |
| `composer.additionalPhpExtensions` | 追加PHP拡張 | `[]` |
| `composer.additionalApkPackages` | 追加APKパッケージ | `[]` |
### Python設定(オプション)
PHPからPythonスクリプトを呼び出す用途(データ処理・機械学習ライブラリの利用など)向けに、Python仮想環境(venv)を導入できます。
| パラメータ | 説明 | デフォルト |
|-----------|------|-----------|
| `python.enabled` | Python有効化 | `false` |
| `python.venvDir` | venv導入先ディレクトリ(`{venvDir}/bin/python3`) | `"/venv"` |
| `python.packages` | pipでインストールするパッケージ | `[]` |
| `python.useRequirementsTxt` | requirements.txt使用 | `false` |
| `python.requirementsTxt` | requirements.txtの内容 | `"requests>=2.31.0"` |
| `python.installOptions` | pip installオプション | `"--no-cache-dir"` |
| `python.additionalApkPackages` | 追加APKパッケージ(ネイティブ拡張のビルド用) | `[]` |
> **📌 Python導入先について**
> Composerと同様、デフォルトの `/venv` は**Webルート外かつPVCにマウントされない使い捨て領域**です。Pod再起動のたびにクリーンな仮想環境へ再構築されます。PVCに永続化したい場合は `python.venvDir` に `/var/www` 配下(例: `/var/www/venv`)を指定してください。
>
> PHPコードからは `exec()` 等で呼び出します:
> ```php
> exec('/venv/bin/python3 /var/www/scripts/process.py 2>&1', $output, $exitCode);
> ```
>
> **⚠️ 注意**: `numpy` や `pillow` など、Alpine(musl)向けの事前ビルド済みwheelが無いパッケージは、Pod起動のたびにソースからビルドされ、Raspberry Pi等のARM環境では起動に数分〜十数分かかる場合があります。ビルドに必要なパッケージ(`gcc`, `musl-dev`, `python3-dev` 等)は `python.additionalApkPackages` に指定してください。永続化(`venvDir`をPVC配下に)すれば再起動のたびの再ビルドを回避できますが、使い捨てによるクリーン環境維持のメリットとのトレードオフになります。
### Selenium設定
v8.5.10-b以降、Seleniumは独立したDeployment/Service(`<release>-phpfpm-selenium`)として起動する(nginx/php-fpmと同一Podのサイドカーではない)。
| パラメータ | 説明 | デフォルト |
|-----------|------|-----------|
| `selenium.enabled` | Selenium有効化 | `false` |
| `selenium.replicaCount` | Selenium Deploymentのレプリカ数 | `1` |
| `selenium.resources` | SeleniumコンテナのCPU/メモリ requests・limits | `{}` |
| `selenium.mountHtml` | PVCの`html`サブパスをSeleniumにもマウントするか(別Podからの同時マウントになるため`true`にする場合は`persistence.accessMode: ReadWriteMany`が必要) | `false` |
| `image.selenium.tag` | Seleniumバージョン | `144.0-chromedriver-144.0` |
`selenium.enabled: true`のとき、php-fpmコンテナの`SELENIUM_URL`環境変数は自動的にこのServiceを指す値(`http://<release>-phpfpm-selenium:4444/wd/hub`)に設定される。`phpEnv.SELENIUM_URL`を独自に指定していても、この自動注入された値が優先される。
### Cron(定期実行)設定
`cron.jobs`に1件以上定義すると、php-fpmと同じPHPイメージ(`image.php`)を使ってスケジュール実行するKubernetes CronJobが、ジョブごとに1つずつ作成される。既定は空配列で、何も作成されない。
| パラメータ | 説明 | デフォルト |
|-----------|------|-----------|
| `cron.jobs` | CronJob定義の配列 | `[]` |
| `cron.jobs[].name` | ジョブ名(英数字とハイフン、CronJob名の一部に使用) | 必須 |
| `cron.jobs[].schedule` | 標準cron形式のスケジュール | 必須 |
| `cron.jobs[].command` | 実行するシェルコマンド(文字列) | 必須 |
| `cron.jobs[].timeZone` | タイムゾーン(Kubernetes 1.27+) | 省略時UTC |
| `cron.jobs[].concurrencyPolicy` | `Allow`/`Forbid`/`Replace` | `Forbid` |
| `cron.jobs[].restartPolicy` | `OnFailure`/`Never` | `OnFailure` |
| `cron.jobs[].successfulJobsHistoryLimit` | 成功ジョブの保持数 | `3` |
| `cron.jobs[].failedJobsHistoryLimit` | 失敗ジョブの保持数 | `1` |
| `cron.jobs[].activeDeadlineSeconds` | 実行タイムアウト(秒) | 省略可 |
| `cron.jobs[].suspend` | `true`でこのジョブだけ一時停止 | `false` |
| `cron.jobs[].resources` | CPU/メモリ requests・limits | `resources.phpfpm`を流用 |
```yaml
# values.yaml
cron:
jobs:
- name: daily-report
schedule: "0 18 * * *"
timeZone: "Asia/Tokyo"
command: "php /var/www/html/cron/daily-report.php"
```
> **📌 CronJobの構成について**
> - PVCルートを`/var/www`にマウントする(php-fpmコンテナと同じ)。`html/`配下の公開コード、または非公開ディレクトリのスクリプト・出力先どちらも利用できる。
> - `externalDatabase` / `phpEnv` / `extraEnv` / `extraEnvFrom` / `SELENIUM_URL`(selenium有効時)は、php-fpmコンテナと同じ内容が渡される。
> - `smtp.enabled: true`の場合、CronJobのPodでも自動的にmsmtpを導入してmsmtprcを配置するため、PHPの`mail()`関数がそのまま使える。
> - **Composer/Pythonとの併用**: `composer.workingDir`/`python.venvDir`が既定の`/composer`・`/venv`(PVC非マウントの使い捨て領域)のままだと、php-fpmの常駐Pod内にしか存在しないためCronJobからは使えない。CronJobから使いたい場合は`composer.workingDir: "/var/www/composer"`のように`/var/www`配下へ変更し、PVC上に永続化すること。
> - CronJobの名前は52文字を超えないよう自動的に切り詰められる(Kubernetesの制約)。`cron.jobs[].name`は短く分かりやすい名前にすること。
> - 生成されるPodのラベル(`app.kubernetes.io/name`)は`<chart>-cron`というバリアント名になっており、nginx/php-fpmのDeploymentのselectorとは一致しない(Selenium分離と同じ設計)。
### 外部データベース設定
| パラメータ | 説明 | デフォルト |
|-----------|------|-----------|
| `externalDatabase.enabled` | 外部DB接続有効化 | `false` |
| `externalDatabase.host` | DBホスト名 | `mariadb-hostname` |
| `externalDatabase.port` | DBポート | `3306` |
| `externalDatabase.database` | データベース名 | `dbname` |
| `externalDatabase.username` | ユーザー名 | `user` |
| `externalDatabase.password` | パスワード | `pass` |
### PHP 汎用環境変数(phpEnv)
| パラメータ | 説明 | デフォルト |
|-----------|------|-----------|
| `phpEnv` | PHP-FPM コンテナに渡す任意の環境変数(キーと値のマップ) | `{}` |
| `extraEnvFrom` | 既存の Secret / ConfigMap をまるごと環境変数として注入(`envFrom` 配列) | `[]` |
| `extraEnv` | 個別の環境変数を追加(生の `env` エントリ配列。`valueFrom`/`secretKeyRef` 可) | `[]` |
PHP 側では `$_ENV['KEY']` または `getenv('KEY')` で取得できます。
`extraEnvFrom` は、別リポジトリの **secret-manager** チャートが各ネームスペースへ配布する
`cred-<名前>` Secret(API キー等)をそのまま読み込む用途を想定しています。認証情報の変更は
secret-manager 側を更新するだけで全プロジェクトに反映できます。
```yaml
extraEnvFrom:
- secretRef:
name: cred-gemini # GEMINI_API_KEY 等が丸ごと環境変数になる
- secretRef:
name: cred-openai
extraEnv:
- name: ANTHROPIC_API_KEY
valueFrom:
secretKeyRef:
name: cred-claude
key: ANTHROPIC_API_KEY
```
### SMTP設定
| パラメータ | 説明 | デフォルト |
|-----------|------|-----------|
| `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.auth.existingSecret` | パスワードを取得する既存 Secret 名(`smtp.auth.password` と自作 Secret の代わり) | `""` |
| `smtp.auth.existingSecretKey` | 上記 Secret 内のキー名 | `password` |
| `smtp.existingMsmtprcSecret` | 完成済み `msmtprc` を持つ既存 Secret 名。指定時はチャートの msmtprc 生成を行わず host/port/auth.* は無視(`from` のみ使用) | `""` |
| `smtp.existingMsmtprcSecretKey` | 上記 Secret 内の `msmtprc` キー名 | `msmtprc` |
| `smtp.from` | 送信元メールアドレス(固定) | `noreply@example.com` |
| `smtp.tls.verify` | TLS証明書検証 | `true` |
| `smtp.tls.allowSelfSigned` | 自己署名証明書を許可 | `false` |
#### secret-manager チャートとの連携(メール設定の一元管理)
**secret-manager** が `cred-gmail` などに生成する完成済み `msmtprc` をそのまま使う場合:
```yaml
smtp:
enabled: true
from: "no-reply@your-domain.com" # PHP の sendmail_from 用に必要
existingMsmtprcSecret: "cred-gmail" # このSecretの msmtprc キーをそのまま利用
```
サーバ設定はチャート側 values に残し、パスワードだけ一元管理したい場合:
```yaml
smtp:
enabled: true
host: "smtp.gmail.com"
port: 587
auth:
enabled: true
username: "no-reply@your-domain.com"
existingSecret: "cred-gmail"
existingSecretKey: "SMTP_PASSWORD"
```
### Ingress設定
| パラメータ | 説明 | デフォルト |
|-----------|------|-----------|
| `ingress.enabled` | Ingress有効化 | `false` |
| `ingress.className` | IngressClass名 | `""` |
| `ingress.annotations` | アノテーション | `{}` |
| `ingress.hosts` | ホスト設定 | `[{host: "example.tld"}]` |
| `ingress.tls` | TLS設定(`secretName`省略可) | `[]` |
> **📌 Ingressのアップロードサイズ上限**
> `ingress.enabled: true` の場合、`nginx.ingress.kubernetes.io/proxy-body-size` アノテーションが `uploadMaxSize` の値で**自動的に**付与されます(明示的に `ingress.annotations` へ指定した場合はそちらが優先されます)。Ingressコントローラーがnginx以外の場合は、`ingress.annotations` にそのコントローラー向けの上限設定を明示的に指定してください。
> **📌 hostとTLS secretNameの共有**
> `ingress.hosts[].host` にYAMLアンカー(例: `&appDomain`)を付けておくと、`ingress.tls[].hosts` で `*appDomain` として再利用でき、ドメイン名を1箇所の変更で揃えられます(デフォルトのvalues.yamlも参照)。
> さらに `ingress.tls[].secretName` は**省略可能**で、省略した場合はそのエントリの最初のhostから自動生成されます(`.`を`-`に置換して`-tls`を付与。例: `example.tld` → `example-tld-tls`)。別の証明書Secret名を使いたい場合のみ `secretName` を明示的に指定してください(指定時はそちらが優先されます)。
### Nginx設定
| パラメータ | 説明 | デフォルト |
|-----------|------|-----------|
| `nginx.customConfig.enabled` | カスタム設定有効化 | `false` |
| `nginx.customConfig.snippet` | `server { }` ブロック末尾へそのまま挿入するnginx設定(下記注意参照) | `""` |
| `nginx.forwardRealIP.enabled` | リアルIP取得有効化 | `false` |
| `nginx.forwardRealIP.header` | リアルIP取得ヘッダー | `"X-Forwarded-For"` |
| `nginx.forwardRealIP.recursive` | 再帰的IP検索 | `true` |
| `nginx.forwardRealIP.trustedProxies` | 信頼するプロキシネットワーク | `["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]` |
| `nginx.forwardRealIP.additionalTrustedProxies` | 追加の信頼プロキシ | `[]` |
> **📌 `nginx.customConfig.snippet` の注意点**
> - 読まれるキーは `snippet` のみです。`serverSnippet` / `httpSnippet` というキーは存在せず、指定しても**無視されます**(過去に作られた未使用テンプレート `configmap-nginx.yaml` 専用のキーで、一度も出力されたことがありません)。
> - 挿入先は `server { }` ブロック内です。`limit_req_zone` など `http` レベル専用のディレクティブは書けません。
> - チャートが自動で出力する設定を重複して書くと、nginxが起動しなくなります。特に **`client_max_body_size` は書かないでください**(`directive is duplicate` で起動失敗)。アップロード上限は `uploadMaxSize` で設定します。
> **📌 sitemap.xml のContent-Type**
> `/sitemap.xml`(静的ファイルとして配置した場合)は、常に `Content-Type: application/xml; charset=utf-8` で配信されます。標準の `mime.types` では `.xml` が `text/xml` 扱いでcharsetが付かず、ブラウザやGoogle Search Consoleなどのツールが正しく解釈できない場合があるための組み込み対応です(設定不要、常時有効)。
> なお、PHPで動的に生成する場合はこの設定は適用されません(PHP側で `header('Content-Type: application/xml; charset=utf-8');` を指定してください)。
## 使用例
### 例1: シンプルなPHPアプリケーション
```yaml
# values.yaml
replicaCount: 2
persistence:
enabled: true
size: 5Gi
service:
type: LoadBalancer
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "512Mi"
cpu: "500m"
```
```bash
helm install my-app cafepieters/phpfpm -f values.yaml
```
### 例2: Composerパッケージ利用
```yaml
# values.yaml
composer:
enabled: true
packages:
- "monolog/monolog:^3.0"
- "guzzlehttp/guzzle:^7.0"
- "vlucas/phpdotenv:^5.0"
installOptions: "--no-dev --optimize-autoloader"
persistence:
enabled: true
size: 2Gi
```
> **📌 Composerの導入先について**
> Composerパッケージはデフォルトで**Webルート外**の `/composer` に導入されます(`composer.workingDir` で変更可能)。
> `vendor/` や `composer.json` がWeb経由で公開されないため、情報漏洩などのセキュリティリスクを回避できます。
>
> `/composer` は**PVCにマウントされない使い捨て領域**です。Pod再起動のたびにクリーンな環境へ再構築されるため、万一不正アクセスでマルウェアを書き込まれても再起動で消え、常にクリーンなComposer環境を維持できます(PVCに永続化したい場合は `/var/www/composer` など `/var/www` 配下を指定)。
>
> PHPコードからは以下のように読み込みます:
> ```php
> require_once '/composer/vendor/autoload.php';
> ```
>
> 旧バージョン(Webルート直下 `/var/www/html` に導入)と同じ動作に戻す場合は `composer.workingDir: "/var/www/html"` を指定してください。
>
> **旧バージョンからのアップグレード**: 旧Webルートに `composer.json` / `composer.lock` / `vendor/` が残っている場合、Pod起動時に自動的に `workingDir` へ移行されます(v8.5.6-b以降)。
### 例3: composer.jsonを使用
```yaml
# values.yaml
composer:
enabled: true
useComposerJson: true
composerJson: |
{
"require": {
"php": ">=8.0",
"slim/slim": "^4.0",
"slim/psr7": "^1.0",
"monolog/monolog": "^3.0"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
installOptions: "--optimize-autoloader"
replicaCount: 3
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
hosts:
- host: api.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: api-tls
hosts:
- api.example.com
```
### 例4: MySQL接続
```yaml
# values.yaml
composer:
enabled: true
packages:
- "illuminate/database:^10.0"
externalDatabase:
enabled: true
host: mysql.default.svc.cluster.local
port: 3306
database: myapp
username: appuser
password: "SecurePassword123"
persistence:
enabled: true
size: 3Gi
```
### 例5: Selenium統合(スクレイピング)
```yaml
# values.yaml
composer:
enabled: true
packages:
- "symfony/dom-crawler:^6.0"
- "symfony/css-selector:^6.0"
- "monolog/monolog:^3.0"
selenium:
enabled: true # php-webdriver/webdriverが自動追加。独立Deployment/Serviceとして起動する
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "3Gi"
cpu: "2000m"
persistence:
enabled: true
size: 5Gi
resources:
nginx:
requests: { memory: "64Mi", cpu: "50m" }
limits: { memory: "128Mi" }
phpfpm:
requests: { memory: "256Mi", cpu: "200m" }
limits: { memory: "512Mi", cpu: "1000m" }
```
**PHPコード例**:
```php
<?php
require_once '/composer/vendor/autoload.php';
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
// Seleniumに接続(SELENIUM_URLはselenium.enabled:trueのとき自動注入される。
// 別Deploymentになったため localhost では到達できない点に注意)
$driver = RemoteWebDriver::create(
getenv('SELENIUM_URL') ?: 'http://<release>-phpfpm-selenium:4444/wd/hub',
DesiredCapabilities::chrome()
);
$driver->get('https://example.com');
echo $driver->getTitle();
$driver->quit();
```
### 例6: カスタムNginx設定
```yaml
# values.yaml
# アップロード上限は snippet ではなく uploadMaxSize で設定する
# (nginx の client_max_body_size を snippet に書くと、チャートの自動出力と重複して起動失敗する)
uploadMaxSize: "100m"
nginx:
customConfig:
enabled: true
# server { } ブロックの末尾にそのまま挿入される。
# PHPの location ~ \.php$ はチャート本体が定義済みのため、ここでは再定義しない
# (タイムアウト等は server レベルで指定すれば PHP の location にも継承される)
snippet: |
fastcgi_read_timeout 300;
fastcgi_send_timeout 300;
location /admin {
auth_basic "Administrator Area";
auth_basic_user_file /etc/nginx/.htpasswd;
}
persistence:
enabled: true
```
> `snippet` で指定する際の注意点は、[Nginx設定](#nginx設定)の「`nginx.customConfig.snippet` の注意点」を参照してください。
### 例7: ベアメタル環境でのリアルIP取得
ベアメタルKubernetesやLoadBalancer経由でのアクセスで、PHPからクライアントの実IPアドレスを取得する設定です。
```yaml
# values.yaml
service:
type: LoadBalancer
nginx:
forwardRealIP:
# リアルIP取得機能を有効化
enabled: true
# リアルIPを取得するヘッダー(環境に応じて変更)
# - X-Real-IP: シンプルな1段プロキシ
# - X-Forwarded-For: 多段プロキシ対応(推奨)
header: "X-Forwarded-For"
# 再帰的にリアルIPを検索(多段プロキシ環境で推奨)
recursive: true
# 信頼するプロキシのネットワーク範囲
# ベアメタルクラスターのPodネットワークとServiceネットワークを指定
trustedProxies:
- "10.0.0.0/8" # Kubernetesデフォルトのクラスタネットワーク
- "172.16.0.0/12" # Dockerデフォルトネットワーク
- "192.168.0.0/16" # プライベートネットワーク
# 追加で信頼するプロキシ(外部LoadBalancerなど)
additionalTrustedProxies:
- "203.0.113.0/24" # 外部LoadBalancerのIPレンジ(例)
persistence:
enabled: true
```
この設定により、PHP側で以下の変数から実IPアドレスが取得できます:
- `$_SERVER['REMOTE_ADDR']` - クライアントの実IPアドレス
- `$_SERVER['HTTP_X_REAL_IP']` - 同上(互換性用)
- `$_SERVER['HTTP_X_FORWARDED_FOR']` - プロキシチェーン全体
**検証用PHPコード:**
```php
<?php
echo "Real IP: " . $_SERVER['REMOTE_ADDR'] . "\n";
echo "X-Real-IP: " . ($_SERVER['HTTP_X_REAL_IP'] ?? 'not set') . "\n";
echo "X-Forwarded-For: " . ($_SERVER['HTTP_X_FORWARDED_FOR'] ?? 'not set') . "\n";
```
**注意事項:**
- `trustedProxies`には、信頼できるプロキシ(LoadBalancer、Ingressコントローラー)のみを指定してください
- 不正なプロキシを信頼すると、IPスプーフィング攻撃のリスクがあります
- `customConfig.snippet`との併用も可能です(競合しません)
### 例8: 画像処理アプリケーション(GD拡張)
```yaml
# values.yaml
composer:
enabled: true
packages:
- "intervention/image:^2.0"
additionalPhpExtensions:
- "gd"
- "exif"
# 注:GDが指定されている場合、依存パッケージ (libpng-dev, libjpeg-turbo-dev, freetype-dev)
# は自動的にインストールされます。手動指定は不要です。
persistence:
enabled: true
size: 10Gi
service:
type: ClusterIP
ingress:
enabled: true
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
hosts:
- host: images.example.com
```
**特記事項**: GD拡張を `additionalPhpExtensions` に指定した場合、以下の依存パッケージが自動的にインストールされます:
- `libpng-dev` - PNG画像処理
- `libjpeg-turbo-dev` - JPEG画像処理
- `freetype-dev` - フォント処理
`additionalApkPackages` で明示的に指定する必要はありません。
### 例9: 本番環境構成(フル機能)
```yaml
# production-values.yaml
replicaCount: 5
image:
php:
tag: "8.5.2-fpm-alpine3.23"
nginx:
tag: "1.29.4-alpine-perl"
composer:
enabled: true
useComposerJson: true
composerJson: |
{
"require": {
"php": ">=8.1",
"slim/slim": "^4.0",
"monolog/monolog": "^3.0",
"vlucas/phpdotenv": "^5.0",
"firebase/php-jwt": "^6.0"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
installOptions: "--no-dev --optimize-autoloader --classmap-authoritative"
additionalPhpExtensions:
- "opcache"
externalDatabase:
enabled: true
host: mysql-primary.database.svc.cluster.local
port: 3306
database: production_db
username: prod_user
password: "{{ .Values.dbPassword }}" # 外部から注入
persistence:
enabled: true
size: 20Gi
storageClass: ssd-storage
service:
type: ClusterIP
port: 80
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/rate-limit: "100"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
hosts:
- host: api.production.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: production-tls
hosts:
- api.production.com
# アップロード上限は snippet ではなく uploadMaxSize で設定する
uploadMaxSize: "50m"
nginx:
customConfig:
enabled: true
snippet: |
fastcgi_read_timeout 300;
# Opcache設定
fastcgi_param PHP_VALUE "
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=0
opcache.validate_timestamps=0
";
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
```
**デプロイ**:
```bash
# Secretを先に作成
kubectl create secret generic db-password --from-literal=password='SuperSecurePass'
# Helmリポジトリからデプロイ
helm install production-api cafepieters/phpfpm \
-f production-values.yaml \
--set externalDatabase.password=$(kubectl get secret db-password -o jsonpath='{.data.password}' | base64 -d)
```
### 例10: APIキーなどの任意環境変数を PHP に渡す(phpEnv)
`phpEnv` を使うと、DB 接続情報と同様に任意のキーと値を PHP-FPM コンテナの環境変数として渡せます。
```yaml
# values.yaml
phpEnv:
API_KEY: "your-api-key"
STRIPE_SECRET: "sk_live_xxxxxxxxxxxxxxxx"
REDIS_URL: "redis://localhost:6379"
EXTERNAL_SERVICE_URL: "https://api.example.com"
```
**PHPでの使用例**:
```php
<?php
// $_ENV または getenv() で取得
$apiKey = getenv('API_KEY');
$stripe = $_ENV['STRIPE_SECRET'];
$redisUrl = getenv('REDIS_URL');
echo "API Key: " . $apiKey;
```
**確認コマンド**:
```bash
# 設定された環境変数を確認
kubectl exec -it <pod-name> -c php-fpm -- env | grep -E "API_|STRIPE_|REDIS_"
```
**注意事項**:
- 機密性の高い値(APIキー、シークレット等)は `--set phpEnv.KEY=value` でデプロイ時に注入するか、Kubernetes の Secret を別途作成して参照することを推奨します。
- `phpEnv` の値はすべてプレーンテキストとして環境変数に設定されます(externalDatabase の password のみ Secret 経由)。
### 例11: SMTP設定によるメール送信
PHPアプリケーションからSMTP経由でメール送信を行う設定です。
```yaml
# values.yaml
smtp:
enabled: true
# Gmail の場合
host: "smtp.gmail.com"
protocol: "starttls" # または "tls" (port 465)
port: 587
# Office365の場合は以下のようにコメント解除
# host: "smtp.office365.com"
# port: 587
# protocol: "starttls"
auth:
enabled: true
username: "your-email@gmail.com"
# password は以下の方法で指定:
# helm install ... --set smtp.auth.password='your-password'
password: ""
# 送信元アドレス(PHPで上書き可能)
from: "noreply@your-domain.com"
tls:
verify: true
allowSelfSigned: false
persistence:
enabled: true
```
**PHPでの使用例**:
```php
<?php
// 設定を初期化
require_once '/etc/smtp-config/php-smtp-config.php';
SmtpConfig::init();
// デフォルトの送信元で送信
$to = 'user@example.com';
$subject = 'テストメール';
$body = 'このメールはSMTP経由で送信されています。';
if (mail($to, $subject, $body)) {
echo 'メール送信成功';
} else {
echo 'メール送信失敗';
}
// 異なる送信元で上書き指定
$headers = [
'From: custom@your-domain.com',
'Return-Path: custom@your-domain.com',
];
SmtpConfig::mail($to, $subject, $body, $headers, 'custom@your-domain.com');
```
### 例12: Python連携(requirements.txtを使用)
PHPからPythonスクリプトを呼び出してデータ処理を行う設定です。
```yaml
# values.yaml
python:
enabled: true
useRequirementsTxt: true
requirementsTxt: |
requests>=2.31.0
pandas>=2.0.0
installOptions: "--no-cache-dir"
```
**PHPでの使用例**:
```php
<?php
$script = escapeshellarg('/var/www/scripts/process.py');
exec("/venv/bin/python3 {$script} 2>&1", $output, $exitCode);
if ($exitCode === 0) {
echo implode("\n", $output);
} else {
error_log('Python script failed: ' . implode("\n", $output));
}
```
`/var/www/scripts/` のようなPVC上の非公開フォルダにPythonスクリプトを配置すれば、Web経由で公開されずに済みます(詳細はPVCマウント構成の項を参照)。
**デプロイ手順**:
```bash
# 1. Secretにパスワードを設定して展開
helm install my-app cafepieters/phpfpm \
-f values.yaml \
--set smtp.auth.password='your-secure-password'
# または、既存のSecretから取得
helm install my-app cafepieters/phpfpm \
-f values.yaml \
--set smtp.auth.password=$(kubectl get secret email-password -o jsonpath='{.data.password}' | base64 -d)
```
**設定詳細**:
- **protocol**:
- `auto`: 自動判定(推奨)
- `starttls`: SMTP + STARTTLS(ポート587)
- `tls`: SSL/TLS(ポート465)
- **from**: 固定の送信元アドレス
- PHPコードで `$fromAddress` パラメータを指定することで上書き可能
- `mail($to, $subject, $body, $headers, '-f custom@example.com')` で指定
- **認証**: PLAIN認証に対応(ほとんどのSMTPサーバーで利用可能)
**よくある設定**:
| プロバイダ | ホスト | ポート | プロトコル |
|-----------|-------|--------|----------|
| 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 |
## アップグレード
```bash
# リポジトリを更新
helm repo update cafepieters
# 最新バージョンにアップグレード
helm upgrade my-phpfpm cafepieters/phpfpm -f values.yaml
# 特定のパラメータのみ変更
helm upgrade my-phpfpm cafepieters/phpfpm --set replicaCount=5
# 特定のバージョンにアップグレード
helm upgrade my-phpfpm cafepieters/phpfpm --version 2.1.0
# ロールバック
helm rollback my-phpfpm 1
```
## アンインストール
```bash
# チャートの削除
helm uninstall my-phpfpm
# PVCも削除する場合
kubectl delete pvc -l app.kubernetes.io/name=phpfpm
```
## トラブルシューティング
### Pod起動が遅い
**原因**: Composerパッケージのダウンロード時間
**対策**:
```yaml
composer:
installOptions: "--no-dev --optimize-autoloader --prefer-dist"
```
### メモリ不足エラー
**対策**:
```yaml
resources:
requests:
memory: "512Mi"
limits:
memory: "2Gi"
```
### PHP拡張エラー
**対策**: 必要なAPKパッケージを追加
```yaml
composer:
additionalApkPackages:
- "build-base"
- "autoconf"
```
### php-fpm が CrashLoopBackOff/全リクエストが 502(`Address in use` / `unsupported FastCGI protocol version: 60`)
**症状**: php-fpm コンテナが `CrashLoopBackOff` になり、nginx ログに `upstream sent unsupported FastCGI protocol version: 60`、php-fpm ログに次が出る。
```
[ERROR] unable to bind listening socket for address '9000': Address in use (98)
[ERROR] FPM initialization failed
```
**原因**: php-fpm が 9000 を bind できず起動失敗し、nginx が非 FastCGI 応答を受けて 502 になる。v8.5.9-c 以前は php-fpm を setup シェルの子プロセスとして起動していたため、コンテナ再起動時に PID1 のシェルが `SIGTERM` を子へ転送せず、旧 php-fpm が残存して 9000 を握ったまま次回起動が二重 bind で失敗する自己増殖ループに陥ることがあった。
**対策**: **v8.5.9-d 以降にアップグレードする**(起動を `exec php-fpm -F` に変更済み。php-fpm が PID1 になりシグナルを直接受けて 9000 を確実解放、`-F` で必ずフォアグラウンド起動)。アップグレード後も残存プロセスを持つ既存 pod が残る場合は作り直す。
```bash
helm upgrade <リリース名> cafepieters/phpfpm --version 8.5.9-d -f values.yaml
kubectl delete pod -n <namespace> -l app.kubernetes.io/name=phpfpm
```
### `selenium.enabled: true` でコンテナが不安定になるとサイト全体が完全停止する
**症状**: `selenium.enabled: true` の構成で、いずれかのコンテナ(php-fpm・Selenium 等)が `CrashLoopBackOff` になっている間、Selenium を全く使わない静的ページも含めてサイト全体が閲覧不能(502等)になる。`kubectl get pod` で `2/3` や `1/3` の READY 表示が続く。
**原因**: v8.5.10-b より前は Selenium が nginx/php-fpm と同一 Pod のサイドカーコンテナだった。Kubernetes は Pod の Ready 条件を「全コンテナが Ready」の AND で判定するため、readinessProbe を持たないコンテナは Running であれば Ready 扱いになる一方、いずれか1つでも CrashLoopBackOff で Waiting/Terminated を繰り返している間は Pod 全体が Ready=false になる。Service は Ready な Pod にしかトラフィックを流さないため、`replicaCount: 1`(冗長性なし)の構成では **1つのコンテナの不調がサイト全体の完全停止に直結していた**。
これに加えて、`resources` values キーが v8.5.10-a 以前はテンプレート側で参照されておらず、どのコンテナにも requests/limits が反映されない「死んだ設定」だった。全コンテナが `QoS Class: BestEffort` になり、ノードのメモリ逼迫時に OOM Killer の対象になりやすい状態だった(Chromium を含む Selenium は特にメモリを消費するため、この影響を受けやすい)。
**対策**: **v8.5.10-c 以降にアップグレードする**(v8.5.10-b は既存リリースの
アップグレードが `spec.selector: ... field is immutable` で失敗する不具合を含むため、
v8.5.10-b を経由せず直接 v8.5.10-c 以降を使うこと。詳細は `CHANGELOG-8.5.10-c.md`)。
1. Selenium が独立した Deployment/Service(`<release>-phpfpm-selenium`)になり、Selenium 単体の不調がメイン Pod(nginx/php-fpm)の Ready 状態に一切影響しなくなった。
2. `resources.nginx` / `resources.phpfpm` / `selenium.resources` で実際に requests/limits を設定できるようになった。本番運用では設定を強く推奨する(未設定のままだと引き続き BestEffort のままで、OOM Kill のリスクは残る)。
```bash
helm upgrade <リリース名> cafepieters/phpfpm --version 8.5.10-c -f values.yaml
```
アップグレード後に確認すべき点は `CHANGELOG-8.5.10-b.md` の「互換性(重要・要対応)」を参照(`selenium.mountHtml` / `phpEnv.SELENIUM_URL` の扱い)。
### データベース接続エラー
**確認**:
```bash
# 環境変数確認
kubectl exec -it <pod-name> -c php-fpm -- env | grep DB_
# 接続テスト
kubectl exec -it <pod-name> -c php-fpm -- mysql -h $DB_HOST -u $DB_USER -p$DB_PASS
```
### ログ確認
```bash
# PHP-FPMログ
kubectl logs <pod-name> -c php-fpm
# Nginxログ
kubectl logs <pod-name> -c nginx
# Seleniumログ(有効時。v8.5.10-b以降は別Pod・別Deploymentのため -c 指定は不要)
kubectl logs -l app.kubernetes.io/component=selenium
# リアルタイム監視
kubectl logs -f <pod-name> -c php-fpm
```
## 高度な設定
### カスタムイメージの使用
```yaml
image:
php:
registry: my-registry.com
repository: custom-php-fpm
tag: "latest"
```
### 水平オートスケーリング
```yaml
# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: phpfpm-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-phpfpm
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
```
### リソース制限の推奨値
| 環境 | CPU Request | CPU Limit | Memory Request | Memory Limit |
|------|-------------|-----------|----------------|--------------|
| 開発 | 100m | 500m | 256Mi | 512Mi |
| ステージング | 200m | 1000m | 512Mi | 1Gi |
| 本番(小) | 500m | 1000m | 512Mi | 1Gi |
| 本番(大) | 1000m | 2000m | 1Gi | 2Gi |
## セキュリティ考慮事項
### データベースパスワードの管理
```bash
# Secretの作成
kubectl create secret generic db-secret \
--from-literal=password='YourSecurePassword'
# values.yamlで参照
externalDatabase:
password: "{{ .Values.dbPassword }}"
# デプロイ時に注入
helm install my-app . \
--set dbPassword=$(kubectl get secret db-secret -o jsonpath='{.data.password}' | base64 -d)
```
### NetworkPolicyの適用
```yaml
# networkpolicy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: phpfpm-netpol
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: phpfpm
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 80
```
## パフォーマンス最適化
### Opcacheの有効化
```yaml
composer:
additionalPhpExtensions:
- "opcache"
nginx:
customConfig:
enabled: true
snippet: |
fastcgi_param PHP_VALUE "
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
";
```
### Composerの最適化
```yaml
composer:
installOptions: "--no-dev --optimize-autoloader --classmap-authoritative --apcu-autoloader"
additionalPhpExtensions:
- "apcu"
additionalApkPackages:
- "apcu-dev"
```
## よくある質問(FAQ)
**Q: Helmリポジトリを追加するには?**
A: 以下のコマンドでリポジトリを追加できます。
```bash
helm repo add cafepieters https://git.cafepieters.com/api/packages/helmchart/helm
helm repo update
# 利用可能なバージョンを確認
helm search repo cafepieters/phpfpm --versions
```
**Q: PHPバージョンを変更するには?**
A: `image.php.tag`を変更してください。
```yaml
image:
php:
tag: "8.3.15-fpm-alpine3.21"
```
**Q: 複数ドメインに対応できますか?**
A: Ingressで複数ホストを設定できます。
```yaml
ingress:
hosts:
- host: domain1.com
paths: [...]
- host: domain2.com
paths: [...]
```
**Q: SeleniumとComposerは同時に使えますか?**
A: はい、完全に共存可能です。
**Q: 既存のPHPアプリをデプロイするには?**
A: PVCにファイルをコピーしてください。
```bash
kubectl cp ./my-app <pod-name>:/var/www/html/ -c php-fpm
```
## 関連ドキュメント
- [Composer使用ガイド](./COMPOSER_GUIDE.md)
- [マイグレーションガイド](./MIGRATION_GUIDE.md)
- Selenium別Deployment化の設計・経緯は本READMEの「Selenium設定」セクションと `CHANGELOG-8.5.10-b.md`(設計)・`CHANGELOG-8.5.10-c.md`(既存リリースのアップグレードを壊す不具合の修正)を参照(旧: 本リンク先の設計ドキュメントは作成されず実装が先行したため削除)
## ライセンス
このHelmチャートはMITライセンスで公開されています。
## サポート
- Helmリポジトリ: https://git.cafepieters.com/api/packages/helmchart/helm
- Issue: https://git.cafepieters.com/helmchart/phpfpm/issues
- リポジトリ: https://git.cafepieters.com/helmchart/phpfpm
## 変更履歴
### v2.0.0
- Composer対応追加
- PHP拡張の柔軟な追加機能
- 統合的な依存関係管理
### v1.0.0
- 初回リリース
- Nginx + PHP-FPM基本構成
- Selenium統合
- 外部データベース対応