本篇将在mac 上学习k8s系列(8)external auth的基础上基于nginx-ingress 的access_by_lua_block +redis 来实现一个全局的rate limiter:用nginx lua连接redis,用redis计数来做集群粒度的rate-limit。
ngx_lua 模块提供了配置指令 access_by_lua,用于在 access 请求处理阶段插入用户 Lua 代码。这条指令运行于 access 阶段的末尾,因此总是在 allow 和 deny 这样的指令之后运行,虽然它们同属 access 阶段。一般我们通过 access_by_lua 在 ngx_access 这样的模块检查过客户端 IP 地址之后,再通过 Lua 代码执行一系列更为复杂的请求验证操作,比如实时查询数据库或者其他后端服务,以验证当前用户的身份或权限。
首先,我们启动一个简单的redis服务,一般redis、mysql等有状态的服务,我们一般通过pv使用nfs服务存储,并且使用使用StatefulSet创建redis-cluster集群节点和headless service,保证每个pod的ip不变。本文重点介绍如何使用access_by_lua_block,所以搭建一个简单的无状态的redis。
创建一个ConfigMap来保存redis的配置:
---kind: ConfigMapapiVersion: v1metadata:name: redis-configlabels:app: redisdata:redis.conf: |-dir /srvport 6379bind 0.0.0.0appendonly yesdaemonize no#protected-mode norequirepass 123456pidfile /srv/redis-6379.pid
然后通过DeployMent启动redis的pod
---apiVersion: apps/v1kind: Deploymentmetadata:name: redislabels:app: redisspec:replicas: 1selector:matchLabels:app: redistemplate:metadata:labels:app: redisspec:containers:- name: redisimage: redis:latestcommand:- "sh"- "-c"- "redis-server usr/local/redis/redis.conf"ports:- containerPort: 6379resources:limits:cpu: 1000mmemory: 1024Mirequests:cpu: 1000mmemory: 1024MilivenessProbe:tcpSocket:port: 6379initialDelaySeconds: 300timeoutSeconds: 1periodSeconds: 10successThreshold: 1failureThreshold: 3readinessProbe:tcpSocket:port: 6379initialDelaySeconds: 5timeoutSeconds: 1periodSeconds: 10successThreshold: 1failureThreshold: 3volumeMounts:- name: configmountPath: /usr/local/redis/redis.confsubPath: redis.confvolumes:- name: configconfigMap:name: redis-config
启动redis服务
apiVersion: v1kind: Servicemetadata:name: redislabels:app: redisspec:type: NodePortports:- name: tcpport: 6379nodePort: 30379selector:app: redis
测试下redis服务是否正常启动了:
%redis-cli -h 127.0.0.1 -p 30379 -a 123456Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.127.0.0.1:30379>
接着,我们创建Ingress
apiVersion: networking.k8s.io/v1kind: Ingressmetadata:name: ingress-with-authannotations:nginx.ingress.kubernetes.io/server-snippet: |access_by_lua_block {local header = ngx.req.get_headers()if header.token thenlocal LIMIT = 100local DELAY = 10local red = require "resty.redis"local redis = red:new()redis:set_timeout(1000)local ok, err = redis:connect("redis.default.svc.cluster.local", 6379)if not ok thenngx.status = 500ngx.say("<h1>系统开小差了</h1>",err)returnendlocal res, err = redis:auth("123456")if not res thenngx.status = 500ngx.say("<h1>系统开小差了</h1>")returnendlocal now = ngx.now()local ok, err = redis:eval('local oldest = redis.call("lindex", ARGV[1], -1);if oldest then if redis.call("llen", ARGV[1]) >= tonumber(KEYS[1]) then if (ARGV[2] - oldest) < tonumber(KEYS[2]) then return nil end end end;redis.call("lpush", ARGV[1], ARGV[2]);redis.call("expire", ARGV[1], KEYS[1]);redis.call("ltrim", ARGV[1], 0, KEYS[1]); return 1', 2, LIMIT-1, DELAY, "limit:"..ngx.md5(header.token), now)if ok ~= 1 thenngx.status = 519ngx.say("<h1>系统繁忙</h1>")redis:set_keepalive(10000, 100)returnendredis:set_keepalive(10000, 100)end}nginx.ingress.kubernetes.io/rewrite-target:spec:rules:- http:paths:- pathType: Prefixpath:backend:service:name: apple-serviceport:number: 5678
我们在注解的nginx.ingress.kubernetes.io/server-snippet: 段插入了lua代码:access_by_lua_block,通过openresty的redis 包来连接redis:
local red = require "resty.redis"local redis = red:new()redis:set_timeout(1000)local ok, err = redis:connect("redis.default.svc.cluster.local", 6379)
通过执行redis lua脚本来进行限流
local now = ngx.now()local ok, err = redis:eval('local oldest = redis.call("lindex", ARGV[1], -1);if oldestthen if redis.call("llen", ARGV[1]) >= tonumber(KEYS[1])then if (ARGV[2] - oldest) < tonumber(KEYS[2])then return nilendendend;redis.call("lpush", ARGV[1], ARGV[2]);redis.call("expire", ARGV[1], KEYS[1]);redis.call("ltrim", ARGV[1], 0, KEYS[1]);return 1',2, LIMIT-1, DELAY, "limit:"..ngx.md5(header.token), now)
通过滑动窗口算法,做用户token维度的限流。
首先我们测试下
kubectl apply -f ingress.yamlcurl -H "token:12344" 127.0.0.1/apple/apple
一个请求的情况下正常工作了,我们来压测下
% ab -n 1000 -c 100 -H "token:12344" 127.0.0.1/applePercentage of the requests served within a certain time (ms)50% 5466% 7775% 9080% 9990% 12295% 14598% 18999% 212100% 258 (longest request)
100个并发是正常工作的,我们换成200个呢
%ab -n 1000 -c 200 -H "token:12344" 127.0.0.1/appleThis is ApacheBench, Version 2.3 <$Revision: 1879490 $>Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/Licensed to The Apache Software Foundation, http://www.apache.org/Benchmarking 127.0.0.1 (be patient)apr_socket_recv: Connection reset by peer (54)Total of 1 requests completed
限流量,至此我们完成了access_by_lua_block +redis 全局的rate limiter的功能。其中也遇到了各种问题,下一节给大家介绍分析思路。






