ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

nginx+php-fpm迁移K8s:同Pod双容器与共享存储实战解析

nginx+php-fpm迁移K8s:同Pod双容器与共享存储实战解析 直接说结论把原来跑在单机上的 nginx php-fpm 搬到 Kubernetes以下简称 k8s里本质上不是“换个地方跑容器”那么简单而是要把原来靠 systemd 管理、靠本机 socket 通信、靠手动改配置的方式彻底重构成“一组可调度、可伸缩、自愈的应用单元”。我最早接手这个需求时以为就是写个 Dockerfile 打个镜像丢进去结果踩了一圈坑才意识到真正值钱的是理解 k8s 里 Pod 的边界、nginx 与 php-fpm 的通信方式、静态文件的共享方案以及 Ingress 的流量模型。这篇文章把我实践下来最稳的一套方案完整拆给你包含镜像构建细节、YAML 编排思路、共享目录的几种方案对比以及把 PHP 站点从“单机裸奔”平滑迁到 k8s 的完整流程。适合已经能用 Docker、写过简单 YAML、但对 k8s 部署 Web 应用还停留在“照抄模板”阶段的同学。1. 方案选型先想清楚架构再动手写 YAML1.1 为什么 nginx 和 php-fpm 必须拆成两个角色先回顾一下传统 LNMP 架构nginx 只负责处理静态资源请求和转发动态请求把 PHP 相关的请求交给 php-fpm 处理两者之间用 FastCGI 协议通信。这个“动静分离”的设计在单机模式下很自然nginx 配置里写一条fastcgi_pass 127.0.0.1:9000;就完事了或者用 Unix Socket 通信。到了 k8s 里第一个要做的决策就是到底把 nginx 和 php-fpm 装进同一个 Pod还是拆成两个独立的 Deployment我强烈建议拆成两个容器、共用一个 Pod也就是常说的“单 Pod 多容器”模式。原因有三点第一nginx 和 php-fpm 的生命周期虽然紧密耦合没有 php-fpmnginx 的 fastcgi_pass 就全挂但它们的镜像维护逻辑完全不同——nginx 官方镜像要经常升级安全补丁php-fpm 则跟着 PHP 版本走拆开之后可以独立替换、独立伸缩。第二两个进程的日志、配置、资源占用各有归属如果放在同一个容器里用 supervisord 硬拉起来Pod 里只有一个容器视角排查问题的时候看日志会非常难受。第三也是最重要的k8s 里的 Pod 是“同一个网络命名空间、同一个存储卷共享单元”容器之间可以通过localhost直接通信。这意味着 nginx 容器里写fastcgi_pass 127.0.0.1:9000;就能命中同 Pod 里的 php-fpm 容器。这个模型几乎完美复刻了单机版 LNMP 的通信方式学习成本和迁移成本都最低。1.2 三种部署架构的对比我实际调研和测试过三种架构直接列成表格给你参考架构方案通信方式伸缩粒度适用场景推荐度nginx php-fpm 同 Pod 双容器容器内 localhost:9000必须一起伸缩但可通过 HPA 对 Pod 水平扩容中小型站点、传统 PHP 应用迁移、团队刚上手 k8s强烈推荐nginx 与 php-fpm 独立 Deployment通过 Service DNS 解析跨 Pod 通信可以各自独立伸缩前端与后端流量差异极大、需要分别做弹性伸缩的大型系统慎用网络开销和排查成本都高静态资源用 nginx动态解析交给独立 php-fpm ServiceController 层 nginx 统一入口php-fpm 单独一组后端动态请求独立扩容动静比悬殊、静态资源走 CDN 的场景有经验后再尝试初学阶段别贪多直接选第一种。等你在 k8s 里跑熟了一个 Pod 的方案理解了 Service、Deployment、HPA 这些概念之后再去拆多 Deployment 架构就顺理成章。1.3 共享静态文件的问题最容易被忽视的坑PHP 项目尤其是 ThinkPHP、Laravel 这类框架通常有一个public目录存放静态资源CSS、JS、图片、上传文件。单机时代nginx 直接读本机磁盘就行。到了 k8s 里问题来了nginx 容器和 php-fpm 容器虽然共享同一个 Pod但它们各自基于不同的镜像容器内的文件系统是隔离的。比如你的 php-fpm 镜像里/var/www/html下有一份完整的应用代码里面有public/uploads/avatar.jpgnginx 容器如果不挂载同一个目录它转发静态请求时根本找不到这个文件只能 404。解决方案核心就一个词共享存储卷。在同一个 Pod 里声明一个volume同时挂载到 nginx 容器和 php-fpm 容器的对应路径两边就能看到同一份文件。实际操作中有几种挂法如果镜像里已经包含完整代码可以用emptyDir卷 initContainer 做一次“代码复制”把 php-fpm 镜像里的代码同步给 nginx 容器。如果代码在共享存储上NFS、CephFS、云厂商的 NAS直接把这个存储挂到两个容器不需要 initContainer。如果只是静态资源单独存放也可以只把public/uploads这个子目录做成共享卷。我后面会详细演示第一种方案因为它在“没有现成共享存储”的场景下最通用而且能帮你理解 initContainer 这个 k8s 核心概念。2. 镜像构建前后端分离的 Dockerfile 写法2.1 php-fpm 镜像的构建细节PHP 官方提供了php:8.2-fpm这类镜像但生产环境几乎不可能直接用裸镜像原因有两条一是缺扩展比如pdo_mysql、redis、gd等常见扩展得自己装二是时区、运行目录、日志配置都还是默认值。我实际用的 Dockerfile 是这个风格FROM php:8.2-fpm RUN apt-get update apt-get install -y \ libzip-dev \ libpng-dev \ libjpeg-dev \ libfreetype6-dev \ --no-install-recommends \ docker-php-ext-configure gd --with-freetype --with-jpeg \ docker-php-ext-install -j$(nproc) pdo_mysql zip gd \ apt-get clean rm -rf /var/lib/apt/lists/* RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ echo Asia/Shanghai /etc/timezone WORKDIR /var/www/html COPY . /var/www/html RUN chown -R www-data:www-data /var/www/html \ chmod -R 755 /var/www/html/storage 2/dev/null || true EXPOSE 9000 CMD [php-fpm]几个关键点解释一下第一务必使用docker-php-ext-install而不是直接apt-get install php-xxx镜像里自带的这个脚本会帮你处理扩展编译流程还能利用多核加速。第二chown -R www-data:www-data这步极其重要。php-fpm 进程默认以www-data用户运行如果代码目录属主是 root写 session 目录、写日志、上传文件的时候会报权限错误。我早期犯过这个错线上报了一堆file_put_contents failed to open stream: Permission denied排查了半天才发现是容器内用户权限问题。第三有些框架目录结构里存在storage、runtime这类写目录Dockerfile 里最好给它们单独设置写权限而不是整个项目chmod 777。777 一时爽安全审计的时候就是灾难。2.2 nginx 镜像与配置文件注入nginx 镜像推荐直接基于nginx:stable-alpine体积小、自带时区和常用工具。需要注意的不是镜像本身而是配置文件的“注入方式”——我建议用ConfigMap保存 nginx 配置而不是把配置写死在镜像里。构建 nginx 镜像的 Dockerfile 极其简单FROM nginx:stable-alpine COPY nginx.conf /etc/nginx/nginx.conf COPY conf.d/default.conf /etc/nginx/conf.d/default.conf RUN mkdir -p /var/www/html但这里有个重要细节镜像必须预留/var/www/html目录因为后面要挂载共享卷进去。如果不先mkdir出来挂载行为在某些容器运行时下可能因为目录不存在而静默失败或产生奇怪权限问题。nginx 的default.conf核心内容是这样的server { listen 80; root /var/www/html/public; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/html/public$fastcgi_script_name; } location ~ /\.(?!well-known).* { deny all; } }注意两个细节一是fastcgi_pass 127.0.0.1:9000这里必须写成 localhost 或者 Pod IP。因为 nginx 和 php-fpm 在同一个 Pod 内所以 localhost 就能通。如果你用的是“拆 Deployment”方案这个位置要改成 php-fpm 的 Service 名比如php-fpm-svc:9000。二是SCRIPT_FILENAME参数特别容易错。nginx 收到/index.php请求后会把这个变量传给 php-fpmphp-fpm 根据这个路径去磁盘找文件。如果路径写错比如少了public前缀php-fpm 会直接报Primary script unknown返回 404而 nginx 日志里看起来一切正常。2.3 代码打的镜像 vs 代码走存储卷这里要做一个关键决策到底把 PHP 代码打进 php-fpm 镜像里还是把代码放在外部存储NFS、云盘挂载进容器我体验过两种模式的差别代码打进镜像的模式优点是部署特别简单——镜像到哪代码就在哪Pod 漂移、节点重启都没压力。缺点也明显每次改代码都要重新构建镜像、推到仓库、滚动更新发布流程比较重。如果你的团队有 CI/CD每次 git push 自动构建镜像那这个缺点其实可以接受。代码走存储卷的模式优点是改完代码上传到共享存储再重启 Pod 就生效不需要重新构建镜像。缺点是引入了外部依赖NFS 挂载的延迟、云盘跨可用区的限制、并发写文件时的锁问题都可能成为新坑。而且一旦存储出问题整个应用全挂。我的建议是项目初上 k8s 阶段先把代码打进镜像。等 k8s 操作熟练了、CI/CD 跑通了再平滑过渡到“镜像里放基础代码 存储卷覆盖可写目录比如 uploads、storage”的混合模式。这样既能保证发布的一致性又解决了动态文件的共享问题。3. 核心资源编排YAML 才是 k8s 的灵魂3.1 DeploymentPod 的“自我修复”保证Deployment 是整个部署的核心对象。它负责维持 Pod 的期望副本数Pod 挂了自动重启镜像更新时滚动升级。下面是我实践中整理出的完整 YAML配合注释来看apiVersion: apps/v1 kind: Deployment metadata: name: php-demo namespace: web labels: app: php-demo spec: replicas: 2 selector: matchLabels: app: php-demo template: metadata: labels: app: php-demo spec: initContainers: - name: copy-code image: registry.example.com/php-demo:1.0.3 imagePullPolicy: IfNotPresent command: - sh - -c - | cp -a /var/www/html/. /data/html/ chown -R www-data:www-data /data/html volumeMounts: - name: web-root mountPath: /data/html containers: - name: nginx image: registry.example.com/nginx-php-demo:1.0.0 imagePullPolicy: IfNotPresent ports: - containerPort: 80 volumeMounts: - name: web-root mountPath: /var/www/html - name: php-fpm image: registry.example.com/php-demo:1.0.3 imagePullPolicy: IfNotPresent ports: - containerPort: 9000 volumeMounts: - name: web-root mountPath: /var/www/html这个 YAML 有几个值得细说的点initContainer 的设计思路initContainers里的容器会在正式容器启动前串行执行完毕。我用它来做“一次性代码搬运”——从 php-fpm 镜像里拷贝代码到共享卷。这个共享卷web-root是一个emptyDir只存在于 Pod 生命周期内Pod 重建时自动重置。initContainer 跑完退出接下来 nginx 容器和 php-fpm 容器同时挂载这个目录看到的就是同一份代码了。为什么 nginx 也用同一个镜像里的代码nginx 容器本身不跑 PHP但它要serve静态文件。如果 nginx 容器里找不到public目录下的 CSS/JS 文件用户打开页面就是惨不忍睹的裸排版。有了共享卷nginx 和 php-fpm 看到完全一致的文件树就不存在这个问题了。emptyDir 的适用边界你可能听说过emptyDir是“临时目录”Pod 一删就没了。是的所以它只适合“代码可重建”的场景——反正代码是从镜像里拷出来的Pod 重建后 initContainer 再拷一遍就行。但用户上传的文件、日志文件这类需要持久化的数据绝对不能放emptyDir。这种情况要换成PersistentVolumeClaim挂载下一节我会用一个实战例子说明。3.2 Service为 Pod 提供稳定访问入口Deployment 创建的 Pod 每次重建后 IP 都会变化所以需要一个稳定的“出入口”。Service 对象干的就是这件事为一组 Pod 分配一个虚拟 IPClusterIP并负责负载均衡转发。apiVersion: v1 kind: Service metadata: name: php-demo-svc namespace: web spec: type: ClusterIP selector: app: php-demo ports: - name: http port: 80 targetPort: 80这里没有任何复杂参数唯一要求是selector.matchLabels必须能匹配到 Deployment 里 Pod 的标签。:80是 Service 暴露的端口targetPort是 Pod 里 nginx 容器的端口。关于 Service 有几件事需要形成直觉第一Service 的负载均衡是四层的它不关心 HTTP 头只做 TCP/UDP 转发。所以它不会帮你做路径路由、域名匹配这些事情那是 Ingress 的职责。第二如果集群里启用了 kube-proxy 的 ipvs 模式负载均衡算法默认是rr轮询iptables 模式下则是随机的。大多数场景不需要管这些但如果你发现某一台 Pod 压力特别大可能是这个原因。第三可以为同一个 Deployment 建多个 Service比如一个 ClusterIP 供集群内部调用一个 NodePort 供外部临时访问。但在正式环境外部流量统一走 Ingress。3.3 IngressHTTP 层的智能路由Ingress 有点像 nginx 里的server_namelocation规则合集它把外部 HTTP(S) 请求按域名和路径分发到不同的 Service。下面是实战可用的 Ingress YAMLapiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: php-demo-ingress namespace: web annotations: nginx.ingress.kubernetes.io/proxy-body-size: 50m nginx.ingress.kubernetes.io/ssl-redirect: false spec: ingressClassName: nginx rules: - host: demo.example.com http: paths: - path: / pathType: Prefix backend: service: name: php-demo-svc port: number: 80解释几个关键配置ingressClassName: nginx指定使用哪个 Ingress Controller。如果你在集群里装的是 ingress-nginx最主流的方案就要填nginx。如果没装 Controller只创建 Ingress 对象是无效的——Ingress 只是声明真正的负载转发由 Controller 实现。proxy-body-size: 50m这个注解是我踩坑后加上的。PHP 应用经常有上传图片、导入 Excel 的需求而 ingress-nginx 默认限制请求体为 1MB。当初用户上传一个 5MB 的压缩包直接 413我排查了好一阵才想到是这个限制。ssl-redirect: false先关掉 HTTPS 强跳转。如果证书还没配好开着这个配置会导致所有 HTTP 请求被 308 重定向到 HTTPS本地调试时看起来就像“网页永远打不开”。3.4 PersistentVolumeClaim持久化动态文件接下来处理“上传文件不丢”的问题。用共享存储解决apiVersion: v1 kind: PersistentVolumeClaim metadata: name: php-demo-uploads-pvc namespace: web spec: accessModes: - ReadWriteMany resources: requests: storage: 20Gi storageClassName: nfs-csi注意accessModes: ReadWriteMany这个字段。ReadWriteOnce代表只能挂载到一个节点如果两个 Pod 被调度到不同节点第二个 Pod 就挂不上这个卷了。而 PHP 应用经常有多个副本同时读写上传目录所以必须用支持多节点读写的ReadWriteMany存储类。如果你用的是云厂商的 NAS、CephFS、NFS 这类存储通常都支持这种模式。在 Deployment 里挂载这个 PVCcontainers: - name: php-fpm ... volumeMounts: - name: uploads mountPath: /var/www/html/public/uploads volumes: - name: uploads persistentVolumeClaim: claimName: php-demo-uploads-pvc这里有个潜在坑同一路径上PVC 挂载会“覆盖”镜像里或 emptyDir 里同路径的内容。比如你镜像里/var/www/html/public/uploads有一些默认图片挂上 PVC 后这些默认图片就看不到了。解决办法是先把默认文件拷到一个临时目录再在启动脚本里拷贝到 PVC 挂载路径。这个机制有点像把 U 盘插到电脑的某个文件夹上文件夹原本的内容就被“盖住”了。4. 实操部署全流程从 nginx php 站点到 k8s 环境4.1 环境检查与命名空间规划正式操作之前先确认集群状态kubectl cluster-info kubectl get nodes -o wide我一般会单独建一个命名空间避免跟其他项目混在一起kubectl create namespace web命名空间像“虚拟集群”里的分区。不同的项目放在不同 namespace资源配额、网络策略、监控范围都能分开。你也可以顺手打上标签方便后面用kubectl label namespace web purposedemo4.2 部署顺序先存储类、再 PVC、再应用、最后 Ingress这套顺序是我的实操心得可以有效减少“来回试错”的时间第一步确认存储类可用如果没装 NFS CSI 插件先装好kubectl get storageclass如果storageClassName填了不存在的存储类PVC 会一直停留在Pending状态而且事件里有明确的报错。可以先创建一个测试 PVC 验证 StorageClass 是否正常工作。第二步创建 PVCkubectl apply -f pvc-uploads.yaml kubectl get pvc -n web看到Bound状态说明 PVC 已经成功绑定到 PV。第三步创建 ConfigMap 保存 nginx 配置kubectl create configmap nginx-php-config -n web \ --from-filedefault.conf./default.conf用 ConfigMap 管理配置的好处是改配置不需要重新构建镜像。但要记住ConfigMap 更新后Pod 里的配置文件不会自动刷新必须滚动重启 Deployment 才能生效。实际操作是kubectl rollout restart deployment/php-demo -n web。第四步创建 Deploymentkubectl apply -f deployment.yaml kubectl get pods -n web -w这里要耐心等到所有 Pod 状态变成Running且READY显示2/2。如果出现0/2大概率是 initContainer 卡住了或者容器启动失败用kubectl describe pod pod-name -n web查看事件。第五步创建 Service 和 Ingresskubectl apply -f service.yaml kubectl apply -f ingress.yaml第六步验证整条链路kubectl get ingress -n web kubectl get svc -n web curl -H Host: demo.example.com http://节点IP/4.3 验证阶段的“神操作”临时端口转发如果集群没有外部负载均衡Ingress Controller 还没暴露出来可以用kubectl port-forward快速联调kubectl port-forward -n web svc/php-demo-svc 8080:80然后本地浏览器直接访问http://localhost:8080。这里走的是 Service 的稳定入口虽然只代理到一个 Pod但能完整验证 nginx 转发 php-fpm、静态文件加载、数据库连接这些核心链路是否正常。4.4 从本机 LNMP 迁移到 k8s 的“搬家”清单如果你现在已经有一套跑在单机上的 nginx php 网站我的迁移建议是分五步走不要一锅端一是代码迁移。把项目代码完整打包特别注意排除运行缓存比如runtime/、var/cache/目录这些目录应该留空交给容器启动时重建。二是环境变量梳理。原来写在 php.ini、.env文件里的数据库连接串、Redis 地址、密钥等敏感信息迁到 k8s 后统一用 ConfigMap非敏感和 Secret敏感管理不要再硬编码进镜像。三是持久化目录识别。列出所有需要写文件的目录uploads、logs、session逐个决定用 PVC 还是 emptyDir。这个步骤最费时间但也是最有价值的。四是域名与证书迁移。DNS 解析改成指向 Ingress Controller 的地址证书做成 Secret 挂到 Ingress。如果你原来用的是宝塔面板自动签发的证书需要重新申请或手动导入 k8s 的 Secret。五是灰度验证。真正切换流量前先用测试域名把 Ingress 命中到一套临时 namespace验证功能无误后再切主域名。这样即使出问题也能秒回滚到原来的单机环境。5. 常见问题与排查技巧实录5.1 问题一访问 PHP 页面返回 404静态资源正常现象首页 HTML 能加载CSS/JS/图片也正常但访问index.php或者带.php的 URL 全部 404。排查思路这类问题几乎都出在 FastCGI 参数上。先进入 nginx 容器看日志kubectl exec -it pod-name -n web -c nginx -- tail -f /var/log/nginx/error.log日志里如果出现Primary script unknown说明SCRIPT_FILENAME参数里的路径在 php-fpm 容器内不对。检查default.conf里fastcgi_param SCRIPT_FILENAME的值确保和 php-fpm 容器里代码的实际绝对路径完全一致。很多 PHP 框架把入口文件放在public/子目录下路径少了这层就直接 404。经验心得nginx 返回 404 是“转发规则没命中”返回 502 才是“后端没起来”。遇到 404 先别怀疑 php-fpm 挂了大概率是try_files或SCRIPT_FILENAME的问题。5.2 问题二PHP 页面返回 502 Bad Gateway现象所有 PHP 请求都返回 502静态资源正常。排查思路502 的含义是 nginx 连接不上 php-fpm。依次检查三层第一层php-fpm 容器是否活着kubectl get pods -n web -o wide如果 php-fpm 容器重启次数很高用kubectl logs pod-name -c php-fpm看启动日志常见原因就是startup failed或扩展加载失败。第二层nginx 里的fastcgi_pass地址是否可达。在 nginx 容器内测试kubectl exec -it pod-name -n web -c nginx -- sh wget -qO- http://127.0.0.1:9000/ping如果这个请求不通说明 php-fpm 没监听 9000 端口或者监听地址配置成了127.0.0.1:9000以外的地址。第三层资源限制。如果 Pod 内存 limit 设置得过小php-fpm 容器启动后会因为 OOM 被杀表现为“启动后几秒就重启”。用kubectl describe pod看看容器状态里有没有OOMKilled字眼。经验心得php-fpm 容器被 OOM 杀的概率比想象中大得多尤其是框架比较重的应用Laravel 单进程就能吃掉 100MB 内存。建议给 php-fpm 单独设置 resources并准备一定比例的 swap 或调大 memory limit。5.3 问题三上传文件提示失败或 413现象POST 请求上传文件返回 413 Request Entity Too Large。排查思路413 是 nginx或 Ingress Controller在 HTTP 层拒绝了超大体量的请求。这个问题的可能位置有两层都得改第一层ingress-nginx 的默认proxy-body-size是 1MB需要在 Ingress 注解里调整nginx.ingress.kubernetes.io/proxy-body-size: 50m第二层Pod 里 nginx 容器的http块或server块中加入client_max_body_size 50m;经验心得如果你上传报错发生在“项目内部”比如框架返回 413 页面那就是 Pod 内 nginx 的配置问题如果是在“外部入口”就被拦截那就是 Ingress 注解没加。排查的时候先抓 Ingress Controller 的日志能区分到底是哪一层拦截的。5.4 问题四Pod 一直 CrashLoopBackOff现象kubectl get pods显示状态为CrashLoopBackOff容器反复启动失败。排查思路先从事件和日志入手kubectl describe pod pod-name -n web kubectl logs pod-name -n web -c php-fpm --previous最常见的三类原因一是 php-fpm 启动失败比如配置里写的listen 127.0.0.1:9000和启动脚本里的参数冲突。二是 initContainer 失败。比如cp -a复制时源目录为空或者没有递归复制权限。initContainer 失败的话正式容器永远不会启动状态也会表现为 CrashLoopBackOff。三是健康检查失败。如果你配置了readinessProbe而探测路径返回的并不是 200k8s 会认为容器“未就绪”多次失败后可能重启容器。经验心得CrashLoopBackOff看起来吓人实际上只是“启动失败-重启-再失败”循环。关键是看describe里的事件和logs里的日志先把这两个信息拿到手90% 的问题都能定位。5.5 实操心得汇总速查表症状最可能原因优先排查动作404 on .phpSCRIPT_FILENAME 路径错误 / try_files 配置不当查看 nginx error.log502 Bad Gatewayphp-fpm 未监听 / OOM / fastcgi_pass 地址错误describe pod 容器日志413 Upload Too LargeIngress proxy-body-size / nginx client_max_body_size查看 Ingress Controller 日志Pod CrashLoopBackOff启动命令错误 / 扩展无法加载 / initContainer 失败describe pod logs --previous上传文件后丢失PV/PVC 未挂载或挂载路径错误检查df -h和挂载目录6. 我个人总结的几条实战经验最后分享几个我在这个项目里踩过坑之后形成的心得不太成体系但是每条都实打实有代价。第一永远先做最小化验证再上全套。我第一次部署 nginx php 时直接一套 YAML 全怼上去结果 Ingress、PVC、ConfigMap 一起出问题根本不知道从哪开始排查。正确做法是先起一个最简单的 Deployment一个 nginx 容器验证集群网络通不通再逐步叠加 php-fpm、共享卷、Ingress。每加一层就验证一次这样出了问题一定知道是刚加进来的那一层惹的祸。第二不要把 php-fpm 的配置分散到多个地方。php 项目涉及php.ini、php-fpm.conf、pool.d/www.conf、nginxfastcgi_params好几层配置。我建议在镜像构建时就把所有 PHP 相关配置固化好run 起来后不要再通过 ConfigMap 覆盖。否则谁能记住哪个配置放在哪个文件里三个月后回来看代码绝对一脸懵。第三日志必须集中化。Pod 一重启kubectl logs能看到的历史日志就没了。k8s 里如果不做日志采集等于你把故障现场反复扔进碎纸机。最简单的方案是在集群里部署一个 Filebeat 或 Fluent Bit把容器日志采集到 Elasticsearch 或 Loki。你不需要一开始就搞完整的大数据链路但“日志能留存、能搜索”这个底线必须守住。第四对 ConfigMap、Secret 的变更要小心。k8s 不会因为你更新了 ConfigMap 就自动更新正在运行的 Pod。我早期改过 nginx 配置后等了好几分钟发现“没生效”一度以为是配置写错了。后来养成了习惯改完配置顺手kubectl rollout restart确认卷挂载的哈希发生变化Pod 更新完成后再验证。这个项目做完之后我最直观的感受是k8s 本身并不难难的是把原来“所有进程挤在一台机器、靠手动运维”的思维模式切换成“一切皆对象、一切可声明、一切可重建”的云原生模式。你不需要记住每个 YAML 字段但一定要理解 Pod 之间如何通信、数据从哪里来、到哪里去、挂了之后会发生什么。把这些链路想清楚nginx php 上 k8s 就是水到渠成的事。如果你正在做类似的迁移建议先把这篇文章里的第一个 Deployment双容器共享卷跑通亲眼看一次 initContainer 如何工作、共享卷如何被两个容器同时读写再往生产环境推。这个实验比读十篇原理文章都管用。
返回列表