NGINX 微服务 Unit + PHP 初探

NGINX 微服务 Unit + PHP 初探
Photo by KOBU Agency / Unsplash

简介

NGINX Unit 是一个动态的网络应用服务器,它的设计初衷是为了运行多种编程语言。如有需要,通过 API 可以动态配置已有应用的参数。

核心功能

  • 可使用 RESTful JSON API 动态配置服务器
  • 可同时运行多语言及多版本的应用
  • 动态语言的进程管理功能(开发中)
  • TLS 支持(开发中)
  • TCP, HTTP, HTTPS, HTTP/2 路由和代理(开发中)

支持的语言

  • Python
  • PHP
  • Go
  • JavaScript / Node.js(开发中)
  • Java(开发中)
  • Ruby(开发中)

使用包管理器安装

目前只支持 CentOS 7.0 和 Ubuntu 16.04。

CentOS

  • 创建 /etc/yum.repos.d/unit.repo,内容如下:
    [unit]
    name=unit repo
    baseurl=http://nginx.org/packages/mainline/centos/7/$basearch/
    gpgcheck=0
    enabled=1
    
  • 安装
    yum install unit
    

Ubuntu

  • 下载 秘钥,该秘钥用于验证 NGINX 源的签名,执行
    sudo apt-key add nginx_signing.key
    
  • 在 /etc/apt/sources.list 增加如下内容:
    deb http://nginx.org/packages/mainline/ubuntu/ xenial nginx
    deb-src http://nginx.org/packages/mainline/ubuntu/ xenial nginx
    
  • 安装
    apt-get update
    apt-get install unit
    

从源码编译安装

安装依赖

  • Ubuntu
    apt-get install build-essential php-dev libphp-embed
    
  • CentOS
    yum install gcc make php-devel php-embedded
    

配置 PHP 模块,最终会生成名为 php.unit.so 的文件

git clone https://github.com/nginx/unit
cd unit
./configure
./configure php

如果是自定义安装的 PHP,需要用如下命令:

./configure php --module=<prefix> --config=<script-name> --lib-path=<pathname>

其中的参数含义如下:

  • --module
    设置 php 模块的文件名前缀,最终生成的文件名为 <prefix>.unit.so
  • --config
    php-config 脚本所在的路径
  • --lib-path
    PHP library 所在的路径

以 PHP7.0 为例,命令如下:

./configure php --module=php70  \
              --config=/usr/lib64/php7.0/php-config  \
              --lib-path=/usr/lib64/php7.0/lib64

得到如下输出:

configuring PHP module
checking for PHP ... found
+ PHP version: 7.0.22-0ubuntu0.16.04.1
+ PHP SAPI: [apache2handler embed cgi cli fpm]
checking for PHP embed SAPI ... found
+ PHP module: php70.unit.so

编译安装

make all
make install

从 Docker 安装

docker pull airycanon/nginx-unit-php

配置和运行

配置文件

一个基础的配置文件必须包含以下两部分:

  • 应用

    下面的示例描述了一个名为 blog 的 PHP 应用

    {
         "applications": {
             "blog": {
                 "type": "php",
                 "workers": 20,
                 "root": "/www/blogs/scripts",
                 "index": "index.php"
             }
         }
    }
    
    配置项 描述
    type 应用的编程语言
    workers workers 的数量
    root 应用的根目录
    index 应用入口文件
    working_directory (可选) 应用的工作目录
    script(可选) 脚本路径,基于应用根目录的相对路径,访问应用内的任意 URL 均会运行该脚本
    user (可选) 运行进程的用户,默认为 nobody
    group (可选) 用户所在的用户组,默认为 user 的用户组
  • 监听器

    监听器指定了 IP 和 端口以及绑定的应用,下面的示例中,端口 8300 的请求全部会被发送至 blogs 这个应用:

    {
         "listeners": {
             "*:8300": {
                 "application": "blog"
             }
         },
         ...
    }
    

创建应用

这里为了简化步骤,采用了 Docker 的方式运行。

  • 首先创建两个目录 applications run 作为容器的 Volume 挂载

    mkdir applications run
    
    docker run -v `pwd`/applications:/applications -v `pwd`/run:/var/run:rw 
    -p 8300:8300 -d airycanon/nginx-unit-php
    
  • 创建一个配置文件 json/config.json,内容如下:

    {
         "listeners": {
             "*:8300": {
                 "application": "blog"
             }
         },
         "applications": {
             "blog": {
                 "type": "php",
                 "workers": 20,
                 "root": "/applications/blog",
                 "index": "index.php"
             }
         }
    }
    
  • 创建一个 applications/blog/index.php,内容如下

    <?php phpinfo(); ?>
    
  • 默认情况下,Unit 通过 Unix domain socket 文件 unit.control.sock 提供 API,通过如下命令创建应用:

    curl -X PUT -d @./json/config.json --unix-socket ./run/control.unit.sock http://localhost/
    

    访问 http://localhost:8300 可以看到 phpinfo 的页面

查看应用配置

  • 查看所有应用的配置:

    curl --unix-socket ./run/control.unit.sock http://localhost/
    
    {
        "applications": {
           "blogs": {
              "type": "php",
              "user": "nobody",
              "group": "nobody",
              "workers": 20,
              "root": "/applications/blog",
              "index": "index.php"
           }
        },
        "listeners": {
           "*:8300": {
              "application": "blog"
           },
        }
    }
    
  • 查看某个应用的配置:

    curl --unix-socket ./run/control.unit.sock http://localhost/applications/blog
    
    {
        "type": "php",
        "user": "nobody",
        "group": "nobody",
        "workers": 20,
        "root": "/applications/blog",
        "index": "index.php"
    }
    

修改应用

  • 修改 *:8300 监听绑定的应用:
    curl -X PUT -d '"blog-dev"' --unix-socket 
    ./run/control.unit.sock http://localhost/listeners/*:8300/application
    
    {
        "success": "Reconfiguration done."
    }
    
  • 修改应用的根目录:
    curl -X PUT -d '"/applications/blog-dev"' --unix-socket
    ./run/control.unit.sock http://localhost/applications/blog/root
    
    {
        "success": "Reconfiguration done."
    }
    

删除应用

  • 删除对 *:8300 端口的监听:
    curl -X DELETE --unix-socket ./run/control.unit.sock
    http://localhost/listeners/*:8300
    
    {
        "success": "Reconfiguration done."
    }
    
  • 删除应用:
    curl -X DELETE --unix-socket ./run/control.unit.sock  http://localhost/applications/blog
    
    {
        "success": "Reconfiguration done."
    }
    

与 NGINX 一起使用

使用 NGINX 作为代理

upstream unit_backend {
    server 127.0.0.1:8300;
}

示例 1

server {

    location / {
        root /var/www/static-data;
    }

    location ~ \.php$ {
        proxy_pass http://unit_backend;
        proxy_set_header Host $host;
    }
}

示例 2

server {

    location /static {
        root /var/www/files;
    }

    location / {
        proxy_pass http://unit_backend;
        proxy_set_header Host $host;
    }
}

设置 Unix domain socket 的 API 可以被远程访问

由于 API 可以动态修改应用配置,请注意增加安全认证

server {
    # Configure SSL encryption
    server 443 ssl;
    ssl_certificate /path/to/ssl/cert.pem;
    ssl_certificate_key /path/to/ssl/cert.key;

    # Configure SSL client certificate validation
    ssl_client_certificate /path/to/ca.pem;
    ssl_verify_client on;

    # Configure network ACLs
    #allow 1.2.3.4; # Uncomment and change to the IP addresses and networks
                    # of the administrative systems.
    deny all;

    # Configure HTTP Basic authentication
    auth_basic on;
    auth_basic_user_file /path/to/htpasswd.txt;

    location / {
        proxy_pass http://unix:/var/run/control.unit.sock
    }
}