Nginx07-基于Lua语言的配置

安装Lua和LuaJIT

LuaJIT 是采用 C 语言编写的 Lua 即时编译器

windows安装
git clone https://luajit.org/git/luajit.git
cd c:\path\to\luajit\src
mingw32-make

yum安装
sudo apt install luajit luajit-5.1-dev

编译安装
git clone https://github.com/LuaJIT/LuaJIT.git
cd LuaJIT
make
sudo make install

配置环境变量
vim /etc/profile
export LUAJIT_LIB=/usr/local/lib
export LUAJIT_INC=/usr/local/include/luajit-5.1
source /etc/profile

luagit -v  # 查看版本
luagit -i  # 进入命令好

Lua语言

数据类型

nil:表示无效值或者未赋值的变量,默认情况下所有未赋值的变量都是 nil。
boolean:表示逻辑值,可以是 true 或 false。
number:表示数值,包括整数和浮点数,没有区分整型和浮点型。
string:表示字符串,可以用单引号或双引号来表示。
table:表示关联数组,是 Lua 中最重要的数据结构,用于表示数组、集合、对象等复杂的数据结构。

特殊的数据类型
function:表示函数,可以是全局函数、局部函数、匿名函数等。
userdata:表示用户自定义的数据类型,通常用于和 C 语言进行交互,扩展 Lua 的功能。

变量

定义局部变量并赋值
local var1 = 1

定义全局变量并赋值
var2 = 2

函数

# 使用function关键字定义函数
local function function_name(argument1,argument2,argument3...)
    function_body
    return function_result
end

模块

在 Lua 中创建模块很简单
创建一个table,table的名称就是模块名,然后将函数、变量等导入这个 table 即可

vim md.lua
local m = {}  # 定义模块的名称
m.str1 = "a"  # 定义一个变量

local function func_local()   # 定义一个局部函数
    print("Are you ok!")
end

function m.func()             # 定义一个全局函数
    print("I am here !")
    func_local()
end
return m

加载模块
使用require加载模块,加载模块时不需要写文件名的.lua 后缀
require("文件名")

Lua常见的上下文(context)

init_by_lua
//在 Nginx 启动时执行一次,用于初始化全局变量和配置。
init_by_lua_block {
    ngx.log(ngx.ERR, "Nginx is starting...")
    my_global_var = "initialized"
}

init_worker_by_lua
//在每个 Nginx worker 进程启动时执行,用于初始化 worker 级别的变量和设置。
init_worker_by_lua_block {
    ngx.log(ngx.ERR, "Worker is starting...")
    my_worker_var = "worker initialized"
}

set_by_lua
//在处理请求时计算和设置变量的值。可以用于 set 指令。
set_by_lua $my_var '
    // 读/写nginx内置变量,语法:ngx.var.VAR_NAME
    return ngx.var.arg_name or "default"
';

rewrite_by_lua:
//在 Nginx 处理阶段重写请求,可以修改请求的 URI 或参数。
server {
    location / {
        rewrite_by_lua_block {
		    // 读/写nginx内置变量,语法:ngx.var.VAR_NAME
            ngx.var.uri = "/new_uri"
        }
    }
}

access_by_lua:
//在访问阶段执行,用于访问控制和权限检查。
server {
    location / {
        access_by_lua_block {
            if ngx.var.arg_token ~= "secret" then
                return ngx.exit(ngx.HTTP_FORBIDDEN)
            end
        }
    }
}

content_by_lua:
//在内容生成阶段执行,用于生成和返回响应内容。
server {
    location /hello {
        content_by_lua_block {
            ngx.say("Hello, world!")
        }
    }
}

header_filter_by_lua
//在响应头过滤阶段执行,用于修改响应头。
server {
    location / {
        header_filter_by_lua_block {
            ngx.header["X-My-Header"] = "MyValue"
        }
    }
}

body_filter_by_lua
//在响应体过滤阶段执行,用于修改响应体内容。
server {
    location / {
        body_filter_by_lua_block {
            local chunk, eof = ngx.arg[1], ngx.arg[2]
            ngx.arg[1] = string.gsub(chunk, "foo", "bar")
        }
    }
}

log_by_lua:
//在日志阶段执行,用于记录日志或进行清理操作。
server {
    location / {
        log_by_lua_block {
            ngx.log(ngx.ERR, "Request completed")
        }
    }
}

Nginx中应用Lua模块

使用ngx_lua模块可以编写高性能的应用程序,无需将逻辑传递给upstream server

OpenResty

  • OpenResty 是一个基于 Nginx 的全功能 Web 应用服务器,是ngnix的增强版。
  • OpenResty 将 Nginx 作为其核心引擎,自带Lua。
  • Nginx使用Lua模块需要先下载Lua模块,并在编译时使用--add-module=/path/to/lua-nginx-module

动态生成内容

通过 Lua 脚本可以在 nginx 中动态生成内容,比如基于请求的特定条件生成响应内容。

location /lua {
    default_type 'text/plain';
    content_by_lua_block {
        ngx.say("Hello, world!")
    }
}
# 当访问/lua路径时,nginx将执行Lua脚本ngx.say("Hello, world!")并将输出发送给客户端。

请求路由

基于请求的内容或其他条件将请求分发到不同的后端服务器

# 指定查找lua脚本的位置
lua_package_path '/path/to/lua-scripts/?.lua;;';
server {
    location /route {
        access_by_lua_block {
            if ngx.var.arg_version == "v1" then
                ngx.var.backend = "http://backend_v1"
            else
                ngx.var.backend = "http://backend_v2"
            end
        }
        proxy_pass $backend;
    }
}

生产例子

假设我们有两个后端服务器,一个位于美国,另一个位于欧洲。我们想要根据用户的 IP 地址将请求路由到相应的服务器上。

# 首先,安装lua-resty-iputils库来解析IP地址的地理位置信息
luarocks install lua-resty-iputils

# 配置 nginx,将 Lua 模块与 IP 地址解析库集成,并根据解析的地理位置信息来路由请求
lua_package_path "/usr/local/lib/lua/?.lua;;";
resolver 8.8.8.8;
lua_shared_dict geo_data 10m;
server {
    listen 80;
    server_name example.com;
    location / {
        access_by_lua_block {
            local geoip = require "resty.iputils"
            local country = geoip.lookup_originating_country()
            if country == "US" then
                ngx.var.backend = "http://us_backend"
            elseif country == "EU" then
                ngx.var.backend = "http://eu_backend"
            else
                ngx.var.backend = "http://default_backend"
            end
        }
        proxy_pass $backend;
    }
}

示例说明:
1、使用 lua_shared_dict 定义了一个共享内存区域 geo_data 用于缓存 IP 地址的地理位置信息。
2、通过 resty.iputils 库的 lookup_originating_country() 函数获取用户的地理位置信息,
   并根据不同的地理位置将请求路由到不同的后端服务器。
3、如果无法解析地理位置信息或者用户位于其他地区,则请求将路由到默认的后端服务器。

nginx中的Lua API 命令

ngx.var.VARIABLE
//获取 nginx 内置变量的值。
常用的内置变量包括 $remote_addr(客户端 IP 地址)、$request_uri(请求的 URI)、$http_user_agent(客户端的 User-Agent 等)。
这个命令允许你在 Lua 脚本中访问这些变量的值,并根据它们进行逻辑判断。

ngx.req.get_uri_args()
//获取请求的 URI 参数。
这个命令返回一个 Lua table,其中包含了请求 URI 中的所有参数及其对应的值。
你可以在 Lua 脚本中使用它来处理请求中的参数信息。

ngx.req.set_uri_args(args_table)
//设置请求的 URI 参数。
这个命令允许你在 Lua 脚本中修改请求的 URI 参数,可以用于重写请求参数或者添加新的参数。

ngx.exit(status)
//终止请求处理并发送指定的 HTTP 状态码给客户端。
这个命令允许你在 Lua 脚本中根据某些条件直接终止请求处理,并返回指定的状态码给客户端。

重定向相关

ngx.req.set_uri(uri, true|false)
//用参数 uri 来重写当前的 URL
rewrite ^/test last; 与 ngx.req.set_uri("/test", true)功能相似
rewrite ^/test break;与 ngx.req.set_uri("/foo", false)功能相似


ngx.redirect(uri, status)
//重定向请求到另一个 URI。
这个命令允许你在 Lua 脚本中进行重定向操作,可以指定重定向的目标 URI 和 HTTP 状态码。

进程相关

exiting = ngx.worker.exiting()
//判断 worker 进程是否退出

count = ngx.worker.id()
//获取 worker 进程的 ID
worker进程的ID从 0 开始,依次递增,最大值是 worker 总数的值减 1

count = ngx.worker.count()
//获取 worker 进程的数量
即 Nginx 配置中 worker_processes 的值

请求头相关

ngx.req.get_headers()
//获取请求头信息。
命令返回一个Lua table,包含请求的所有头部信息。
可在Lua脚本中使用它来获取请求头中的特定信息,比如 User-Agent、Host 等。

ngx.req.clear_header(header_name)
//清除当前请求中指定的请求头。
清除后,如果存在未执行的子请求,则子请求会继承清除后的请求头

ngx.req.set_header(header_name, header_value)
//设置请求头信息。
可在 Lua 脚本中修改请求的头部信息,用于添加新的头部信息或者修改已有的头部信息。

响应头相关

ngx.resp.get_headers()
//获取响应头

ngx.header.HEADER = VALUE
//修改响应头

ngx.header["X-Test"] = nil;
//清除响应头

请求体相关

lua_need_request_body <on|off>
//强制获取请求体
默认off

响应体相关

ok, err = ngx.say(...)
//输出内容到响应体。
这个命令允许你在 Lua 脚本中向客户端发送响应内容,可以是字符串、数字等。

ok, err = ngx.print(...)
//与ngx.say(...)功能一致,输出不含回车符

正则相关

captures, err = ngx.re.match(subject, regex, options?, ctx?, res_table?)
//返回第一次匹配的结果
结果类型是table,captures[0]是全部结果

iterator, err = ngx.re.gmatch(subject, regex, options)
//返回全部匹配的结果
返回的是一个 Lua 迭代器,也是table类型,可以通过迭代的方式获取匹配到的全部数据

captures, err = ngx.re.match(subject, regex, options?, ctx?, res_table?)

Lua上下文综合示例

worker_processes 1;
events {
    worker_connections 1024;
}
http {
    lua_shared_dict my_cache 10m;
    init_by_lua_block {
        ngx.log(ngx.ERR, "Nginx is starting...")
        my_global_var = "initialized"
    }
    init_worker_by_lua_block {
        ngx.log(ngx.ERR, "Worker is starting...")
    }
    server {
        listen 8080;
        location /set {
            set_by_lua $my_var '
                return ngx.var.arg_name or "default"
            ';
            return 200;
        }
        location /rewrite {
            rewrite_by_lua_block {
                ngx.var.uri = "/new_uri"
            }
            proxy_pass http://backend;
        }
        location /access {
            access_by_lua_block {
                if ngx.var.arg_token ~= "secret" then
                    return ngx.exit(ngx.HTTP_FORBIDDEN)
                end
            }
            proxy_pass http://backend;
        }
        location /hello {
            content_by_lua_block {
                ngx.say("Hello, world!")
            }
        }
        location / {
            header_filter_by_lua_block {
                ngx.header["X-My-Header"] = "MyValue"
            }
            body_filter_by_lua_block {
                local chunk, eof = ngx.arg[1], ngx.arg[2]
                ngx.arg[1] = string.gsub(chunk, "foo", "bar")
            }
            log_by_lua_block {
                ngx.log(ngx.ERR, "Request completed")
            }
            proxy_pass http://backend;
        }
    }
}

动态管理upstream

ngx_http_dyups_module

ngx_http_dyups_module 是一个第三方开源软件,它提供 API 动态修改 upstream 的配置,
并且支持 Nginx 的 ip_hash、 keepalive 等与 upstream 有关的配置

#在nginx中编译安装
git clone git://github.com/yzprofile/ngx_http_dyups_module.git
./configure --add-module=/path/ngx_http_dyups_module

#编辑 Nginx 配置文件 /usr/local/nginx/conf/nginx.conf:
http {
    # 添加 dyups 模块接口位置
    server {
        listen 8080;
        location /dyups {
            dyups_interface;
        }
    }

    upstream backend {
        server 127.0.0.1:8081;
        server 127.0.0.1:8082;
    }

    server {
        listen 80;
        location / {
            proxy_pass http://backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

使用 ngx_http_dyups_module 动态更新 upstream

可以使用 HTTP 请求来动态更新 upstream 配置
#添加一个新的后端服务器到 backend
curl -X POST http://localhost:8080/dyups/upstream/backend -d "server 127.0.0.1:8083;"

#查看当前的 upstream 配置:
curl http://localhost:8080/dyups/upstream/backend

删除某个 upstream 配置
curl -X DELETE http://localhost:8080/dyups/upstream/backend

持久化动态更新的 upstream 配置

import requests
import os
import time
from inotify_simple import INotify, flags

DYUPS_URL = 'http://localhost:8080/dyups'
UPSTREAM_FILE = '/etc/nginx/conf.d/dynamic_upstream.conf'
NGINX_CONF_FILE = '/etc/nginx/nginx.conf'

def fetch_upstream_info():
    response = requests.get(f"{DYUPS_URL}/detail")
    if response.status_code == 200:
        return response.json()  # 假设接口返回 JSON 数据
    else:
        raise Exception(f"Failed to fetch upstream info: {response.status_code}")

def generate_upstream_conf(upstream_info):
    conf_lines = []
    for upstream in upstream_info:
        conf_lines.append(f"upstream {upstream['name']} {{")
        for server in upstream['servers']:
            conf_lines.append(f"    server {server};")
        conf_lines.append("}")
    return "\n".join(conf_lines)

def write_upstream_conf(conf_content):
    with open(UPSTREAM_FILE, 'w') as f:
        f.write(conf_content)

def reload_nginx():
    os.system('nginx -s reload')

def main():
    inotify = INotify()
    inotify.add_watch(NGINX_CONF_FILE, flags.MODIFY)
    
    try:
        while True:
            events = inotify.read(timeout=1000)
            for event in events:
                if event.mask & flags.MODIFY:
                    upstream_info = fetch_upstream_info()
                    conf_content = generate_upstream_conf(upstream_info)
                    write_upstream_conf(conf_content)
                    reload_nginx()
                    print("Nginx configuration reloaded with new upstream info.")
            time.sleep(1)
    except KeyboardInterrupt:
        print("Script terminated by user.")

if __name__ == '__main__':
    main()

import requests

DYUPS_URL = 'http://localhost:8080/dyups'
UPSTREAM_FILE = '/usr/local/nginx/conf/dynamic_upstream.conf'
NGINX_RELOAD_CMD = 'sudo /usr/local/nginx/sbin/nginx -s reload'

def fetch_upstream_info():
    response = requests.get(f"{DYUPS_URL}/detail")
    if response.status_code == 200:
        return response.json()  # 假设接口返回 JSON 数据
    else:
        raise Exception(f"Failed to fetch upstream info: {response.status_code}")

def generate_upstream_conf(upstream_info):
    conf_lines = []
    for upstream in upstream_info:
        conf_lines.append(f"upstream {upstream['name']} {{")
        for server in upstream['servers']:
            conf_lines.append(f"    server {server};")
        conf_lines.append("}")
    return "\n".join(conf_lines)

def write_upstream_conf(conf_content):
    with open(UPSTREAM_FILE, 'w') as f:
        f.write(conf_content)

def reload_nginx():
    os.system(NGINX_RELOAD_CMD)

def main():
    try:
        upstream_info = fetch_upstream_info()
        conf_content = generate_upstream_conf(upstream_info)
        write_upstream_conf(conf_content)
        reload_nginx()
        print("Nginx configuration reloaded with new upstream info.")
    except Exception as e:
        print(f"Error: {e}")

if __name__ == '__main__':
    main()

nginx-upsync-module

该模块也可以动态修改upstream,而不需要重载Nginx配置,它的upstream数据存放在Consul中

Consul是一个基于Go语言开发的高可用、分布式系统。它主要用来发现和配置服务,类似于 Zookeeper;也可以用来存储key/value 数据,并提供 API 去操作 key/value 数据。

安装 nginx-upsync-module 和 Consul

git clone https://github.com/weibocom/nginx-upsync-module.git
./configure --add-module=/path/nginx-upsync-module

yum install golang -y
wget -S https://releases.hashicorp.com/consul/1.1.0/consul_1.1.0_linux_amd64.zip
unzip consul_1.1.0_linux_amd64.zip
cp consul /usr/local/bin/
consul agent -dev -client 10.19.48.161 #建议先使用开发模式,以便更好地观察情况

http://服务器 IP:8500/ui/               #访问consul后台

配置nginx配置文件

http {
    # 定义 upsync 配置。配置中心的地址和路径、类型、同步间隔、超时时间
    upsync consul {
        upsync_server 127.0.0.1:8500/v1/kv/upstreams/backend;
        upsync_type consul;
        upsync_interval 5s;
        upsync_timeout 6s;
        upsync_params;
        strong_dependency off;
        # 内存中的 upstream 的每次变更都会同步到这个配置文件中
        upsync_dump_path /usr/local/nginx/conf/servers/upsync_backend.conf;
    }

    # 包含上次同步保存的 upstream 配置文件
    include /usr/local/nginx/conf/servers/*.conf;

    # 定义 upstream 块
    upstream backend {
        least conn;    # 最少连接负载均衡策略
        server 127.0.0.1:8081 down;
        server 127.0.0.1:8082 down;
        upsync_consul; # 加载upsync_consul的upstream配置信息
    }

    server {
        listen 80;
        location / {
            proxy_pass http://backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }

    # 用于显示当前 upstream 配置信息
    server {
        listen 8081;
        location /upstream_show {
            upstream_show;
        }
    }
}

验证

# 查看配置信息
vim /usr/local/nginx/conf/servers/upsync_backend.conf

# 查看挡墙upstream的信息
curl http://127.0.0.1/upstream_show

# 向consul中添加主机信息
curl -X PUT -d 'server 127.0.0.1:8081 weight=1 max_fails=2 fail_timeout=10s;' \
http://127.0.0.1:8500/v1/kv/upstreams/backend/127.0.0.1:8081
curl -X PUT -d 'server 127.0.0.1:8082 weight=1 max_fails=2 fail_timeout=10s;' \
http://127.0.0.1:8500/v1/kv/upstreams/backend/127.0.0.1:8082

posted @ 2026-03-27 11:29  立勋  阅读(4)  评论(0)    收藏  举报