基于 Istio 的流量染色实践

442 阅读5分钟

后端微服务多的时候,调试一个新功能或 bug 可能会涉及多个微服务一起联动。在我之前的开发中,开发完新功能、修复 bug 后,需要同时将涉及的微服务更新到 test 环境提供测试验证。这种模式简单,但带来了一些问题:

  1. 需要合并代码到某个共享分支,比如 test 分支,部署上去后可能带上了其他人未提测的代码,以及可能需要经常处理代码冲突
  2. 部署上去后代码如果有问题可能会影响到整个测试环境,导致测试其他功能验证不了
  3. 当要验证的是微服务 A ,依赖微服务 B 的当前版本,但是由于只有一个测试环境,微服务 B 便不好提测自己的新代码,否则可能会造成无法预知的问题

为了解决上述问题,我们引入了流量染色方案。

image.png

如上图所示,

  1. default 一行是 test 环境上的微服务集合,是一个相对稳定一点的服务版本
  2. 下面 feature 1,feature 2,feature 3 是正在联调、测试中的服务版本
  3. 需要将 feature 头信息贯穿始终,随着每一跳不断往下传
  4. 每个微服务的下一跳需要根据 feature 信息转发到对应的节点
  5. 如果 feature 找不到匹配的节点,需要转发到默认节点(即 default 那一行的服务)

很直观,每个新功能或者修复 bug 都能拉一个独立的节点起来,需要修改多少个微服务便起多少个,其他无需修改的微服务便使用默认的。

实现

我们系统使用了 k8s + Istio sidecar 方案,所以这里只介绍这个方案的使用。它的好处是:

  1. 可以方便快速拉起服务的节点(pod)
  2. 能简单通过配置控制流量的转发
  3. 支持 http、grpc
  4. 实现的代码量少,对业务几乎无侵入
  5. 能配合 CI/CD 实现自动化部署
  6. 开源,业界已经很成熟,社区强大

Istio 简单介绍

图片.png

  1. Istio 是一个 k8s 服务网格系统,主要分为流量控制面和数据面两个部分。
  2. 控制面 istiod 服务主要用于下发流量规则,提供服务注册、指标监控等功能,它通过 grpc 与 sidecar 进行通信。
  3. 数据面主要是由多个 envoy sidecar 组成,通过 grpc 将 ip 相关信息自动注册到 istiod 中,并获取流量规则。当有流量转发时,根据规则进行转发。

sidecar 模式

图片.png

  1. sidecar 模式会截取所有出口/入口流量
    • 入口流量:代理到本地的微服务
    • 出口流量:根据 feature 信息转发到对应的下一跳节点
  2. envoy proxy 除了代理转发流量,还可以接入链路追踪、监控指标、日志打印等功能
  3. 该模式对服务几乎无侵入性,只需要的处理是在请求下游时,需要把 feature 信息能够正常往下传即可

ingress-gateway

  1. istio 提供了个 ingress controller 组件,用于接入外网流量
  2. 区别于 nginx ingress controller,它不仅支持7层的 HTTP、GRPC、Websocket 协议,还支持3层的 TCP、UDP 协议
  3. 它实际也是个 envoy,也会注册到 istiod 并自动同步流量规则用于分发流量,在流量染色中它用于分发第一跳 http 服务流量

配置

假设有 2 个微服务 yoo、hoo,流量从网关到 yoo,再到 hoo

  1. helm chart 部署 istiod, istio-gateway,参考 istio.io/latest/docs…
helm repo add istio https://istio-release.storage.googleapis.com/charts
helm repo update

# install base crd
helm upgrade --install istio-base istio/base -n istio-system --create-namespace --set defaultRevision=default

# install istiod in istio-system namespace
helm upgrade --install istiod istio/istiod -n istio-system

# install istio-gateway in istio-ingress namespace
helm upgrade --install istio-ingress istio/gateway -n istio-ingress --create-namespace
  1. 开启 namespace 下 pod 自动注入 sidecar
kubectl create namespace test
kubectl label namespace test istio-injection=enabled --overwrite
  1. 给每个微服务添加个 middleware,用于设置请求进来的 feature 头信息到 metadata,后续 grpc 请求时会将 ctx 的 metadata 自动带上往下传,下面给出 go 代码例子:
func M(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()
		
		md, ok := metadata.FromOutgoingContext(ctx)
		if !ok {
			md = metadata.MD{}
		}

		md.Set("x-custom-feature", r.Header.Get("x-custom-feature"))

		newCtx := metadata.NewOutgoingContext(ctx, md)

		r = r.WithContext(newCtx)

		h.ServeHTTP(w, r)
	})
}

# grpc client request like, it should use the ctx from middleware:
# hooClient.Hello(ctx, &HelloRequest{})
  1. 部署微服务,并配置 hoo 微服务 DestinationRuleVirtualService
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: hoo
  namespace: test
spec:
  host: hoo
  subsets:
  - labels:
      version: default
    name: default
  - labels:
      version: feature1
    name: feature1
  - labels:
      version: fix-bug
    name: fix-bug
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: hoo
  namespace: test
spec:
  hosts:
  - hoo
  http:
  - match:
    - headers:
        x-custom-feature:
          exact: feature1
    route:
    - destination:
        host: hoo
        subset: feature1
  - match:
    - headers:
        x-custom-feature:
          exact: fix-bug
    route:
    - destination:
        host: hoo
        subset: fix-bug
  - route:
    - destination:
        host: hoo
        subset: default
  • DestinationRule:声明有3个版本,利用labels字段匹配对应的 pod 的 lable,定位到 pod
  • VirturalService:使用http对象配置路由转发规则,通过提取 x-custom-feature 进行匹配转发到对应的 DestinationRule 确定的版本。同时注意要提供一个默认的规则进行兜底
  1. 配置 yoo 服务入口网关和相关的 DestinationRule、VirtualService:
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: demo
  namespace: test
spec:
  selector:
    istio: ingress
  servers:
  - hosts:
    - demo.com
    port:
      name: http
      number: 80
      protocol: HTTP
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: yoo
  namespace: test
spec:
  host: yoo
  subsets:
  - labels:
      version: default
    name: default
  - labels:
      version: feature1
    name: feature1
  - labels:
      version: fix-bug
    name: fix-bug
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: yoo
  namespace: test
spec:
  gateways:
  - demo
  hosts:
  - demo.com
  http:
  - match:
    - headers:
        x-custom-feature:
          exact: feature1
    route:
    - destination:
        host: yoo
        subset: feature1
  - match:
    - headers:
        x-custom-feature:
          exact: fix-bug
    route:
    - destination:
        host: yoo
        subset: fix-bug
  - route:
    - destination:
        host: yoo
        subset: default
  • Gateway:声明了 demo.com 域名和 80 端口提供服务,selector指定关联到部署的网关 pod 。
  • DestinationRule:同 hoo 服务
  • VirturalService:跟 hoo 服务配置不同有2点 -- gateways 和 hosts 数组。gateways 指定了上面 Gateway 声明的 name,表示该 virtualservice 关联它;hosts 指定了 demo.com 域名,表示该流量规则主要应用在 demo.com 域名的流量。

上述的配置如果运行成功,将能实现如下效果:

图片.png


ok 上述便是我这期分析的内容,希望对你有帮助,我们有缘再见。