orange网关传统集群部署模式 1、在orange.conf的 plugins中加入node,表示开启node插件(容器集群节点管理插件 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 "plugins": [ "stat" , "headers" , "monitor" , "redirect" , "rewrite" , "rate_limiting" , "property_rate_limiting" , "basic_auth" , "key_auth" , "jwt_auth" , "hmac_auth" , "signature_auth" , "waf" , "divide" , "kvstore" , "node" ], ... "api": { "auth_enable": true , "credentials": [ { "username" :"api_username" , "password" :"api_password" } ] }
2、部署多个orange节点,同时在每个节点的dashboard启用node插件 3、每个节点dashboard,集群管理中先注册节点添加自己,然后添加节点依次加入其它节点,需要输入其他节点的 ip 端口(7777) 用户名 和 密码
4、A节点的dashboard修改完配置, 在其他节点的dashboard上点击同步配置进行同步
orange多节点主要是同步各节点的数据,这样的话只要在一个节点做配置,其他节点都能同步到
传统模式在k8s的环境下面临几个问题 在无状态的部署下存在一些问题,如下(下图部署两个副本进行测试,注意图中的Address ,访问后台时默认处于轮询 状态) Orange多节点之间以ip进行通信,但orange的dashboard也对添加/修改集群节点的输入框做了限制(只能写ip,所以这里还需要对源码以及数据库字段长度 进行调整) Orange的默认使用lua_shared_dict缓存数据(其中包括插件的配置数据),所以有以下几个问题
关于共享内存这里,计划全改成Redis ,但是也遇到了几个问题
有些缓存操作是放在init_by_lua和log_by_lua中的,但是这类模块不允许操作redis。如果操作对应api会报错: API disabled in the context of xx_by_lua。只有在 content_by_lua 上下文中才能使用 resty.redis 模块,因为这是在响应体生成之前执行的代码。
解决办法:如果一定要在这当中执行redis操作,可以在调用Redis模块之前,使用ngx.timer.at(delay or 0, function() ... end)将代码延迟到下一个请求周期中执行,这样就可以避免在*_by_lua上下文中使用Redis模块了。
但设置被允许的running timers(正在执行回调函数的计时器)的最大数量默认为256 ,如果超过这个数量,就会抛出**lua failed to run timer with function defined at ... N lua_max_running_timers are not enough**,其中N是变量,指的是当前正在运行的running timers的最大数量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ngx.timer.at(delay or 0 , function () local redis = require "resty.redis" local red = redis:new() local ok, err = red:connect("127.0.0.1" , 6379 ) if not ok then ngx.log (ngx.ERR, "failed to connect to Redis: " , err) return end local res, err = red:get("mykey" ) if not res then ngx.log (ngx.ERR, "failed to get Redis key: " , err) return end ngx.log (ngx.INFO, "Redis key value: " , res) red:set_keepalive(10000 , 100 ) end )
orange的全局统计插件中的部分数据如活跃连接数,这里是取自当前网关节点的ngx.var.connections_active,这是个动态值。与此相似的还有其他数据。这类监控数据不适合定性存储
涉及到lua_shared_dict orange_data的代码地方较多。但orange_data仅供缓存插件配置 ,可以不做持久化。
数据加载流程:网关服务启动 -> 从数据库 加载插件数据 -> 缓存至orange_data
开发/改造 针对0.8.1分支进行改造
代码见: GitHub - behappy-other/orange at v0.8.1-k8s
luarocks依赖版本调整 lua-resty-kafka -> 0.09-0
luafilesystem -> 1.8.0-1
数据库字段长度调整 1 2 3 4 5 6 7 8 alter table cluster_node modify name varchar (255 ) default '' not null ; alter table cluster_node modify ip varchar (255 ) default '' not null comment 'ip' ; alter table persist_log modify ip varchar (255 ) default '' not null comment 'ip' ;
注册节点IP逻辑调整 去掉注册节点时的ip限制 在nginx.conf.example中添加env ORANGE_SERVICE;(默认值为orange-headless),后续将使用socket.dns.gethostname() .. "." .. os.getenv("ORANGE_SERVICE")做为注册ip(k8s中,结合statefulset + headless service可将流量打在指定pod) 在orange.conf.example中添加node插件 配置分流,及DNS解析 配置nginx.conf.example ,添加kube-dns ,使k8s环境 下可以解析对应service name 1 2 3 4 5 http { resolver kube-dns.kube-system.svc.cluster.local; ...
添加server ,按**Header:oranger-version对 dashboard**进行分流 通常情况下,Kubernetes集群中的DNS服务器(kube-dns或CoreDNS)会自动为服务名称添加完整的域名(例如service.{namespace}.svc.cluster.local)。但是,如果在Nginx配置文件中指定了其他DNS服务器,则需要自行添加完整的服务名称。
例如,如果在Nginx配置文件中指定了 第三方 DNS服务器(8.8.8.8),则需要在proxy_pass指令中使用完整的服务名称,例如http://service.{namespace}.svc.cluster.local:port。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 server { listen 8888 ; access_log off ; error_log off ; location / { set $backend "http://orange-0.orange-headless.default.svc.cluster.local:9999" ; if ($http_orange_version ) { set $backend "http://${http_orange_version} .orange-headless.default.svc.cluster.local:9999" ; } proxy_pass $backend ; } }
1 2 3 4 5 6 7 8 9 10 11 http { ...... set_real_ip_from 0.0.0.0 /0 ; real_ip_header X-Forwarded-For; real_ip_recursive on ; map $http_upgrade $connection_upgrade { websocket upgrade; default $http_connection ; }
解决426 upgrade required问题:nginx反向代理默认走的http 1.0版本 1 2 3 4 http { ...... proxy_http_version 1 .1 ;
调整使用到lua_shared_dict的插件,将其改成Redis rate_limiting monitor waf stat property_rate_limiting 调整Makefile,改良服务的启动速度 将下载依赖的步骤单独提出来,之后只要rockspec文件内容不变,就不需要额外下载依赖
去除make dev操作中的luarocks install ...指令 新增make dependency指令 1 2 3 4 5 6 7 8 .PHONY : dependencydependency: ifneq ($(LUAROCKS_VER) ,'luarocks 3') luarocks install rockspec/orange-master-0.rockspec --server=https://luarocks.cn --tree=deps --only-deps --local else luarocks install --server=https://luarocks.cn --lua-dir=/usr/local/openresty/luajit rockspec/orange-master-0.rockspec --tree=deps --only-deps --local endif
property_rate_limiting插件 - 防刷功能添加封禁时长 未配置封禁时间,则按429处理 -> 限流 配置了封禁时间,则按403处理 -> 指定时间内不可访问 Dockerfile 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 FROM centos:7 WORKDIR /opt/orange EXPOSE 80 7777 8888 9999 RUN ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && yum update -y \ && yum install -y wget \ && wget http://dl.fedoraproject.org/pub/epel/epel-release-latest-7 .noarch.rpm \ && rpm -ivh epel-release-latest-7 .noarch.rpm \ && yum install -y yum-utils git libuuid-devel pcre-devel openssl-devel gcc gcc-c++ make perl-Digest-MD5 lua-devel cmake3 curl libtool autoconf automake openresty-resty readline-devel unzip gettext kde-l10n-Chinese which net-tools \ && yum -y reinstall glibc-common \ && ln -s /usr/bin/cmake3 /usr/bin/cmake \ && localedef -c -f UTF-8 -i zh_CN zh_CN.utf8 ENV LC_ALL zh_CN.utf8RUN cd /usr/local/src \ && git clone https://gitee.com/xiaowu_wang/lor.git \ && cd lor \ && make install RUN cd /usr/local/src \ && git clone https://gitee.com/xiaowu_wang/luarocks.git \ && cd luarocks \ && git checkout v3.9.2 \ && ./configure --prefix=/usr/local/luarocks --with-lua=/usr --with-lua-include=/usr/include \ && make \ && make install \ && ln -s /usr/local/luarocks/bin/luarocks /usr/local/bin/luarocks RUN cd /usr/local/src \ && wget https://openresty.org/download/openresty-1.21 .4.1 .tar.gz \ && wget https://bitbucket.org/nginx-goodies/nginx-sticky-module-ng/get/08 a395c66e42.zip -O ./nginx-goodies-nginx-sticky-module-ng-08 a395c66e42.zip \ && tar -xzvf openresty-1.21 .4.1 .tar.gz \ && unzip -D nginx-goodies-nginx-sticky-module-ng-08 a395c66e42.zip \ && mv nginx-goodies-nginx-sticky-module-ng-08 a395c66e42 openresty-1.21 .4.1 /nginx-sticky-module-ng \ && cd openresty-1.21 .4.1 \ && ./configure --prefix=/usr/local/openresty --with-http_stub_status_module --with-http_v2_module --with-http_ssl_module --with-http_realip_module --add -module=./nginx-sticky-module-ng \ && make \ && make install \ && ln -s /usr/local/openresty/nginx/sbin/nginx /usr/bin/nginx \ && ln -s /usr/local/openresty/bin/openresty /usr/bin/openresty \ && ln -s /usr/local/openresty/bin/resty /usr/bin/resty \ && ln -s /usr/local/openresty/bin/opm /usr/bin/opm \ && openresty COPY rockspec ./rockspec/ COPY Makefile ./Makefile RUN make dependency COPY . . CMD make dev && make install && sleep 10 && resty bin/orange start && tail -f /opt/orange/logs/access.log
Statefulset.yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 apiVersion: v1 kind: Service metadata: name: orange-headless spec: publishNotReadyAddresses: false clusterIP: None selector: app: orange --- apiVersion: v1 kind: Service metadata: name: orange spec: selector: app: orange ports: - port: 80 name: http-web targetPort: 80 nodePort: 35080 appProtocol: HTTP - port: 7777 name: http-api targetPort: 7777 nodePort: 35077 appProtocol: HTTP - port: 8888 name: http-fronted targetPort: 8888 nodePort: 35088 appProtocol: HTTP - port: 9999 name: http-admin targetPort: 9999 appProtocol: HTTP type: NodePort --- apiVersion: apps/v1 kind: StatefulSet metadata: name: orange labels: app: orange spec: serviceName: orange-headless updateStrategy: type: RollingUpdate rollingUpdate: maxUnavailable: 0 replicas: 2 selector: matchLabels: app: orange template: metadata: labels: app: orange spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - podAffinityTerm: topologyKey: "kubernetes.io/hostname" namespaces: - default labelSelector: matchLabels: app: orange weight: 100 containers: - name: orange image: $REGISTRY_ADDRESS/${NODE_ENV}/${CI_PROJECT_NAME}:v${CI_PIPELINE_ID} imagePullPolicy: IfNotPresent lifecycle: preStop: exec: command: - /bin/sh - -c - "while [ $(netstat -plunt | grep tcp | wc -l | xargs) -ne 0 ]; do sleep 1; done" livenessProbe: tcpSocket: port: 80 readinessProbe: tcpSocket: port: 80 env: - name: ORANGE_SERVICE value: orange-headless ports: - containerPort: 80 - containerPort: 7777 - containerPort: 8888 - containerPort: 9999 dnsPolicy: ClusterFirst restartPolicy: Always
测试 多副本 扩容测试 新副本可以在10秒内启动成功
访问测试 多次访问,呈轮询效果
控制台 访问8888端口,根据自定义header:orange-version,将流量打入指定pod上
节点注册 查看全局统计(此处如果传的header:orange-version为orange-1,则Address呈现为orange-1) 配置同步 防刷测试 配置dashboard,限制ip :一小时10次,封禁120秒 Jmeter压测 测试从100线程开始
观察2xx 和4xx ,以及conn active (活跃连接数)
活跃连接数维持在100上下
在单节点抗压测试下,orange是可以有效防止因压力过大导致崩溃的问题的
使用专业工具LOIC 进行DDos攻击 测试从100线程开始,等待结果响应再发送下一次。看idle空闲线程 始终有剩余,说明没跑满
观察2xx 和4xx ,以及conn active (活跃连接数)
虽然是100线程,但LOIC的工作原理是向目标服务器发送大量 TCP 、UDP 或 HTTP 数据包以中断服务,所以看到的活跃连接数要远高于100
产生的问题
redis压力 随着压力再加大,redis连接开始逐渐崩掉。所以需要考虑压力更大的情况下,单机redis是否能撑得住。如果和业务模块共用一个redis,会不会拖垮整个业务系统
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 2023 /06 /27 18 :15 :45 [error ] 20172 #20172 : *154221 lua tcp socket connect timed out, when connecting to 192.168 .56 .100 :6379 , context: ngx.timer, client: 192.168 .56 .1 , server: 0.0 .0 .0 :80 2023 /06 /27 18 :15 :45 [error ] 20172 #20172 : *154221 lua entry thread aborted: runtime error : /opt/orange/orange//orange/utils/redis.lua:102 : API disabled in the context of ngx.timerstack traceback : coroutine 0 : [C]: in function 'say ' /opt /orange /orange //orange /utils /redis.lua :102: in function 'connect_mod ' /opt /orange /orange //orange /utils /redis.lua :195: in function 'get ' /opt /orange /orange //orange /plugins /base_redis.lua :32: in function 'get_string ' /opt /orange /orange //orange /plugins /stat /stat.lua :25: in function 'init ' /opt /orange /orange //orange /plugins /stat /stat.lua :55: in function </opt /orange /orange //orange /plugins /stat /stat.lua :53>, context : ngx.timer , client : 192.168.56.1, server : 0.0.0.0:80 2023/06/27 18:15:45 [error ] 20172#20172: *154228 lua tcp socket connect timed out , when connecting to 192.168.56.100:6379, context : ngx.timer , client : 192.168.56.1, server : 0.0.0.0:80 2023/06/27 18:15:45 [error ] 20172#20172: *154228 lua entry thread aborted : runtime error : /opt /orange /orange //orange /utils /redis.lua :102: API disabled in the context of ngx.timer stack traceback :coroutine 0: [C ]: in function 'say ' /opt /orange /orange //orange /utils /redis.lua :102: in function 'connect_mod ' /opt /orange /orange //orange /utils /redis.lua :195: in function 'get ' /opt /orange /orange //orange /plugins /base_redis.lua :32: in function 'get_string ' /opt /orange /orange //orange /plugins /stat /stat.lua :25: in function 'init ' /opt /orange /orange //orange /plugins /stat /stat.lua :55: in function </opt /orange /orange //orange /plugins /stat /stat.lua :53>, context : ngx.timer , client : 192.168.56.1, server : 0.0.0.0:80 2023/06/27 18:15:45 [error ] 20172#20172: *154233 lua tcp socket connect timed out , when connecting to 192.168.56.100:6379, context : ngx.timer , client : 192.168.56.1, server : 0.0.0.0:80 2023/06/27 18:15:45 [error ] 20172#20172: *154233 lua entry thread aborted : runtime error : /opt /orange /orange //orange /utils /redis.lua :102: API disabled in the context of ngx.timer
work_connections are not enough nginx默认worker_connections (单个工作进程可以允许同时建立外部连接的数量) 为 4096 ,数字越大,能同时处理的连接越多,对应需要的资源也越高
这里修改默认值为512 ,测试300 线程,再进行DDos攻击
活跃连接数已经来到1000
日志开始出现大量512 worker_connections are not enough错误
1 2 3 4 5 2023 /06 /28 09 :47 :21 [alert] 7829 #7829 : *51849 512 worker_connections are not enough, client: 192.168 .56 .1 , server: , request: "GET / HTTP/1.0" 2023 /06 /28 09 :47 :21 [alert] 7829 #7829 : *51849 512 worker_connections are not enough, client: 192.168 .56 .1 , server: , request: "GET / HTTP/1.0" 2023 /06 /28 09 :47 :21 [alert] 7829 #7829 : *51849 512 worker_connections are not enough, client: 192.168 .56 .1 , server: , request: "GET / HTTP/1.0" 2023 /06 /28 09 :47 :21 [alert] 7829 #7829 : *51849 512 worker_connections are not enough while connecting to upstream, client: 192.168 .56 .1 , server: , request: "GET / HTTP/1.0" , upstream: "http://[::1]:8001/" 2023 /06 /28 09 :47 :21 [alert] 7829 #7829 : *51859 512 worker_connections are not enough, context: ngx.timer, client: 192.168 .56 .1 , server: 0.0 .0 .0 :80
TODO 其他插件的DDos模拟测试
结论 Orange可以抵挡高并发场景下的冲击
但依靠Orange抵挡专业的DDos攻击,还需要其他的办法
插件开发补充 ngx_lua_waf --- Nginx防火墙 ngx_lua_waf 是一个高性能的轻量级 web 应用防火墙,基于 lua-nginx-module。
它具有以下功能:
防止sql注入,本地包含,部分溢出,fuzzing测试,xss,SSRF等web攻击 防止svn/备份之类文件泄漏 防止ApacheBench之类压力测试工具的攻击 屏蔽常见的扫描黑客工具,扫描器 屏蔽异常的网络请求 屏蔽图片附件类目录php执行权限 防止webshell上传 经过 unixhot 的修改和重构,拥有了以下功能:
支持IP白名单和黑名单功能,直接将黑名单的IP访问拒绝 支持URL白名单,将不需要过滤的URL进行定义 支持User-Agent的过滤,匹配自定义规则中的条目,然后进行处理(返回403) 支持CC攻击防护,单个URL指定时间的访问次数,超过设定值,直接返回403 支持Cookie过滤,匹配自定义规则中的条目,然后进行处理(返回403) 支持URL过滤,匹配自定义规则中的条目,如果用户请求的URL包含这些,返回403 支持URL参数过滤,原理同上 支持日志记录,将所有拒绝的操作,记录到日志中去 日志记录为JSON格式,便于日志分析,例如使用ELKStack进行攻击日志收集、存储、搜索和展示 新增插件 - orange/plugins/bot_detection 详见orange/plugins/bot_detection/README.md
新增插件 - orange/plugins/sql_injections 详见orange/plugins/sql_injections/README.md
新增插件 - orange/plugins/xss_code 详见orange/plugins/xss_code/README.md