编程

Nginx Unit 作为 PHP 和 Laravel 服务器

1065 2023-11-22 03:25:00

Nginx Unit 是一个 Nginx 带来的 “通用 Web 应用服务器”。它是一个可以和你的代码库“直接”通信的 web 服务器,帮你以代码能够理解的方式将 HTTP 请求传递给代码。

对于 PHP 支持,它有一个创建 PHP 进程的 PHP 模块,类似于 PHP-FPM,但不需要 PHP-FPM。摆脱 PHP-FPM 对我来说真的很好,所以我决定看看它是如何工作的。

安装 PHP

我们将在 Ubuntu 服务器上以通常的方式安装 PHP——使用 ppa:ondrej/php 存储库。这基本上是在 Ubuntu 服务器上使用 PHP 的实际方式。它可以让我们获得最新的 PHP,并在同一台服务器上安装多个版本的 PHP(如果需要的话)。

Unit 的一个问题是:它希望使用系统设置的默认 PHP 版本。幸运的是,我们可以自己重新编译它的 PHP 模块(请参阅此处),以使用我们的ppa:ondrej/php 安装的PHP。

首先,我们如常安装 PHP。

sudo add-apt-repository -y ppa:ondrej/php
sudo apt-get install -y php8.2-dev php8.2-embed \
                   php8.2-bcmath php8.2-cli php8.2-common php8.2-curl \
                   php8.2-gd php8.2-intl php8.2-mbstring php8.2-mysql php8.2-pgsql \
                   php8.2-redis php8.2-soap php8.2-sqlite3 php8.2-xml php8.2-zip
curl -sLS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/bin/ --filename=composer

3 件需要注意的事:

  1. 我们需要 php8.2-dev ( -dev 版本) 以获取特定的 PHP "header" 文件,允许我们稍后编译 Unit 的 PHP 模块
  2. 我们需要 php8.2-embed,因为这是 Unit 使用的用于启用 PHP 进程的 SAPI。
  3. 我们不需要 php-fpm,因此不必安装

其他的如正常安装 PHP 那样。

安装 Nginx Unit

我们可以根据 Nginx Unit 的文档进行安装 - 没有需要特别注意的!

我使用的是 Ubuntu 22.04,按照该系统的说明:

sudo curl --output /usr/share/keyrings/nginx-keyring.gpg  \
      https://unit.nginx.org/keys/nginx-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ jammy unit
deb-src [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ jammy unit
" | sudo tee /etc/apt/sources.list.d/unit.list

sudo apt-get update
sudo apt-get install -y unit

我只安装了 unit, 既没安装 unit-dev 也还没安装 unit-php,因为我们需要自己编译 PHP 模块。

手动编译 Unit 的 PHP 模块

接下来,我们手动重新编译 Unit 的 PHP 模块, 以使用 ppa:ondre/php。这是从前面提到的 GitHub issue 中获得的,该 issue 有助于指出我们如何使之生效。

以用户 root 运行:

cd /opt

# Latest version of Unit as of this release
VERSION="1.31.0"
curl -O https://unit.nginx.org/download/unit-$VERSION.tar.gz
tar xzf unit-$VERSION.tar.gz
cd unit-$VERSION

./configure --prefix=/usr --state=/var/lib/unit --control=unix:/var/run/control.unit.sock \
    --pid=/var/run/unit.pid --log=/var/log/unit.log --tmp=/var/tmp --user=unit --group=unit \
    --tests --openssl --modules=/usr/lib/unit/modules --libdir=/usr/lib/x86_64-linux-gnu
./configure php --module=php82 --config=php-config8.2
make php82
make install php82-install

# Restart 
systemctl unit restart

# Check logs to ensure PHP module is loaded
cat /var/log/unit.log

我们自己编译了 PHP 模块,它使用了当前安装的 PHP 版本(从 ppa:ondrej/php 库中获取)。

成功!

创建 Laravel 应用

本例中,我们直接在服务器上创建一个新的 Laravel 应用:

mkdir -p /var/www
cd /var/www
composer create-project laravel/laravel html

# Ensure files are owned by "unit", the user created
# by unit, so PHP can write to log files, etc
chown -R unit:unit /var/www/html

再简单不过!

配置 Unit

现在配置 Unit,以运行应用。

新建 /var/www/unit.json

{
    "listeners": {
        "*:80": {
            "pass": "routes"
        }
    },

    "routes": [
        {
            "match": {
                "uri": "!/index.php"
            },
            "action": {
                "share": "/var/www/html/public$uri",
                "fallback": {
                    "pass": "applications/laravel"
                }
            }
        }
    ],

    "applications": {
        "laravel": {
            "type": "php",
            "root": "/var/www/html/public/",
            "script": "index.php",
            "processes": {}
        }
    }
}

然后让 Unit 使用该配置来运行应用:

sudo curl -X PUT --data-binary @unit.json --unix-socket \
       /var/run/control.unit.sock http://localhost/config/

前往应用(当前监听 80 端口)!使用 curl localhost 测试。

Control API

你可以使用 Control API 检索和更新配置。

我们来 GET 获取配置:

curl -X GET \
    --unix-socket /var/run/control.unit.sock \
    http://localhost/config/

Unit 的优点

我们可以移除掉 PHP-FPM!很好,因为它使我们的应用放入容器变得简单多了。

Unit 似乎也更高效——我使用 ab 一次发送 100 个请求,总共10000个请求,并不会让其宕机。

Unit 的缺点

有一些取舍!

首先,如果不重新编译 Unit PHP 模块,我们就无法切换 PHP 版本。这意味着在使用 Unit 时很难(不可能?)同时运行多个版本的 PHP。

您可能在 Unit 前面还需要另一个 HTTP 层(Nginx、Cloudflare、Cloudfront、fly.io 的 HTTP 层等)。事实证明,Unit 要么让一些标准的配置变得困难,要么根本做不到。一些示例:

  1. (还)不支持 Gzip 压缩
  2. 它使得保护点文件和其他路由变得困难
  3. 不容易为静态资源设置缓存 header