源宝导读:明源云客的终端用户越来越多,也涌现出线上流量活动的场景,大量的访问和接口请求导致服务器出现较高负载。本文将介绍云客团队为了缓解服务器压力,通过K8S进行分流与资源隔离的实践过程。
一、背景
PaaS和B2C的主要客户云客的终端用户越来越多,也开始有流量活动的需求。
近期云客需要开始一个流量活动,B2C配合对特定接口做了压测并且对系统容量进行了调整。当时的压测结果是200并发300ms完成基本符合云客的要求。可是活动开始后事情跟我们预想的是不一样的,压测只测试了注册接口,但是实际登陆接口也会有巨大的请求量。由于登陆接口有比较多的调用第三方所以在请求量大的是对服务器的CPU带来较高的负载。在这个情况下我们不断的对服务进行扩容。可是扩到190个核心服务服务依然是没法正常。
由于当时部署的是整个服务组统一给所有用户提供服务,如下图:
按照这个情况只要一个租户的流量特别大给集群带来压力就会导致整个服务异常,所有人都无法正常访问。出于将影响范围缩小的考虑,我们需要将活动的流量分流并保障即便活动的客户出现问题也只是影响活动租户不会影响到其他租户。
二、分流
在思考如何分流时,第一个想到的是之前已经在云链和云空间落地的利用openresty识别url参数,header,cookie中的特征信息然后进行路由这个方案。但是由于paas和B2C部署是基于k8s这个方案就变的不那么好了。
使用openresty就需要在集群之外架设将流量分配到两个集群,那么会遇到这些问题:
分流集群通常不是常驻有需要的时候才会部署,这样每次需要分流前都要去维护分流策略。
为了保障openresty的可用性,可能需要在集群外部部署多个openresty服务。那么流量的路径会变的很复杂且冗长,这会让排查问题变的困难,同时会影响访问速度。
需要时间完成分流策略的代码,并且要进行一定的压力测试才能够正式上线接管流量。会需要很比较多的时间。
由于这些问题,考虑从k8s的ingress来进行分流。有了下图的设想:
ingress-controller的选择
我们使用的ingress-controller是nginx-ingress,nginx-ingress自带一个分流功能,定义上也十分符合k8s和helm的的管理方式。查阅官方文档后发现,目前nginx-ingress只支持header,cookie进行分流,可是现状是现有项目分流的特征信息在url的query才有。只能再去寻找其他ingress-controller。
查找并对比了traefik,ambassador,haproxy,alibaba-ingress这几个ingress-controller。
traefik在功能上是满足的且有很多新的功能十分值得尝试,并且解决了nginx令人诟病的静态配置问题。但是不支持tcp协议路由,需要k8s版本高于1.14。由于一个网络插件升级的问题我们的k8s集群一直还在1.12版本,所以这次用不上了。
ambassador。如果严格遵循k8s的定义,ambassador其实不算一个ingress-controller。它并不是通过ingress去定义路由规则,而是直接在service上加上注解定义路由规则(更像一个api网关)。这样的话我们需要改动的点就比较多了。
haproxy的优势在负载均衡算法,在七层协议的路由能力不能满足需求。pass
alibaba-ingress,阿里云基于nginx-ingress进行了一些定制开发和优化的ingress-controller。基本是与我们现在用的nginx-ingress一致,在分流的规则定义上有一些差异。nginx的分流是分别在两个namespace定义两条ingress规则(注解有差异,通过注解匹配请求),alibaba的则是在同一个ingress规则定义2个后端服务再通过注解匹配请求。alibaba-ingress的分流规则支持基于query的匹配是符合我们需求的,但是两年没有更新版本并且官方不提供helm部署方式。
基于符合需求且影响点最小化的思路,选用了alibaba-ingress来作为这次分流的ingress-controller。
三、部署
在选定了分流的方案之后,如何高效稳定的部署两组服务成为第二个需要解决的问题。
目前我们的部署方式:将一组服务打包成一个产品进行部署。
如何部署分流的服务组,有两个方案:
将分流的服务一个个加入产品里面进行部署。
将现有产品在其他namespace再部署一份。
方案1:所有服务都在一个namespace中ingress规则很好配(ingress规则不能路由到不同namespace的service),但是会让服务管理变的十分复杂,并且需要修改应用配置。放弃。
方案2:两组服务分别 部署在两个namespace需要解决跨namespace配置ingress规则的问题,但是管理上简单,也不需要对应用做任何改动。
改变部署存在的影响点太多,时间紧迫必须选择影响点最少的办法。所以方案2是首选。
如何让ingress规则跨namespace
既然ingress的规则不能跨namespace路由,那就想办法把其他namespace的service弄到当前namespace。在k8s中有一种ExternalName类型的service,提供了映射外部服务的能力(其实就是一个类似cname解析的功能)。
部署示意:
分两个命名空间部署两组一模一样的服务,在常驻服务的命名空间中用ExternalName类型的service将分流服务命名空间内的服务映射过来。ingress在同一个命名空间下进行流量分配。
四、资源隔离
在分流和部署的方案都确定以后,需要考虑最后一个问题。如何保障正常用户不被活动的用户影响。根据我们的现状,之所以正常用户会被影响的主要原因就是资源被活动用户消耗殆尽导致整个系统的崩溃,基于这个情况为了保护正常用户的访问只需要将正常用户和活动用户使用的资源进行隔离即可。
K8S提供了几种管理容器调度到节点的功能:
节点选择器(nodeSelector),非常简单的方法来将 pod 约束到具有特定标签的节点上
节点亲和(nodeAffinity),节点亲和分为硬和软两种,“硬”类似节点选择器只是可以用更复杂的约束规则,“软”则是尽可能运行到符合条件的节点上。
节点瑕疵和容忍度(Taint和Toleration),为特定节点打上瑕疵标签,只有能够容忍该瑕疵的pod才能运行到这类节点上。
实现隔离
利用1和3的功能,就能够实现我们期望的效果。
为活动的租户新增一批节点,给这些节点打上瑕疵标签。保持正常业务的容器无法被调度到这些节点。
按照之前部署中介绍的方案,为活动租户新部署一套服务。
新增的一组服务,配置节点选择器约束到特定节点,配置节点容忍新增节点的瑕疵标签。
经过上述步骤就能够将两组服务的资源很好的隔离开。
五、快速配置
完成了功能性的诉求,最后就是需要在使用上变得便利。因为之前选择方案都会考虑配置和部署的便利性,所以最终只需要对现有的chart(部署模版)进行一些小小的调整就能实现快速配置分流以及隔离。
新增节点
通过阿里云新增节点,之后通过下面命令对节点打标签和瑕疵
kubectl label node cn-hangzhou.i-bp1faickzmmuat2f09dr b2c=canary
kubectl taint node cn-hangzhou.i-bp1faickzmmuat2f09dr b2c=canary:NoSchedule
部署分流服务
我们使用helm部署,只需要复制常驻服务的values文件新增几个字段并关闭ingress配置然后用helm部署到新的namespace即可。
以一个服务的values举例,复制内容如下:
b2c-nginx-proxy:
replicaCount: 5
image:
tag: "20190920"
env:
enabled: true
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: nginx
hosts:
- example.mypaas.com.cn
tls:
- secretName: example.com.cn
hosts:
- example.mypaas.com.cn
修改后内容如下:
b2c-nginx-proxy:
replicaCount: 5
image:
tag: "20190920"
env:
enabled: true
ingress:
enabled: false
annotations:
kubernetes.io/ingress.class: nginx
hosts:
- example.mypaas.com.cn
tls:
- secretName: example.com.cn
hosts:
- example.mypaas.com.cn
tolerations:
- key: b2c
operator: "Equal"
value: "canary"
effect: "NoSchedule"
nodeSelector:
b2c: canary
常驻服务开启分流配置
常驻服务的values文件新增几行即可实现分流以一个服务的values举例:
b2c-nginx-proxy:
replicaCount: 3
image:
tag: "20190920"
env:
enabled: true
ingress:
enabled: true
canary:
enable: true #开启分流
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/service-match: | #分流规则配置
b2c-nginx-proxy-canary: query("tenant_code", /.*zhaosadmin.*/)
hosts:
- example.mypaas.com.cn
tls:
- secretName: example.com.cn
hosts:
- example.mypaas.com.cn
简单的三步即可完成租户分流并且隔离。
六、应用
当所有验证ok后,立马将这套方案在生产上线,将当时活动的租户分流并隔离。同时开始缩减集群规模把为正常租户提供服务的服务组节点数量恢复成常驻节点数量,保留四个节点继续为活动租户提供服务。
到目前为止正常租户的服务没有受到过影响。
七、云擎的灰度策略
灰度发布的技术本质跟这个分流并没有太大的区别。只是使用场景上的差异,所以这套方案可以放到云擎直接实现灰度发布。简单介绍一下,将来各团队接入使用时也能提前准备。
支持的路由规则
云擎支持的ingress-controller只会是nginx-ingress,因为alibaba-ingress几年没有更新,不敢在生产环节长期使用。
所以云擎只能支持根据header和cookie进行路由。如果各团队希望能够灰度发布需要在请求头中加入特定标识。
数据库的灰度
没有租户库的团队,需要保持灰度的两个版本在数据结构上是兼容的
有租户库的团队,选择租户的范围进行数据升级
产品内部的服务调用
需要使用灰度发布必须保持各个服务间的调用都要走内网调用。不能通过公网域名调用,这样会导致调用关系混乱。
支持的特性
由于是利用nginx-ingress实现的,所以还是会遵循一些nginx本身的限制。
只能创建一条灰度规则
一条规则内可以同时定义header,cookie,权重。优先级关系:header -> cookie -> 权重。
header支持自定义值并且值可以用正则匹配,cookie只支持固定值:always或never
分流规则所有配置遵循原有的所有配置,如:最大传输大小,是否强制https,超时时间等
八、未来
现阶段利用nginx-ingress云擎能够以极低的成本提供灰度发布的能力,但是nginx-ingress整体来看功能还是有欠缺。不过在这次事件中为了找方案,了解了其他优秀的ingress-controller。后续应该会把所有的ingress-controller替换成traefik来实现更多功能。
------ END ------
作者简介
尹同学: 运维负责人,目前在云技术创新中心负责技术运维工作。
也许您还想看