基于Istio的服务版本路由改造

在切换Istio过程中,鉴于Istio强大的路由功能,希望对服务架构中的路由服务进行改造(替换),使用Istio中的相关路由功能来代替传统路由服务,故进行如下探索......

原服务架构如下图:

服务请求步骤:

(1)客户端通过网关请求服务;

(2)网关对用户进行鉴权(不通过则返回错误,通过则下一步);

(3)网关获取服务地址;

(4)网关转发请求到具体业务服务;

服务模型:

通过服务type+version来唯一确定服务地址:type+version -> serviceUrl/contextPath;

type:数字规定的服务类型,代表某一具体服务;
version:数字规定的服务版本,用于区分具体服务的不同版本;
contextPath:应用的上下文路径(例如tomcat中项目的contextPath:http://localhost:8080/myWebApp);

也就是说客户端通过类似http://gatewayUrl/type/version/svcPath的地址来通过网关请求具体的服务,
网关通过type+version来调用路由服务获取服务的具体地址:servcieUrl/contextPath,并转发该请求到:http://serviceUrl/contextPath/svcPathsvcPath:具体的业务Url,由客户端指定);

路由服务就是用来专门维护服务路由信息的,服务路由信息格式:type+version -> serviceUrl/contextPath

接下来的问题就是如何在Istio中实现type+version -> serviceUrl/contextPath的映射;

路由方案

初步提出路由服务替换方案如下(假定服务为svc1,type=128, verison=1,contextPath=/base):

方案1:

定义一个VirtualService,由该VirtualService统一维护所有路由信息,然后网关统一请求该VirtualService;
例如:设定该VirtualService为route.default,网关统一请求:route.default/{type}/{version}/svcPath,
而在VirtualService route中做如下定义:

...... 

# match(uri.prefix==/type/version) -> rewrite(uri=/contextPath)-> route.destination(host+subset)

# match(uri.prefix==/128/1) -> rewrite(uri=base) -> svc1.v1
  - match:
    - uri:
        prefix: /128/1
    rewire:
      uri: /base
    route:
    - destination:
        host: svc1.default
        subset: v1
# match(uri.prefix==/128/2) -> rewrite(uri=base) -> svc1.v2
  - match:
    - uri:
        prefix: /128/2
    rewire:
      uri: /base
  rewire:
    uri: /base
    route:
    - destination:
        host: svc1.default
        subset: v2 
# match(uri.prefix==/129/1) -> rewrite(uri=base) -> svc2.v1
  - match:
    - uri:
        prefix: /129/1
    rewire:
      uri: /base
    route:
    - destination:
        host: svc2.default
        subset: v1
......

通过上述VirtualServie配置,完成如下服务路由功能:

route.default/128/1/svcPath -> svc1.default/base/svcPath 

 优点:不用写代码(可替换原路由服务),以后route规则都在该virutalService中维护(支持各自服务维护自己的VS+DR);
 缺点:统一维护配置繁琐,维护量大,新服务发布时需要手动添加映射规则;

方案2:

保留原路由服务,在K8s集群中添加K8s Api监听服务,动态监听Deployment(自定义labels app, type, version, contextPath)资源的变化并将路由信息注册到路由服务中;

例如:在default命名空间定义K8s Deployment.svc1,并且自定义labels:app=svc1(需保证app值与对应k8s.Service同名), type=128,version=1,contextPath=/base,当K8s Api监听服务监听到该Deployment.svc1新创建(或发生变化时)则去动态维护原路由服务中的路由信息,格式如下:

type version serviceUrl
128 1 svc1.default/base

同时再定义K8s Deployment.svc1-v2,并且自定义labels:app=svc1,type=128,version=2,contextPath=/base当K8s Api监听服务监听到该Deployment.svc1-v2新创建(或发生变化时)则同样去添加svc1-v2的路由信息,格式如下(第二行):

type version serviceUrl
128 1 svc1.default/base
128 2 svc1.default/base

优点:动态注册服务路由信息,无需手动维护;
缺点:原路由服务依然存在,与Istio路由功能重合,并没有达到真正改造的目的,且还需再维护K8s Api监听服务;

方案3:

客户端替换type+versionsvcName.namespace/contextPath
直接通过http://gatewayUrl/svcName.namespace/contextPath/svcPath请求网关,
而网关直接转发http://svcName.namespace/contextPath/svcPath到具体服务;
进一步优化:
替换type+versionsvcName,namespace由网关指定(根据具体环境进行配置),
version统一在istio中维护,前端、网关无需知道版本号,
若具体服务鉴权策略不同,可在服务名svcName上添加固定后缀,例如:xx_1, xx_2来区分鉴权策略(命名空间等);

优点:客户端直接通过svcName/contextPath进行服务调用,网关无需进行type->service的转换;
缺点:客户端改动较大,且有的客户端写死在硬件中无法进行修改(硬伤);

方案4:

在方案3中客户端无法替换type+version,那就使K8s Service.name=type,客户端无需改动而在K8s中对Service.name进行替换,

例如:服务svc1,type=128, verison=1,contextPath=/base,原来在k8s中服务名称为svc1,现在直接定义服务名称为s128
注:实际测试域名不能数字开头(字母、'-'、数字,且字母开头和结尾),故在服务type前面加上固定字母前缀s
客户端服务请求地址不变:http://gatewayUrl/128/1/svcPathhttp://gatewayUrl/type/version/svcPath),
网关在收到请求后直接进行服务转发:http://s128.default/1/svcPathhttp://s{type}.{namespace}/{version}/svcPath),
namespace由网关指定(根据具体环境进行配置),
具体的路由规则由每个服务的VirtualService+DestinationRule进行维护(与方案1配置类似);

完整配置如下:

# Deployment svc1 配置
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: svc1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: svc1
      version: v1
  template:
    metadata:
      labels:
        app: svc1
        version: v1
    spec:
      containers:
      - name: app
        image: xxx/svc:1
---
# Service s128 配置
# Service.name=s128(s+type)

apiVersion: v1
kind: Service
metadata:
  name: s128
  labels:
    app: svc1
spec:
  ports:
  - port: 80
    targetPort: 8080
    name: http
  selector:
    app: svc1
---
# VirtualService svc1 配置
# match ( uri.prefix==/1 ) -> rewrite ( /1->/base ) -> route s128.default -> subset v1

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: svc1
spec:
  hosts:
  - s128.default.svc.cluster.local
  gateways:
  - mesh
  http:
  - match:
    - uri:
        prefix: /1
    rewrite:
      uri: /base
    route:
    - destination:
        host: s128.default.svc.cluster.local
        subset: v1
---
# DestinationRule svc1 配置
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: svc1
spec:
  host: s128.default.svc.cluster.local
  subsets:
  - name: v1
    labels:
      version: v1
---
 

上述路由配置将:http://s128.default/1/svcPathhttp://s{type}.{namespace}/{version}/svcPath

转换为http://s128.default/base/svcPathhttp://s{type}.{namespace}/{contextPath}/svcPath);

且在上述VritualService+DestinationRule中可以灵活指定路由规则;

优点:客户端无需改动,可直接通过Istio路由替换原路由服务,直接定义K8s服务名为type则网关无需进行type->service的转换;
缺点:K8s服务名需要改造,每个服务需要单独维护Istio路由规则;

综上,考虑采用方案4,方案4将每个服务的路由单独配置维护(相比于方案1),避免集中管理(过于庞大难于维护),可以完全替代原路由服务(相比于方案2)且不必添加K8s Api监听服务(不用编写程序和维护代码),而且不用修改原客户端调用方式(相比于方案3),仅通过Istio相关路由配置即可实现灵活的路由控制(可以满足当前系统的路由控制需求);

路由版本控制

关于当前系统的路由控制,围绕服务type+version来展开(type+version -> http://svcUrl/contextPath),而关于version则存在多种含义,总结如下:
(1)迭代版本(正常的迭代开发,服务地址不变)
    1+1 -> http://svcUrl-1/svc1
(2)金丝雀版本(服务地址相同,但却路由到2个不同版本,对客户端透明)
    2+1 -> http://svcUrl-2/svc2
    2+2 -> http://svcUrl-2/svc2
(3)同一服务的不同controller
    3+1 -> http://svcUrl-3/svc3/ctrl1
    3+2 -> http://svcUrl-3/svc3/ctrl2
(4)完全不同的服务
    4+1 -> http://svcUrl-4-1/svc4-1
    4+2 -> http://svcUrl-4-2/svc4-2

采用方案4,则对以上4种version定义均可支持,示例配置如下:

(1)迭代版本

无需特殊配置,仅去修改每次部署的Docker 镜像版本即可(用于日常迭代开发);

(2)金丝雀版本

# Deployment svc2-1 配置
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: svc2-1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: svc2
      version: v1
  template:
    metadata:
      labels:
        app: svc2
        version: v1
    spec:
      containers:
      - name: app
        image: xxx/svc2:1
---

# Deployment svc2-1 配置
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: svc2-2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: svc2
      version: v2
  template:
    metadata:
      labels:
        app: svc2
        version: v2
    spec:
      containers:
      - name: app
        image: xxx/svc2:2
---
# Service s129 配置
# Service.name=s129(s+type)

apiVersion: v1
kind: Service
metadata:
  name: s129
  labels:
    app: svc2
spec:
  ports:
  - port: 80
    targetPort: 8080
    name: http
  selector:
    app: svc2
---
# VirtualService svc2 配置
# match ( uri.prefix==/1 ) -> rewrite ( /1->/base ) -> route s129.default -> subset v1
# match ( uri.prefix==/2 ) -> rewrite ( /2->/base ) -> route s129.default -> subset v2

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: svc2
spec:
  hosts:
  - s129.default.svc.cluster.local
  gateways:
  - mesh
  http:
  - match:
    - uri:
        prefix: /1
    rewrite:
      uri: /base
    route:
    - destination:
        host: s129.default.svc.cluster.local
        subset: v1
  - match:
    - uri:
        prefix: /2
    rewrite:
      uri: /base
    route:
    - destination:
        host: s129.default.svc.cluster.local
        subset: v2
---
# DestinationRule svc2 配置
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: svc2
spec:
  host: s129.default.svc.cluster.local
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
---

(3)同一服务的不同controller

# Deployment svc3 配置
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: svc3
spec:
  replicas: 1
  selector:
    matchLabels:
      app: svc3
      version: v1
  template:
    metadata:
      labels:
        app: svc3
        version: v1
    spec:
      containers:
      - name: app
        image: xxx/svc3:1
---

# Service s130配置
# Service.name=s130(s+type)

apiVersion: v1
kind: Service
metadata:
  name: s130
  labels:
    app: svc3
spec:
  ports:
  - port: 80
    targetPort: 8080
    name: http
  selector:
    app: svc3
---
# VirtualService svc2 配置
# match ( uri.prefix==/1 ) -> rewrite ( /1->/base/ctrl1 ) -> route s130.default -> subset v1
# match ( uri.prefix==/2 ) -> rewrite ( /2->/base/ctrl2 ) -> route s130.default -> subset v1

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: svc3
spec:
  hosts:
  - s130.default.svc.cluster.local
  gateways:
  - mesh
  http:
  - match:
    - uri:
        prefix: /1
    rewrite:
      uri: /base/ctrl1
    route:
    - destination:
        host: s130.default.svc.cluster.local
        subset: v1
  - match:
    - uri:
        prefix: /2
    rewrite:
      uri: /base/ctrl2
    route:
    - destination:
        host: s130.default.svc.cluster.local
        subset: v1
---
# DestinationRule svc3 配置
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: svc3
spec:
  host: s130.default.svc.cluster.local
  subsets:
  - name: v1
    labels:
      version: v1
---

(4)完全不同的服务

# Deployment svc4-1 配置
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: svc4-1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: svc4-1
      version: v1
  template:
    metadata:
      labels:
        app: svc4-1
        version: v1
    spec:
      containers:
      - name: app
        image: xxx/svc4-1:1
---

# Deployment svc4-2 配置
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: svc4-2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: svc4-2
      version: v1
  template:
    metadata:
      labels:
        app: svc4-2
        version: v1
    spec:
      containers:
      - name: app
        image: xxx/svc4-2:1
---

# Service s131配置
# Service.name=s131(s+type)

apiVersion: v1
kind: Service
metadata:
  name: s131
  labels:
    app: svc4-1
spec:
  ports:
  - port: 80
    targetPort: 8080
    name: http
  selector:
    app: svc4-1
---

# Service s132配置
# Service.name=s132(s+type)

apiVersion: v1
kind: Service
metadata:
  name: s132
  labels:
    app: svc4-2
spec:
  ports:
  - port: 80
    targetPort: 8080
    name: http
  selector:
    app: svc4-2
---
# VirtualService svc4 配置
# match ( uri.prefix==/1 ) -> rewrite ( /1->/base1 ) -> route s131.default -> subset v1
# match ( uri.prefix==/2 ) -> rewrite ( /2->/base2 ) -> route s132.default -> subset v1

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: svc4
spec:
  hosts:
  - s131.default.svc.cluster.local
  gateways:
  - mesh
  http:
  - match:
    - uri:
        prefix: /1
    rewrite:
      uri: /base1
    route:
    - destination:
        host: s131.default.svc.cluster.local
        subset: v1
  - match:
    - uri:
        prefix: /2
    rewrite:
      uri: /base2
    route:
    - destination:
        host: s132.default.svc.cluster.local
        subset: v1
---
# DestinationRule svc4-1 配置
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: svc4-1
spec:
  host: s131.default.svc.cluster.local
  subsets:
  - name: v1
    labels:
      version: v1
---

# DestinationRule svc4-2 配置
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: svc4-2
spec:
  host: s132.default.svc.cluster.local
  subsets:
  - name: v1
    labels:
      version: v1
---

(5)金丝雀版本 - 补充(在原基础上添加基于header[user-name]=lhq路由)

# Deployment svc2-1 配置 - 同示例(2)
# Deployment svc2-1 配置 - 同示例(2)
# Service s129 配置 - 同示例(2)

---
# VirtualService svc2 配置
# match ( uri.prefix==/1 and header[user-name]=luohq) -> rewrite ( /1->/base ) -> route s129.default -> subset v1
# match ( uri.prefix==/2 ) -> rewrite ( /2->/base ) -> route s129.default -> subset v2

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: svc2
spec:
  hosts:
  - s129.default.svc.cluster.local
  gateways:
  - mesh
  http:
  - match:  #匹配prefix==/1并且header[user-name]==lhq则路由到v1
    - uri:
        prefix: /1
      header:
        user-name:
          exact: luohq

    rewrite:
      uri: /base
    route:
    - destination:
        host: s129.default.svc.cluster.local
        subset: v1
  - match:
    - uri:
        prefix: /1
    rewrite:
      uri: /base
    route:
    - destination:
        host: s129.default.svc.cluster.local
        subset: v1
  - match:
    - uri:
        prefix: /2
    rewrite:
      uri: /base
    route:
    - destination:
        host: s129.default.svc.cluster.local
        subset: v2
---
# DestinationRule svc2 配置
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: svc2
spec:
  host: s129.default.svc.cluster.local
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
---

注:在Istio中关于match中header的定义需要注意,header名称必须全部包括且仅包括[小写字母,'-' ],实际测试过程中user(user中没有中横线)不生效,改为user-name生效(若请求header为USER-NAME也是生效的);

官方header说明:

以上,基于Istio的服务版本路由改造方案基本完成。

发布了56 篇原创文章 · 获赞 6 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/luo15242208310/article/details/93195333