「PHP 應該要被淘汰,因為它難以被容器化。」--PHP 經常面對的批評。
本篇文章不會詳述 PHP 應用程式的佈署方法,僅僅說明目前主流的幾種做法,以及歸納我認為良好的 Better Practice。
雖然說是「傳統」,但並不表示「過時」。事實上這些做法至今仍是 PHP 應用程式佈署的主流,對於小型規模的團隊而言,比起導入什麼新潮的 Container 甚至是 Kubernetes,這樣的方式反而比較適合它們。
LAP 為 Linux, Apache, PHP 之縮寫,其職責分工相當明確:在 Linux 上使用 Apache 作為網頁伺服器並提供 PHP 服務。
一般而言,若要在 Apache 上使用 PHP,需要安裝 libapache2_mod_php(這個名字來自於 Debian 的 Repository),這是一個在編譯 PHP source code 時可以選擇編譯的 Apache 模組。
這樣的架構有個優點
同時也存在一些缺點
threded MPM
來執行 PHP註:WAMP 只是將 Linux 換成 Windows,事實上優缺點與設定方式大同小異。
LNP 為 Linux, Nginx, PHP 之縮寫,相較於 LAMP 僅是將 Web Server 改為 Nginx。
Nginx 與 PHP 溝通依賴 FastCGI 協定,不像 apache 那樣需要另外編譯一個模組,其設定上較 Apache 簡化許多。
這樣的架構具備以下優點
同時也可能有些缺點
註:WNMP 只是將 Linux 換成 Windows,事實上優缺點與設定方式大同小異。
註:事實上也不一定要用 Nginx,大部份主流的 Web Server 都有提供 FastCGI Interface,這邊與以下內容可以自由代換。
WIP 是 Work In Progress Windows, IIS, PHP 的縮寫,雖然這年頭用 Windows + IIS 的人實在不多見了……
IIS 與 PHP 溝通的方式與 Nginx 類似:依賴 Windows CGI,不過可以使用 Web Platform Installer 簡化環境的安裝。
題外話,到維基百科上查到 Web PI 的最後穩定版是 6 年前發佈的 5.0,不確定微軟是否有繼續更新的打算?
我絕對不會否認,PHP 是個非常難以容器化的語言。
將 PHP 容器化時,常常要面對幾個問題
就我個人而言,PHP 是否與 Nginx 放在同一個 Image 這沒有正確答案:這取決於你是否想要同時維護 PHP 與 Nginx 的狀態。如果在同個 Container,它們會被視為同一服務處理(尤其是出錯時),如果在不同 Container,它們之間的結合不一定需要是一對一,甚至可以一對多(一個 Nginx Container 對多個 PHP Container),這有利於橫向擴展。
至於 Apache 的話因為綁定 libapache2_mod_php,所以通常它們會在同一個 Image。
註:其實還有另外一派的想法是:讓 PHP 與 Nginx 位於同個 Pod 但不同 Container。我個人認為這是目前最佳解,然而因為 Kubernetes Pod 目前仍未成為標準,且各家服務實現程度有所差距,故此處暫不討論(或許明年鐵人賽就能討論了?)
一般來說,我會在專案的根目錄建立 Dockerfile
FROM composer
WORKDIR /code
COPY . .
RUN composer install --no-dev
並且將 vendor/
加入 .dockerignore
,避免在 COPY
時把它加進去。
會這麼做的目的是:
phpunit/phpunit
composer.json
, composer.lock
及 src/
加入即可。通常 assets 會使用像是 webpack 這樣的工具做最佳化,我個人是推薦使用 Use multi-stage builds 的功能
FROM node AS asset-builder
WORKDIR /code
# 這邊甚至可以只 COPY package.json 及原始檔案
COPY . .
# 這邊會視你的 webpack 指令是怎麼寫的
RUN npm run production
FROM composer
WORKDIR /code
COPY . .
# 這邊看編譯出來的位置下去複製
COPY --from=asset-builder /code/public/app.css /code/public/app.css
COPY --from=asset-builder /code/public/app.js /code/public/app.js
RUN composer install --no-dev
註:打包 assets 或打包 composer dependencies 的順序是沒什麼關係的(除非有明確的相依性)
Roadrunner 是一套用 Golang 寫的 PHP Web Server,在不需要啟用 FastCGI 的前提下就能夠產生用於 production 的 Web Server 服務,使其能夠直接包在一個容器中執行。
它相容於 PSR-7,所以諸如 Slim Framework 就能良好地被支援,對於 Laravel 的支援度也算相當高,不過無法用於像 Wordpress 那樣不支援 PSR-7 的環境。
需要注意的是,對於一些會讓 Process 停擺的函式(如 die
, exit
)會無法使用。
swoole 是一個 PHP 的 extension,它大幅度改寫 PHP 的底層運作,並且提升 PHP 運行效率(在部份 case 中甚至能與 Golang 一較高下)
swoole 允許啟動一個 web server (事實上 swoole 內建了 tcp 及 udp server 的啟動能力,並且可以用於設計 websocket 或 gRPC service),並直接將其包在一個容器中執行。
swoole 目前還是存在許多問題,例如對於 Xdebug 以及 PostgresQL 的支援性都還不夠高,且因為底層改寫 MySQL 或 Redis 等連接方式,導致與一般使用邏輯迥異。
就個人角度而言,如果是既有專案,不妨重構為 podman/k8s 這樣以 Pod 為單位的現代化容器服務;如果是新建立的專案,可以考慮使用 roadrunner 甚至是 swoole 去做現代化容器設計。
就目前階段而言,我認為 roadrunner 比 swoole 來得方便,尤其是對於既有程式思維與設計方式而言。