文档章节

Knative Service 之流量灰度和版本管理

阿里云官方博客
 阿里云官方博客
发布于 07/22 11:09
字数 2238
阅读 9
收藏 0

本篇主要介绍 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-go
COPY . .

RUN CGO_ENABLED=0 GOOS=linux go build -v -o rest-api-go
FROM registry.cn-hangzhou.aliyuncs.com/knative-sample/alpine-sh:3.9
COPY --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/v1alpha1
kind: Service
metadata:
 name: stock-service-example
 namespace: default
spec:
 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-ingressgateway
export 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.sh
Welcome to the v1 app!

灰度 50% 的流量到 v2

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

apiVersion: serving.knative.dev/v1alpha1
kind: Service
metadata:
  name: stock-service-example
  namespace: default
spec:
  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.sh
Welcome to the v2 app!
└─# ./run-test.sh
Welcome to the v1 app!
└─# ./run-test.sh
Welcome to the v2 app!
└─# ./run-test.sh
Welcome to the v1 app!

提前验证 Revision

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

apiVersion: serving.knative.dev/v1alpha1
kind: Service
metadata:
  name: stock-service-example
  namespace: default
spec:
  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 revision
NAME                       SERVICE NAME               GENERATION   READY   REASON
stock-service-example-v1   stock-service-example-v1   1            True
stock-service-example-v2   stock-service-example-v2   2            True
stock-service-example-v3   stock-service-example-v3   3            True

可以看到现在已经创建出来了三个 Revision 。
此时我们再看一下 stock-service-example 的真实生效:

└─# kubectl get ksvc stock-service-example -o yaml
apiVersion: serving.knative.dev/v1beta1
kind: Service
metadata:
  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/bash
export INGRESSGATEWAY=istio-ingressgateway
export 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/v1alpha1
kind: Service
metadata:
  name: stock-service-example
  namespace: default
spec:
  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.sh
Welcome to the v1 app!
└─# ./run-test.sh
Welcome to the v2 app!
└─# ./run-test.sh
Welcome to the v1 app!
└─# ./run-test.sh
Welcome to the v2 app!
└─# ./run-test.sh
Welcome 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 灰度的比例并不能真实的代表流量的比例,是一个间接的灰度方法。


原文链接
本文为云栖社区原创内容,未经允许不得转载。

© 著作权归作者所有

阿里云官方博客
粉丝 186
博文 1881
码字总数 4607581
作品 0
杭州
程序员
私信 提问
Knative:重新定义 Serverless | GIAC 实录

Knative 是Google 发起的 Serverless 项目,希望通过提供一套简单易用的 Serverless 开源方案,将 Serverless 标准化。 本文根据敖小剑在 2018 年上海 GIAC 演讲内容整理,文末有PPT获取地址...

s花苞酱
01/02
0
0
Knative-开源的Serverless架构方案

Knative(发音为 kay-nay-tiv)是谷歌开源的一套 Serverless 架构方案,它扩展了 Kubernetes,提供了一组中间件,提高了构建可在本地、云和第三方数据中心等地方运行的现代化、以源为中心且基...

openthings
05/13
163
0
分享 KubeCon 2019 (上海)关于 Serverless 及 Knative 相关演讲会议

有幸参加了 KubeCon 2019 上海大会,并参加了 Knative 及 Serverless 相关的几场分享会,收获满满。这里简单介绍一下各个演讲主题的主要内容。详细的演讲主题文档可以在KubeCon官方获取:htt...

元毅
06/26
0
0
国内在Minikube上搭建Knative及示例演示

什么是Serverless?什么是Mnative? 什么是 Severless, 下面是 CNCF 对 Serverless 架构给出的定义: “Serverless computing refers to the concept of building and running applications t......

xiaomin0322
07/11
21
0
阿里云Kubernetes容器服务上体验Knative

概述 Knative Serving是一种可缩放至零、请求驱动的计算运行环境,构建在 Kubernetes 和 Istio 之上,支持为 serverless 应用、函数提供部署与服务。Knative Serving的目标是为Kubernetes提供...

阿里云云栖社区
01/31
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Taro 兼容 h5 踩坑指南

最近一周在做 Taro 适配 h5 端,过程中改改补补,好不酸爽。 本文记录📝遇到的问题,希望为有相同需求的哥们👬节约点时间。 Taro 版本:1.3.9。 解决跨域问题 h5 发请求会报跨域问题,需...

dkvirus
47分钟前
3
0
Spring boot 静态资源访问

0. 两个配置 spring.mvc.static-path-patternspring.resources.static-locations 1. application中需要先行的两个配置项 1.1 spring.mvc.static-path-pattern 这个配置项是告诉springboo......

moon888
今天
2
0
hash slot(虚拟桶)

在分布式集群中,如何保证相同请求落到相同的机器上,并且后面的集群机器可以尽可能的均分请求,并且当扩容或down机的情况下能对原有集群影响最小。 round robin算法:是把数据mod后直接映射...

李朝强
今天
4
0
Kafka 原理和实战

本文首发于 vivo互联网技术 微信公众号 https://mp.weixin.qq.com/s/bV8AhqAjQp4a_iXRfobkCQ 作者简介:郑志彬,毕业于华南理工大学计算机科学与技术(双语班)。先后从事过电子商务、开放平...

vivo互联网技术
今天
19
0
java数据类型

基本类型: 整型:Byte,short,int,long 浮点型:float,double 字符型:char 布尔型:boolean 引用类型: 类类型: 接口类型: 数组类型: Byte 1字节 八位 -128 -------- 127 short 2字节...

audience_1
今天
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部