k8s中 fpm 和 nginx 的文件共享问题

引言

初看这是一个值得记录的问题吗?或者说这算是一个问题吗?各种数据卷挂载,然后一顿操作不就完成了么?我也是这么认为的。看人讨论 fpm 与 nginx 的文件共享问题。想到自己当初也遇到了类似的困惑,记得当时很是纠结和折腾。但是一时之间记不起来具体是哪一个环节遇到了问题。索性打开以前写的编排代码查了个究竟,记录之。

docker 镜像构建哲学

  • 首先当然是镜像体积越小越好。有利于网络传输有利于分发,镜像体积小也是 docker 比之于虚拟机方案最大的优势之一呐。alpine 等小体积基础 linux 镜像的问世就是例证。
  • 一个容器一个服务。说出话来这个可能有的同学会不服,直接将所需要的服务打包运行在一个镜像中不就好了。拿经典的 lnmp 来说,将操作系统、数据库、fpm、nginx全部打包到一个镜像中启动起来不就好了么。省去了存储和网络隔离的各种麻烦。即使不打包数据库,打包个lnp也行呐。确实可以这么做,但是实际操作起来并不是那么理想。想想有一个进程意外退出的情况,我们可能需要再打包一个supervisor进去。或者写一个 shell 脚本打包进去以实现健康检查的逻辑,以借助 k8s 的健康检查策略避免一个服务进程挂掉导致整个容器无法提供服务的情况出现。这样就好了么?考虑日志收集和性能测量监控问题,又有很多事情要做了……。显然一般情况下将所有关联服务打包到一个镜像之中不会给我们带来便利,相反会让我们失去 docker 和 k8s 这两个神器给我们带来的各种便利。

为什么一定要共享文件

php-fpm 无法直接提供服务,必须借助 nginx 或者是 apache 之类的 web 服务器才能对外提供服务。 虽然有 -S 选项可以启动一个 web 服务,但是遗憾的是只能同时执行一次请求,不能用于生产环境。web 服务需要解析 http url 以确定需要执行哪个脚本文件,接着通过 fcgi 协议从 php-fpm 获取数据最终返回给前端。啰嗦了这么多,其实只是为了说明:php-fpm 和 nginx 都需要访问同一套 php 的代码,无论如何都无法避开这个文件共享问题。

代码的迭代更新问题

理所当然的,我们需要将同一份 php 代码文件挂载到 nginx 和 fpm 的容器中。此时我们还需要考虑代码的迭代和更新问题。以下讨论几种方式的优劣:

  • 将代码统一更新到一个外部数据卷中,所有的 lnp pod 都挂载这个外部数据卷。就可以很容易的实现代码的迭代更新。这种方式的优势在于更新很方便快速,毕竟只需要更新维护一份代码。事实上,我认为这种方式百害而无一利。我们需要做到整个代码文件的同步映射需要费很大的劲。当然可以简单粗暴的直接删除目录下的所有文件,然后再推送新的代码进去,但是在部署的这段时间将会导致所有服务短暂的不可用。回滚等操作也是一大难的问题。最致命的是堆外部存储的访问是有网络 io 消耗的。解释型语言的性能问题已经堪忧了,再来这么一道岂不是雪上加霜。虽然有 CSI 插件可以实现将存储同步至本地,但是仍然无法规更新时避服务不可用的问题以及其他更多的风险。还有一个问题是不规范的编码会直接将日志写到文件,而且是到处分散的项目目录下的文件中,很是随心所欲。一些老旧项目尤为明显,改都改不过来,这种飘逸的做法无力吐槽。这会导致共享存储可能被塞满,或者影响文件同步……这种方案想想就好。

  • 编写一个可以拉取代码的镜像,比如使用 git 进行代码拉取。然后通过initContainers选项在nginx 和 fpm 容器启动前代码拉取到共享目录中。嗯~很有逼格的一个方案,但是代码一般都是存储在私有仓库中的,你可能需要将 git 仓库的认证信息打包到镜像中或者是 k8ssecret 中。这也好办,但是如何告诉这个容器要更新什么代码呢?哪一个标签还是哪一个分支?虽然没有共享外部存储那么鸡肋但是实施起来是真的要费些吃奶的力气的。虽然有 gitRepo 等实现可以达成这个目标,但是仍然不是最优。

  • php 的代码直接打包到一个镜像中,比如 fpm 镜像。代码更新后直接更新 deployment 的镜像标签不就好了嘛。虽然镜像体积会变得有些大,但是有 docker 镜像的分层缓存策略,实际上后续的更新中只会拉取新增的代码下来。这个看起来是最简便安全的,事实也是如此。然而最让人郁闷的地方也就是这里了。

dockerVOLUME 指令(匿名数据卷)可以在容器启动时自动创建一个挂载目录,并且将镜像中对应目录下的文件拷贝到该目录挂载的实际系统目录中。按照以上所述,此时只需要将 nginx 也挂载到这个代码目录就完成了代码共享。至少在docker-composeswarm 中没有问题。

于是兴高采烈的创建一个 emptyDir 存储卷,并且将 nginx 和 fpm 镜像中VOLUME路径挂载到这个数据卷中。然后就很郁闷的发现nginx并不能共享到预期的 php 代码文件。此时 fpm 容器下面的的目录中也找不到预期的代码文件。我一度认为这是 k8sbug 。微信上呼叫宋进超大神,得到的回复是emptyDir起始是空的,细品!按理来说最终都会挂载到一个系统路径上,docker应该依然把匿名卷中的内容拷贝过去才是。但是人家 k8s 就是这么严谨。不知道是屏蔽了 docker 的复制操作还是自己将目录清空了。

知道了问题原因,解决起来也简单。将共享目录和存放代码的docker匿名卷路径分开,使用initContainers执行一个复制操作即可。

nginx 和 fpm 可以不在一个pod中吗

是不是可以将 nginx 分离出去呢,这样在启动很多 php 项目的时候可以少启动很多个nginx减少了集群资源的消耗。原理上可行,即使不能共享网络空间(nginx中通过127.0.0.1访问fpm),但是仍然可以通过servicecgi请求发送的目标 fpm 。共享存储卷换成hostPath。但是在维护上会带来很多不便,比如 nginx的配置会变得很杂,由于hostPath的特性同样会给代码的迭代跟新带来额外的负担。 综上nginx和fpm最好是在同一个pod中。

updatedupdated2020-06-302020-06-30
加载评论