" NGINX Unit 是一个动态Web和应用服务器,可以运行多种编程语言的应用。Unit 是轻量级的、支持多种语言,并且可以通过API进行动态配置。Unit 的设计允许开发或运维需要的情况下,重新配置特定应用参数。 "
官网,当前版本1.22.0。
看起来支持的语言也不少Go、Python、PHP、Java…
Ubuntu 20.10 安装:
-
curl -sL https://nginx.org/keys/nginx_signing.key | apt-key add -
-
创建文件 /etc/apt/sources.list.d/unit.list 并在里面添加如下内容:
deb https://packages.nginx.org/unit/ubuntu/ groovy unit
deb-src https://packages.nginx.org/unit/ubuntu/ groovy unit
-
安装 Unit 基础包
apt update
apt install unit
apt install unit-dev unit-go unit-jsc11 unit-jsc13 unit-jsc14 unit-jsc15 unit-perl unit-php unit-python3.8 unit-ruby
其实我只需要unit-go unit-dev unit-python
-
安装Go模块
go get unit.nginx.org/go
命令:
systemctl enable unit 允许自动启动
systemctl restart unit 重启
systemctl stop unit
systemctl disable unit
一个go代码测试
package main
import (
"fmt"
"net/http"
unit "unit.nginx.org/go"
)
func handler(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "text/plain")
fmt.Fprintf(w, "Method : %s\n", r.Method)
fmt.Fprintf(w, "URL : %s\n", r.URL.Path)
fmt.Fprintf(w, "Host : %s\n", r.Host)
}
func main() {
http.HandleFunc("/", handler)
unit.ListenAndServe(":8000", nil)
}
编译,虽然会提示NginxUnit未运行,不过它是可以独立访问的。有点尴尬的在于,通过apt安装的是go1.14版,而我本机已升级到1.16。另外,go get unit时,是需要安装mercurial,而非git。
查看通讯端口: sudo /usr/sbin/unitd 我的显示 unix:/var/run/control.unit.sock (用于替换示例中的进程端口)
查看当前的配置: sudo curl –unix-socket /var/run/control.unit.sock http://localhost
返回了一个空壳
{
"certificates": {},
"config": {
"listeners": {},
"applications": {}
}
}
这是配置文件:unit.config
{
"applications": {
"example_go": {
"type": "go",
"executable": "/home/ease/soease/unit/t1"
}
},
"listeners": {
"*:8500": {
"application": "example_go"
}
}
}
通过curl将unit.config文件内容传递给unitd程序,返回Reconfiguration done表示配置成功。
curl -X PUT -d @unit.config –unix-socket control.unit.sock http://localhost/config (解释:通过进程端口,将配置文件unit.config发送到本地配置的config节点下)
通过 curl 127.0.0.1:8500 即可检验是否成功。
修改测试1
配置文件我稍作了修改,中文也可以识别
{
"applications": {
"测试": {
"type": "go",
"executable": "/home/ease/soease/unit/t1"
}
},
"listeners": {
"*:8500": {
"application": "测试"
}
}
}
修改测试2
再修改,两个端口调用同一程序
{
"certificates": {},
"config": {
"applications": {
"测试": {
"type": "go",
"executable": "/home/ease/go/my/src/github.com/soease/unit/t1"
}
},
"listeners": {
"*:8500": {
"application": "测试"
},
"*:8080": {
"application": "测试"
}
}
}
}
修改测试3
//试试恢复为golang原装http
package main
import (
"fmt"
"net/http"
//unit "unit.nginx.org/go"
)
func handler(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "text/plain")
fmt.Fprintf(w, "t2")
fmt.Fprintf(w, "Method : %s\n", r.Method)
fmt.Fprintf(w, "URL : %s\n", r.URL.Path)
fmt.Fprintf(w, "Host : %s\n", r.Host)
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8002", nil)
}
同时修改配置文件
{
"applications": {
"test1": {
"type": "go",
"executable": "/home/ease/soease/unit/t1"
},
"test2": {
"type": "go",
"executable": "/home/ease/soease/unit/t2"
}
},
"listeners": {
"*:8500": {
"application": "test1"
},
"*:8080": {
"application": "test2"
}
}
}
可以通过ps看到,unit调用了t2,但发送配置文件被卡在那里,没有返回正确的信息。
将以上的golang代码中改回unit的web服务,以上配置就正常了。
通过多次试图使用原装http服务,都不成功。结论:必须调用unit模块的http服务模块。
修改测试4
t1的端口为8001,t2的端口为8002。看看这样的配置在unit会“打架”不。
{
"applications": {
"测试1": {
"type": "go",
"executable": "/home/ease/soease/unit/t1"
},
"测试2": {
"type": "go",
"executable": "/home/ease/soease/unit/t2"
}
},
"listeners": {
"*:8001": {
"application": "测试2"
},
"*:8002": {
"application": "测试1"
}
}
}
结果:
curl localhost:8001 调用了t2
curl localhost:8002 调用了t1
感觉它在程序中的端口指定并没有实际意义。(后来在官方文档中看到,它会忽略golang中的端口设置,只管unit中的配置端口)
写header
package main
import (
"fmt"
"io"
"net/http"
"unit.nginx.org/go"
)
func handler(w http.ResponseWriter, r *http.Request) {
var buf [4096]byte
len, _ := r.Body.Read(buf[:])
w.Header().Set("Request-Method", r.Method)
w.Header().Set("Request-Uri", r.RequestURI)
w.Header().Set("Server-Protocol", r.Proto)
w.Header().Set("Server-Protocol-Major", fmt.Sprintf("%v", r.ProtoMajor))
w.Header().Set("Server-Protocol-Minor", fmt.Sprintf("%v", r.ProtoMinor))
w.Header().Set("Content-Length", fmt.Sprintf("%v", len))
w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
w.Header().Set("Custom-Header", r.Header.Get("Custom-Header"))
w.Header().Set("Http-Host", r.Header.Get("Host"))
io.WriteString(w, string(buf[:len]))
}
func main() {
http.HandleFunc("/", handler)
unit.ListenAndServe(":7080", nil)
}
问题1
如果代码中必须由unit来调用的话,那么Gin这种框架又要如何来适应呢? 若需要Gin框架开发者为修改的话,unit的适应性将打折扣。
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
在官方文档中框架的使用,不少Python和PHP的框架应用,没找到golang的。
我试着通过Python的示例来解决。
看看Python的两个框架示例:
Flask
文件wsgi.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
return "Hello, World!"
chown -R unit:unit /path/to/app/ 让unit的用户unit能够有运行权限
{
"listeners": {
"*:80": {
"pass": "applications/flask"
}
},
"applications": {
"flask": {
"type": "python 3.x",
"path": "/path/to/app/",
"home": "/path/to/app/venv/",
"module": "wsgi",
"callable": "app"
}
}
}
Bottle
文件wsgi.py
from bottle import Bottle, template
app = Bottle()
@app.route('/hello/<name>')
def hello(name):
return template('Hello, {{name}}!', name=name)
# run(app, host='localhost', port=8080) 注意,这里注释掉了run
chown -R unit:unit /path/to/app/ 让unit的用户unit能够有运行权限
{
"listeners": {
"*:80": {
"pass": "applications/bottle"
}
},
"applications": {
"bottle": {
"type": "python x.y",
"path": "/path/to/app/",
"home": "/path/to/app/venv/",
"module": "wsgi",
"callable": "app"
}
}
}
我修改配置文件
{
"listeners": {
"*:8888": {
"pass": "applications/mygo"
}
},
"applications": {
"mygo": {
"type": "go",
"executable":"/home/ease/go/my/src/github.com/soease/unit/t6"
}
}
}
package main
import (
"io"
"net/http"
//"unit.nginx.org/go"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Hello, Go on Unit!!!")
})
http.ListenAndServe(":8081", nil)
}
发现:在发送配置文件时,配置文件不成功,卡死在发送处,没有反馈信息;unit已运行t6服务,t6能正常访问。
结果:失败!
问题2
当我重新编译了其中一个服务,必须得重启unit服务,重载配置也没用。估计是还有地方没学会。
完整的配置示例
{
"certificates": {
"bundle": {
"key": "RSA (4096 bits)",
"chain": [
{
"subject": {
"common_name": "example.com",
"alt_names": [
"example.com",
"www.example.com"
],
"country": "US",
"state_or_province": "CA",
"organization": "Acme, Inc."
},
"issuer": {
"common_name": "intermediate.ca.example.com",
"country": "US",
"state_or_province": "CA",
"organization": "Acme Certification Authority"
},
"validity": {
"since": "Sep 18 19:46:19 2018 GMT",
"until": "Jun 15 19:46:19 2021 GMT"
}
},
{
"subject": {
"common_name": "intermediate.ca.example.com",
"country": "US",
"state_or_province": "CA",
"organization": "Acme Certification Authority"
},
"issuer": {
"common_name": "root.ca.example.com",
"country": "US",
"state_or_province": "CA",
"organization": "Acme Root Certification Authority"
},
"validity": {
"since": "Feb 22 22:45:55 2016 GMT",
"until": "Feb 21 22:45:55 2019 GMT"
}
}
]
}
},
"config": {
"settings": {
"http": {
"header_read_timeout": 10,
"body_read_timeout": 10,
"send_timeout": 10,
"idle_timeout": 120,
"max_body_size": 6291456,
"static": {
"mime_types": {
"text/plain": [
".log",
"README",
"CHANGES"
]
}
},
"discard_unsafe_fields": false
}
},
"listeners": {
"*:8000": {
"pass": "routes",
"tls": {
"certificate": "bundle"
}
},
"127.0.0.1:8001": {
"pass": "applications/drive"
},
"*:8080": {
"pass": "upstreams/rr-lb"
}
},
"routes": [
{
"match": {
"uri": "/admin/*",
"scheme": "https",
"arguments": {
"mode": "strict",
"access": "!raw"
},
"cookies": {
"user_role": "admin"
}
},
"action": {
"pass": "applications/cms"
}
},
{
"match": {
"host": "admin.emea-*.*.example.com",
"source": "*:8000-9000"
},
"action": {
"pass": "applications/blogs/admin"
}
},
{
"match": {
"host": ["blog.example.com", "blog.*.org"],
"source": "*:8000-9000"
},
"action": {
"pass": "applications/blogs/core"
}
},
{
"match": {
"host": "example.com",
"source": "127.0.0.0-127.0.0.255:8080-8090",
"uri": "/chat/*"
},
"action": {
"pass": "applications/chat"
}
},
{
"match": {
"host": "example.com",
"source": [
"10.0.0.0/7:1000",
"10.0.0.0/32:8080-8090"
]
},
"action": {
"pass": "applications/store"
}
},
{
"match": {
"host": "wiki.example.com"
},
"action": {
"pass": "applications/wiki"
}
},
{
"match": {
"uri": "/legacy/*"
},
"action": {
"return": 301,
"location": "https://legacy.example.com"
}
},
{
"match": {
"scheme": "http"
},
"action": {
"proxy": "http://127.0.0.1:8080"
}
},
{
"action": {
"share": "/www/static/",
"fallback": {
"proxy": "http://127.0.0.1:9000"
}
}
}
],
"applications": {
"blogs": {
"type": "php",
"targets": {
"admin": {
"root": "/www/blogs/admin/",
"script": "index.php"
},
"core" : {
"root": "/www/blogs/scripts/"
}
},
"limits": {
"timeout": 10,
"requests": 1000
},
"options": {
"file": "/etc/php.ini",
"admin": {
"memory_limit": "256M",
"variables_order": "EGPCS",
"expose_php": "0"
},
"user": {
"display_errors": "0"
}
},
"processes": 4
},
"chat": {
"type": "external",
"executable": "bin/chat_app",
"group": "www-chat",
"user": "www-chat",
"working_directory": "/www/chat/",
"isolation": {
"namespaces": {
"cgroup": false,
"credential": true,
"mount": false,
"network": false,
"pid": false,
"uname": false
},
"uidmap": [
{
"host": 1000,
"container": 0,
"size": 1000
}
],
"gidmap": [
{
"host": 1000,
"container": 0,
"size": 1000
}
],
"automount": {
"language_deps": false,
"procfs": false,
"tmpfs": false
}
}
},
"cms": {
"type": "ruby",
"script": "/www/cms/main.ru",
"working_directory": "/www/cms/"
},
"drive": {
"type": "perl",
"script": "app.psgi",
"threads": 2,
"thread_stack_size": 4096,
"working_directory": "/www/drive/",
"processes": {
"max": 10,
"spare": 5,
"idle_timeout": 20
}
},
"store": {
"type": "java",
"webapp": "/www/store/store.war",
"classpath": ["/www/store/lib/store-2.0.0.jar"],
"options": ["-Dlog_path=/var/log/store.log"]
},
"wiki": {
"type": "python",
"module": "asgi",
"protocol": "asgi",
"callable": "app",
"environment": {
"DJANGO_SETTINGS_MODULE": "wiki.settings.prod",
"DB_ENGINE": "django.db.backends.postgresql",
"DB_NAME": "wiki",
"DB_HOST": "127.0.0.1",
"DB_PORT": "5432"
},
"path": "/www/wiki/",
"processes": 10
}
},
"upstreams": {
"rr-lb": {
"servers": {
"192.168.1.100:8080": { },
"192.168.1.101:8080": {
"weight": 2
}
}
}
},
"access_log": "/var/log/access.log"
}
}
配置
删除对象 curl -X DELETE –unix-socket /path/to/control.unit.sock ‘http://localhost/config/listeners/*:8400’
type: external (Go and Node.js), java, perl, php, python, or ruby
environment: 环境变量,用于程序调用
{
"type": "python 3.6",
"processes": 16,
"working_directory": "/www/python-apps",
"path": "blog",
"module": "blog.wsgi",
"user": "blog",
"group": "blog",
"limits": {
"timeout": 10,
"requests": 1000
},
"environment": {
"DJANGO_SETTINGS_MODULE": "blog.settings.prod",
"DB_ENGINE": "django.db.backends.postgresql",
"DB_NAME": "blog",
"DB_HOST": "127.0.0.1",
"DB_PORT": "5432"
}
}