前言
它来了它来了,它带着需求过来了,终于新需求方案评审来了,在这之前产品拉着我们看了许多需求,由于在方案评审之前需要做好方案,因此这里简单记录一下其中一个云原生需求方案可行性验证的过程。
需求
- 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,有兴趣的小伙伴可以看看。