文章目录
- ngx_lua介绍
- Nginx
- lua
- ngx_lua模块的原理:
- ngx_lua 模块执行顺序与阶段
- ngx_lua应用场景
- JWT
- nginx镜像构造
- lua-redis
- 蓝绿部署
- 特性
- 注意:
- 蓝绿部署架构图
- nginx配置
- 服务脚本
- 部署
- 使用
- 职责分工
ngx_lua介绍
Nginx
Nginx是Web服务器、HTTP反向代理和TCP代理服务器。
特点:
- 性能非常高
- 资源占用CPU、内存非常节省
- 内存池设计非常稳定
- 高度模块化易于扩展
我们常常拿Nginx与Apache做比较,其实它们各有各的适用场景。Nginx相对于Apache的优势在于处理高并发很好的解决了C10K问题,这就靠Nginx的epoll网络I/O模型。nginx的epoll机制比apache的select机制更适用于高并发的场景。
lua
Lua 是一个功能强大、快速、轻量的可嵌入式脚本语言由标准的 ANSI C 实现由于拥有一组精简的强大特性以及容易使用的 C API这使得它可以很容易嵌入或扩展到其他语言中使用。
特点:
- 适合嵌入
- 支持协程coroutine
- 用同步的语义来实现异步的调用
lua在脚本语言里速度上有很大的优势加上Nginx两者结合在高并发负载的情况下仍然可以游刃有余。
ngx_lua模块的原理:
- 每个worker(工作进程)创建一个Lua VM,worker内所有协程共享VM;
- 将Nginx I/O原语封装后注入 Lua VM,允许Lua代码直接访问;
- 每个外部请求都由一个Lua协程处理,协程之间数据隔离;
- Lua代码调用I/O操作等异步接口时,会挂起当前协程(并保护上下文数据),而不阻塞worker;
- I/O等异步操作完成时还原相关协程上下文数据,并继续运行;
ngx_lua 模块执行顺序与阶段
ngx_lua属于nginx的一部分,它的执行指令都包含在nginx的11个步骤之中了,相应的处理阶段可以做插入式处理,即可插拔式架构,不过ngx_lua并不是所有阶段都会运行的;另外指令可以在http、server、server if、location、location if几个范围进行配置
ngx_lua应用场景
对于Nginx粘合Lua来开发应用可以说是一把锋利的瑞士军刀,可以帮我们很容易的解决很多问题,基于Nginx+Lua的常用架构模式中一些常见实践和场景:
- WEB应用防火墙(waf)
- 限流
- 降级
- 服务质量监控
- 灰度/蓝绿发布
JWT
请参考:JWT校验
nginx镜像构造
请参考:nginx整合lua、jwt、cjson、redis、mysql模块镜像构建
lua-redis
请参考:nginx中lua-redis使用
蓝绿部署
蓝绿部署上线以后,支持任意时刻生产上线、随时随地切换分支。实现一键部署、分支一键切换,秒级生效。
nginx整合lua、jwt、cjson、redis、mysql等模块,通过开发lua/shell脚本,实现维护共享内存、流量拦截、蓝绿切换、白名单维护。实现蓝绿部署。
特性
- 蓝绿部署:
蓝绿两部分,随时切换。细粒度的蓝绿,每个后端及每个前端服务都可以单独蓝绿切换 - 用户切流:
根据userId/userName/clientIp实现用户分流 - 动态配置lua多进程全局共享内存
- 动态upstream:动态的实现蓝绿环境切换
- 动态用户配置:动态实现userId/userName/clientIp更新,热加载
宗旨:在最少组件依赖、最简单架构设计的情况下满足业务需求。因为依赖的组件越多,架构越复杂,越不容易把控,同时出问题的概率越大
注意:
- 部署蓝绿的基本前提:首先需要对nginx相当熟悉,对nginx的代理转发、跨域等相当的了解
- 在请求进行代理转发的时候最好显示的指定request_uri
在以下两种情况需要显示指定/重写request_uri- 请求request_uri与转发的request_uri不一致的情况下,必须显示的指定request_uri
- 请求的站点为"/"的情况下,必须显示的指定request_uri,或者重写request_uri
- 在用到nginx多进程全局共享内存【ngx_shared_dict】时,遇到【锁竞争】的问题。原因:在高并发场景下,不同的worker进程可能会竞争访问同一个共享字典条目,当许多worker试图同时访问该条目时,可能会导致锁竞争,从而降低性能
蓝绿部署架构图
蓝绿分组互相切换,例如:蓝->a,绿->b;切换为 蓝->b,绿->a
nginx配置
- nginx.conf
user root;
#基本优化
worker_processes 8;
worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 1000000;
worker_rlimit_nofile 120000;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
# Load dynamic modules. See /usr/share/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 120000;
use epoll;
}
http {
log_format main escape=json '$remote_addr - $remote_user [$time_local] '
'"<$host> $request" $status $bytes_sent '
'"$http_referer" "$http_user_agent" '
'"$request_time $upstream_response_time $pipe" '
'"$gzip_ratio" || "$request_body" '
'"token:$http_authorization"';
lua_package_path "/usr/local/lua_core/lib/lua/?.lua;/etc/nginx/conf.d/lua/?.lua;;";
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 30;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
gzip on;
gzip_min_length 10k;
gzip_buffers 4 16k;
gzip_comp_level 3;
gzip_types text/xml text/javascript application/javascript text/css text/plain text/json application/json;
keepalive_requests 8192;
#lua_shared_dict upstreams 1m;
#lua_shared_dict user_ids 5m;
#lua_shared_dict user_names 5m;
#lua_shared_dict client_ips 5m;
#初始化全局变量
init_by_lua_file /etc/nginx/conf.d/lua/lua-init-redis-cmd.lua;
#init_by_lua_block {
# upstreams = {}
# user_ids = {}
# user_names = {}
# client_ips = {}
#}
include /etc/nginx/conf.d/*.conf;
#全局配置上传大小
client_max_body_size 200m;
client_body_buffer_size 1024k;
deny 192.148.0.200;
}
- dynamic_shared_dict.conf
server {
listen 8001;
#server_name localhost;
allow 192.168.0.12;
deny all;
location = /_lua_shared_dict_init { # 单个worker生效,无用
content_by_lua_file /etc/nginx/conf.d/lua/lua-init-redis.lua;
}
location = /_lua_shared_dict_print {
default_type 'text/plain';
content_by_lua_file /etc/nginx/conf.d/lua/print-lua-shared.lua;
}
location = /_switch_upstream {
content_by_lua_file /etc/nginx/conf.d/lua/switch-upstream-redis-cmd.lua;
}
location = /_get_upstream {
content_by_lua_file /etc/nginx/conf.d/lua/get-upstream-redis-cmd.lua;
}
location = /_update_white_list {
content_by_lua_file /etc/nginx/conf.d/lua/update-white-list-redis-cmd.lua;
}
location = /_get_white_list {
content_by_lua_file /etc/nginx/conf.d/lua/get-white-list-redis-cmd.lua;
}
}
- frontend.conf
server {
listen 9002;
#server_name localhost;
#set_by_lua_file $frontend /etc/nginx/conf.d/lua/lua-set-frontend.lua;
#set_by_lua_file $backend_upstream /etc/nginx/conf.d/lua/lua-set-backend.lua;
#root /etc/nginx/html/$frontend;
#验证前端切流
#location / {
# index index.html index.htm;
#}
#验证后端切流
location / {
default_type 'text/html';
set_by_lua_file $backend_upstream /etc/nginx/conf.d/lua/lua-set-backend.lua;
#proxy_next_upstream off;
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_pass http://$backend_upstream; #重启下游服务
}
# 在以下两种情况需要显示指定/重写request_uri
#- 请求request_uri与转发的request_uri不一致的情况下,必须显示的指定request_uri
#- 请求的站点为"/"的情况下,必须显示的指定request_uri,或者重写request_uri
location /test1/test/aa {
default_type 'text/html';
set_by_lua_file $backend_upstream /etc/nginx/conf.d/lua/lua-set-backend.lua;
set_by_lua_block $replace_uri {
ngx.log(ngx.WARN,ngx.var.request_uri)
return string.gsub(ngx.var.request_uri, "^/test1/test/aa", "/test/aa",1)
}
#proxy_next_upstream off;
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_pass http://b_backend; #重启下游服务
#proxy_pass http://b_backend/test/aa;
proxy_pass http://${backend_upstream}${replace_uri};
}
location / {
default_type 'text/html';
set_by_lua_file $backend_upstream /etc/nginx/conf.d/lua/lua-set-backend.lua;
set_by_lua_block $replace_uri {
ngx.log(ngx.WARN,ngx.var.request_uri)
return string.gsub(ngx.var.request_uri, "^/", "/",1)
}
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_pass http://${backend_upstream}${replace_uri};
}
}
- pass.conf
#dynamic_shared_dict.conf
upstream a_backend01 {
server 192.168.0.23:8081;
}
upstream b_backend01 {
server 192.168.0.23:8082;
}
upstream a_backend02 {
server 192.168.0.23:8081;
}
upstream b_backend02 {
server 192.168.0.23:8082;
}
- test.conf
server {
listen 9001;
server_name localhost;
allow 172.17.0.1;
deny all;
location /redis {
default_type 'text/plain';
content_by_lua_file /etc/nginx/conf.d/lua/test-redis.lua;
}
}
服务脚本
-
lua脚本
请参考:ngx-lua蓝绿部署lua脚本 -
部署脚本blue-green-deploy.sh
#!/bin/bash
set -e
HOST_PORT="127.0.0.1:8001"
CONTAINER="ngx_lua"
function shared_dict_print() {
curl http://${HOST_PORT}/_lua_shared_dict_print
}
function share_dict_init() {
curl http://${HOST_PORT}/_lua_shared_dict_init
}
function switch_upstream() {
#backend=a_backend
curl http://${HOST_PORT}/_switch_upstream?$1
docker exec -it ${CONTAINER} nginx -s reload
}
function switch_upstream_advance() {
PRO=$1
GREEN=`curl http://${HOST_PORT}/_get_upstream?${PRO} | grep green | awk -F '=' '{print $2}'`
curl http://${HOST_PORT}/_switch_upstream?${PRO}=${GREEN}
/bin/bash -c "docker exec ${CONTAINER} nginx -s reload"
}
function get_upstream() {
#all/backend/taskcenter/frontend
curl http://${HOST_PORT}/_get_upstream?$1
}
function update_white_list() {
curl http://${HOST_PORT}/_update_white_list \
-X "POST" \
--data ${1} \
--compressed
/bin/bash -c "docker exec ${CONTAINER} nginx -s reload"
}
function get_white_list() {
#all/userids/usernames/clientips
curl http://${HOST_PORT}/_get_white_list?$1
}
function ngx_reload() {
/bin/bash -c "docker exec ${CONTAINER} nginx -s reload"
}
function printUsage(){
echo -e "Usage: [shared_dict_print] [share_dict_init] [switch_upstream] [get_upstream] [update_white_list] [get_white_list]"
}
function main() {
case "$1" in
(shared_dict_print)
shared_dict_print
;;
(share_dict_init)
share_dict_init
;;
(switch_upstream)
switch_upstream_advance $2
;;
(get_upstream)
get_upstream $2
;;
(update_white_list)
update_white_list $2
;;
(get_white_list)
get_white_list $2
;;
(ngx_reload)
ngx_reload
;;
(*)
printUsage
exit 1;
;;
esac
}
main $@
- nginx日志滚动脚本
#!/bin/bash
# rotatelog.sh
# 0 0 * * * /bin/bash /var/log/nginx/rotatelog.sh
BASE=/var/log/nginx
DATE=$(TZ='Asia/Chongqing' date "+%Y%m%d")
mv ${BASE}/access.log ${BASE}/access.${DATE}.log
mv ${BASE}/error.log ${BASE}/error.${DATE}.log
/bin/bash -c "docker container kill ngx_lua -s USR1"
find ${BASE} -mtime +30 -name "*access*" | xargs rm -f
find ${BASE} -mtime +10 -name "*error*" | xargs rm -f
exit 0
部署
- 启动redis
docker run -itd \
-v /data/redis:/data \
--name redis \
-p 6379:6379 \
--privileged=true \
redis --appendonly yes --requirepass "xxx"
docker exec -it redis redis-cli -h 127.0.0.1 -p 6379 --pass "xxx"
默认有16 db [0~15]
查看当前db的keys数量: dbsize
切换db: select n
查看当前db数量: CONFIG GET databases
#redis 用法:
https://www.cnblogs.com/ysocean/p/9080940.html#_label1
https://segmentfault.com/a/1190000007207616/
#openresty ngx_lua共享内存
https://blog.csdn.net/weixin_43931625/article/details/125829576
https://www.w3cschool.cn/openresty1 (包括openresty各种组件(redis等))
- 启动nginx
docker run --rm --name=ngx_lua -it \
-p 8001:8001 \
-p 9001:9001 \
-p 9002:9002 \
-v /etc/nginx/nginx.conf:/etc/nginx/nginx.conf:rw \
-v /etc/nginx/conf.d:/etc/nginx/conf.d:rw \
-v /etc/nginx/html:/etc/nginx/html:rw \
--privileged=true \
ponylee/centos7-nginx:latest
使用
配置白名单(ip或者usernames),验证
-
ip白名单验证
查看ip白名单
非ip白名单访问验证
添加ip白名单 – 管理员在后台操作
ip白名单访问验证
删除ip白名单 – 管理员在后台操作
非ip白名单访问验证 -
username白名单验证
查看username白名单
非username白名单访问验证
添加username白名单 – 管理员在后台操作
username白名单访问验证
删除username白名单 – 管理员在后台操作
非username白名单访问验证curl http://127.0.0.1:8001/_update_white_list \ -X "GET" \ --data '{"update":{"clientips":["192.168.0.15"]}}' \ --compressed curl http://127.0.0.1:8001/_update_white_list \ -X "GET" \ --data '{"update":{"usernames":["zhangsan"]}}' \ --compressed
-
蓝绿切换
switch_upstream backend01
职责分工
- 后台管理人员职责
- 维护白名单
- 开发人员需要做的
- 部署预发分支(green分支),如有异常重新部署,白名单成员需求验证
- 蓝绿切换,蓝绿分组互相切换(例如:蓝->a,绿->b;切换为 蓝->b,绿->a)
如有异常,蓝绿再次切换
正常,结束