> 文章列表 > 蓝绿部署技术方案

蓝绿部署技术方案

蓝绿部署技术方案

文章目录

  • 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模块的原理:

  1. 每个worker(工作进程)创建一个Lua VM,worker内所有协程共享VM;
  2. 将Nginx I/O原语封装后注入 Lua VM,允许Lua代码直接访问;
  3. 每个外部请求都由一个Lua协程处理,协程之间数据隔离;
  4. Lua代码调用I/O操作等异步接口时,会挂起当前协程(并保护上下文数据),而不阻塞worker;
  5. 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更新,热加载

宗旨:在最少组件依赖、最简单架构设计的情况下满足业务需求。因为依赖的组件越多,架构越复杂,越不容易把控,同时出问题的概率越大

注意:

  1. 部署蓝绿的基本前提:首先需要对nginx相当熟悉,对nginx的代理转发、跨域等相当的了解
  2. 在请求进行代理转发的时候最好显示的指定request_uri
    在以下两种情况需要显示指定/重写request_uri
    • 请求request_uri与转发的request_uri不一致的情况下,必须显示的指定request_uri
    • 请求的站点为"/"的情况下,必须显示的指定request_uri,或者重写request_uri
  3. 在用到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_urilocation /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_backendcurl http://${HOST_PORT}/_switch_upstream?$1docker exec -it ${CONTAINER} nginx -s reload
}function switch_upstream_advance() {PRO=$1GREEN=`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/frontendcurl 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/clientipscurl 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;;(*)printUsageexit 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 -fexit 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#_label1https://segmentfault.com/a/1190000007207616/
#openresty ngx_lua共享内存https://blog.csdn.net/weixin_43931625/article/details/125829576https://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"]}}' \\--compressedcurl http://127.0.0.1:8001/_update_white_list \\-X "GET" \\--data '{"update":{"usernames":["zhangsan"]}}' \\--compressed
    
  • 蓝绿切换

    switch_upstream backend01

职责分工

  • 后台管理人员职责
    • 维护白名单
  • 开发人员需要做的
    • 部署预发分支(green分支),如有异常重新部署,白名单成员需求验证
    • 蓝绿切换,蓝绿分组互相切换(例如:蓝->a,绿->b;切换为 蓝->b,绿->a)
      如有异常,蓝绿再次切换
      正常,结束