在上一篇文章,利用NGINX的keyval,变量map,通过API接口实现了基于服务级别维度和域名维度进行了限流的动态控制。但是在很多情况下Ingress Controller都是多实例部署,那么限流就需要以集群整体来考量而不应该以单个实例来控制,特别是在IC数量动态性的情况下以单实例来控制会导致限流的不稳定。同时,引入IC集群级别不应该带来额外的复杂性,例如应该一个API call即可实现集群的整个体性动作而无需每个IC实例去控制。
需求分析
- 所有IC应能够形成一个集群并共享相关限流状态
- 限流执行动作仅需一个API Call而无需向每个NGINX实例调用
- 能实现限流状态的查看与输出
- 能实现集群状态信息的查看与输出
- 当k8s扩缩NGINX的IC实例后IC集群能自动化发现与移除
解决方案
- 利用NGINX Plus的zone_sync模块实现集群配置,并在集群之间同步limit限流状态信息以及keyval同步
- 利用NGINX Plus的API以及dashboard统计和观察限流状态及同步状态
- 利用k8s的headless service实现IC集群成员的自动化发现与维护
实践演示
1. 首先来看一个伪配置,看要实现一个NGINX集群的最小配置是什么
1 2 3 4 5 6 7 8 |
stream { server { listen 12345; zone_sync; zone_sync_server 1.1.1.1:12345; zone_sync_server 2.2.2.2:12345; } } |
可以看出,首先需要在NGINX中配置一个stream的四层配置,因为zone sync是依赖四层来通信的,在server配置块中最重要的就是zone_sync_server的配置,这里配置集群中所有的实例的IP机器监听的端口。
但是,显然上述配置在k8s的IC环境下是很难去实践的,如果使用的是Daemonset模式来部署IC,这里的server相对固定不会经常发生变化,可以手工来配置;如果是以deployment方式来不是IC,则这里的server就可能会经常随着deployment的变化而变化,根据pod的网络类型,有可能这里的IP会发生经常变化。 显然需要一个更加能与k8s兼容的自动化方式。
zone_sync模块提供了这样的能力,可以将上述配置的server IP改为使用域名,因此我们只需要配置相应的k8s中的一个headless svc的域名即可,因为只有使用headless svc才可以获得真实的pod ip,那么上述的配置将变成如下这样:
1 2 3 4 5 6 7 8 9 |
stream { resolver 10.96.0.10 valid=5s; server { listen 12345; zone_sync; zone_sync_server nginx-ic-svc.nginx-ingress.svc.cluster.local:12345 resolve; } } |
resolver配置的是DNS的IP,这里为k8s集群内的DNS svc IP,valid控制每次DNS解析结果缓存多久,为了能保证尽快发现pod的变化,这里不宜设置过大。zone_sync_server后则配置的是IC的headless svc的域名,域名后面的resolve参数非常重要,这个参数可以保证无法解析域名的时候配置可以正常启动同时又能让缓存结束后能再次查询域名从而确保能很快获取到DNS解析的变化。
2. 所以在集群里首先暴露IC的headless svc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[root@k8s-master-v1-16 deployment]# cat ingress-controller-svc.yaml kind: Service apiVersion: v1 metadata: namespace: nginx-ingress name: nginx-ic-svc spec: selector: app: nginx-ingress clusterIP: None ports: - protocol: TCP port: 12345 targetPort: 12345 |
查看下结果,可以看到headless svc已经成功, 解析对应域名也能获得两个pod地址:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[root@k8s-master-v1-16 deployment]# kubectl describe svc nginx-ic-svc -n nginx-ingress Name: nginx-ic-svc Namespace: nginx-ingress Labels: <none> Annotations: <none> Selector: app=nginx-ingress Type: ClusterIP IP: None Port: <unset> 12345/TCP TargetPort: 12345/TCP Endpoints: 10.244.1.60:12345,10.244.2.68:12345 Session Affinity: None Events: <none> |
1 2 3 4 5 |
#nslookup nginx-ic-svc.nginx-ingress.svc.cluster.local. Name: nginx-ic-svc.nginx-ingress.svc.cluster.local. Address 1: 10.244.2.68 10-244-2-68.nginx-ic-svc.nginx-ingress.svc.cluster.local Address 2: 10.244.1.60 10-244-1-60.nginx-ic-svc.nginx-ingress.svc.cluster.local |
3. 由于我们要用TCP 12345用于集群间状态通信,因此在IC的deployment里需要暴露容器的12345端口,修改配置并执行,确认配置成功
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
- image: myf5/nginx-plus-ingress-opentracing:1.6.3 imagePullPolicy: IfNotPresent name: nginx-plus-ingress ports: - name: http containerPort: 80 hostPort: 80 - name: https containerPort: 443 hostPort: 443 - name: prometheus containerPort: 9113 - name: apiport containerPort: 8888 hostPort: 8888 - name: apiport-write containerPort: 9999 hostPort: 7777 - name: ic-cluster-sync containerPort: 12345 |
4.将对应的stream下的配置注入NGINX里,由于配置的命令都是在stream段落下,因此在NGINX启动所使用的configmap里增加stream-snippets即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
kind: ConfigMap apiVersion: v1 metadata: name: nginx-config namespace: nginx-ingress data: stream-snippets: | resolver 10.96.0.10 valid=5s; server { listen 12345; zone_sync; zone_sync_server nginx-ic-svc.nginx-ingress.svc.cluster.local:12345 resolve; } |
应用该configmap,NGINX将自动更新配置。可以通过查看相关NGINX实例的log确认没有错误信息,此时如果NGINX无法解析上述域名,则会在日志中报出错误提示,如果一切正常,日志里可以看到相关集群间建立连接的日志信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
2020/03/19 14:08:51 [notice] 65#65: resolving discovered new node "10.244.2.69" 2020/03/19 14:08:51 [notice] 65#65: resolving discovered new node "10.244.1.61" 2020/03/19 14:08:51 [notice] 65#65: *59 connected to peer "10.244.2.69", node: 10.244.2.69 2020/03/19 14:08:51 [notice] 65#65: *59 node is online, node: 10.244.2.69 2020/03/19 14:08:51 [notice] 66#66: *61 accepted client 10.244.2.69, client: 10.244.2.69, server: 0.0.0.0:12345 2020/03/19 14:08:51 [notice] 65#65: *59 zone "limitreq_uri" done while sending snapshots, node: 10.244.2.69 2020/03/19 14:08:51 [notice] 65#65: *59 zone "limitper_server" done while sending snapshots, node: 10.244.2.69 2020/03/19 14:08:51 [notice] 66#66: *61 detected local connection id, closing, client: 10.244.2.69, server: 0.0.0.0:12345 10.244.2.69 [19/Mar/2020:14:08:51 +0000] TCP 200 0 29 0.000 2020/03/19 14:08:51 [notice] 65#65: *59 node is offline: ignore self while sending snapshots, node: 10.244.2.69 2020/03/19 14:08:51 [notice] 65#65: *60 connected to peer "10.244.1.61", node: 10.244.1.61 2020/03/19 14:08:51 [notice] 65#65: *60 node is online, node: 10.244.1.61 2020/03/19 14:08:51 [notice] 65#65: *60 zone "limitreq_uri" done while sending snapshots, node: 10.244.1.61 2020/03/19 14:08:51 [notice] 65#65: *60 zone "limitper_server" done while sending snapshots, node: 10.244.1.61 |
5. 在上一篇文章中,在配置keyval_zone, limit_req_zone的时候其实并没有指定这些zone需要同步,因此要想在集群里实现两者的同步,编辑nginx对应的configmap文件,并更新配置:
1 2 3 4 5 6 |
keyval_zone zone=limitreq_uri:64k timeout=2h type=prefix sync; keyval_zone zone=limitper_server:64k timeout=2h sync; limit_req_zone $limit_key zone=req_zone_10:1m rate=10r/s sync; limit_req_zone $limit_key zone=req_zone_20:1m rate=20r/s sync; limit_req_zone $limit_key_servername zone=perserver:10m rate=50r/s sync; |
6. 至此,集群的配置工作就完成了,此时进入NGINX的dashboard界面可以看到类似如下
如果点入cluster可以看到如下界面,注意界面显示的Nodes online数量并不包含自己,由于测试环境只部署了2个IC,所以图中显示为1
测试
通过API,更新一个keyval来测试一下,这里假设对limiter_server进行限流
curl -X POST "http://172.16.10.212:7777/api/6/http/keyvals/limitper_server" -H "accept: application/json" -H "Content-Type: application/json" -d "{ \"cafe.example.com\": \"1\"}"
在对172.16.10.212执行API call更新limitper_server后,来查看确认一些组内另一台172.16.10.211 NGINX上是否接受到了同步与状态更新,可以看到此台中的limitper_server zone接受到了一条记录更新:
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 |
curl -X GET "http://172.16.10.211:7777/api/5/stream/zone_sync/" -H "accept: application/json" { "status": { "nodes_online": 1, "msgs_in": 3, "msgs_out": 0, "bytes_in": 126, "bytes_out": 0 }, "zones": { "limitreq_uri": { "records_total": 0, "records_pending": 0 }, "limitper_server": { "records_total": 1, <<<<已接受到更新 "records_pending": 0 }, "req_zone_10": { "records_total": 0, "records_pending": 0 }, "req_zone_20": { "records_total": 0, "records_pending": 0 }, "perserver": { "records_total": 0, "records_pending": 0 } } } |
接下来对172.16.10.211上的IC发起一些访问激活限流,看限流信息是否可以同步到172.16.10.212上,首先看上述的输出内容中perserver zone是没有记录的, 执行流量测试后,可以看到以下输出中显示已经接收到状态更新:
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 |
curl -X GET "http://172.16.10.212:7777/api/6/stream/zone_sync/" -H "accept: application/json" { "status": { "nodes_online": 1, "msgs_in": 34, "msgs_out": 1, "bytes_in": 1434, "bytes_out": 68 }, "zones": { "limitreq_uri": { "records_total": 0, "records_pending": 0 }, "limitper_server": { "records_total": 1, "records_pending": 0 }, "req_zone_10": { "records_total": 0, "records_pending": 0 }, "req_zone_20": { "records_total": 0, "records_pending": 0 }, "perserver": { "records_total": 1, "records_pending": 0 } } } |
再来具体看下211这台NGINX的限流统计, 可以看到perserver zone中的被拒绝的访问数量:
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 |
curl -X GET "http://172.16.10.211:7777/api/6/http/limit_reqs/" -H "accept: application/json" { "perserver": { "passed": 219, "delayed": 0, "rejected": 1781, "delayed_dry_run": 0, "rejected_dry_run": 0 }, "req_zone_20": { "passed": 0, "delayed": 0, "rejected": 0, "delayed_dry_run": 0, "rejected_dry_run": 0 }, "req_zone_10": { "passed": 0, "delayed": 0, "rejected": 0, "delayed_dry_run": 0, "rejected_dry_run": 0 } } |
总结
通过设置NGINX Plus的集群同步能力,实现了在IC的集群级别限流,同时对限流动作的API调用只需调用一个,简化了操作。通过与k8s的headless svc配合,实现了当部署的NGINX实例发生变化时候IC集群能能自动发现与删除,无需人工操作,实现了整体配置的在k8s维度的原生性,更为重要的是限流的状态同样自适应实例的扩缩而自动化同步和调整。NGINX的API接口本身提供了集群状态、限流结果的输出,dashboard也可以以图形化的方式展示集群状态。
另:
对于上篇文章提到的keyval持久性问题,由于但新的NGINX加入实例后会自动被当前正在运行的实例来更新状态,所以实际上无需考虑状态的持久化。如果希望实现的是当首次整体部署IC的时候就自动启动写入相关keyval的内容,那么就需要考虑持久化,配置也很简单,在keyval_zone指令里增加state参数即可:
1 2 |
keyval_zone zone=limitreq_uri:64k state=/your-store-mount-path/limitreq_uri.keyval timeout=2h type=prefix sync; keyval_zone zone=limitper_server:64k state=/your-store-mount-path/limitper_server timeout=2h sync; |
具体存储路径可以通过挂载共享存储到容器目录后来统一存储。
原创文章,转载请注明
https://myf5.net
文章评论