[Docker]应该把 nginx 和 PHP 放在一个 image 里还是分开?

因为老板想搞 K8S,但是我连 都不懂,就觉得还是要学一点点 的,之前还是看了一点点的,甚至折腾过一个开发环境的方案,但是,很长时间不弄了以后,就全都还回去了。

这次我又想自己搭建一个基于 Docker 的开发环境,以前只是把 Docker 当成一个易于分发的开发环境来思考,所以,我记得以前费了很大的力气,做出了一个单一的 image,把 + + Redis + Memcahed 全部压到一个 image 里面了,然后用 Volume 映射代码,MySQL 连接本地网络公用的实例,形成一个开箱即用的开发环境。

这次,因为稍微看了点编排的概念,开始纠结,这些东西真的应该压到一个 image 里面么?为啥不是多个 docker image,然后,怎么想办法编排一下子?不是传说有个最优实践是一个 container 里面只放一个服务嘛?

第一个纠结的就是 nginx 和 PHP 到底应该放在一个 image 里面还是不同的 image 里面呢?

网上搜了一下,发现还是有不少文章讲 nginx 和 PHP 分开放无法访问的问题。看来,显然有人做过尝试了,而且遇到了问题。就看看他们遇到了什么问题。经过一番分析,我感觉我想明白了这个事情,到底应该放在一起,还是分开放。

分开或者合并的原理

其实,经常配置 nginx 和 PHP 的话,就会知道,这俩在原理上,分开和合并都是完全可能的。而且,从提供的接口层面,我们看不出来到底鼓励怎么做。

常见的配置方法是,使用 fastcgi 的方式来配合 nginx 和 PHP,我这两年的经验,用 debian 的 apt-get 安装的默认配置看,nginx 和 PHP 的连接方式,是用的 UNIX sock 文件,这种情况下显然是必须在一台机器上了。但是,显然,我们知道 fastcgi 是支持 TCP 协议的,就是大家很熟悉的 9000 端口,流行的配置文件都是 tcp://127.0.0.1:9000 这样的编写方式。这个本地 IP 地址,看起来也是部署在一台机器的。

不过呢,既然支持 TCP,就必然可以分布在不同的机器上面,原理上完全成立的。

网上流行的问题是什么?

那么那些把 nginx 和 PHP 放到不同 image 的同学遇到了什么问题呢?其实,是路径问题。

其实,我想,因为部署在一起的方式太过于流行了(可能的根本原因是互联网的绝大部分网站的规模很小,都在单台服务器上),以至于很多人没有注意过路径这个问题。

nginx 是一个服务器应用程序,每次要伺服的时候,都要从一个文件根目录出发,寻找需要伺服的文件路径。而 PHP 的 FPM 进程,也是一个服务器应用程序,它也有一个问题,就是需要从一个文件根目录出发,去寻找需要解释的文件路径。

因为最为流行的部署方式是放在一起的,往往也包含了静态文件和动态文件部署在一起的问题(前后端不分离是更为流行的做法),所以,用到的文件根目录,都是在一起的,所以,很显然,如果分开部署 nginx 和 PHP 的话,一定会遇到文件路径寻址的问题。

nginx 配置文件里,会用 root 变量指定一个 server 寻址的根目录,合并部署的时候,和 PHP 的根目录是一样的,用 document_root 变量(就是 root 的别名)传递给 fastcgi,但是,分开部署的时候,一个 server 的 root 变量,指的 nginx 所在的计算机的路径,但是 fastcgi 需要使用的 SCRIPT_FILENAME 参量,里面的路径,要用的是 PHP 所在的计算机的路径。既然是两台计算机,路径可以吻合,也可以不吻合,所以,分开部署的话,还能正确使用,是有一定概率的。你怎么知道 nginx 的 image 和 PHP 的 image 正好基于一个发型版?在 Docker 的世界下,两个 image 来自天南海北的两个人制作的可能性很高。

怎么解决路径问题?

要说怎么解决这个问题,其实,说到这里,知道了原理,就非常好解决,梳理好两个服务器程序应该使用的路径参数就好了。

document_root 这个变量,一般会继承 server 段落的 root 变量的配置,或者 http 段落的 root 的配置。如果这个 root 和 PHP 所在的机器,驴唇不对马嘴,那么可以猜测一定跑不起来。

解决方法是,把 PHP 所在机器的 root 在 location 段落里重新设定。或者,设置 SCRIPT_FILENAME 这个 fastcgi_param 的时候,用绝对路径直接写,不要用 $document_root$script_name 这种变量的写法。

然而,像我这么纠结的人,还是很不满意的,因为这种写法让我觉得恶心。为什么呢?因为耦合了。

nginx 在一台机器上,以服务的面貌提供自己的服务,而 PHP 在另一台机器上,也以服务的面貌提供自己的服务。但是,如果 nginx 的配置,必须知道 PHP 那台机器的文件路径,我想,这就是它知道了它理该不知道的事实,这就是耦合,这就是丑陋。

其实,nginx 作为一个服务,从客户端那里得到了 script_name,当然,它自己解释不了,也不拥有这个文件,所以,用 fastcgi 把 script_name 传递给 PHP 所在的服务就行了。这是最最必要的操作了。能不能不用搞清楚 PHP 所在的计算机的路径呢?当然可以,只要使用相对路径就行了。

那就需要 PHP 的 fastcgi 启动的时候,知道自己的根目录在什么地方,然后传过来相对路径,都可以自己找到正确的位置,从而解决了一个耦合。PHP 的 FPM 当然可以这么配置,只是因为一起部署的缺省配置太过流行,咱们从没注意过这个可能性而已。

到底应该放在一个 image 里还是分开?

答案是:视情况而定。(KAO!跟没说一样)

其实,PHP 的 FPM 是支持一个叫 pool 的特性的,我们可以在一个 pool 里面通过 chroot 和 chdir 之类的特性来把访问限制在一个特定的路径里,就是代码所在的根目录。

但是,那样的话,如果你一台机器上有多个网站的源代码,你就必须把根路径指向多个网站的共同根目录,不然的话,PHP 就只能伺服其中一个。

我们知道,世界上绝大多数网站的规模很小,所以,一台 Linux 可以同时支持很多网站的使用,所以,绝大多数缺省配置,FPM 只配置了一个 pool。这种情况下,nginx 传递相对路径的时候,必须加一个网站名的前缀。懂道理的话,会很简单啦,怎么都不会搞混。但是,显然增加了这套架构的学习成本,不是每个人都能很快搞那么明白的。

所以,详细回答一下“到底应该放一个 image 还是分开?”这个问题。

如果,你只是在本地,做一个给自己用的开发环境,我强烈建议放在一个 image 里面。一个程序员,往往会开发 N 多个网站的代码,放在一个里面,最省资源。配置也最为熟悉和简单,网上随手一搜,搜出来的配置很大概率可以部署成功。

如果,在线上环境,部署一个流量弹性范围很广,或者增长可能性很高的服务的时候,分开部署的优势比较大。因为,nginx 的性能是非常好的,远远好于 PHP。分开部署后,PHP 的 FPM 进程不够用了以后,可以不断扩容,增加 container 数量就行了。但是,这种方案的话,学习成本较高,需要程序员对这几个服务的配置有比较深的理解,就算自动扩容,执行动作感觉也不是单纯增加一个 container 就行的,毕竟一个 container 就有一个入口 IP,还要把扩容出来的入口 IP 告诉 nginx 所在的 container。

结论

其实吧,最流行的方案,恰恰是最正确的方案。比如,你可以直接下载到 LNMP 完备的 image,这种东西需求量最大,所以最流行。因为都是单个程序员用来解决自己开发环境的。就算拿去用在生产,问题也不大,小流量的服务和网站,才是这个世界的主流。不过想明白为什么是这个样子,就要花点心思。