记录一次knative中按照header tag 切分流量不生效的问题

前言

它来了它来了,它带着需求过来了,终于新需求方案评审来了,在这之前产品拉着我们看了许多需求,由于在方案评审之前需要做好方案,因此这里简单记录一下其中一个云原生需求方案可行性验证的过程。

需求

  • url传参将流量打到某一固定版本上
  • revision配置流量百分比

分析

由于我们的云原生是基于knative做的,然后之前也有看过一些书介绍过knative,因此我记得kantive是原生支持header tag 流量切分的,链接

- revisionName: tag-header-revision-1
    percent: 0
    tag: rev1 # 这里不能同时设置多个tag
- revisionName: tag-header-revision-2
    percent: 100

所以这里需要做实验验证一下,这是其一,其二是需求是一个revison可以配置多个kv对打进去,但是knative的一个traffic字段只支持一个tag,对应一个revision。所以这就遇到了一些问题。好在有大佬提醒,可以试试通过设置多个规则看看行不行,于是就开始了实验之旅。

实验

环境准备

knative 0.16,kubernetes 1.16.6,istio 0.14

按照官方文档走:

开启功能Enabling tag header based routing
kubectl patch cm config-features -n knative-serving -p '{"data":{"tag-header-based-routing":"Enabled"}}'
构建测试镜像

这里直接选择使用官方的demo

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: tag-header
  namespace: default
spec:
  template:
    metadata:
      name: tag-header-revision-1
    spec:
      containers:
      - image: naison/knative-helloworld-go:latest # 从gcr.io转存到docker仓库
        imagePullPolicy: IfNotPresent
        env:
        - name: TARGET
          value: "First Revision"
---
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: tag-header
  namespace: default
spec:
  template:
    metadata:
      name: tag-header-revision-2
    spec:
      containers:
      - image: naison/knative-helloworld-go:latest
        imagePullPolicy: IfNotPresent
        env:
        - name: TARGET
          value: "Second Revision"
  traffic:
  - revisionName: tag-header-revision-1
    percent: 0 # 这个的流量比例为0,讲道理是不会击中的
    tag: rev1  # 如果能够击中rev1,说明tag生效了
  - revisionName: tag-header-revision-2
    percent: 100 # 所有流量都走rev2
创建对应版本

直接apply上面的yaml,检查revision和pod状态:

naison@P_CAIWFENG-MB0 headertag % kubectl get pods
NAME                                                READY   STATUS    RESTARTS   AGE
tag-header-revision-1-deployment-6c674878c7-xxt8d   2/2     Running   0          20s
tag-header-revision-2-deployment-5ffd79f896-z4f7k   2/2     Running   0          21s
naison@P_CAIWFENG-MB0 headertag % kubectl get revision        
NAME                    CONFIG NAME   K8S SERVICE NAME        GENERATION   READY   REASON
tag-header-revision-1   tag-header    tag-header-revision-1   1            True    
tag-header-revision-2   tag-header    tag-header-revision-2   2            True         

可以看见都起来了

流量测试

获取路由信息:

naison@P_CAIWFENG-MB0 headertag % kubectl get routes -o yaml
···
    traffic:
    - latestRevision: false
      percent: 0
      revisionName: tag-header-revision-1
      tag: rev1
      url: http://rev1-tag-header.default.127.0.0.1.xip.io
    - latestRevision: false
      percent: 100
      revisionName: tag-header-revision-2
    url: http://tag-header.default.127.0.0.1.xip.io

记住这里的 url中的host: tag-header.default.127.0.0.1.xip.io

获取kingress信息

naison@P_CAIWFENG-MB0 headertag % kubectl get kingress -o yaml
...
    privateLoadBalancer:
      ingress:
      - domainInternal: cluster-local-gateway.istio-system.svc.cluster.local
    publicLoadBalancer:
      ingress:
      - domainInternal: istio-ingressgateway.istio-system.svc.cluster.local

这里有两个kingress地址,

流量会经过ingress,然后按照host进行转发到route,route按照路由规则,路由到具体的revision上

使用测试命令,这里的两个ingress值都可以使用

naison@P_CAIWFENG-MB0 headertag % curl istio-ingressgateway.istio-system.svc.cluster.local -H "Host:tag-header.default.127.0.0.1.xip.io" -H "Knative-Serving-Tag:rev1"
Hello Second Revison!
naison@P_CAIWFENG-MB0 headertag % curl istio-ingressgateway.istio-system.svc.cluster.local -H "Host:tag-header.default.127.0.0.1.xip.io" -H "Knative-Serving-Tag:rev2"
Hello Second Revison!
测试结果

无论怎么携带tag,都是会击中rev2,这就怪了,如果携带的tag是rev1,讲道理是应该要击中rev1的,但是却击中了rev2,多次测试结果还是一样,都是击中了rev2,这时只能得出一个结论:tag没生效

结果分析
可能的原因一:configmap修改后没有重启pod,从而使其生效

手册上说的是更改configmap名为config-features,经检查里边的值

    # Controls whether tag header based routing feature are enabled or not.
    # 1. Enabled: enabling tag header based routing
    # 2. Disabled: disabling tag header based routing
    tag-header-based-routing: "disabled"
  tag-header-based-routing: Enabled
kind: ConfigMap

是被改了,一般来说,configmap的用法就是挂进pod里边,然后pod使用这个文件或者环境变量,也有可能是knative的pod没重启,因此这里又将knative-serving 和istio-system namespace下面的所有pod重启了,结果还是不生效,然后就想着是不是这个configmap是否真的有pod在引用,然后

naison@P_CAIWFENG-MB0 headertag % kubectl get all -A -o yaml | grep config-features
naison@P_CAIWFENG-MB0 headertag % 

结果为空,说明没有给pod使用,一度以为是不是这个kantive有问题,然后反应到也有可能是在crd的接口实现里边,使用k8s client或者到这个configmap的值,然后在实现中逻辑操作,于是git clone了一个kantive-serving的代码仓库,全局搜索了一下,
在master分支,pkg/apis/config/features.go:58这里,找到如下代码:

asFlag("tag-header-based-routing", &nc.TagHeaderBasedRouting));

configmap的名称为config-features,所以文件名为config/features.go,不错不错,命名很有规律,
果然这个configmap是在接口中调用的,不是pod直接引用的,简单追了一下代码,这个值会给设置到kingress里边去。所以这里的knative是好的,没问题的。但是就是不生效,这是为啥尼?

可能的原因二:knative版本问题,0.16版本不支持此功能?

然后就想着会不会是knaive的版本问题,虽然官方文档写着这个特性是0.16及其向上都是支持的,

A Knative cluster that has an ingress controller installed with Knative version 0.16 and above.

但是我们看的文档是基于0.19的,然后就准备在环境上升级一个knative,而之前的安装方式是基于helm安装的,这里我们打算安装一个原生的knative,并且我本地就有一个0.18版本的knative(当时就没想到在本地测试一下,后边才反应过来,服了我自己),因此我可以docker save出来镜像,然后scp上去,因为环境上是无法链接gcr.io的,所以说干就干,按照kantive的手册,将yaml wget下来,更改image标签为私有仓地址,然后将本地save出来的镜像scp到服务器上,load, tag,push到私有仓库,一波操作,然后apply yaml,发现一堆knative-serving namespace的pod状态不正确,

2021-02-03 16:59:35 ~/test # kubectl get pods --namespace knative-serving
NAME                               READY   STATUS              RESTARTS   AGE
activator-6fc959b489-qs7dc         0/1     Running             1          71s
autoscaler-6f6ddf44b4-b47pw        0/1     Running             1          71s
controller-cd8b546f-f844l          0/1     CrashLoopBackOff    3          71s
default-domain-nlj7t               1/1     Running             0          23s
istio-webhook-78b95d594-nctb7      0/1     ContainerCreating   0          43s
networking-istio-8cf94dd57-q86vn   0/1     CrashLoopBackOff    2          43s
webhook-bf5cff5c7-rtcl8            0/1     CrashLoopBackOff    3          71s

然后随便log了一个,

2021-02-03 17:00:20 ~/test # kubectl logs pods/controller-cd8b546f-f844l -n knative-serving
2021/02/03 09:00:00 Registering 5 clients
2021/02/03 09:00:00 Registering 5 informer factories
2021/02/03 09:00:00 Registering 12 informers
2021/02/03 09:00:00 Registering 7 controllers
{"level":"info","ts":"2021-02-03T09:00:00.361Z","caller":"logging/config.go:110","msg":"Successfully created the logger."}
{"level":"info","ts":"2021-02-03T09:00:00.361Z","caller":"logging/config.go:111","msg":"Logging level set to: info"}
{"level":"info","ts":"2021-02-03T09:00:00.361Z","logger":"controller","caller":"profiling/server.go:59","msg":"Profiling enabled: false","commit":"db4879e","knative.dev/pod":"controller-cd8b546f-f844l"}
{"level":"fatal","ts":"2021-02-03T09:00:00.362Z","logger":"controller","caller":"sharedmain/main.go:287","msg":"Version check failed","commit":"db4879e","knative.dev/pod":"controller-cd8b546f-f844l","error":"kubernetes version \"1.16.6\" is not compatible, need at least \"1.17.0\" (this can be overridden with the env var \"KUBERNETES_MIN_VERSION\")","stacktrace":"knative.dev/pkg/injection/sharedmain.CheckK8sClientMinimumVersionOrDie\n\tknative.dev/pkg@v0.0.0-20201103163404-5514ab0c1fdf/injection/sharedmain/main.go:287\nknative.dev/pkg/injection/sharedmain.MainWithConfig\n\tknative.dev/pkg@v0.0.0-20201103163404-5514ab0c1fdf/injection/sharedmain/main.go:184\nknative.dev/pkg/injection/sharedmain.MainWithContext\n\tknative.dev/pkg@v0.0.0-20201103163404-5514ab0c1fdf/injection/sharedmain/main.go:142\nknative.dev/pkg/injection/sharedmain.Main\n\tknative.dev/pkg@v0.0.0-20201103163404-5514ab0c1fdf/injection/sharedmain/main.go:116\nmain.main\n\tknative.dev/serving/cmd/controller/main.go:45\nruntime.main\n\truntime/proc.go:203"}

提示:


kubernetes version “1.16.6” is not compatible, need at least “1.17.0” (this can be overridden with the env var “KUBERNETES_MIN_VERSION”)


0.18版本的knative和k8s 1.16.6不兼容,可以通过设置环境变量解决,但我们可是要弄生产环境的,不兼容可不行,所以这时就惊退两难了。

另辟蹊径,本地测试 - 脑瓜终于转起来了

好在脑袋转的快,我的本地不就有一个新版的knative吗,试试不久知道了,果然按照0.19的指导,重复上面的步骤,携带tag rev1是可以打到revision1的,说明header tag是生效的,并且设置多个规则的方式也可以生效,

  traffic:
  - revisionName: tag-header-revision-1
    percent: 0
    tag: rev1
  - revisionName: tag-header-revision-1 # 可设置多个tag对应一个revision
    percent: 0
    tag: rev2
  - revisionName: tag-header-revision-2
    percent: 100

也就是多个tag对应一个revision是可以生效的。到这里应该说是可以算是完结了,因为在knative0.18上是可以生效的,如果我们要使用这个功能,只需要将knative升级从0.16到0.18,或者最新的0.20,就可以解决问题了,但是我们的测试环境的k8s 1.16.6和knative0.18不兼容也是一个问题,虽然可以通过环境变量解决,不过这时想到官方文档说的0.16是可以生效的,那为啥我这里做实验就是不生效尼?

最后的倔强:看代码

既然knative0.16版本不生效,就看看这个版本的代码是不是没有用到这个tag,于是

naison@P_CAIWFENG-MB0 serving % git switch origin/release-0.16

全局搜索tagHeaderBasedRouting
在pkg/network/network.go:252这里,找到如下代码:

nc.TagHeaderBasedRouting = strings.EqualFold(data[TagHeaderBasedRoutingKey], "enabled")
TagHeaderBasedRoutingKey = "tagHeaderBasedRouting"

看着名称貌似不对,反应了2秒,他妹的,两个tag不一样,
然后就发现问题了,新版开关名称是:tagHeaderBasedRouting,但是旧版叫做tag-header-based-routing,惊呆了,同时有注意到文件名为network/network.go,貌似不是在config-features这个configmap里边,果然通过

naison@P_CAIWFENG-MB0 headertag % kubectl get configmap -A -o yaml | grep "tagHeaderBasedRouting" 
···
{"annotations":{"knative.dev/example-checksum":"6a69cdef"},"labels":{"serving.knative.dev/release":"v0.16.0"},"name":"config-network","namespace":"knative-serving"}

从一大堆输出中发现了configmap的名称为config-network,然后去看了下这个configmap

naison@P_CAIWFENG-MB0 headertag % kubectl edit configmap config-network -n knative-serving 
···
    # Controls whether tag header based routing feature are enabled or not.
    # 1. Enabled: enabling tag header based routing
    # 2. Disabled: disabling tag header based routing
    tagHeaderBasedRouting: "disabled"
  tagHeaderBasedRouting: Enabled
kind: ConfigMap

发现人家的example的里就有提示可以开启此功能,然后赶紧改了下,又按照上面的测试步骤跑了一边,果然在0.16的版本也是正确的,喜大普奔。至此,按照header tag切分流量的方案可行性在kantive0.16和0.18上就验证通过了。

后记

这一波操作还是蛮好玩的,终于是在方案评审之前验证了方案可行性,可以的,knative yes !!!

遗留问题

需求:revision需要按照并发数和cpu扩容
方案:使用knative的原生autoscale特性,文档链接

在做可行性验证的时候,遇到了一个小问题,hpa不支持concurrency扩容,kpa不支持cpu扩容,下面这两个用法,会报错
hpa示例:

annotations:
  autoscaling.knative.dev/target: "10"
  autoscaling.knative.dev/class: hpa.autoscaling.knative.dev
  autoscaling.knative.dev/metric: "concurrency"

kpa示例:

annotations:
  autoscaling.knative.dev/target: "10"
  autoscaling.knative.dev/class: kpa.autoscaling.knative.dev
  autoscaling.knative.dev/metric: "cpu"

但是反过来就是好的,根据官方文档描述:

The default KPA Autoscaler supports the concurrency and rps metrics.
The HPA Autoscaler supports the concurrency, rps and cpu metrics.

hpa是支持concurrency和cpu的,但我在knative 0.18的版本上测试结果和文档有出入,不知道是不是用错了还是文档不对,虽然极有可能是我的用法错误,但是并没有发现很明显的错误,所以暂且把这个问题记录一下,后面可以追一追knative的源码确定一下。

备注

以上的测试都是使用的apply yaml的方式,但在实际上我们是需要使用go代码实现的,因此需要将对应的yaml,使用knative client翻译一遍,我这里有个简单的demo,有兴趣的小伙伴可以看看。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值