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

浙公网安备 33010602011771号