k8s

Kruise Rollout+Istio全链路灰度

环境

部署两个服务 mocka 和 mockb,服务 mocka 会调用服务 mockb,服务只会保留流量中的 header my-request-id 而去除其它 header。

file

istio:1.23.2
k8s:1.26.15
kruise-rollout: 0.5.0

步骤

创建应用

apiVersion: v1
kind: Service
metadata:
  name: mocka
  labels:
    app: mocka
    service: mocka
spec:
  ports:
  - port: 8000
    name: http
  selector:
    app: mocka
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mocka-base
  labels:
    app: mocka
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mocka
  template:
    metadata:
      labels:
        app: mocka
        version: base
    spec:
      containers:
      - name: default
        image: registry.cn-beijing.aliyuncs.com/aliacs-app-catalog/go-http-sample:1.0
        imagePullPolicy: Always
        env:
        - name: version
          value: base
        - name: app
          value: mocka
        - name: upstream_url
          value: "http://mockb:8000/"
        ports:
        - containerPort: 8000
---
apiVersion: v1
kind: Service
metadata:
  name: mockb
  labels:
    app: mockb
    service: mockb
spec:
  ports:
  - port: 8000
    name: http
  selector:
    app: mockb
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mockb-base
  labels:
    app: mockb
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mockb
  template:
    metadata:
      labels:
        app: mockb
        version: base
    spec:
      containers:
      - name: default
        image: registry.cn-beijing.aliyuncs.com/aliacs-app-catalog/go-http-sample:1.0
        imagePullPolicy: Always
        env:
        - name: version
          value: base
        - name: app
          value: mockb
        ports:
        - containerPort: 8000

file

创建gateway

apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: sample-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
    - hosts:
        - "*"
      port:
        name: http
        number: 80
        protocol: HTTP

创建VirtualService

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: vs-mocka
spec:
  gateways:
  - sample-gateway
  hosts:
    - "*"
  http:
  - route:
      - destination:
          host: mocka
          subset: version-base
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: vs-mockb
spec:
  hosts:
    - mockb
  http:
    - route:
        - destination:
            host: mockb
            subset: version-base

创建DestinationRule

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: dr-mocka
spec:
  host: mocka
  trafficPolicy:
    loadBalancer:
      simple: ROUND_ROBIN
  subsets:
    - labels:
        version: base
      name: version-base
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: dr-mockb
spec:
  host: mockb
  trafficPolicy:
    loadBalancer:
      simple: ROUND_ROBIN
  subsets:
    - labels:
        version: base
      name: version-base

file

访问链路如下:
file

测试流量访问

kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].nodePort}'
kubectl get po -l istio=ingressgateway -n istio-system -o jsonpath='{.items[0].status.hostIP}'
curl http://GATEWAY_IP:PORT

file

配置Rollout

Rollout 以及 TrafficRouting 配置的策略如下:

  • 添加了匹配 my-request-id=canary 的 header 规则,包含指定 header 的流量会走灰度环境
  • 为发布的 pod 添加了 label istio.service.tag=gray 以及 version=canary

为什么要添加这两个label?

  • 在配置中为新版本 pod 打上了两个 label,其中 istio.service.tag=gray 的目的是为了在 DestinationRule 中指定包含该 label 的 pod 作为一个 subset,lua 脚本会为 DestinationRule 自动添加该 Subset。
  • 添加 version=canary 的目的是为了覆盖原始版本中的 version=baselabel,如果不覆盖该 label,原始 DestinationRule 也会将稳定版本的流量导入新版本 pod 中。
apiVersion: rollouts.kruise.io/v1alpha1
kind: Rollout
metadata:
  name: rollouts-a
  annotations:
    rollouts.kruise.io/rolling-style: canary
    rollouts.kruise.io/trafficrouting: mocka-tr
spec:
  objectRef:
    workloadRef:
      apiVersion: apps/v1
      kind: Deployment
      name: mocka-base
  strategy:
    canary:
      steps:
      - replicas: 1
        pause: {}
      patchPodTemplateMetadata:
        labels:
          istio.service.tag: gray
          version: canary
---
apiVersion: rollouts.kruise.io/v1alpha1
kind: Rollout
metadata:
  name: rollouts-b
  annotations:
    rollouts.kruise.io/rolling-style: canary
    rollouts.kruise.io/trafficrouting: mockb-tr
spec:
  objectRef:
    workloadRef:
      apiVersion: apps/v1
      kind: Deployment
      name: mockb-base
  strategy:
    canary:
      steps:
      - replicas: 1
        pause: {}
      patchPodTemplateMetadata:
        labels:
          istio.service.tag: gray
          version: canary

配置TrafficRouting

apiVersion: rollouts.kruise.io/v1alpha1
kind: TrafficRouting
metadata:
  name: mocka-tr
spec:
  strategy:
    matches:
      - headers:
        - type: Exact
          name: my-request-id
          value: canary
  objectRef:
  - service: mocka
    customNetworkRefs:
    - apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      name: vs-mocka
    - apiVersion: networking.istio.io/v1beta1
      kind: DestinationRule
      name: dr-mocka
---
apiVersion: rollouts.kruise.io/v1alpha1
kind: TrafficRouting
metadata:
  name: mockb-tr
spec:
  strategy:
    matches:
      - headers:
        - type: Exact
          name: my-request-id
          value: canary
  objectRef:
  - service: mockb
    customNetworkRefs:
    - apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      name: vs-mockb
    - apiVersion: networking.istio.io/v1beta1
      kind: DestinationRule
      name: dr-mockb

file

灰度发布

修改 mocka 和 mockb 中的环境变量为 version=canary 开始发布。Rollout自动创建了两个新的灰度deployment。

file

此时查看 VirtualService,lua脚本自动添加了将带有 my-request-id=canary 的 header 的流量路由至 subset=canary 版本。

file

file

查看 DestinationRule,lua脚本自动添加了对于包含 label istio.service.tag=gray 新的 subset。

file

file

再次测试流量访问

curl http://GATEWAY_IP:PORT -H "my-request-id:canary"

file

可以看到所有流量均通过 canary 版本服务。

pod日志如下,可以看到透传了header my-request-id: canary

file

流量路径如图所示:

file

使用EnvoyFilter进行流量染色

要想实现全链路灰度有一个核心就是header透传。除了上面使用VirtualService+DestinationRule的方法外,还可以使用EnvoyFilter在入口网关进行染色。下面的 EnvoyFilter 定义了一个 Lua 脚本,会对包含 agent=pc 的流量染色,为其添加 my-request-id=canary,而其它流量则添加 my-request-id=base

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: http-request-labelling-according-source
  namespace: istio-system
spec:
  workloadSelector:
    labels:
      app: istio-ingressgateway
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: GATEWAY
      listener:
        filterChain:
          filter:
            name: "envoy.filters.network.http_connection_manager"
            subFilter:
              name: "envoy.filters.http.router"
    patch:
      operation: INSERT_BEFORE
      value:
       name: envoy.lua
       typed_config:
         "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
         inlineCode: |
           function envoy_on_request(request_handle)
           local header = "agent"
            headers = request_handle:headers()
             version = headers:get(header)
             if (version ~= nil) then
              if (version == "pc") then
                headers:add("my-request-id","canary")
              else
                headers:add("my-request-id","base")
              end
             else
              headers:add("my-request-id","base")
             end
           end

测试流量访问:

curl http://GATEWAY_IP:PORT -H "agent:pc"

file

lua脚本解释

envoy.lua

https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter

  • envoy_on_request是 Envoy 用来处理请求的生命周期钩子函数之一。它在每个请求到达时被调用。
  • request_handle 是一个对象,允许访问请求的 header 和其他上下文信息。
  • 定义了 header = "agent"
  • 调用 request_handle:headers() 方法获取请求的 header 集合,并将其赋值给变量 headers。这样就可以访问请求中的所有 header。
  • 从 headers 表中获取名为 "agent" 的 header 的值,并将其赋值给变量 version。如果请求中没有 "agent" header,那么 version 的将为 nil。

DestinationRule/trafficRouting.lua

https://github.com/openkruise/rollouts/blob/master/lua_configuration/networking.istio.io/DestinationRule/trafficRouting.lua

上面发布灰度版本后,dr和vs都会被lua脚本自动修改掉。就是这个脚本。

local spec = obj.data.spec
local canary = {}
canary.labels = {}
canary.name = "canary"
local podLabelKey = "istio.service.tag"
canary.labels[podLabelKey] = "gray"
table.insert(spec.subsets, canary)
return obj.data
  • obj.data 这里就是 DestinationRule 中获取 spec 字段,并将其赋值给局部变量 spec。
  • 定义一个名为 canary 的表,canary.labels 是一个子表,用于存储标签,为该子集设置名称 "canary"。
  • 定义一个变量 podLabelKey,值为 "istio.service.tag",将标签键值对 istio.service.tag: gray 添加到 canary.labels中,表示灰度版本。
  • 将 canary 表插入到 spec.subsets 列表中。实现了向 spec.subsets 动态添加一个带有灰度标签的新子集。
  • 返回修改后的 obj.data,包含了新增的 canary 子集。这样修改后的数据可以用于更新资源配置,使灰度路由规则生效。

VirtualService/trafficRouting.lua

https://github.com/openkruise/rollouts/blob/master/lua_configuration/networking.istio.io/VirtualService/trafficRouting.lua

代码可以动态生成和修改 VirtualService,以支持金丝雀发布策略。它通过检查服务的权重、匹配条件和请求头修改器,来决定如何将流量路由到不同的服务版本(稳定版本或金丝雀版本)。

  • 如果 obj.canaryWeight 为 -1,则将其设置为 100,并将 obj.stableWeight 设置为 0。这意味着如果没有指定金丝雀权重,则默认将所有流量引导到金丝雀版本。
  • GetHost(destination):从目标的 host 字段中提取主机名,去掉域名部分。
  • GetRulesToPatch(spec, stableService, protocol):查找与稳定服务匹配的路由规则。
  • CalculateWeight(route, stableWeight, n):计算每个路由的权重。如果路由已经有权重,则根据稳定权重进行计算;否则,均匀分配稳定权重。
  • GenerateRoutesWithMatches(spec, matches, stableService, canaryService, requestHeaderModifier):生成带有匹配条件的路由,并将其插入到 spec.http 的开头。
  • GenerateRoutes(spec, stableService, canaryService, stableWeight, canaryWeight, protocol):生成不带匹配条件的路由。查找与稳定服务匹配的规则,并为每个规则添加金丝雀路由。
  • 主逻辑:根据 obj.matches 的存在与否,调用相应的路由生成函数。如果存在匹配条件,则调用 GenerateRoutesWithMatches;否则,调用 GenerateRoutes 为 HTTP、TCP 和 TLS 协议生成路由。
分类: k8s
0 0 投票数
文章评分
订阅评论
提醒
guest

0 评论
最旧
最新 最多投票
内联反馈
查看所有评论

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部
0
希望看到您的想法,请您发表评论x