Knative 之流量灰度和版本管理

本文涉及的产品
Serverless 应用引擎 SAE,800核*时 1600GiB*时
注册配置 MSE Nacos/ZooKeeper,118元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 本篇主要介绍 Knative Serving 的流量灰度,通过一个 rest-api 的例子演示如何创建多个 Revision、并在不同的 Revision 之间按照流量比例灰度。

本文选自《Knative 云原生应用开发指南》
knative海报.png

本篇主要介绍 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/v1
kind: Service
metadata:
 name: traffic-example
 namespace: default
spec:
 template:
   metadata:
     name: traffic-example-v1
   spec:
     containers:
     - image: registry.cn-hangzhou.aliyuncs.com/knative-sample/rest-api-go:v1
       env:
         - name: RESOURCE
           value: v1
       readinessProbe:
         httpGet:
           path: /

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

验证 Serving 的各个资源

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

1.png

  • Knative Service
kubectl get ksvc traffic-example --output yaml
  • Knative Configuration
kubectl get configuration -l \
"serving.knative.dev/service=traffic-example" --output yaml
  • Knative Revision
kubectl get revision -l \
"serving.knative.dev/service=traffic-example" --output yaml
  • Knative Route
kubectl get route -l \
"serving.knative.dev/service=traffic-example" --output yaml

访问 rest-api 服务

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

#!/bin/bash
#****************************************************************#
# Create Date: 2019-11-06 14:38
#********************************* ******************************#

SVC_NAME="traffic-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!

灰度 20% 的流量到 v2

创建 v2 revision revision-v2.yaml 文件,内容如下:

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: traffic-example
  namespace: default
spec:
  template:
    metadata:
      name: traffic-example-v2
    spec:
      containers:
      - image: registry.cn-hangzhou.aliyuncs.com/knative-sample/rest-api-go:v1
        env:
          - name: RESOURCE
            value: v2
        readinessProbe:
          httpGet:
            path: /
  traffic:
  - tag: v1
    revisionName: traffic-example-v1
    percent: 80
  - tag: v2
    revisionName: traffic-example-v2
    percent: 20
  - tag: latest
    latestRevision: true
    percent: 0

我们对比一下 v1 版本和 v2 版本可以发现,v2 版本的 Service 中增加了 traffic: 的配置。在 traffic 中指定了每一个 Revision。

2.png

执行 kubectl apply -f revision-v2.yaml 安装 v2 版本的配置。然后多次执行for ((i=1; i<=10; i++)); do ./run-test.sh; done这条命令就能看到现在返回的结果中 v1 和 v2 的比例基本是 8:2  的比例。下面这是我真实测试的结果。

└─# for ((i=1; i<=10; i++)); do ./run-test.sh; done
Welcome to the v1 app!
Welcome to the v2 app!
Welcome to the v1 app!
Welcome to the v1 app!
Welcome to the v1 app!
Welcome to the v1 app!
Welcome to the v2 app!
Welcome to the v1 app!
Welcome to the v2 app!
Welcome to the v2 app!

提前验证 Revision

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

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

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: traffic-example
  namespace: default
spec:
  template:
    metadata:
      name: traffic-example-v3
    spec:
      containers:
      - image: registry.cn-hangzhou.aliyuncs.com/knative-sample/rest-api-go:v1
        env:
          - name: RESOURCE
            value: v2
        readinessProbe:
          httpGet:
            path: /
  traffic:
  - tag: v1
    revisionName: traffic-example-v1
    percent: 80
  - tag: v2
    revisionName: traffic-example-v2
    percent: 20
  - tag: latest
    latestRevision: true
    percent: 0

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

└─# kubectl get revision -l "serving.knative.dev/service=traffic-example"
NAME                 CONFIG NAME       K8S SERVICE NAME     GENERATION   READY   REASON
traffic-example-v1   traffic-example   traffic-example-v1   1            True
traffic-example-v2   traffic-example   traffic-example-v2   2            True
traffic-example-v3   traffic-example   traffic-example-v3   3            True

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

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

└─# kubectl get ksvc traffic-example -o yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  annotations:
...
status:
...
  
  traffic:
  - latestRevision: false
    percent: 80
    revisionName: traffic-example-v1
    tag: v1
    url: http://v1-traffic-example.default.knative.kuberun.com
  - latestRevision: false
    percent: 20
    revisionName: traffic-example-v2
    tag: v2
    url: http://v2-traffic-example.default.knative.kuberun.com
  - latestRevision: true
    percent: 0
    revisionName: traffic-example-v3
    tag: latest
    url: http://latest-traffic-example.default.knative.kuberun.com
  url: http://traffic-example.default.knative.kuberun.com

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

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

- latestRevision: true
    percent: 0
    revisionName: traffic-example-v3
    tag: latest
    url: http://latest-traffic-example.default.knative.kuberun.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 traffic-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/v1
kind: Service
metadata:
  name: traffic-example
  namespace: default
spec:
  template:
    metadata:
      name: traffic-example-v3
    spec:
      containers:
      - image: registry.cn-hangzhou.aliyuncs.com/knative-sample/rest-api-go:v1
        env:
          - name: RESOURCE
            value: v3
        readinessProbe:
          httpGet:
            path: /
  traffic:
  - tag: v1
    revisionName: traffic-example-v1
    percent: 80
  - tag: v2
    revisionName: traffic-example-v2
    percent: 10
  - tag: v3
    revisionName: traffic-example-v3
    percent: 10
  - tag: latest
    latestRevision: true
    percent: 0

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

3.png

revision-v3-2.yaml 增加了到 v3 的流量转发。此时执行 for ((i=1; i<=10; i++)); do ./run-test.sh; done 可以看到 v1、v2 和 v3 的比例基本是:8:1:1

└─# for ((i=1; i<=10; i++)); do ./run-test.sh; done
Welcome to the v3 app!
Welcome to the v1 app!
Welcome to the v1 app!
Welcome to the v1 app!
Welcome to the v2 app!
Welcome to the v1 app!
Welcome to the v1 app!
Welcome to the v1 app!
Welcome to the v1 app!
Welcome to the v1 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 灰度的比例并不能真实的代表流量的比例,是一个间接的灰度方法。

阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的技术圈。”

相关文章
|
测试技术 微服务 负载均衡
微服务部署:蓝绿部署、滚动部署、灰度发布、金丝雀发布
在项目迭代的过程中,不可避免需要”上线“。上线对应着部署,或者重新部署;部署对应着修改;修改则意味着风险。 目前有很多用于部署的技术,有的简单,有的复杂;有的得停机,有的不需要停机即可完成部署。
2847 0
|
11月前
|
Kubernetes 测试技术 微服务
Kruise Rollout 全链路灰度实践
OpenKruise 是一个基于 Kubernetes 的扩展套件,主要聚焦于云原生应用的自动化,比如部署、发布、运维以及可用性防护。本文介绍通过 OpenKruise 构建自动化运维的方式实现基于 Istio 的全链路灰度功能。
|
Cloud Native 测试技术 开发工具
通过 OpenKruise 实现基于 Higress 的全链路灰度
通过 OpenKruise 实现基于 Higress 的全链路灰度
|
Kubernetes Cloud Native Java
灰度发布、蓝绿部署、金丝雀都是啥?
在滚动部署中,应用的新版本逐步替换旧版本。实际的部署发生在一段时间内。在此期间,新旧版本会共存,而不会影响功能和用户体验。这个过程可以更轻易的回滚和旧组件不兼容的任何新组件。
灰度发布、蓝绿部署、金丝雀都是啥?
|
弹性计算 Kubernetes 测试技术
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
453 0
|
Kubernetes 测试技术 UED
灰度(金丝雀)发布、蓝绿部署、滚动发布
灰度(金丝雀)发布、蓝绿部署、滚动发布
|
Kubernetes Cloud Native 前端开发
构建基于 Ingress 的全链路灰度能力
微服务架构下,有一些需求开发,涉及到微服务调用链路上的多个微服务同时发生了改动,通常每个微服务都会有灰度环境或分组来接受灰度流量,我们希望通过进入上游灰度环境的流量,也能进入下游灰度的环境中,确保1个请求始终在灰度环境中传递,即使这个调用链路上有一些微服务没有灰度环境,这些应用请求下游的时候依然能够回到灰度环境中。通过 MSE 提供的全链路灰度能力,可以在不需要修改任何您的业务代码的情况下,能够轻松实现上述能力。
构建基于 Ingress 的全链路灰度能力
|
测试技术 Serverless vr&ar
Knative 灰度发布和自动弹性|学习笔记(一)
快速学习 Knative 灰度发布和自动弹性
143 0
Knative 灰度发布和自动弹性|学习笔记(一)
|
测试技术 Serverless vr&ar
Knative 灰度发布和自动弹性|学习笔记(二)
快速学习Knative 灰度发布和自动弹性
157 0
Knative 灰度发布和自动弹性|学习笔记(二)
|
Java 中间件 测试技术
全链路灰度新功能:MSE上线配置标签推送
微服务场景下,全链路灰度作为一种低成本的新功能验证方式,得到了越来越广泛的应用。除了微服务实例和流量的灰度,微服务应用中的配置项也应该具备相应的灰度能力,以应对灰度应用对特殊配置的诉求。
全链路灰度新功能:MSE上线配置标签推送