天天看點

Knative 之流量灰階和版本管理

本文選自 《Knative 雲原生應用開發指南》

本篇主要介紹 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

各個資源配置。

Knative 之流量灰階和版本管理
  • 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。

Knative 之流量灰階和版本管理

執行

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 的差別:

Knative 之流量灰階和版本管理

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 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,做最懂雲原生開發者的技術圈。”