因為老板想搞 K8S,但是我連 Docker 都不懂,就覺得還是要學一點點 Docker 的,之前還是看了一點點的,甚至折騰過一個開發環境的方案,但是,很長時間不弄了以后,就全都還回去了。
這次我又想自己搭建一個基于 Docker 的開發環境,以前只是把 Docker 當成一個易于分發的開發環境來思考,所以,我記得以前費了很大的力氣,做出了一個單一的 image,把 PHP + nginx + 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,這種東西需求量最大,所以最流行。因為都是單個程序員用來解決自己開發環境的。就算拿去用在生產,問題也不大,小流量的服務和網站,才是這個世界的主流。不過想明白為什么是這個樣子,就要花點心思。
相關閱讀