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

本文涉及的产品
任务调度 XXL-JOB 版免费试用,400 元额度,开发版规格
函数计算FC,每月15万CU 3个月
Serverless 应用引擎免费试用套餐包,4320000 CU,有效期3个月
简介: 本篇主要介绍 Knative Serving 的流量灰度,通过一个 rest-api 的例子演示如何创建不同的 Revision、如何在不同的 Revision 之间按照流量比例灰度。

作者|冬岛 阿里云技术专家

本篇主要介绍 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 灰度的比例并不能真实的代表流量的比例,是一个间接的灰度方法。

相关文章
|
JSON Kubernetes 数据格式
K8S client-go Patch example
我在本文中主要会介绍使用client-go的Patch方式,主要包括strategic merge patch和json-patch
|
容器 Kubernetes API
深入解析 Kubebuilder:让编写 CRD 变得更简单
作者 | 刘洋(炎寻) 阿里云高级开发工程师 导读:自定义资源 CRD(Custom Resource Definition)可以扩展 Kubernetes API,掌握 CRD 是成为 Kubernetes 高级玩家的必备技能,本文将介绍 CRD 和 Controller 的概念,并对 CRD 编写框架 Kubebuilder 进行深入分析,让您真正理解并能快速开发 CRD。
12691 3
|
11月前
|
数据可视化 BI 项目管理
还在手忙脚乱?快用看板可视化工具打造你的高效工作流
看板可视化工具源自生产线管理,现广泛应用于项目管理,通过卡片和泳道设计直观展示任务状态,提升团队协作效率。文章介绍了5款热门工具:板栗看板、Trello、Asana、Monday.com和Jira,分析了各自特点及适用场景,帮助团队选择合适的工具,实现高效工作流。
219 3
|
安全 前端开发 网络安全
网络空间安全之一个WH的超前沿全栈技术深入学习之路(2-1):渗透测试行业术语扫盲)作者——LJS
网络空间安全之一个WH的超前沿全栈技术深入学习之路(2-1):渗透测试行业术语扫盲)作者——LJS
|
前端开发 JavaScript 机器人
从零开始构建一个聊天机器人
【8月更文挑战第7天】构建聊天机器人是一个涉及多个步骤和技术的复杂过程。从前期准备到实际部署,每一步都需要仔细规划和执行。通过不断学习和实践,你可以逐步掌握构建聊天机器人的技巧和方法,为企业创造更大的价值。
|
JSON Kubernetes 负载均衡
第一次看 config dump
前言各位,知道的越多,就越会发现自己的无知。在面对服务网格这样的新兴概念之时,就更是如此了。回想昨日,满头大汗地研究VirtualService和DestinationRule是干什么用的自己仿佛还近在眼前。然而,在搞明白了服务网格的基本概念之后,我却发现自己甚至坠落进更大的疑惑之中了。如果你看过了一些istio的基本知识与概念,你应该知道istio为每个数据面的Pod都注入了一个Sidecar,
第一次看 config dump
|
物联网 开发者
NB-IoT 中 PTW 和 eDRX 周期配置 | 学习笔记
快速学习 NB-IoT 中 PTW 和 eDRX 周期配置
NB-IoT 中 PTW 和 eDRX 周期配置 | 学习笔记
|
JSON 监控 Serverless
函数计算操作报错合集之执行提示Unrecognized option 'preset' ,该怎么办
在使用函数计算服务(如阿里云函数计算)时,用户可能会遇到多种错误场景。以下是一些常见的操作报错及其可能的原因和解决方法,包括但不限于:1. 函数部署失败、2. 函数执行超时、3. 资源不足错误、4. 权限与访问错误、5. 依赖问题、6. 网络配置错误、7. 触发器配置错误、8. 日志与监控问题。
311 0
|
计算机视觉
OpenCV(二十八):连通域分割
OpenCV(二十八):连通域分割
727 0
|
关系型数据库 MySQL 数据安全/隐私保护
64位 windows10,MYSQL8.0.13重置密码(忘记密码或者无法登录)
原文:64位 windows10,MYSQL8.0.13重置密码(忘记密码或者无法登录) 上一节的MySQL的配置安装里,并没有用到配置文件my.ini。那在MYSQL8.0.13如何解决密码重置问题呢。
5701 0