Kubernetes 部署策略(滾動(dòng)發(fā)布巍棱、金色雀發(fā)布、藍(lán)綠發(fā)布)-轉(zhuǎn)載

Kubernetes 部署策略

在Kubernetes中有幾種不同的方式發(fā)布應(yīng)用蛋欣,所以為了讓應(yīng)用在升級(jí)期間依然平穩(wěn)提供服務(wù)航徙,選擇一個(gè)正確的發(fā)布策略就非常重要了。

選擇正確的部署策略是要依賴于我們的業(yè)務(wù)需求的陷虎,下面我們列出了一些可能會(huì)使用到的策略:

  • 重建(recreate):停止舊版本部署新版本
  • 滾動(dòng)更新(rolling-update):一個(gè)接一個(gè)地以滾動(dòng)更新方式發(fā)布新版本
  • 藍(lán)綠(blue/green):新版本與舊版本一起存在到踏,然后切換流量
  • 金絲雀(canary):將新版本面向一部分用戶發(fā)布,然后繼續(xù)全量發(fā)布
  • A/B測(cè)(a/b testing):以精確的方式(HTTP 頭尚猿、cookie窝稿、權(quán)重等)向部分用戶發(fā)布新版本。A/B測(cè)實(shí)際上是一種基于數(shù)據(jù)統(tǒng)計(jì)做出業(yè)務(wù)決策的技術(shù)谊路。在 Kubernetes 中并不原生支持讹躯,需要額外的一些高級(jí)組件來(lái)完成改設(shè)置(比如Istio、Linkerd、Traefik潮梯、或者自定義 Nginx/Haproxy 等)骗灶。

重建(Recreate) - 最好在開發(fā)環(huán)境

策略定義為RecreateDeployment,會(huì)終止所有正在運(yùn)行的實(shí)例秉馏,然后用較新的版本來(lái)重新創(chuàng)建它們耙旦。

spec:
  replicas: 3
  strategy:
    type: Recreate
image.png

重新創(chuàng)建策略是一個(gè)虛擬部署,包括關(guān)閉版本A萝究,然后在關(guān)閉版本A后部署版本B. 此技術(shù)意味著服務(wù)的停機(jī)時(shí)間取決于應(yīng)用程序的關(guān)閉和啟動(dòng)持續(xù)時(shí)間免都。

我們這里創(chuàng)建兩個(gè)相關(guān)的資源清單文件,app-v1.yaml:

apiVersion: v1
kind: Service
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    targetPort: http
  selector:
    app: my-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
        version: v1.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9101"
    spec:
      containers:
      - name: my-app
        image: containersol/k8s-deployment-strategies
        ports:
        - name: http
          containerPort: 8080
        - name: probe
          containerPort: 8086
        env:
        - name: VERSION
          value: v1.0.0
        livenessProbe:
          httpGet:
            path: /live
            port: probe
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: probe
          periodSeconds: 5

app-v2.yaml 文件內(nèi)容如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  replicas: 3
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
        version: v2.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9101"
    spec:
      containers:
      - name: my-app
        image: containersol/k8s-deployment-strategies
        ports:
        - name: http
          containerPort: 8080
        - name: probe
          containerPort: 8086
        env:
        - name: VERSION
          value: v2.0.0
        livenessProbe:
          httpGet:
            path: /live
            port: probe
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: probe
          periodSeconds: 5

上面兩個(gè)資源清單文件中的 Deployment 定義幾乎是一致的帆竹,唯一不同的是定義的環(huán)境變量VERSION值不同绕娘,接下來(lái)按照下面的步驟來(lái)驗(yàn)證Recreate策略:

  1. 版本1提供服務(wù)
  2. 刪除版本1
  3. 部署版本2
  4. 等待所有副本準(zhǔn)備就緒

首先部署第一個(gè)應(yīng)用:

$ kubectl apply -f app-v1.yaml
service "my-app" created
deployment.apps "my-app" created

測(cè)試版本1是否部署成功:

$ kubectl get pods -l app=my-app
NAME                      READY     STATUS    RESTARTS   AGE
my-app-7b4874cd75-m5kct   1/1       Running   0          19m
my-app-7b4874cd75-pc444   1/1       Running   0          19m
my-app-7b4874cd75-tlctl   1/1       Running   0          19m
$ kubectl get svc my-app
NAME      TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
my-app    NodePort   10.108.238.76   <none>        80:32532/TCP   5m
$ curl http://127.0.0.1:32532
Host: my-app-7b4874cd75-pc444, Version: v1.0.0

可以看到版本1的應(yīng)用正常運(yùn)行了。為了查看部署的運(yùn)行情況栽连,打開一個(gè)新終端并運(yùn)行以下命令:

$ watch kubectl get po -l app=my-app

然后部署版本2的應(yīng)用:

$ kubectl apply -f app-v2.yaml

這個(gè)時(shí)候可以觀察上面新開的終端中的 Pod 列表的變化险领,可以看到之前的3個(gè) Pod 都會(huì)先處于Terminating狀態(tài),并且3個(gè) Pod 都被刪除后才開始創(chuàng)建新的 Pod秒紧。

然后測(cè)試第二個(gè)版本應(yīng)用的部署進(jìn)度:

$ while sleep 0.1; do curl http://127.0.0.1:32532; done
curl: (7) Failed connect to 127.0.0.1:32532; Connection refused
curl: (7) Failed connect to 127.0.0.1:32532; Connection refused
......
Host: my-app-f885c8d45-sp44p, Version: v2.0.0
Host: my-app-f885c8d45-t8g7g, Version: v2.0.0
Host: my-app-f885c8d45-sp44p, Version: v2.0.0
......

可以看到最開始的階段服務(wù)都是處于不可訪問的狀態(tài)绢陌,然后到第二個(gè)版本的應(yīng)用部署成功后才正常訪問,可以看到現(xiàn)在訪問的數(shù)據(jù)是版本2了熔恢。

最后脐湾,可以執(zhí)行下面的命令來(lái)清空上面的資源對(duì)象:

$ kubectl delete all -l app=my-app

結(jié)論:
1.應(yīng)用狀態(tài)全部更新
2.停機(jī)時(shí)間取決于應(yīng)用程序的關(guān)閉和啟動(dòng)消耗的時(shí)間

滾動(dòng)更新(rolling-update)

滾動(dòng)更新通過(guò)逐個(gè)替換實(shí)例來(lái)逐步部署新版本的應(yīng)用,直到所有實(shí)例都被替換完成為止叙淌。它通常遵循以下過(guò)程:在負(fù)載均衡器后面使用版本 A 的實(shí)例池秤掌,然后部署版本 B 的一個(gè)實(shí)例,當(dāng)服務(wù)準(zhǔn)備好接收流量時(shí)(Readiness Probe 正常)凿菩,將該實(shí)例添加到實(shí)例池中机杜,然后從實(shí)例池中刪除一個(gè)版本 A 的實(shí)例并關(guān)閉,如下圖所示:

image.png

下圖是滾動(dòng)更新過(guò)程應(yīng)用接收流量的示意圖:

image.png

下面是 Kubernetes 中通過(guò) Deployment 來(lái)進(jìn)行滾動(dòng)更新的關(guān)鍵參數(shù):

spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 2           # 一次可以添加多少個(gè)Pod
      maxUnavailable: 1  # 滾動(dòng)更新期間最大多少個(gè)Pod不可用

maxSurgemaxUnavailable可以設(shè)置為0衅谷,但是不能同時(shí)為0

現(xiàn)在仍然使用上面的 app-v1.yaml 這個(gè)資源清單文件椒拗,新建一個(gè)定義滾動(dòng)更新的資源清單文件 app-v2-rolling-update.yaml,文件內(nèi)容如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  replicas: 10
  # maxUnavailable設(shè)置為0可以完全確保在滾動(dòng)更新期間服務(wù)不受影響获黔,還可以使用百分比的值來(lái)進(jìn)行設(shè)置蚀苛。
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
        version: v2.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9101"
    spec:
      containers:
      - name: my-app
        image: containersol/k8s-deployment-strategies
        ports:
        - name: http
          containerPort: 8080
        - name: probe
          containerPort: 8086
        env:
        - name: VERSION
          value: v2.0.0
        livenessProbe:
          httpGet:
            path: /live
            port: probe
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: probe
          # 初始延遲設(shè)置高點(diǎn)可以更好地觀察滾動(dòng)更新過(guò)程
          initialDelaySeconds: 15
          periodSeconds: 5

上面的資源清單中我們?cè)诃h(huán)境變量中定義了版本2,然后通過(guò)設(shè)置strategy.type=RollingUpdate來(lái)定義該 Deployment 使用滾動(dòng)更新的策略來(lái)更新應(yīng)用玷氏,接下來(lái)我們按下面的步驟來(lái)驗(yàn)證滾動(dòng)更新策略:

  1. 版本1提供服務(wù)
  2. 部署版本2
  3. 等待直到所有副本都被版本2替換完成

同樣堵未,首先部署版本1應(yīng)用:

$ kubectl apply -f app-v1.yaml
service "my-app" created
deployment.apps "my-app" created

測(cè)試版本1是否部署成功:

$ kubectl get pods -l app=my-app
NAME                      READY     STATUS    RESTARTS   AGE
my-app-7b4874cd75-h8c4d   1/1       Running   0          47s
my-app-7b4874cd75-p4l8f   1/1       Running   0          47s
my-app-7b4874cd75-qnt7p   1/1       Running   0          47s
$ kubectl get svc my-app
NAME      TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
my-app    NodePort   10.109.99.184   <none>        80:30486/TCP   1m
$ curl http://127.0.0.1:30486
Host: my-app-7b4874cd75-qnt7p, Version: v1.0.0

同樣,在一個(gè)新終端中執(zhí)行下面命令觀察 Pod 變化:

$ watch kubectl get pod -l app=my-app

然后部署滾動(dòng)更新版本2應(yīng)用:

$ kubectl apply -f app-v2-rolling-update.yaml
deployment.apps "my-app" configured

這個(gè)時(shí)候在上面的 watch 終端中可以看到多了很多 Pod盏触,還在創(chuàng)建當(dāng)中渗蟹,并沒有一開始就刪除之前的 Pod块饺,同樣,這個(gè)時(shí)候執(zhí)行下面命令雌芽,測(cè)試應(yīng)用狀態(tài):

$ while sleep 0.1; do curl http://127.0.0.1:30486; done
Host: my-app-7b4874cd75-vrlj7, Version: v1.0.0
......
Host: my-app-7b4874cd75-vrlj7, Version: v1.0.0
Host: my-app-6b5479d97f-2fk24, Version: v2.0.0
Host: my-app-7b4874cd75-p4l8f, Version: v1.0.0
......
Host: my-app-6b5479d97f-s5ctz, Version: v2.0.0
Host: my-app-7b4874cd75-5ldqx, Version: v1.0.0
......
Host: my-app-6b5479d97f-5z6ww, Version: v2.0.0

們可以看到上面的應(yīng)用并沒有出現(xiàn)不可用的情況授艰,最開始訪問到的都是版本1的應(yīng)用,然后偶爾會(huì)出現(xiàn)版本2的應(yīng)用世落,直到最后全都變成了版本2的應(yīng)用淮腾,而這個(gè)時(shí)候看上面 watch 終端中 Pod 已經(jīng)全部變成10個(gè)版本2的應(yīng)用了,我們可以看到這就是一個(gè)逐步替換的過(guò)程屉佳。

如果在滾動(dòng)更新過(guò)程中發(fā)現(xiàn)新版本應(yīng)用有問題谷朝,我們可以通過(guò)下面的命令來(lái)進(jìn)行一鍵回滾:

$ kubectl rollout undo deploy my-app
deployment.apps "my-app"

如果你想保持兩個(gè)版本的應(yīng)用都存在,那么我們也可以執(zhí)行 pause 命令來(lái)暫停更新:

$ kubectl rollout pause deploy my-app
deployment.apps "my-app" paused

這個(gè)時(shí)候我們?cè)偃パh(huán)訪問我們的應(yīng)用就可以看到偶爾會(huì)出現(xiàn)版本1的應(yīng)用信息了武花。

如果新版本應(yīng)用程序沒問題了圆凰,也可以繼續(xù)恢復(fù)更新:

$ kubectl rollout resume deploy my-app
deployment.apps "my-app" resumed

最后,可以執(zhí)行下面的命令來(lái)清空上面的資源對(duì)象:

$ kubectl delete all -l app=my-app

結(jié)論:
1.版本在實(shí)例之間緩慢替換
2.rollout/rollback 可能需要一定時(shí)間
3.無(wú)法控制流量

藍(lán)/綠(blue/green) - 最好用來(lái)驗(yàn)證 API 版本問題

藍(lán)/綠發(fā)布是版本2 與版本1 一起發(fā)布体箕,然后流量切換到版本2送朱,也稱為紅/黑部署。藍(lán)/綠發(fā)布與滾動(dòng)更新不同干旁,版本2(綠) 與版本1(藍(lán))一起部署,在測(cè)試新版本滿足要求后炮沐,然后更新更新 Kubernetes 中扮演負(fù)載均衡器角色的 Service 對(duì)象争群,通過(guò)替換 label selector 中的版本標(biāo)簽來(lái)將流量發(fā)送到新版本,如下圖所示:

image.png

下面是藍(lán)綠發(fā)布策略下應(yīng)用方法的示例圖:

image.png

在 Kubernetes 中大年,我們可以用兩種方法來(lái)實(shí)現(xiàn)藍(lán)綠發(fā)布换薄,通過(guò)單個(gè) Service 對(duì)象或者 Ingress 控制器來(lái)實(shí)現(xiàn)藍(lán)綠發(fā)布,實(shí)際操作都是類似的翔试,都是通過(guò) label 標(biāo)簽去控制轻要。
實(shí)現(xiàn)藍(lán)綠發(fā)布的關(guān)鍵點(diǎn)就在于 Service 對(duì)象中 label selector 標(biāo)簽的匹配方法,比如我們重新定義版本1 的資源清單文件 app-v1-single-svc.yaml垦缅,文件內(nèi)容如下:

apiVersion: v1
kind: Service
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    targetPort: http
  # 注意這里我們匹配 app 和 version 標(biāo)簽冲泥,當(dāng)要切換流量的時(shí)候,我們更新 version 標(biāo)簽的值壁涎,比如:v2.0.0
  selector:
    app: my-app
    version: v1.0.0
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-v1
  labels:
    app: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
      version: v1.0.0
  template:
    metadata:
      labels:
        app: my-app
        version: v1.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9101"
    spec:
      containers:
      - name: my-app
        image: containersol/k8s-deployment-strategies
        ports:
        - name: http
          containerPort: 8080
        - name: probe
          containerPort: 8086
        env:
        - name: VERSION
          value: v1.0.0
        livenessProbe:
          httpGet:
            path: /live
            port: probe
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: probe
          periodSeconds: 5

上面定義的資源對(duì)象中凡恍,最重要的就是 Service 中 label selector 的定義:

selector:
  app: my-app
  version: v1.0.0

版本2 的應(yīng)用定義和以前一樣,新建文件 app-v2-single-svc.yaml怔球,文件內(nèi)容如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-v2
  labels:
    app: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
      version: v2.0.0
  template:
    metadata:
      labels:
        app: my-app
        version: v2.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9101"
    spec:
      containers:
      - name: my-app
        image: containersol/k8s-deployment-strategies
        ports:
        - name: http
          containerPort: 8080
        - name: probe
          containerPort: 8086
        env:
        - name: VERSION
          value: v2.0.0
        livenessProbe:
          httpGet:
            path: /live
            port: probe
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: probe
          periodSeconds: 5

然后按照下面的步驟來(lái)驗(yàn)證使用單個(gè) Service 對(duì)象實(shí)現(xiàn)藍(lán)/綠部署的策略:

  1. 版本1 應(yīng)用提供服務(wù)
  2. 部署版本2 應(yīng)用
  3. 等到版本2 應(yīng)用全部部署完成
  4. 切換入口流量從版本1 到版本2
  5. 關(guān)閉版本1 應(yīng)用

首先嚼酝,部署版本1 應(yīng)用:

$ kubectl apply -f app-v1-single-svc.yaml
service "my-app" created
deployment.apps "my-app-v1" created

測(cè)試版本1 應(yīng)用是否部署成功:

$ kubectl get pods -l app=my-app
NAME                         READY     STATUS    RESTARTS   AGE
my-app-v1-7b4874cd75-7xh6s   1/1       Running   0          41s
my-app-v1-7b4874cd75-dmq8f   1/1       Running   0          41s
my-app-v1-7b4874cd75-t64z7   1/1       Running   0          41s
$ kubectl get svc -l app=my-app
NAME      TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
my-app    NodePort   10.106.184.144   <none>        80:31539/TCP   50s
$ curl http://127.0.0.1:31539
Host: my-app-v1-7b4874cd75-7xh6s, Version: v1.0.0

同樣,新開一個(gè)終端竟坛,執(zhí)行如下命令觀察 Pod 變化:

$ watch kubectl get pod -l app=my-app

然后部署版本2 應(yīng)用:

$ kubectl apply -f app-v2-single-svc.yaml
deployment.apps "my-app-v2" created

然后在上面 watch 終端中可以看到會(huì)多3個(gè)my-app-v2開頭的 Pod闽巩,待這些 Pod 部署成功后钧舌,我們?cè)偃ピL問當(dāng)前的應(yīng)用:

$ while sleep 0.1; do curl http://127.0.0.1:31539; done
Host: my-app-v1-7b4874cd75-dmq8f, Version: v1.0.0
Host: my-app-v1-7b4874cd75-dmq8f, Version: v1.0.0
......

我們會(huì)發(fā)現(xiàn)訪問到的都是版本1 的應(yīng)用,和我們剛剛部署的版本2 沒有任何關(guān)系涎跨,這是因?yàn)槲覀?Service 對(duì)象中通過(guò) label selector 匹配的是version=v1.0.0這個(gè)標(biāo)簽洼冻,我們可以通過(guò)修改 Service 對(duì)象的匹配標(biāo)簽,將流量路由到標(biāo)簽version=v2.0.0的 Pod 去:

$ kubectl patch service my-app -p '{"spec":{"selector":{"version":"v2.0.0"}}}'
service "my-app" patched

然后再去訪問應(yīng)用六敬,可以發(fā)現(xiàn)現(xiàn)在都是版本2 的信息了:

$ while sleep 0.1; do curl http://127.0.0.1:31539; done
Host: my-app-v2-f885c8d45-r5m6z, Version: v2.0.0
Host: my-app-v2-f885c8d45-r5m6z, Version: v2.0.0
......

如果你需要回滾到版本1碘赖,同樣只需要更改 Service 的匹配標(biāo)簽即可:

$ kubectl patch service my-app -p '{"spec":{"selector":{"version":"v1.0.0"}}}'

如果新版本已經(jīng)完全符合我們的需求了,就可以刪除版本1 的應(yīng)用了:

$ kubectl delete deploy my-app-v1

最后外构,同樣普泡,執(zhí)行如下命令清理上述資源對(duì)象:

$ kubectl delete all -l app=my-app

結(jié)論:
1.實(shí)時(shí)部署/回滾
2.避免版本問題,因?yàn)橐淮胃氖钦麄€(gè)應(yīng)用的改變
3.需要兩倍的資源
4.在發(fā)布到生產(chǎn)之前审编,應(yīng)該對(duì)整個(gè)應(yīng)用進(jìn)行適當(dāng)?shù)臏y(cè)試

金絲雀(Canary) - 讓部分用戶參與測(cè)試

金絲雀部署是讓部分用戶訪問到新版本應(yīng)用撼班,在 Kubernetes 中,可以使用兩個(gè)具有相同 Pod 標(biāo)簽的 Deployment 來(lái)實(shí)現(xiàn)金絲雀部署垒酬。新版本的副本和舊版本的一起發(fā)布砰嘁。在一段時(shí)間后如果沒有檢測(cè)到錯(cuò)誤,則可以擴(kuò)展新版本的副本數(shù)量并刪除舊版本的應(yīng)用勘究。

如果需要按照具體的百分比來(lái)進(jìn)行金絲雀發(fā)布矮湘,需要盡可能的啟動(dòng)多的 Pod 副本,這樣計(jì)算流量百分比的時(shí)候才方便口糕,比如缅阳,如果你想將 1% 的流量發(fā)送到版本 B,那么我們就需要有一個(gè)運(yùn)行版本 B 的 Pod 和 99 個(gè)運(yùn)行版本 A 的 Pod景描,當(dāng)然如果你對(duì)具體的控制策略不在意的話也就無(wú)所謂了十办,如果你需要更精確的控制策略,建議使用服務(wù)網(wǎng)格(如 Istio)超棺,它們可以更好地控制流量向族。

image.png

在下面的例子中,我們使用 Kubernetes 原生特性來(lái)實(shí)現(xiàn)一個(gè)窮人版的金絲雀發(fā)布棠绘,如果你想要對(duì)流量進(jìn)行更加細(xì)粒度的控制件相,請(qǐng)使用豪華版本的 Istio。下面是金絲雀發(fā)布的應(yīng)用請(qǐng)求示意圖:

image.png

接下來(lái)我們按照下面的步驟來(lái)驗(yàn)證金絲雀策略:

  1. 10個(gè)副本的版本1 應(yīng)用提供服務(wù)
  2. 版本2 應(yīng)用部署1個(gè)副本(意味著小于10%的流量)
  3. 等待足夠的時(shí)間來(lái)確認(rèn)版本2 應(yīng)用足夠穩(wěn)定沒有任何錯(cuò)誤信息
  4. 將版本2 應(yīng)用擴(kuò)容到10個(gè)副本
  5. 等待所有實(shí)例完成
  6. 關(guān)閉版本1 應(yīng)用

首先弄唧,創(chuàng)建版本1 的應(yīng)用資源清單适肠,app-v1-canary.yaml,內(nèi)容如下:

apiVersion: v1
kind: Service
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    targetPort: http
  selector:
    app: my-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-v1
  labels:
    app: my-app
spec:
  replicas: 10
  selector:
    matchLabels:
      app: my-app
      version: v1.0.0
  template:
    metadata:
      labels:
        app: my-app
        version: v1.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9101"
    spec:
      containers:
      - name: my-app
        image: containersol/k8s-deployment-strategies
        ports:
        - name: http
          containerPort: 8080
        - name: probe
          containerPort: 8086
        env:
        - name: VERSION
          value: v1.0.0
        livenessProbe:
          httpGet:
            path: /live
            port: probe
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: probe
          periodSeconds: 5

其中核心的部分也是 Service 對(duì)象中的 label selector 標(biāo)簽候引,不在具有版本相關(guān)的標(biāo)簽了侯养,然后定義版本2 的資源清單文件,app-v2-canary.yaml澄干,文件內(nèi)容如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-v2
  labels:
    app: my-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
      version: v2.0.0
  template:
    metadata:
      labels:
        app: my-app
        version: v2.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9101"
    spec:
      containers:
      - name: my-app
        image: containersol/k8s-deployment-strategies
        ports:
        - name: http
          containerPort: 8080
        - name: probe
          containerPort: 8086
        env:
        - name: VERSION
          value: v2.0.0
        livenessProbe:
          httpGet:
            path: /live
            port: probe
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: probe
          periodSeconds: 5

版本1 和版本2 的 Pod 都具有一個(gè)共同的標(biāo)簽app=my-app逛揩,所以對(duì)應(yīng)的 Service 會(huì)匹配兩個(gè)版本的 Pod柠傍。

首先,部署版本1 應(yīng)用:

$ kubectl apply -f app-v1-canary.yaml
service "my-app" created
deployment.apps "my-app-v1" created

然后測(cè)試版本1 應(yīng)用是否正確部署了:

$ kubectl get svc -l app=my-app
NAME          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
my-app        NodePort    10.105.133.213   <none>        80:30760/TCP   47s
$ curl http://127.0.0.1:30760
Host: my-app-v1-7b4874cd75-tsh2s, Version: v1.0.0

同樣辩稽,新開一個(gè)終端惧笛,查看 Pod 的變化:

$ watch kubectl get po

然后部署版本2 應(yīng)用:

$ kubectl apply -f app-v2-canary.yaml
deployment.apps "my-app-v2" created

然后在 watch 終端頁(yè)面可以看到多了一個(gè) Pod,現(xiàn)在一共 11 個(gè) Pod逞泄,其中只有1 個(gè) Pod 運(yùn)行新版本應(yīng)用患整,然后同樣可以循環(huán)訪問該應(yīng)用,查看是否會(huì)有版本2 的應(yīng)用信息:

$ while sleep 0.1; do curl http://127.0.0.1:30760; done
Host: my-app-v1-7b4874cd75-bhxbp, Version: v1.0.0
Host: my-app-v1-7b4874cd75-wmcqc, Version: v1.0.0
Host: my-app-v1-7b4874cd75-tsh2s, Version: v1.0.0
Host: my-app-v1-7b4874cd75-ml58j, Version: v1.0.0
Host: my-app-v1-7b4874cd75-spsdv, Version: v1.0.0
Host: my-app-v2-f885c8d45-mc2fx, Version: v2.0.0
......

正常情況下可以看到大部分都是返回的版本1 的應(yīng)用信息喷众,偶爾會(huì)出現(xiàn)版本2 的應(yīng)用信息各谚,這就證明我們的金絲雀發(fā)布成功了,待確認(rèn)了版本2 的這個(gè)應(yīng)用沒有任何問題后到千,可以將版本2 應(yīng)用擴(kuò)容到10 個(gè)副本:

$ kubectl scale --replicas=10 deploy my-app-v2
deployment.extensions "my-app-v2" scaled

其實(shí)這個(gè)時(shí)候訪問應(yīng)用的話新版本和舊版本的流量分配是1:1了昌渤,確認(rèn)了版本2 正常后,就可以刪除版本1 的應(yīng)用了:

$ kubectl delete deploy my-app-v1
deployment.extensions "my-app-v1" deleted

最終留下的是 10 個(gè)新版本的 Pod 了憔四,到這里我們的整個(gè)金絲雀發(fā)布就完成了膀息。

同樣,最后了赵,執(zhí)行下面的命令刪除上面的資源對(duì)象:

$ kubectl delete all -l app=my-app

結(jié)論:
1.部分用戶獲取新版本
2.方便錯(cuò)誤和性能監(jiān)控
3.快速回滾
4.發(fā)布較慢
5.流量精準(zhǔn)控制很浪費(fèi)(99%A / 1%B = 99 Pod A潜支,1 Pod B)

如果你對(duì)新功能的發(fā)布沒有信心,建議使用金絲雀發(fā)布的策略柿汛。

A/B測(cè)試(A/B testing) - 最適合部分用戶的功能測(cè)試

A/B 測(cè)試實(shí)際上是一種基于統(tǒng)計(jì)信息而非部署策略來(lái)制定業(yè)務(wù)決策的技術(shù)毁腿,與業(yè)務(wù)結(jié)合非常緊密。但是它們也是相關(guān)的苛茂,也可以使用金絲雀發(fā)布來(lái)實(shí)現(xiàn)。

除了基于權(quán)重在版本之間進(jìn)行流量控制之外鸠窗,A/B 測(cè)試還可以基于一些其他參數(shù)(比如 Cookie妓羊、User Agent、地區(qū)等等)來(lái)精確定位給定的用戶群稍计,該技術(shù)廣泛用于測(cè)試一些功能特性的效果躁绸,然后按照效果來(lái)進(jìn)行確定。

我們經(jīng)吵枷可以在今日頭條的客戶端中就會(huì)發(fā)現(xiàn)有大量的 A/B 測(cè)試净刮,同一個(gè)地區(qū)的用戶看到的客戶端有很大不同。

要使用這些細(xì)粒度的控制硅则,仍然還是建議使用 Istio淹父,可以根據(jù)權(quán)重或 HTTP 頭等來(lái)動(dòng)態(tài)請(qǐng)求路由控制流量轉(zhuǎn)發(fā)。


image.png

下面是使用 Istio 進(jìn)行規(guī)則設(shè)置的示例怎虫,因?yàn)?Istio 還不太穩(wěn)定暑认,以下示例規(guī)則將來(lái)可能會(huì)更改:

route:
- tags:
  version: v1.0.0
  weight: 90
- tags:
  version: v2.0.0
  weight: 10

關(guān)于在 Istio 中具體如何做 A/B 測(cè)試困介,我們這里就不再詳細(xì)介紹了,我們?cè)趇stio-book文檔中有相關(guān)的介紹蘸际。

image.png

結(jié)論:
1.幾個(gè)版本并行運(yùn)行
2.完全控制流量分配
3.特定的一個(gè)訪問錯(cuò)誤難以排查座哩,需要分布式跟蹤
4.Kubernetes 沒有直接的支持,需要其他額外的工具

總結(jié)

發(fā)布應(yīng)用有許多種方法粮彤,當(dāng)發(fā)布到開發(fā)/測(cè)試環(huán)境的時(shí)候根穷,重建或者滾動(dòng)更新通常是一個(gè)不錯(cuò)的選擇。

  • 在生產(chǎn)環(huán)境导坟,滾動(dòng)更新或者藍(lán)綠發(fā)布比較合適屿良,但是新版本的提前測(cè)試是非常有必要的。
  • 如果你對(duì)新版本的應(yīng)用不是很有信心的話乍迄,那應(yīng)該使用金絲雀發(fā)布管引,將用戶的影響降到最低。
  • 最后闯两,如果你的公司需要在特定的用戶群體中進(jìn)行新功能的測(cè)試褥伴,例如,移動(dòng)端用戶請(qǐng)求路由到版本 A漾狼,桌面端用戶請(qǐng)求路由到版本 B重慢,那么你就看使用A/B 測(cè)試,通過(guò)使用 Kubernetes 服務(wù)網(wǎng)關(guān)的配置逊躁,可以根據(jù)某些請(qǐng)求參數(shù)來(lái)確定用戶應(yīng)路由的服務(wù)似踱。
    ————————————————
    版權(quán)聲明:本文為CSDN博主「看,未來(lái)」的原創(chuàng)文章稽煤,遵循CC 4.0 BY-SA版權(quán)協(xié)議核芽,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
    原文鏈接:https://blog.csdn.net/qq_43762191/article/details/125922745
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末酵熙,一起剝皮案震驚了整個(gè)濱河市轧简,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌匾二,老刑警劉巖哮独,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異察藐,居然都是意外死亡皮璧,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門分飞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)悴务,“玉大人,你說(shuō)我怎么就攤上這事譬猫〔沂伲” “怎么了邦泄?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)裂垦。 經(jīng)常有香客問我顺囊,道長(zhǎng),這世上最難降的妖魔是什么蕉拢? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任特碳,我火速辦了婚禮,結(jié)果婚禮上晕换,老公的妹妹穿的比我還像新娘午乓。我一直安慰自己,他們只是感情好闸准,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布益愈。 她就那樣靜靜地躺著,像睡著了一般夷家。 火紅的嫁衣襯著肌膚如雪蒸其。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天库快,我揣著相機(jī)與錄音摸袁,去河邊找鬼。 笑死义屏,一個(gè)胖子當(dāng)著我的面吹牛靠汁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播闽铐,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼蝶怔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了兄墅?” 一聲冷哼從身側(cè)響起添谊,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎察迟,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耳高,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡扎瓶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泌枪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片概荷。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖碌燕,靈堂內(nèi)的尸體忽然破棺而出误证,到底是詐尸還是另有隱情继薛,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布愈捅,位于F島的核電站遏考,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蓝谨。R本人自食惡果不足惜灌具,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望譬巫。 院中可真熱鬧咖楣,春花似錦、人聲如沸芦昔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)咕缎。三九已至珠十,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锨阿,已是汗流浹背宵睦。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留墅诡,地道東北人壳嚎。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓洁闰,卻偏偏與公主長(zhǎng)得像实束,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子康辑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容