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

本篇主要介绍 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", ""), 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= 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:
periodSeconds:

首次安装会创建出一个叫做 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:
periodSeconds:
traffic:
- tag: v1
revisionName: stock-service-example-v1
percent:
- tag: v2
revisionName: stock-service-example-v2
percent:
- tag: latest
latestRevision: true
percent:

我们对比一下 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:
periodSeconds:
traffic:
- tag: v1
revisionName: stock-service-example-v1
percent:
- tag: v2
revisionName: stock-service-example-v2
percent:
- tag: latest
latestRevision: true
percent:

执行 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 True
stock-service-example-v2 stock-service-example-v2 True
stock-service-example-v3 stock-service-example-v3 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:
revisionName: stock-service-example-v1
tag: v1
url: http://v1-stock-service-example.default.example.com
- latestRevision: false
percent:
revisionName: stock-service-example-v2
tag: v2
url: http://v2-stock-service-example.default.example.com
- latestRevision: true
percent:
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:
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 ` 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:
periodSeconds:
traffic:
- tag: v1
revisionName: stock-service-example-v1
percent:
- tag: v2
revisionName: stock-service-example-v2
percent:
- tag: v3
revisionName: stock-service-example-v3
percent:
- tag: latest
latestRevision: true
percent:

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

Knative 基本功能深入剖析:Knative Serving 的流量灰度和版本管理的更多相关文章

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

    Knative Serving 默认情况下,提供了开箱即用的快速.基于请求的自动扩缩容功能 - Knative Pod Autoscaler(KPA).下面带你体验如何在 Knative 中玩转 Au ...

  2. Knative 基本功能深入剖析:Knative Serving 之服务路由管理

    导读:本文主要围绕 Knative Service 域名展开,介绍了 Knative Service 的路由管理.文章首先介绍了如何修改默认主域名,紧接着深入一层介绍了如何添加自定义域名以及如何根据 ...

  3. Knative 基本功能深入剖析:Knative Eventing 之 Sequence 介绍

    作者 | 元毅,阿里云容器平台高级开发工程师,负责阿里云容器平台 Knative 相关工作. 导读:在实际的开发中我们经常会遇到将一条数据需要经过多次处理的场景,称为 Pipeline.那么在 Kna ...

  4. Knative 实战:如何在 Knative 中配置自定义域名及路由规则

    作者 | 元毅 阿里云智能事业群高级开发工程师 当前 Knative 中默认支持是基于域名的转发,可以通过域名模板配置后缀,但目前对于用户来说并不能指定全域名设置.另外一个问题就是基于 Path 和 ...

  5. ftp功能深度剖析 + 线程 031

    一 打印进度条 import time for i in range(20): # \r 回到行首打印内容 如果有同一行内容,那么就被抹掉了 n = '>'* i print('\r%s'%n, ...

  6. 云原生生态周报 Vol. 13 | Forrester 发布企业级容器平台报告

    业界要闻 近日,全球知名市场调研机构 Forrester 发布首个企业级公共云容器平台报告.其中,阿里云容器服务的市场表现全球前三.中国第一,同时创造中国企业最好成绩,进入强劲表现者象限.报告显示,阿 ...

  7. Knative Serverless 之道:如何 0 运维、低成本实现应用托管?

    作者 | 牛秋霖(冬岛)  阿里云容器平台技术专家 关注"阿里巴巴云原生"公众号,回复关键词"1205"即可观看 Knative-Demo 演示视频. 导读:S ...

  8. 开放下载 | 《Knative 云原生应用开发指南》开启云原生时代 Serverless 之门

    点击下载<Knative 云原生应用开发指南> 自 2018 年 Knative 项目开源后,就得到了广大开发者的密切关注.Knative 在 Kubernetes 之上提供了一套完整的应 ...

  9. 初识 Knative: 跨平台的 Serverless 编排框架

    Knative 是什么 Knative 是 Google 在 2018 的 Google Cloud Next 大会上发布的一款基于 Kubernetes 的 Serverless 框架.Knativ ...

随机推荐

  1. oracle学习笔记(十六) PL/SQL 异常和goto语句

    PL/SQL 异常和goto语句 异常 预定义异常 oracle常见预定义异常: 错误号 异常错误信息名称 说明 ORA-0001 DUP_VAL_ON_INDEX 试图破坏一个唯一性限制 ORA-0 ...

  2. centos 配置sentry+钉钉+邮件通知

    1.sentry官方推荐docker方式安装.使用docker-compose,最好是centos7 2.卸载旧版本 yum remove docker docker-common docker-se ...

  3. Zipkin架构简介

    Zipkin基本概念 Span:基本工作单元,一次链路调用就会创建一个Span Trace:一组Span的集合,表示一条调用链路.举个例子:当前存在服务A调用服务B然后调用服务C,这个A->B- ...

  4. vue浏览器全屏实现

    1.项目中使用的是sreenfull插件,执行命令安装 npm install --save screenfull 2.安装好后,引入项目,用一个按钮进行控制即可,按钮方法如下: toggleFull ...

  5. leaflet-webpack 入门开发系列二加载不同在线地图切换显示(附源码下载)

    前言 leaflet-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载地址 w ...

  6. git终端提交代码

  7. [转]Eclipse插件开发之基础篇(3) 插件的测试与调试

    原文地址:http://www.cnblogs.com/liuzhuo/archive/2010/08/17/eclipse_plugin_1_1_2.html 1. 使用JUnit对插件进行测试 E ...

  8. 10.JavaCC官方入门指南-例5

    例5:计算器--添加乘除法运算 1.calculator2.jj 根据上一个例子,可知要添加乘法和除法运算是很简单的,我们只需在词法描述部分添加如下两个token: TOKEN : { < TI ...

  9. C# Session 操作类

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  10. Windows下的命令行终端 cmder

    Windows下有很多比系统自带的cmd或者PowerShell好用的命令行工具,cmder是最为推荐的一款. 1.从cmder官网直接下载,一般下载full版本,下载完成后解压文件到自己指定的目录, ...