免费注册!6月19-20日,「亚马逊云科技中国峰会」重磅来袭! 了解详情
写点什么

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:427449
用户头像
阿里云容器平台 ACK,企业云原生转型最佳搭档

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

关注

评论

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

架构实战营 - 架构设计文档模板

华仔

架构设计 文档模板

源中瑞智慧党建系统,打造党建管理系模式

13530558032

LeetCode题解:73. 矩阵置零,栈,JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

游戏测试-战斗内核的自动化测试

行者AI

人工智能

手把手教你写一个经典躲避游戏

HZFEStudio

大前端 游戏 canvas

RocketMQ NameServer工作原理与源码解析

HByte

RocketMQ

Coinbase上市意味着什么?

猫Buboo

马斯克的狗狗币实验

猫Buboo

身边的最优化问题

PeterOne

人工智能 算法

编程实战:如何管理代码里的常量

华为云开发者联盟

常量 代码 web开发 final常量类 复用常量

量化策略系统搭建,马丁策略交易

如何推进一个“以行动为导向的”事件驱动架构?

VoltDB

数据库 数据分析 实时数据分析 事件驱动架构

区块链电子签章应用平台--助推企业数字化转型升级

13530558032

Leveldb解读之四:Compaction

Jowin

leveldb

合约交易所跟单,合约跟单社区开发

区块链食品溯源--为食品溯源认证

13530558032

Python基础语法和数据类型最全总结

华为云开发者联盟

Python

浅谈BSS3.0产品“守成”之策下 • 运维提升篇

鲸品堂

软件 运维 提升

亲爱的开发者,您收到一个启动智能世界的魔方

白洞计划

40个问题让你快速掌握Java多线程的精髓

华为云开发者联盟

Java 多线程 synchronized Thread Semaphore

面对大规模 K8s 集群,如何先于用户发现问题?

阿里巴巴云原生

容器 云原生 k8s 监控 调度

几个快速提升工作效率的小工具(Listary等)分享一下(强烈建议收藏)

码农飞哥

2021最全网页视频下载攻略 【建议收藏】

科技猫

软件 工具 网站 分享 视频处理

B站HR炫耀资产、贬低面试者?无独有偶,校招污点事件记录帮你避雷

北游学Java

Java 面试

使用 rocketmq-spring-boot-starter 来配置、发送和消费 RocketMQ 消息

阿里巴巴云原生

容器 开发者 云原生 中间件 API

What's new in dubbo-go v1.5.6

阿里巴巴云原生

开发者 云原生 dubbo 中间件 Go 语言

Dubbo 入门简介

青年IT男

dubbo

扩展 GRTN:云原生趋势下的 RTC 架构演进

阿里云CloudImagine

阿里云 云原生 RTC SRS流媒体服务器

Pytorch数据加载

Sakura

4月日更

用C++ 和OpenCV 实现视频目标检测(YOLOv4模型)

博文视点Broadview

ISO/IEC 5055:软件代码质量的标尺

华为云开发者联盟

软件 代码质量 源代码 ISO/IEC 软件质量模型

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