# 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(`-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://-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`)は`-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 -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 =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 -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 =2.31.0 pandas>=2.0.0 installOptions: "--no-cache-dir" ``` **PHPでの使用例**: ```php &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 -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(`-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 -c php-fpm -- env | grep DB_ # 接続テスト kubectl exec -it -c php-fpm -- mysql -h $DB_HOST -u $DB_USER -p$DB_PASS ``` ### ログ確認 ```bash # PHP-FPMログ kubectl logs -c php-fpm # Nginxログ kubectl logs -c nginx # Seleniumログ(有効時。v8.5.10-b以降は別Pod・別Deploymentのため -c 指定は不要) kubectl logs -l app.kubernetes.io/component=selenium # リアルタイム監視 kubectl logs -f -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 :/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統合 - 外部データベース対応