NGINX Unit

Docker 中的 Unit§

要使用我们提供的映像在容器化 Unit 中运行你的应用,至少需要

  • 将你的应用文件挂载到容器中的目录。

  • 将 Unit 的侦听器端口发布到主机。

例如

$ export UNIT=$(                                             \
      docker run -d --mount type=bind,src="$(pwd)",dst=/www  \
      -p 8080:8000 unit:1.32.1-python3.11                 \
  )

该命令将存储了你的应用文件的宿主的当前目录挂载到容器的/www/目录,并将容器的端口8000(侦听器将使用该端口)发布为主机上的端口8080,并将容器的 ID 保存到UNIT环境变量中。

接下来,通过控制套接字将配置上传到 Unit

$ docker exec -ti $UNIT curl -X PUT --data-binary @/www/config.json  \
      --unix-socket /var/run/control.unit.sock  \
      http://localhost/config

此命令假定你的配置存储在主机上容器挂载目录中的config.json中;如果该文件在端口8000上定义了一个侦听器,则你的应用现在可以通过主机的端口8080访问。有关 Unit 配置的详细信息,请参阅配置

注意

有关应用容器化示例,请参阅我们的示例GoJavaNode.jsPerlPHPPythonRuby Dockerfile;此外,请参阅以下更详细的讨论。

现在提供一些详细的场景。

容器化单元中的应用§

假设我们有一个具有少量依赖项的 Web 应用,例如 Flask 官方的你好,世界应用

$ cd /path/to/app/
$ mkdir webapp
$ cat << EOF > webapp/wsgi.py

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'
EOF

无论它有多么基本,它已经存在一个依赖项,因此我们将其列在一个名为requirements.txt的文件中

$ cat << EOF > requirements.txt

flask
EOF

接下来,为该应用创建一个简单的单元配置

$ mkdir config
$ cat << EOF > config/config.json

{
    "listeners":{
        "*:8000":{
            "pass":"applications/webapp"
        }
    },

    "applications":{
        "webapp":{
            "type":"python 3",
            "path":"/www/",
            "module": "wsgi",
             "callable": "app"
        }
    }
}
EOF

最后,让我们创建log/state/目录,分别存储单元日志和状态

$ mkdir log
$ touch log/unit.log
$ mkdir state

到目前为止,我们的文件结构

/path/to/app
├── config
│   └── config.json
├── log
│   └── unit.log
├── requirements.txt
├── state
└── webapp
    └── wsgi.py

一切都已准备好进行容器化单元。首先,让我们创建一个Dockerfile来安装应用先决条件

FROM unit:1.32.1-python3.11
COPY requirements.txt /config/requirements.txt
RUN python3 -m pip install -r /config/requirements.txt
$ docker build --tag=unit-webapp .

接下来,我们启动一个容器并将其映射到我们的目录结构

$ export UNIT=$(                                                         \
      docker run -d                                                      \
      --mount type=bind,src="$(pwd)/config/",dst=/docker-entrypoint.d/   \
      --mount type=bind,src="$(pwd)/log/unit.log",dst=/var/log/unit.log  \
      --mount type=bind,src="$(pwd)/state",dst=/var/lib/unit             \
      --mount type=bind,src="$(pwd)/webapp",dst=/www                     \
      -p 8080:8000 unit-webapp                                           \
  )

注意

通过此映射,单元会将其状态和日志存储在您的文件结构中。默认情况下,我们的 Docker 镜像会将它们的日志输出转发到Docker 日志收集器

我们已将源config/映射到容器中的/docker-entrypoint.d/;如果状态为空,官方镜像会将在其中找到的任何.json文件上传到单元的config部分。现在我们可以测试该应用

$ curl -X GET localhost:8080

    Hello, World!

要在文件系统中重新定位应用,您只需移动文件结构

$ mv /path/to/app/ /new/path/to/app/

要将您的应用切换到不同的单元镜像,请首先准备一个相应的Dockerfile

FROM unit:1.32.1-minimal
COPY requirements.txt /config/requirements.txt
# This time, we took a minimal Unit image to install a vanilla Python 3.9
# module, run PIP, and perform cleanup just like we did earlier.

# First, we install the required tooling and add Unit's repo.
RUN apt update && apt install -y curl apt-transport-https gnupg2 lsb-release  \
    &&  curl -o /usr/share/keyrings/nginx-keyring.gpg                         \
           https://unit.nginx.ac.cn/keys/nginx-keyring.gpg                      \
    && echo "deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg]            \
           https://packages.nginx.org/unit/debian/ `lsb_release -cs` unit"    \
           > /etc/apt/sources.list.d/unit.list

# Next, we install the module, download app requirements, and perform cleanup.
RUN apt update && apt install -y unit-python3.9 python3-pip                   \
    && python3 -m pip install -r /config/requirements.txt                     \
    && apt remove -y curl apt-transport-https gnupg2 lsb-release python3-pip  \
    && apt autoremove --purge -y                                              \
    && rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/*.list
$ docker build --tag=unit-pruned-webapp .

从新镜像运行一个容器;单元会自动获取映射的状态

$ export UNIT=$(                                                         \
      docker run -d                                                      \
      --mount type=bind,src="$(pwd)/log/unit.log",dst=/var/log/unit.log  \
      --mount type=bind,src="$(pwd)/state",dst=/var/lib/unit             \
      --mount type=bind,src="$(pwd)/webapp",dst=/www                     \
      -p 8080:8000 unit-pruned-webapp                                    \
  )

容器化应用§

假设您有一个可用于单元的Express应用,存储在myapp/目录中,名为app.js

#!/usr/bin/env node

const http = require('http')
const express = require('express')
const app = express()

app.get('/', (req, res) => res.send('Hello, Unit!'))

http.createServer(app).listen()

其单元配置存储在同一目录中的config.json

{
    "listeners": {
        "*:8080": {
            "pass": "applications/express"
        }
    },

    "applications": {
        "express": {
            "type": "external",
            "working_directory": "/www/",
            "executable": "/usr/bin/env",
            "arguments": [
                "node",
                "--loader",
                "unit-http/loader.mjs",
                "--require",
                "unit-http/loader",
                "app.js"
            ]
        }
    }
}

生成的文件结构

myapp/
├── app.js
└── config.json

注意

不要忘记对app.js文件chmod +x,以便单元可以运行它。

让我们准备一个Dockerfile,以便在镜像中安装和配置该应用

# Keep our base image as specific as possible.
FROM unit:1.32.1-node15

# Same as "working_directory" in config.json.
COPY myapp/app.js /www/

# Install and link Express in the app directory.
RUN cd /www && npm install express && npm link unit-http

# Port used by the listener in config.json.
EXPOSE 8080

当您基于此镜像启动容器时,将 config.json 文件挂载到 初始化 Unit 状态

$ docker build --tag=unit-expressapp .

$ export UNIT=$(                                                                             \
      docker run -d                                                                          \
      --mount type=bind,src="$(pwd)/myapp/config.json",dst=/docker-entrypoint.d/config.json  \
      -p 8080:8080 unit-expressapp                                                           \
  )

$ curl -X GET localhost:8080

     Hello, Unit!

注意

此机制允许仅在容器启动时 Unit 状态为空时初始化 Unit;否则,将忽略 /docker-entrypoint.d/ 的内容。继续之前的示例

$ docker commit $UNIT unit-expressapp  # Store a non-empty Unit state in the image.

# cat << EOF > myapp/new-config.json   # Let's attempt re-initialization.
  ...
  EOF

$ export UNIT=$(                                                                                     \
      docker run -d                                                                                  \
      --mount type=bind,src="$(pwd)/myapp/new-config.json",dst=/docker-entrypoint.d/new-config.json  \
      -p 8080:8080 unit-expressapp                                                                   \
  )

此处,当我们从更新的镜像运行容器时,Unit 不会/docker-entrypoint.d/ 目录获取 new-config.json,因为 Unit 的状态已在之前初始化并保存。

若要在启动后配置应用,请通过 控制 API 提供文件或显式代码段

$ cat << EOF > myapp/new-config.json
  ...
  EOF

$ export UNIT=$(                                                                     \
      docker run -d                                                                  \
      --mount type=bind,src="$(pwd)/myapp/new-config.json",dst=/cfg/new-config.json  \
      unit-expressapp                                                                \
  )

$ docker exec -ti $UNIT curl -X PUT --data-binary @/cfg/new-config.json  \
         --unix-socket /var/run/control.unit.sock  \
         http://localhost/config

$ docker exec -ti $UNIT curl -X PUT -d '"/www/newapp/"'  \
         --unix-socket  /var/run/control.unit.sock  \
         http://localhost/config/applications/express/working_directory

此方法适用于具有外部依赖项的任何 Unit 支持的应用。

多语言镜像§

之前,Unit 具有一个包含所有支持语言模块的 -full Docker 镜像,但它已在版本 1.22.0 中停止使用。如果您仍然需要多语言镜像,请使用以下从基于 Debian 11 的最小 Unit 镜像开始并安装官方语言模块包的 Dockerfile 模板

FROM unit:1.32.1-minimal
# We take a minimal Unit image and install language-specific modules.

# First, we install the required tooling and add Unit's repo.
RUN apt update && apt install -y curl apt-transport-https gnupg2 lsb-release  \
    &&  curl -o /usr/share/keyrings/nginx-keyring.gpg                         \
           https://unit.nginx.ac.cn/keys/nginx-keyring.gpg                      \
    && echo "deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg]            \
           https://packages.nginx.org/unit/debian/ `lsb_release -cs` unit"    \
           > /etc/apt/sources.list.d/unit.list

# Next, we install the necessary language module packages and perform cleanup.
RUN apt update && apt install -y                                              \
        unit-jsc11 unit-perl unit-php unit-python2.7 unit-python3.9 unit-ruby \
    && apt remove -y curl apt-transport-https gnupg2 lsb-release              \
    && apt autoremove --purge -y                                              \
    && rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/*.list

您可以构建自定义 模块,而不是使用包;使用这些 Dockerfile.* 模板 作为参考。

启动自定义§

最后,您可以通过添加新的 Dockerfile 层来自定义 Unit 在容器中启动的方式

FROM unit:1.32.1-minimal

CMD ["unitd-debug","--no-daemon","--control","unix:/var/run/control.unit.sock"]

上述 CMD 指令用其调试版本替换了默认的 unitd 可执行文件。使用 Unit 的 命令行选项 来更改其启动行为,例如

FROM unit:1.32.1-minimal

CMD ["unitd","--no-daemon","--control","0.0.0.0:8080"]

这会用 IP 套接字地址替换 Unit 的默认 UNIX 域控制套接字。