# 從 Nginx + PHP-FPM 轉換 Laravel Octane + RoadRunner ###### tags: `Laravel`,`PHP`,`RoadRunner`,`Octane` Nginx + PHP-FPM 的組合從 2010 年後稱霸 PHP 應用程式伺服器,PHP-FPM 也是 PHP 官方標準,所以 Nginx + PHP-FPM 的組合基本上是目前 PHP 網站的主流。 然而 Laravel 搭配 Nginx + PHP-FPM 在高併發時仍會出現一些問題: - Laravel:每個 HTTP request 都會都會重新載入 service provider、env 設定、解析路由及控制器等等初始化工作,降低了效能。 - PHP-FPM: - 每個 worker process 只能處理一個 request,如果達到最大 process 數量(`pm.max_children`),就會剩下的 request 就會進入等待,可能會發生 timeout 的狀況。 - process 通常會設定 dynamic 模式,也就是根據 server 機器規格,設定最小數量(`pm.start_servers`),跟動態擴展的最大數量(`pm.max_children`),若在短時間大量請求時,會花費大量 CPU 去 fork 新的 process。另外 process 也會佔用記憶體資源,即時是閒置的狀態。 - 未支援長連結(ex: WebSocket)。 為了解決高併發時,啟動新 process 初始化問題,新的架構 Laravel Octane + RoadRunner 誕生了: - [Laravel Octane](https://laravel.com/docs/master/octane): Laravel 官方推出的高效能應用程式伺服器, Octane 讓 Laravel 應用保持在記憶體中,無需重新載入初始化設定,減少重新啟動的開銷,大幅提升請求處理速度。另外 Octane 也支援 FrankenPHP、Swoole、RoadRunner 這些高效能的 PHP app server。 Octane 在 Start 時就進行初始化 Laravel app 跟 kernel,之後的每個 request 使用記憶體儲存的 app 跟 kernel,而非每個 request 進行 app 跟 kernel 初始化,減少初始化時間。但是由於不會執行 kernel terminate 階段,對於有些 terminate 階段才會做執行的功能,可能要特別注意有無問題,[Mololog 的 Buffer Handler](https://hackmd.io/VLu8f2ywRbyLtpzejE-Igg) 就是一的例子。 - [RoadRunner](https://docs.roadrunner.dev/docs):高效能的 PHP app server,由 Golang 撰寫,負責負載均衡及進程管理,並把請求分配給 PHP Worker。其最大特點是把 PHP 應用程序保持在記憶體中,減少啟動成本。另外也支援長連結跟 gRPC。 ## 轉換流程 轉換流程很簡單,就分成 RoadRunner 跟 Laravel Octane 兩部分。 ### RoadRunner 本人原本專案 Dockerfile 會安裝 Nginx, Supervisor, php-fpm,並把 config 檔案 COPY 至 image 裡,如下: ```dockerfile FROM alpine:3.20 COPY nginx.conf /etc/nginx/http.d/default.conf COPY upstream.conf /etc/nginx/http.d/upstream.conf COPY php.ini /etc/php82/conf.d/99_override.ini COPY php-fpm.ini /etc/php82/php-fpm.d/www.conf COPY supervisord-web.conf /etc/supervisor/conf.d/supervisord.conf RUN adduser -u 1001 -D -S -G www-data www-data && \ apk add --no-cache --update \ curl-dev \ openssl-dev \ supervisor \ nginx \ php82 \ php82-fpm \ php82-pdo \ php82-pdo_mysql \ ...(略) WORKDIR /var/www CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] ``` 轉換成 RoadRunner 後,只保留 PHP 及套件,外加上 RoadRunner Server 和設定檔為 `.rr.yaml`。另外值得注意的是 RoadRunner 預設 `WORKDIR` 是 `/app`,跟 nginx 預設不同,當然可以從設定檔去做調整。 ```dockerfile FROM ghcr.io/roadrunner-server/roadrunner:2024 as roadrunner FROM alpine:3.20 COPY .rr.yaml /etc/.rr.yaml RUN apk add --no-cache --update \ curl-dev \ openssl-dev \ php82 \ php82-pdo \ php82-pdo_mysql \ ...(略) # Copy RoadRunner COPY --from=roadrunner /usr/bin/rr /usr/bin/rr EXPOSE 80/tcp WORKDIR /app ``` ### Laravel Octane Laravel Octane 可透過 composer 跟 php artisan 指令安裝,Laravel Octane 會處理框架本身、PHP Workers 與 RoadRunner 彼此間的介接問題。安裝 Octane 下列步驟 - 安裝 Octane ``` composer require laravel/octane php artisan octane:install ``` 若執行 `php artisan octane:install` 無法正常安裝,可自行手動安裝 RoadRunner 套件: ``` composer require spiral/roadrunner composer require spiral/roadrunner-http composer require spiral/roadrunner-cli ``` - 增加環境變數 OCTANE_SERVER `.env` 增加環境變數 `OCTANE_SERVER=roadrunner` - Laravel Octance 支援[檔案異動監聽](https://laravel.com/docs/12.x/octane#watching-for-file-changes) 啟用檔案異動監聽才不需要在異動檔案時另外手動重啟 Octance Server,在開發環境是很實用的,若要啟用,在 Start Octane 時增加 `--watch` 標記即可,但環境必須安裝 node.js 和 [chokidar](https://github.com/paulmillr/chokidar) 套件(`yarn add chokidar --dev`)。 上述 Laravel Octane 相關的套件安裝完成後,可以開始處理 web 服務的 image 了,在 Build 線上環境的 image 時,通常會從基底 server image 開始,再把必要的 Laravel 資料夾/檔案複製打包成 web 服務專用的 image ```dockerfile FROM <上一步驟 RoadRunner Serve Image 存放位置> ARG APP_KEY ARG AWS_ACCESS_KEY_ID ARG AWS_SECRET_ACCESS_KEY ...(略) ENV APP_KEY=$APP_KEY ENV AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID ENV AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY ...(略) WORKDIR /app COPY app ./app COPY bootstrap ./bootstrap COPY config ./config COPY database ./database COPY public ./public COPY resources ./resources COPY routes ./routes COPY storage ./storage COPY vendor ./vendor COPY artisan ./artisan COPY composer.json ./ COPY composer.lock ./ COPY server.php ./ COPY .rr.yaml ./ COPY entrypoint.sh ./ RUN chmod +x /app/entrypoint.sh ENTRYPOINT ["/app/entrypoint.sh"] ``` 原本是透過 Supervisor 啟用/監控 nginx 跟 php-fpm 運作,是否需要透過 Supervisor 啟用/監控 Laravel Octane 看系統需求,假設 Octane Server 掛了,Supervisor 會嘗試自動重啟,但 K8S 通常會設定 Liveness Probe,Octane Server 掛了,health api 沒有回應,K8S 也會自動重啟 pod,所以就看需求囉! 這邊例子沒有用 Supervisor,直接改成 Run container 時執行 `entrypoint.sh`,該 shell script 會透過 `php artisan octane:start` 指令啟用 Octane Server,以下範例 workers 數量設定為 2 倍的 CPU 核心數。 ```shell #!/bin/sh CORES=$(nproc --all) WORKERS=$((CORES * 2)) exec php artisan octane:start --server=roadrunner --host=0.0.0.0 --rpc-port=6001 --port=80 --workers=$WORKERS ``` #### 注意事項 Laravel Octane 提供 workers 數量設定(`--workers`)以及每個 worker 可以處理 request 最大上限(`--max-requests`),若為特別設定,Octane 預設**並非**使用 roadrunner `.rr.yaml` 設定檔的數值(`pool.num_workers` & `pool.max_jobs`),而是把 workers 數量設定為 CPU 核心數,request 最大上限設定為 500。 ## 差異測試 以下使用一樣的 K8S 機器、一致 Laravel/PHP 版本和程式架構,針對單一 pod 做簡單的 health api 壓力測試 ### Nginx + PHP FPM - 每個 Nginx Worker 加上 PHP-FPM Worker 佔了大概三百多 mb 虛擬記憶體,這個 pod 有 8 組 workers。  - 當一秒發送 200 requests,99% 的 requests 300 ms 前就回應,不過 CPU 部分就很明顯往上衝了,如果觸發 HPA 設定的 CPU 使用率,可能就會進行 autoscaling。目前使用的系統架構,每秒超過 200 requests 就會觸發 autoscaling(80% CPU 使用率)。   ### Laravel Octance + RoadRunner - 一樣 8 個 RoadRunner workers,RoadRunner workers 佔用的虛擬記憶體比較小,大概只有一百多 mb。  - 當一秒發送 200 requests,response 回應速度稍微慢一些,不過 CPU 使用率只有微微上升。   - 當一秒發送 500 requests,response 回應速度明顯慢了許多,而 CPU 使用率往上衝到了 autoscaling 臨界點(80% CPU 使用率),不過整體而言 Laravel Octance + RoadRunner 的 CPU 使用表現上會比較好一些。  
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up