蓝绿部署技术方案

文章目录

  • 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几个范围进行配置

蓝绿部署技术方案_第1张图片

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试图同时访问该条目时,可能会导致锁竞争,从而降低性能

蓝绿部署架构图

蓝绿部署技术方案_第2张图片
蓝绿分组互相切换,例如:蓝->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)
      如有异常,蓝绿再次切换
      正常,结束

你可能感兴趣的:(nginx,运维部署,lua,开发语言)