PHP容器化实战:ThinkPHP WebSocket与Kubernetes部署
1. 项目背景与核心挑战
在云原生架构逐渐成为主流的今天,PHP应用的容器化部署却面临着独特的挑战。不同于Java或.NET等语言相对成熟的容器化支持,PHP在Kubernetes环境中的部署往往需要更多手工配置。特别是当我们需要同时支持传统的HTTP请求和WebSocket实时通信时,问题会变得更加复杂。
ThinkPHP作为国内广泛使用的PHP框架,其Worker组件提供了WebSocket服务能力。但在Docker容器中运行php think worker:server -d命令时,会遇到进程管理、端口暴露和Nginx代理等一系列技术难点。核心挑战主要体现在:
- 单一容器需要同时运行Nginx(处理HTTP请求)和PHP Worker(处理WebSocket连接)
- WebSocket服务需要长期运行的进程,而传统PHP-FPM模式不适合这种场景
- Kubernetes的Service资源对TCP长连接的支持需要特殊配置
- ThinkPHP的Worker模式在容器环境中需要额外的进程守护机制
2. 基础镜像选择与环境准备
2.1 为什么选择webdevops/php-nginx镜像
经过多次实践对比,webdevops/php-nginx镜像相比官方PHP镜像更适合生产环境部署,主要原因包括:
- 集成化程度高:已经预装了Nginx和PHP-FPM,并配置好了两者之间的通信
- Supervisor支持:内置了Supervisor进程管理系统,可以方便地管理后台进程
- 灵活的配置覆盖:允许通过挂载文件的方式覆盖默认的Nginx和PHP配置
- 扩展管理便捷:提供了方便的PHP扩展安装方式
对于ThinkPHP项目,我们选择webdevops/php-nginx:7.4版本,确保与大多数现有项目的PHP版本兼容。
2.2 必备PHP扩展的安装
ThinkPHP Worker和常见项目依赖的扩展需要通过Dockerfile安装。关键扩展及其作用:
RUN chmod +x /usr/local/bin/install-php-extensions && sync && \ install-php-extensions \ pcntl \ # 进程控制,WebSocket服务必需 sockets \ # Socket通信支持 redis \ # 缓存和Session存储 pdo_mysql \ # 数据库连接 zip \ # 压缩包处理 gd \ # 图像处理 opcache # 性能优化特别提醒:pcntl和sockets扩展是WebSocket服务能够正常运行的基础,缺少这两个扩展会导致Worker无法启动。
3. Dockerfile深度配置
3.1 多阶段构建优化
虽然基础镜像已经提供了很多功能,但我们仍需要定制Dockerfile来满足特定需求:
FROM webdevops/php-nginx:7.4 # 暴露WebSocket服务端口 EXPOSE 80 2346 # 安装PHP扩展 ADD ./containerConfig/install-php-extensions /usr/local/bin/ RUN chmod +x /usr/local/bin/install-php-extensions && \ install-php-extensions pcntl sockets redis pdo_mysql # 拷贝项目代码 COPY . /app # 配置Nginx RUN cp /app/containerConfig/vhost.conf /opt/docker/etc/nginx/vhost.conf # 配置Supervisor启动脚本 RUN cp /app/containerConfig/10-init.sh /opt/docker/bin/service.d/supervisor.d/10-init.sh # 设置目录权限 RUN chown -R application:application /app && \ chmod -R 755 /app/runtime3.2 关键配置详解
- 端口暴露:除了默认的80端口外,还需要暴露WebSocket服务端口(示例中为2346)
- 权限设置:确保运行时用户(application)对项目目录有正确的读写权限
- 配置覆盖:用项目特定的Nginx配置和Supervisor脚本覆盖默认配置
重要提示:在Kubernetes环境中,这些端口需要在后续的Service和Deployment资源中再次声明,否则流量无法到达Pod。
4. Nginx与WebSocket的特殊配置
4.1 vhost.conf的核心配置
Nginx需要同时处理HTTP请求和代理WebSocket连接,关键配置如下:
server { listen 80; server_name _; root "/app/public"; # HTTP请求处理 location / { try_files $uri /index.php?s=$uri; } # PHP文件处理 location ~ \.php$ { fastcgi_pass php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } # WebSocket代理配置 location /ws { proxy_pass http://127.0.0.1:2346; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; } }4.2 WebSocket代理的注意事项
- 协议升级头:必须正确设置
Upgrade和Connection头,否则无法建立WebSocket连接 - 路径区分:建议为WebSocket设置特定路径(如
/ws),避免与普通HTTP路由冲突 - 超时设置:可以适当增加
proxy_read_timeout,避免长连接被意外断开
5. Supervisor进程管理配置
5.1 10-init.sh启动脚本
由于WebSocket服务需要作为守护进程运行,我们通过Supervisor来管理:
#!/bin/bash # 等待Nginx和PHP-FPM启动完成 sleep 5 # 启动ThinkPHP Worker cd /app && php think worker:server -d5.2 常见问题排查
- 启动顺序问题:添加
sleep 5确保基础服务就绪后再启动Worker - 工作目录:必须切换到项目目录(
/app)再执行命令 - 日志输出:建议重定向Worker输出到文件方便调试:
php think worker:server -d >> /app/runtime/worker.log 2>&1
6. Kubernetes部署配置
6.1 Deployment资源定义
apiVersion: apps/v1 kind: Deployment metadata: name: thinkphp-app spec: replicas: 2 selector: matchLabels: app: thinkphp-app template: metadata: labels: app: thinkphp-app spec: containers: - name: php-app image: your-registry/php-demo:v1 ports: - containerPort: 80 name: http - containerPort: 2346 name: websocket resources: limits: memory: "512Mi" cpu: "500m"6.2 Service资源定义
apiVersion: v1 kind: Service metadata: name: thinkphp-service spec: selector: app: thinkphp-app ports: - name: http port: 80 targetPort: 80 - name: websocket port: 2346 targetPort: 2346 type: ClusterIP6.3 Ingress配置(可选)
如果需要从外部访问,可以配置Ingress:
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: thinkphp-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - host: yourdomain.com http: paths: - path: / pathType: Prefix backend: service: name: thinkphp-service port: number: 80 - path: /ws pathType: Prefix backend: service: name: thinkphp-service port: number: 23467. 测试与验证
7.1 WebSocket连接测试
可以使用wscat工具测试WebSocket服务是否正常工作:
# 安装wscat npm install -g wscat # 测试连接 wscat -c ws://your-service-ip:23467.2 常见问题解决方案
- 连接被拒绝:检查Pod是否正常运行,端口是否正确暴露
- 协议升级失败:确认Nginx配置了正确的
Upgrade头 - Worker进程退出:检查PHP错误日志,确认
pcntl扩展已安装 - Kubernetes服务发现:确保Service的selector与Pod标签匹配
8. 性能优化建议
- 资源限制:为容器设置合理的CPU和内存限制,避免单个Pod占用过多资源
- 连接池配置:调整Worker的
worker_num参数,根据Pod资源配置合理的工作进程数 - 持久化连接:对于数据库和Redis连接,使用长连接减少握手开销
- 监控配置:为Worker添加状态检查接口,方便Kubernetes的存活探针检测
9. 生产环境注意事项
- 日志收集:配置Fluentd或Filebeat收集Nginx和PHP日志
- 滚动更新:设置适当的
maxSurge和maxUnavailable保证更新时不中断服务 - 配置分离:将敏感配置通过ConfigMap和Secret管理,而非直接打包进镜像
- 水平扩展:WebSocket服务的有状态特性需要考虑会话保持或共享状态方案
在实际部署中,我发现通过适当调整Worker的heartbeat_check_interval参数可以显著提高长连接的稳定性。同时,建议为WebSocket服务单独部署一组Pod,与HTTP服务分离,这样可以更灵活地调整资源配置和扩展策略。
