PHP应用容器化上K8s全攻略:从镜像构建到滚动发布

目录导读
- 为什么PHP上K8s需要打破传统思维
- 前置准备:Docker镜像的PHP-FPM与Nginx解耦设计
- 核心编排:Deployment、Service、Ingress三件套实战
- 有状态难题:Session共享与MySQL连接池的K8s解法
- 配置管理:ConfigMap与Secret在PHP项目中的落地方案
- CI/CD集成:GitLab CI自动构建镜像并滚动更新
- 常见坑位避雷:热更新、日志收集与优雅终止
- 问答精选:PHP开发者最常见的10个K8s疑问
为什么PHP上K8s需要打破传统思维
传统PHP部署(LNMP架构)直接装在ECS或物理机上,而K8s要求应用无状态、可水平扩展,PHP本身是解释型语言,无编译产物,但PHP-FPM进程与Nginx静态文件分离是上云的关键,你需要将PHP代码打入镜像,把/var/www/html作为只读挂载,而session和日志必须外置到Redis与标准输出。
前置准备:Docker镜像的PHP-FPM与Nginx解耦设计
必杀技:双容器同Pod
# php-deployment.yaml
spec:
containers:
- name: php-fpm
image: myapp-php:7.4-fpm
volumeMounts:
- name: code
mountPath: /var/www/html
- name: nginx
image: nginx:1.21
volumeMounts:
- name: code
mountPath: /usr/share/nginx/html
volumes:
- name: code
emptyDir: {}
先通过initContainer将代码从Git仓库拉取到emptyDir共享卷。关键点:Nginx容器内的fastcgi_pass必须指向0.0.1:9000,因为同Pod共享网络命名空间。
核心编排:Deployment、Service、Ingress三件套实战
- Deployment:设置
replicas: 3,strategy: RollingUpdate,maxSurge: 1,maxUnavailable: 0,保证PHP服务不中断。 - Service:ClusterIP类型,
targetPort: 9000(PHP-FPM),port: 80(Nginx)。 - Ingress:使用
nginx-ingress,配置TLS证书,并强制ssl-redirect: true。
PHP代码更新每次docker build后打tag为新版本,修改Deployment的image版本即可触发滚动。
有状态难题:Session共享与MySQL连接池的K8s解法
Session问题:多副本下PHP默认文件Session失效,必须改用Redis存储,在PHP配置中修改:
session.save_handler = redis session.save_path = "tcp://redis-service:6379"
MySQL连接:环境变量注入连接信息,但需注意max_connections,推荐使用ProxySQL作为DB中间件,或使用mysql-operator自动管理连接池。
配置管理:ConfigMap与Secret在PHP项目中的落地方案
将.env配置抽离成ConfigMap(非敏感)与Secret(敏感),并挂载为文件:
apiVersion: v1
kind: ConfigMap
metadata:
name: php-config
data:
php.ini: |
memory_limit=256M
max_execution_time=60
在Deployment中引用:
envFrom:
- configMapRef:
name: php-config
重点:修改ConfigMap后需重启Pod或使用rollout restart,PHP-FPM不会自动热加载。
CI/CD集成:GitLab CI自动构建镜像并滚动更新
.gitlab-ci.yml核心步骤:
composer install --no-devdocker build生成含Nginx配置的镜像- 推送镜像到私有Registry
- 使用
kubectl set image deployment/myapp php-fpm=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA - 执行
kubectl rollout status检测是否健康
白屏发布:结合readinessProbe检查/healthz接口,确保PHP-FPM能连接Redis与DB后再接收流量。
常见坑位避雷
- 容器内PHP进程权限:必须运行在
www-data用户,runAsNonRoot: true,否则Nginx读取不到文件。 - 日志:PHP的
error_log需指向/dev/stdout,用filelog驱动统一收集。 - 优雅终止:PHP-FPM处理完请求后退出,K8s发送SIGTERM,需在
lifecycle.preStop中延迟5秒。
问答精选:PHP开发者最常见的10个K8s疑问
Q1: PHP代码更新必须重新构建镜像吗?
A: 是,K8s不做代码同步,只认镜像,可以用emptyDir+initContainer实现git clone,但生产环境建议构建不可变镜像。
Q2: 如何水平扩展PHP进程数?
A: 调整pm.max_children,它对应每个副本的进程数,通过Deployment副本数乘max_children计算总容量。
Q3: K8s中PHP能用file_put_contents写本地文件吗?
A: 不行,多副本下数据会丢失,必须挂载PV或使用OSS/S3对象存储,临时文件使用/tmp临时卷。
Q4: 为什么Pod重启后PHP-FPM连接MySQL失败?
A: 检查MySQL的wait_timeout与max_connections,PHP短连接方案下需要调整pconnect为false。
Q5: 如何做PHP灰度发布?
A: 使用Argo Rollouts或Ingress Canary,将A/B版本按权重分配流量。
Q6: K8s环境下的PHP Cron任务怎么跑?
A: 使用CronJob资源,独立副本与Job执行,避免与web服务争抢资源。
Q7: opcache配置有什么建议?
A: K8s容器内代码不可变,开启opcache.validate_timestamps=0提升性能,但注意每次发版本要清缓存。
Q8: 如何迁移老项目的.htaccess重写规则?
A: 转为Nginx的location与rewrite规则,在ConfigMap中维护nginx.conf片段。
Q9: 镜像内需要装gd、pdo_mysql扩展吗?
A: 用官方php:7.4-fpm-alpine为基础,通过docker-php-ext-install写入Dockerfile,保证扩展与PHP版本锁定。
Q10: 监控PHP-FPM状态怎么弄?
A: 暴露/status接口,用Prometheus的php-fpm-exporter采集,再配Grafana大盘。
部署PHP到K8s并非玄学,核心路径是:镜像瘦身→无状态改造→配置外置→健康检查,记住每个Pod设计成“Nginx+PHP-FPM双容器”是官方推荐的模式,既保持传统LNMP思维,又完全拥抱云原生,若你的业务是高并发低延迟,请务必抛弃file_put_contents,全面拥抱Redis与对象存储。动手先写一个Deployment,比看十篇教程都有效。