写点什么

Knative 基本功能深入剖析:Knative Serving 的流量灰度和版本管理

  • 2019-08-12
  • 本文字数:5822 字

    阅读完需:约 19 分钟

Knative 基本功能深入剖析:Knative Serving 的流量灰度和版本管理

本篇主要介绍 Knative Serving 的流量灰度,通过一个 rest-api 的例子演示如何创建不同的 Revision、如何在不同的 Revision 之间按照流量比例灰度。

部署 rest-api v1

  • 代码


测试之前我们需要写一段  rest-api 的代码,并且还要能够区分不同的版本。下面我基于官方的例子进行了修改,为了使用方便去掉了 github.com/gorilla/mux 依赖,直接使用 Golang 系统包  net/http 替代。这段代码可以通过 RESOURCE 环境变量来区分不同的版本。


package main
import ( "fmt" "io/ioutil" "log" "net/http" "net/url" "os"
"flag")
var resource string
func main() { flag.Parse() //router := mux.NewRouter().StrictSlash(true)
resource = os.Getenv("RESOURCE") if resource == "" { resource = "NOT SPECIFIED" }
root := "/" + resource path := root + "/{stockId}"
http.HandleFunc("/", Index) http.HandleFunc(root, StockIndex) http.HandleFunc(path, StockPrice)
if err := http.ListenAndServe(fmt.Sprintf(":%s", "8080"), nil); err != nil { log.Fatalf("ListenAndServe error:%s ", err.Error()) }}
func Index(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Welcome to the %s app! \n", resource)}
func StockIndex(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "%s ticker not found!, require /%s/{ticker}\n", resource, resource)}
func StockPrice(w http.ResponseWriter, r *http.Request) { stockId := r.URL.Query().Get("stockId")
url := url.URL{ Scheme: "https", Host: "api.iextrading.com", Path: "/1.0/stock/" + stockId + "/price", }
log.Print(url)
resp, err := http.Get(url.String()) if err != nil { fmt.Fprintf(w, "%s not found for ticker : %s \n", resource, stockId) return }
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
fmt.Fprintf(w, "%s price for ticker %s is %s\n", resource, stockId, string(body))}
复制代码


  • Dockerfile


创建一个叫做 Dockerfile 的文件,把下面这些内容复制到文件中。执行 docker build --tag registry.cn-hangzhou.aliyuncs.com/knative-sample/rest-api-go:v1 --file ./Dockerfile .  命令即可完成镜像的编译。


你在测试的时候请把 registry.cn-hangzhou.aliyuncs.com/knative-sample/rest-api-go:v1 换成你自己的镜像仓库地址。


编译好镜像以后执行 docker push registry.cn-hangzhou.aliyuncs.com/knative-sample/rest-api-go:v1 把镜像推送到镜像仓库。


FROM registry.cn-hangzhou.aliyuncs.com/knative-sample/golang:1.12 as builder
WORKDIR /go/src/github.com/knative-sample/rest-api-goCOPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -v -o rest-api-goFROM registry.cn-hangzhou.aliyuncs.com/knative-sample/alpine-sh:3.9COPY --from=builder /go/src/github.com/knative-sample/rest-api-go/rest-api-go /rest-api-go
CMD ["/rest-api-go"]
复制代码


  • Service 配置


镜像已经有了,我们开始部署 Knative Service。把下面的内容保存到 revision-v1.yaml 中,然后执行 kubectl apply -f revision-v1.yaml 即可完成 Knative Service 的部署。


apiVersion: serving.knative.dev/v1alpha1kind: Servicemetadata: name: stock-service-example namespace: defaultspec: template:   metadata:     name: stock-service-example-v1   spec:     containers:     - image: registry.cn-hangzhou.aliyuncs.com/knative-sample/rest-api-go:v1       env:         - name: RESOURCE           value: v1       readinessProbe:         httpGet:           path: /         initialDelaySeconds: 0         periodSeconds: 3
复制代码


首次安装会创建出一个叫做 stock-service-example-v1 的 Revision,并且是把 100% 的流量都打到 stock-service-example-v1 上。

验证 Serving 的各个资源

如下图所示,我们先回顾一下 Serving 涉及到的各种资源。接下来我们分别看一下刚才部署的 revision-v1.yaml 各个资源配置。



  • Knative Service


kubectl get ksvc stock-service-example --output yaml
复制代码


  • Knative Configuration


kubectl get configuration -l \"serving.knative.dev/service=stock-service-example" --output yaml
复制代码


  • Knative Revision


kubectl get revision -l \"serving.knative.dev/service=stock-service-example" --output yaml
复制代码


  • Knative Route


kubectl get route -l \"serving.knative.dev/service=stock-service-example" --output yaml
复制代码

访问 rest-api 服务

我们部署的 Service 名称是: stock-service-example。访问这个 Service 需要获取 Istio Gateway 的 IP,然后使用 stock-service-example Domain 绑定 Host 的方式发起 curl 请求。为了方便测试我写成了一个脚本。创建一个 run-test.sh 文件,把下面这些内容复制到文件内,然后赋予文件可执行权限。执行执行此脚本就能得到测试结果。


#!/bin/bash
SVC_NAME="stock-service-example"export INGRESSGATEWAY=istio-ingressgatewayexport GATEWAY_IP=`kubectl get svc $INGRESSGATEWAY --namespace istio-system --output jsonpath="{.status.loadBalancer.ingress[*]['ip']}"`export DOMAIN_NAME=`kubectl get route ${SVC_NAME} --output jsonpath="{.status.url}"| awk -F/ '{print $3}'`
curl -H "Host: ${DOMAIN_NAME}" http://${GATEWAY_IP}
复制代码


测试结果:


从下面的命令输出结果可以看到现在返回的是 v1 的信息,说明请求打到 v1 上面了。


└─# ./run-test.shWelcome to the v1 app!
复制代码

灰度 50% 的流量到 v2

修改 Service 创建 v2 revision , 创建一个 revision-v2.yaml 文件,内容如下:


apiVersion: serving.knative.dev/v1alpha1kind: Servicemetadata:  name: stock-service-example  namespace: defaultspec:  template:    metadata:      name: stock-service-example-v2    spec:      containers:      - image: registry.cn-hangzhou.aliyuncs.com/knative-sample/rest-api-go:v1        env:          - name: RESOURCE            value: v2        readinessProbe:          httpGet:            path: /          initialDelaySeconds: 0          periodSeconds: 3  traffic:  - tag: v1    revisionName: stock-service-example-v1    percent: 50  - tag: v2    revisionName: stock-service-example-v2    percent: 50  - tag: latest    latestRevision: true    percent: 0
复制代码


我们对比一下 v1 版本和 v2 版本可以发现,v2 版本的 Service 中增加了 traffic: 的配置。在 traffic 中指定了每一个 Revision。 执行 kubectl apply -f revision-v2.yaml 安装 v2 版本的配置。然后执行测试脚本就能看到现在返回的结果中 v1 和 v2 基本上是各占 50% 的比例。下面这是我真实测试的结果。


└─# ./run-test.shWelcome to the v2 app!└─# ./run-test.shWelcome to the v1 app!└─# ./run-test.shWelcome to the v2 app!└─# ./run-test.shWelcome to the v1 app!
复制代码

提前验证 Revision

上面展示的 v2 的例子,在创建 v2 的时候直接就把流量分发到 v2 ,如果此时 v2 有问题就会导致有 50% 的流量异常。下面我们就展示一下如何在转发流量之前验证新的 revision 服务是否正常。我们再创建一个 v3 版本。


创建一个 revision-v3.yaml 的文件,内容如下:


apiVersion: serving.knative.dev/v1alpha1kind: Servicemetadata:  name: stock-service-example  namespace: defaultspec:  template:    metadata:      name: stock-service-example-v3    spec:      containers:      - image: registry.cn-hangzhou.aliyuncs.com/knative-sample/rest-api-go:v1        env:          - name: RESOURCE            value: v3        readinessProbe:          httpGet:            path: /          initialDelaySeconds: 0          periodSeconds: 3  traffic:  - tag: v1    revisionName: stock-service-example-v1    percent: 50  - tag: v2    revisionName: stock-service-example-v2    percent: 50  - tag: latest    latestRevision: true    percent: 0
复制代码


执行 kubectl apply -f revision-v3.yaml 部署 v3 版本。然后查看一下 Revision 情况:


└─# kubectl get revisionNAME                       SERVICE NAME               GENERATION   READY   REASONstock-service-example-v1   stock-service-example-v1   1            Truestock-service-example-v2   stock-service-example-v2   2            Truestock-service-example-v3   stock-service-example-v3   3            True
复制代码


可以看到现在已经创建出来了三个 Revision 。


此时我们再看一下 stock-service-example 的真实生效:


└─# kubectl get ksvc stock-service-example -o yamlapiVersion: serving.knative.dev/v1beta1kind: Servicemetadata:  annotations:...status:...  traffic:  - latestRevision: false    percent: 50    revisionName: stock-service-example-v1    tag: v1    url: http://v1-stock-service-example.default.example.com  - latestRevision: false    percent: 50    revisionName: stock-service-example-v2    tag: v2    url: http://v2-stock-service-example.default.example.com  - latestRevision: true    percent: 0    revisionName: stock-service-example-v3    tag: latest    url: http://latest-stock-service-example.default.example.com  url: http://stock-service-example.default.example.com
复制代码


可以看到 v3 Revision 虽然创建出来了,但是因为没有设置 traffic,所以并不会有流量转发。此时你执行多少次 ./run-test.sh 都不会得到 v3 的输出。


在 Service 的 status.traffic 配置中可以看到 latest Revision 的配置:


- latestRevision: true    percent: 0    revisionName: stock-service-example-v3    tag: latest    url: http://latest-stock-service-example.default.example.com
复制代码


每一个 Revision 都有一个自己的 URL,所以只需要基于 v3 Revision 的 URL 发起请求就能开始测试了。


我已经写好了一个测试脚本,你可以把下面这段脚本保存在 latest-run-test.sh 文件中,然后执行这个脚本就能直接发起到 latest 版本的请求:


#!/bin/bashexport INGRESSGATEWAY=istio-ingressgatewayexport GATEWAY_IP=`kubectl get svc $INGRESSGATEWAY --namespace istio-system --output jsonpath="{.status.loadBalancer.ingress[*]['ip']}"`export DOMAIN_NAME=`kubectl get route ${SVC_NAME} --output jsonpath="{.status.url}"| awk -F/ '{print $3}'`
export LAST_DOMAIN=`kubectl get ksvc stock-service-example --output jsonpath="{.status.traffic[?(@.tag=='latest')].url}"| cut -d'/' -f 3`
curl -H "Host: ${LAST_DOMAIN}" http://${GATEWAY_IP}
复制代码


测试 v3 版本如果没问题就可以把流量分发到 v3 版本了。


下面我们再创建一个文件 revision-v3-2.yaml , 内容如下:


apiVersion: serving.knative.dev/v1alpha1kind: Servicemetadata:  name: stock-service-example  namespace: defaultspec:  template:    metadata:      name: stock-service-example-v3    spec:      containers:      - image: registry.cn-hangzhou.aliyuncs.com/knative-sample/rest-api-go:v1        env:          - name: RESOURCE            value: v3        readinessProbe:          httpGet:            path: /          initialDelaySeconds: 0          periodSeconds: 3  traffic:  - tag: v1    revisionName: stock-service-example-v1    percent: 40  - tag: v2    revisionName: stock-service-example-v2    percent: 30  - tag: v3    revisionName: stock-service-example-v3    percent: 30  - tag: latest    latestRevision: true    percent: 0
复制代码


用 vimdiff 看一下 revision-v3.yaml 和 revision-v3-2.yaml 的区别:



revision-v3-2.yaml 增加了到 v3 的流量转发。此时执行 ./run-test.sh 可以看到 v1、v2 和 v3 的比例基本是:4:3:3


└─# ./run-test.shWelcome to the v1 app!└─# ./run-test.shWelcome to the v2 app!└─# ./run-test.shWelcome to the v1 app!└─# ./run-test.shWelcome to the v2 app!└─# ./run-test.shWelcome to the v3 app!... ...
复制代码

版本回滚

Knative Service 的 Revision 是不能修改的,每次 Service Spec 的更新创建的 Revision 都会保留在 kube-apiserver 中。如果应用发布到某个新版本发现有问题想要回滚到老版本的时候只需要指定相应的 Revision,然后把流量转发过去就行了。

小结

Knative Service 的灰度、回滚都是基于流量的。Workload(Pod) 是根据过来的流量自动创建出来的。所以在 Knative Serving 模型中流量是核心驱动。这和传统的应用发布、灰度模型是有区别的。


假设有一个应用 app1 ,传统的做法首先是设置应用的实例个数( Kubernetes 体系中就是 Pod ),我们假设实例个数是 10 个。如果要进行灰度发布,那么传统的做法就是先发布一个 Pod,此时 v1 和 v2 的分布方式是:v1 的 Pod 9 个,v2 的 Pod 1 个。如果要继续扩大灰度范围的话那就是 v2 的 Pod 数量变多,v1 的 Pod 数量变少,但总的 Pod 数量维持 10 个不变。


在 Knative Serving 模型中 Pod 数量永远都是根据流量自适应的,不需要提前指定。在灰度的时候只需要指定流量在不同版本之间的灰度比例即可。每一个 Revision 的实例数都是根据流量的大小自适应,不需要提前指定。


从上面的对比中可以发现 Knative Serving 模型是可以精准的控制灰度影响的范围的,保证只灰度一部分流量。而传统的模型中 Pod 灰度的比例并不能真实的代表流量的比例,是一个间接的灰度方法。


作者介绍:


冬岛,阿里云容器平台技术专家,负责阿里云容器平台Knative 相关工作。


相关文章:


《初识 Knative:跨平台的 Serverless 编排框架》


《Knative 初体验:Serving Hello World》


《Knative 初体验:Eventing Hello World》


《Knative 初体验:Build Hello World》


《Knative 初体验:CI/CD 极速入门》


《Knative 基本功能深入剖析:Knative Serving 自动扩缩容 Autoscaler》


2019-08-12 08:427349
用户头像
阿里云容器平台 ACK,企业云原生转型最佳搭档

发布了 43 篇内容, 共 23.5 次阅读, 收获喜欢 81 次。

关注

评论

发布
暂无评论
发现更多内容

通过 MSE 实现基于Apache APISIX的全链路灰度

阿里巴巴云原生

Apache 阿里云 微服务 云原生 灰度发布

在互联网+的潮流中,企业客户服务该何去何从?

Baklib

互联网+ 客户服务

数字藏品加速破圈,助力产业发现新机遇

智捷云

NFT 区块链数字藏品 数字藏品 智捷云 智捷云科技

字节跳动CVPR 2022多项目夺魁,获模拟人脑感知、长视频理解挑战双料冠军

字节跳动视频云技术团队

计算机视觉 图像处理 图片处理 视频理解 视频云

web前端技术学习完后怎么找工作呢

小谷哥

模块2作业

十分钟生成影视级室内设计效果,红星美凯龙设计云如何升级传统家居行业

阿里云弹性计算

gpu 智能家居 异构计算

如何使用IDEA数据库工具连接TDengine?

TDengine

数据库 tdengine 时序数据库

零基础小白该如何选择web前端课程呢

小谷哥

在web前端培训中怎么提升前端技术能力

小谷哥

2022年5月视频用户洞察:用户使用时长增长 平台降本增效初见成效

易观分析

视频行业

快速构建企业级应用的开发平台

力软低代码开发平台

四个简单例子教你通过用户行为记录提高用户体验

观测云

字节跳动数据质量动态探查及相关前端实现

字节跳动数据平台

字节跳动 数据监控 数据探查

算法题每日一练---第1天:猴子分香蕉

知心宝贝

算法 前端 后端 云开发 7月月更

不吹不黑JAVA Stream的collect用法与原理,远比你想象的更强大

程序员小毕

Java 程序员 程序人生 stream collect

深入揭秘 epoll 是如何实现 IO 多路复用的

C++后台开发

网络编程 epoll IO多路复用 C++后台开发 C++开发

Kafka Topic Partition Offset 这一长串都是啥?

华为云开发者联盟

后端 华为云

C 语言入门(八)

逝缘~

7月月更

Mria+RLOG新架构下的EMQX 5.0如何实现1亿MQTT连接

EMQ映云科技

物联网 IoT mnesia emqx 7月月更

WhaleDI消息队列稳定性提升实践

鲸品堂

中间件

不同学习方式的web前端程序员有什么区别

小谷哥

面试官:你确定Redis是单线程的进程吗?

Java永远的神

Java redis 程序员 架构 面试

万物皆可Cassandra:HUAWEI Tag背后的神仙数据库

华为云开发者联盟

数据库 后端

模块七

Geek_2ce415

怎么学自动化测试

和牛

测试

Vue3:显示markdown文档

空城机

markdown Vue3 7月月更

企业遇到知识管理困境该怎么办?这里有解决方案!

Baklib

Redis 持久化 - RDB 源码详细分析|保姆级别分析!全网最全

领创集团Advance Intelligence Group

持久化 rdb redis 底层原理

软件研发团队如何管理成员工时,科学分配资源?

万事ONES

零基础可以自学web前端技术吗

小谷哥

Knative 基本功能深入剖析:Knative Serving 的流量灰度和版本管理_容器_阿里云容器平台_InfoQ精选文章