作者:苏万亮,中国移动云能力中心软件研发工程师,专注于云原生、微服务、算力网络等领域。
前言
近期团队想利用Istio对产品做全链路灰度,但是对于产品已经选择Apisix作为网关的场景,出于不改变产品原有架构的考虑,不想再引入Istio Ingress Gateway,而只依赖开源的Apisix又无法对产品做到全链路灰度,因此便调研能否将Apisix和Istio结合实现全链路灰度。本文就介绍一下两种可行的方案,与大家一起探讨学习。
问题介绍
上图是简化的一个产品服务调用关系图。其中,Apisix ingress承接外部流量,根据路由规则请求client服务,client服务还会请求provider服务。现在的目的就是对服务client和provider全部做灰度,实现全链路灰度。而Apisix只能对入口服务client进行灰度,做不到全链路灰度。因此考虑引入Istio来实现全链路灰度,为了不改变产品的原有架构,不引入Istio Ingress Gateway,依旧只使用Apisix ingress一种网关。结合Apisix和Istio实现全链路灰度,在配置具体流量规则时共有两种方案,下面对两种方案做详细介绍。
方案一:网关注入sidecar,重写host
对于Istio的灰度方式,我们知道若一个已注入sidecar的服务所配置的灰度规则(virtualservices和destinationrules)想生效,需要请求访问它的服务同样也注入sidecar(是一个istio envoy)。因此对于入口服务client,若引入Istio Ingress Gateway当然没有问题,Istio Ingress Gateway本身就是一个envoy,现不引入Istio Ingress Gateway的话,就需要对Apisix的数据面注入sidecar,如下图所示。
对于服务client,其正常灰度规则配置如下:
apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: client spec: host: client subsets: - labels: version: v0-0-1 name: v0-0-1 - labels: version: v0-0-2 name: v0-0-2
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: client
spec:
hosts:
- client
http:
- match:
- headers:
client_version:
exact: v0-0-2
route:
- destination:
host: client
subset: v0-0-2
- route:
- destination:
host: client
subset: v0-0-1
上述规则中,hosts的配置为client(kubernetes的完全限定域名),表示根据访问client服务的请求是否带请求头client_version:v0-0-2决定流量路由到某版本。而对于Apisix来说,一般配置规则为:hosts为www.xxx.com,匹配到某路径转发到client服务。这样Apisix和Istio的规则就匹配不上,导致灰度规则不生效。
因此若想服务client的灰度规则生效的话,则需要在外部流量进入Apisix网关后,重写其host。对于重写host,可使用Apisix的插件proxy-rewrite将请求host(www.xxx.com)改成k8s内部域名(test.ns.svc.cluster.local)。具体配置如下:
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
name: ingress-testclient
spec:
http:
- backends:
- serviceName: client
servicePort: 8070
match:
hosts:- www.xxx.com
paths: - /client/*
name: testclient-route-default
plugins:
- www.xxx.com
- config:
host: client.wgb.svc.cluster.local
enable: true
name: proxy-rewrite - config:
burst: 1
key: remote_addr
key_type: var
nodelay: true
rate: 1
rejected_code: 503
rejected_msg: limit-req-error
enable: true
name: limit-req通过上述配置,外部流量经过Apisix后,其请求头host:www.xxx.com就被重写成host:client.wgb.svc.cluster.local,从而匹配到服务client的virtualservices中的host,灰度规则生效。
上述方案虽然可以实现client服务的灰度,但其很明显的问题就是若client服务本身会根据传入的host值来处理业务,那丢失原有的host,对业务带来的影响就很大。
方案二、各施其职,不重写host
对于Apisix来说,虽然不能对产品做到全链路灰度,但是其针对入口服务,即图中的client服务,是具备灰度发布能力的。因此第二个方案就是将入口服务交给Apisix来灰度,其余非入口服务交给Istio来灰度。
通常一个产品由多个微服务构成的话,其大部分服务都不是入口服务,因此都可以用Istio实现这些服务的灰度发布。Istio的灰度方式不做赘述,以provider服务为例,通常是会部署两个deployment对应其两个版本,一个service关联这两个版本的pod,再通过配置virtualservices和destinationrules即可实现灰度。对于整个产品全链路灰度,最好是能将灰度版本的服务部署方式统一起来,即对于入口服务client,也部署两个deployment对应其两个版本,一个service关联这两个版本的pod,但用Apisix的路由配置来实现灰度。具体方式就是利用ApisixRoute+ApisixUpstream实现下发规则路由到不同版本的client服务。Apisix相关配置如下:
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
name: client
spec:
subsets:
- serviceName: client
- name: v0-0-2
labels:
version: v0-0-2 - name: v0-0-1
labels:
version: v0-0-1
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
name: ingress-testclient
spec:
http:
- backends:
- serviceName: client
servicePort: 8070
subset: v0-0-2
match:
exprs:- op: Equal
subject:
name: client_version
scope: Header
value: v0-0-2
hosts: - testclient.com
paths: - /client/*
name: route-v2
priority: 1
- op: Equal
- serviceName: client
- backends:
- serviceName: client
servicePort: 8070
subset: v0-0-1
match:
hosts:- testclient.com
paths: - /client/*
name: route-default从上述配置可以看出,首先ApisixUpstream根据不同版本的pod的标签创建subset,配置规则与Istio中的destinationrules类似,其次ApisixRoute也只需要定义好匹配规则,选择路由到服务client的某个subset。整个配置同样只需要client服务存在两个deployment对应其两个版本,一个service关联这两个版本的pod。
需要注意的是虽然client服务是用Apisix做灰度,但其也需要注入sidecar,因为provider的灰度规则需要client同样是一个envoy。另外该方案仅针对服务的灰度发布能力,没有对Apisix的数据面注入sidecar,若产品还需要提供完整的服务拓扑或链路追踪功能,对Apisix的数据面注入sidecar即可。
总结
本文针对产品已使用Apisix作为网关,并想在不改变产品原有架构的前提下,对产品的所有服务实现全链路灰度,提出了两种可行的方案。两种方案各有利弊,方案一实现较简单,但需重写host,要考虑产品能否接受,不适用于所有产品;方案二无需重写host,产品都能接受,但灰度逻辑略复杂,配置也略复杂。因此,使用哪种方案需根据产品本身架构来决定。
- testclient.com
- serviceName: client