跳至主要内容

Laravel 佈署指南

· 閱讀時間約 8 分鐘
Vincent Chi

相較於比較現代的程式語言(如 Golang 或 Nodejs),PHP 的佈署是相對麻煩許多的--這主要是因為 PHP 的執行環境需要綁定一個網頁伺服器。

PHP 官方主要支援兩種佈署方式:

  • Apache + PHP Module:較簡單,由 Apache 統一管理 PHP 的生命周期
  • PHP-FPM:能夠搭配大部份支援 FastCGI 協定的 Web Server

註:在大部份應用情境下,Apache + PHP Module 會比 Apache + PHP-FPM 慢上一些,尤其是當 Apache + PHP-FPM 時可以啟用 Event MPM,相較於傳統的 prefork MPM 而言可以快上 50%

Laravel 應用程式的佈署

Laravel 是當前 PHP 社群中最熱門的框架,這得益於其設計模式的應用及社群間的活躍程度。

與大部份的 PHP 應用程式類似,Laravel 的佈署仍然受限於 PHP 的佈署模型。只不過,為了實現某些進階功能(如 Cronjob 或 Queue),Laravel 的複雜度又更高了一些。

總結來說,一個 Laravel 應用程式的佈署需要以下三種功能:

  • Web:提供傳統的 HTTP Request 與 Response 的服務
  • Queue Worker:執行 Queue Job,用於處理耗時較高的任務或用於降低尖峰負載
  • Cronjob Worker:執行 Cronjob,用於處理定時任務

Web 的佈署設計

Nginx

礙於篇幅緣故,這邊不詳細列出 Nginx 的 Config,只要保持幾個注意事項即可:

  • Error Log 應該輸出到 /dev/stderr;Access log 應該輸出到 /dev/stdout
  • FastCGI 應該用 {fpm_container_name}:9000 的型式表現,這是因為 Container Runtime 會自動分配 Domain Name 給容器

可以參閱 Laravel 官方的 Nginx 設定檔改寫

FROM nginx:alpine

WORKDIR /app

COPY . .

# COPY nginx.conf /etc/nginx/nginx.conf
# COPY www.conf /etc/nginx/conf.d/www.conf

CMD ["nginx", "-g", "daemon off;"]
  • 可以利用 Multi-stage Build 的特性,先行編譯 Assets 資源,再 COPY 到 nginx image 之中
  • 記得使用適當的 Nginx Config

PHP 與 PHP-FPM

FROM composer AS builder

WORKDIR /code

COPY . .

RUN composer install

FROM alpine:edge

ENV PHP_PACKAGES php81 php81-fpm \
php81-bcmath php81-ctype php81-dom php81-exif php81-fileinfo php81-gd php81-intl php81-mbstring php81-opcache \
php81-openssl php81-pcntl php81-pdo php81-pdo_sqlite php81-pdo_pgsql php81-posix \
php81-session php81-tokenizer php81-xml php81-xmlwriter \
php81-pecl-redis
ENV SOFTWARES postgresql

WORKDIR /app

RUN apk update && apk upgrade && \
apk add ${PHP_PACKAGES} ${SOFTWARES}

COPY --from=builder /code .

CMD ["php-fpm81", "--allow-to-run-as-root", "-F"]
  • composer install 時,可以加入一些優化參數
  • 建議使用 alpine 作為 PHP-FPM 的 Base Image
    • 官方提供的 php:alpine-fpm 在安裝額外 extension 時會花費較多時間(因需要重新編譯),且以 pecl 安裝外部 extension 時偶爾會出現下載失敗導致 Image 建置失敗的例外狀況
    • Alpine Linux 中可以利用 APK 安裝與更新各種擴充套件,惟要注意各版本對於軟體的支援程度不一
      • Alpine 3.15 若要安裝 PHP 8.1 需要使用 testing repository
  • 記得修改 php-fpm.conf,將 listen 改為 0.0.0.0:9000,這是為了讓 nginx 容器能夠存取 php-fpm 容器的必要設定
    • 務必注意:php-fpm 容器絕對不應該對外開放,應該只有 Nginx 容器能夠以 FastCGI 存取 php-fpm 容器

Queue, Schedule Worker

事實上 Queue Worker 與 Schedule Worker 的 Image 是相同的,只不過它使用的指令不同。

  • Queue Worker:php artisan queue:work
  • Schedule Worker:php artisan schedule:work

根據 Laravel 的官方文件,Schedule Worker 應該使用 php artisan schedule:run 啟動,然而這個指令是一次性的(配合 cron,每分鐘執行一次),此處為了配合 Docker Compose 沒有提供 Cronjob 的機制,改用 php artisan schedule:work 的方式啟動常駐服務

事實上,可以直接把上一個階段構建的 PHP Image 直接拿來使用,只需要更換啟動指令即可。

PHP-FPM 的效能監測

對於一些比較現代的應用程式,通常都會整合 Prometheus 指標並且利用 Grafana 呈現清晰易懂的 Dashboard。

PHP-FPM 預設就有提供 Status Page,以 HTML, JSON, XML 格式的狀態資料,並且從 PHP 8.1 之後加入 Openmetrics 的指標。

註:Openmetrics 與 Prometheus 格式是相容的。

啟用 PHP-FPM Status Page

PHP-FPM 的 Status Page 是預設關閉的,需要另外以設定開啟。

打開 PHP-FPM 的設定檔(通常位於 /etc/php/php-fpm.d/www.conf),找到 pm.status_path 將其 uncomment,並且依照慣例設定為 /metrics

pm.status_path = /metrics

對於部份較繁忙的 PHP-FPM,可以另外開啟一個 PHP-FPM Pool,專門用來存取 Status Page:

pm.status_listen = 0.0.0.0:9001

只不過,與大部份 Metrics Service 不同,PHP-FPM 的 Metrics 並非使用 HTTP 而是 FCGI 協定,所以我們需要一個服務專門轉接。

使用 Nginx 轉接 FCGI

眾所周知,可以使用 Nginx 轉接 FCGI,開啟 Nginx 的設定檔(通常位於 /etc/nginx/conf.d/default.conf)加入以下內容

server {
location = /metrics {
fastcgi_pass php-fpm:9001;
include fastcgi_param;
fastcgi_param SCRIPT_FILENAME /metrics;
fastcgi_param SCRIPT_NAME /metrics;
fastcgi_param QUERY_STRING "openmetrics"
}
}

Docker Compose

對於複數個 Container,一般會使用 Docker Compose 進行管理。

註:在 Production 上通常會用 Kubernetes 進行容器編排,但這已經超出本文的範圍,因此略過不提。

目前的服務一共有:

  • WebServer
  • PHP-FPM
  • Metrics WebServer (Nginx)
  • Queue Worker
  • Cron Worker

我們可以設計一個 Docker Compose 的設定檔:

networks:
laravel:
driver: bridge

services:

webserver:
image: webserver # 以 Nginx 作為 Base Image 所建立的 image,用於提供 Laravel 的網頁服務
ports:
- "80:80"
networks: ["laravel"]

php-fpm:
image: php # 以 alpine:edge 作為 Base Image 所建立的 image,用於執行 PHP 程式(FPM, CLI 等)
command: ["php-fpm81", "-F"]
networks: ["laravel"]

queue-worker:
image: php
command: ["php81", "artisan", "queue:work"] # 如果有使用 Laravel Horizon,也可以改成 ["php81", "artisan", "horizon"]
networks: ["laravel"]

cron-worker:
image: php-fpm
command: ["php81", "artisan", "schedule:work"]

metrics:
image: metrics # 以 Nginx 作為 Base Image 所建立的 image
ports:
- "8080:8080"
networks: ["laravel"]

最後,只要設定好 Prometheus 及 Grafana,即可從 metrics:8080 取得狀態資料。

補充資訊

2021 年 5 月,Laravel 官方發佈名為 Laravel Octane 的套件,採用 SwooleRoadrunner 作為沒有 Web Server 的 PHP 佈署解決方案。

這些解決方案會從根本上改變 PHP Runtime 的特性,許多生態系內的既有工具(如 XDebug 或一些 Profiler)都需要修改後才能使用,儘管它們(宣稱)可以提供令人驚豔的效能,但需要權衡其開發成本是否足以支應這樣的效能改善。