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 配置的详细信息,请参阅配置。
现在提供一些详细的场景。
容器化单元中的应用§
假设我们有一个具有少量依赖项的 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 层来自定义 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 域控制套接字。