必须用多阶段构建,因其强制剥离构建与运行阶段:构建阶段装依赖、生成缓存;运行阶段仅保留php-fpm、最小基础系统、非root用户及精简扩展,避免镜像臃肿、权限混乱和安全隐患。

PHP 容器化部署最常踩的坑,不是功能跑不起来,而是镜像臃肿、启动慢、权限混乱、探针失败、FPM 子进程反复重启——这些问题几乎都源于构建阶段和运行阶段没切开,或者 FPM 配置没适配容器生命周期。
为什么必须用多阶段构建?
单阶段 Dockerfile 把 composer install、编译扩展、复制代码全塞进一个镜像,结果是:镜像体积动辄 800MB+,含大量构建依赖(build-base、gcc)、临时文件、dev-only 包,还可能残留 root 权限或调试工具(如 xdebug),直接暴露在生产环境里就是安全隐患。
多阶段构建的核心价值不是“看起来高级”,而是强制剥离:构建阶段只管装依赖、生成缓存;运行阶段只留 PHP-FPM 进程、最小基础系统、非 root 用户、精简扩展。最终镜像可压到 120MB 左右(Alpine)或 280MB(Debian slim)。
- 构建阶段用
composer:2或php:8.2-cli-slim-bookworm,只装composer和必要工具 - 运行阶段用
php:8.2-fpm-alpine或php:8.2-fpm-slim-bookworm,禁用所有 dev 扩展(--no-dev) -
COPY --from=builder只复制/app/vendor、/app/public、/app/storage等运行必需目录,别复制整个/app - 务必在运行阶段执行
adduser -u 1000 -S www并用USER www切换,避免以 root 运行 PHP-FPM
PHP-FPM 在 Kubernetes 里为什么老被杀?
Kubernetes 的 livenessProbe 默认用 HTTP 请求检测,但 PHP-FPM 本身不监听 HTTP,它只通过 Unix socket 或 TCP 接收 FastCGI 请求。如果 probe 直接访问 /ping 或 /status,而你又没配 Nginx 做代理转发,就会持续失败,触发重启循环。
正确做法是让探针走 PHP-FPM 自带的 status 页面,但必须满足两个前提:FPM 配置中开启 pm.status_path,且 Web 服务器(Nginx/OpenResty)显式将该路径代理过去。
- 在
www.conf中启用:pm.status_path = /status,并确保ping.path = /ping - Nginx 配置里加 location 块:
location ~ ^/(status|ping)$ { fastcgi_pass php-fpm:9000; fastcgi_param SCRIPT_FILENAME $fastcgi_script_name; } - K8s
livenessProbe改为 HTTP GEThttp://:80/status,而非直接连 PHP-FPM 的 9000 端口 - 注意:
pm.status_path默认关闭,且需配合pm = dynamic或ondemand才生效
PHP-FPM 参数怎么调才不翻车?
容器里默认的 pm.max_children = 5 是开发值,在 K8s 里极易成为瓶颈。但盲目调高又会导致 OOM 被 kill——因为每个子进程内存占用不是固定的,受 OPcache、框架加载、请求内容影响很大。
关键不是设一个“最大值”,而是根据容器 resources.limits.memory 反推合理范围,并配合 pm.start_servers 和 pm.min/max_spare_servers 实现弹性伸缩。
- 先估算单个 PHP-FPM worker 内存:用
php -r "echo memory_get_peak_usage(true) / 1024 / 1024;"测典型请求峰值,通常在 30–80MB - 若容器 limit 设为
512Mi,保守按 60MB/worker 算,pm.max_children不宜超过 7 -
pm = ondemand更适合低流量或突发场景;pm = dynamic更稳,但需设好start_servers(建议为max_children * 0.3) - 务必关闭
catch_workers_output = yes,否则日志会打满 stdout,干扰 K8s 日志采集
ConfigMap 和 Secret 怎么安全喂给 PHP-FPM?
很多人把 .env 文件 COPY 进镜像,或用环境变量硬编码在 Deployment 里——这两种方式在 K8s 里都不可审计、不可轮换、无法热更新。
正确姿势是:ConfigMap 存非敏感配置(如 APP_ENV=prod、LOG_LEVEL=error),Secret 存数据库密码、API keys,再通过 volumeMount 挂载到容器内固定路径,由 PHP 启动脚本或框架自动加载。
- 不要在
php.ini里写opcache.enable=${OPCACHE_ENABLE}—— PHP 不解析环境变量 - 推荐方式:挂载 ConfigMap 到
/var/www/html/.env,用vlucas/phpdotenv加载;或挂载到/usr/local/etc/php/conf.d/env.ini,用env[DB_PASSWORD]语法(需 PHP 7.4+ + FPM 配置clear_env = no) - Secret 必须 base64 编码后写入 YAML,挂载时用
subPath精确指定文件,避免整个 Secret 目录被挂载导致权限泄露 - 在 Deployment 中显式设置
securityContext.runAsNonRoot: true和runAsUser: 1000,防止挂载后文件属主不匹配
真正难的不是写对某一行配置,而是理解容器里没有“重启服务”这个动作——PHP-FPM 进程一旦启动,它的配置、用户、资源限制就锁死了。所有调优必须在构建时固化、在 K8s 资源定义里声明清楚,运行时几乎无法动态修正。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











