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

本篇主要介紹 Knative Serving 的流量灰度,經過一個 rest-api 的例子演示如何建立不一樣的 Revision、如何在不一樣的 Revision 之間按照流量比例灰度。linux

部署 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 把鏡像推送到鏡像倉庫。git

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 上。github

驗證 Serving 的各個資源
以下圖所示,咱們先回顧一下 Serving 涉及到的各類資源。接下來咱們分別看一下剛纔部署的 revision-v1.yaml 各個資源配置。golang

  • 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 文件,把下面這些內容複製到文件內,而後賦予文件可執行權限。執行執行此腳本就能獲得測試結果。docker

#!/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 上面了。json

└─# ./run-test.sh
Welcome to the v1 app!

灰度 50% 的流量到 v2

修改 Service 建立 v2 revision , 建立一個 revision-v2.yaml 文件,內容以下:vim

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% 的比例。下面這是我真實測試的結果。api

└─# ./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 的文件,內容以下:bash

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 狀況:app

└─# 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 能夠看到 v一、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 灰度的比例並不能真實的表明流量的比例,是一個間接的灰度方法。



本文做者:冬島

閱讀原文

本文爲雲棲社區原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索