《前端運維》五、k8s--3灰度釋出、滾動更新與探針

Zaking發表於2022-03-29

一、灰度釋出

  灰度釋出是一種釋出方式,也叫金絲雀釋出,起源是礦工在下井之前會先放一隻金絲雀到井裡,如果金絲雀不叫了,就代表瓦斯濃度高。原因是金絲雀對瓦斯氣體很敏感。灰度釋出的做法是:會在現存舊應用的基礎上,啟動一個新版應用,但是新版應用並不會直接讓使用者訪問。而是先讓測試同學去進行測試。如果沒有問題,則可以將真正的使用者流量慢慢匯入到新版,在這中間,持續對新版本執行狀態做觀察,直到慢慢切換過去,這就是所謂的A/B測試。當然,你也可以招募一些灰度使用者,給他們設定獨有的灰度標示(Cookie,Header),來讓他們可以訪問到新版應用,當然,如果中間切換出現問題,也應該將流量迅速地切換到老應用上。

1)準備新版本的service

  拷貝一份deployment檔案:

cp deployment-user-v1.yaml deployment-user-v2.yaml

  修改之前寫過的內容:

apiVersion: apps/v1  #API 配置版本
kind: Deployment     #資源型別
metadata:
+  name: user-v2     #資源名稱
spec:
  selector:
    matchLabels:
+      app: user-v2 #告訴deployment根據規則匹配相應的Pod進行控制和管理,matchLabels欄位匹配Pod的label值
  replicas: 3 #宣告一個 Pod,副本的數量
  template:
    metadata:
      labels:
+        app: user-v2 #Pod的名稱
    spec:   #組內建立的 Pod 資訊
      containers:
      - name: nginx #容器的名稱
+        image: registry.cn-beijing.aliyuncs.com/zhangrenyang/nginx:user-v2
        ports:
        - containerPort: 80 #容器內對映的埠

  然後service的檔案內容是這樣的:

apiVersion: v1
kind: Service
metadata:
+  name: service-user-v2
spec:
  selector:
+    app: user-v2
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: NodePort

  啟動:

kubectl apply -f deployment-user-v2.yaml service-user-v2.yaml

2)根據cookie切分流量

  基於 Cookie 切分流量。這種實現原理主要根據使用者請求中的 Cookie 是否存在灰度標示 Cookie去判斷是否為灰度使用者,再決定是否返回灰度版本服務

  • nginx.ingress.kubernetes.io/canary:可選值為 true / false 。代表是否開啟灰度功能
  • nginx.ingress.kubernetes.io/canary-by-cookie:灰度釋出 cookie 的 key。當 key 值等於 always 時,灰度觸發生效。等於其他值時,則不會走灰度環境 ingress-gray.yaml

  我們建立一個ingress-gray.yaml檔案:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: user-canary
annotations:
  kubernetes.io/ingress.class: nginx
  nginx.ingress.kubernetes.io/rewrite-target: /
  nginx.ingress.kubernetes.io/canary: "true"
  nginx.ingress.kubernetes.io/canary-by-cookie: "vip_user"
spec:
rules:
- http:
    paths: 
     - backend:
        serviceName: service-user-v2
        servicePort: 80
backend:
   serviceName: service-user-v2
   servicePort: 80

  使檔案生效:

kubectl apply -f ./ingress-gray.yaml 

  獲取外部介面:

kubectl -n ingress-nginx get svc

  測試:

curl http://172.31.178.169:31234/user
curl http://118.190.156.138:31234/user
curl --cookie "vip_user=always"  http://172.31.178.169:31234/user

3)基於header切分流量

  基於 Header 切分流量,這種實現原理主要根據使用者請求中的 header 是否存在灰度標示 header去判斷是否為灰度使用者,再決定是否返回灰度版本服務。

  修改下上面的ingress-gray.yml檔案即可:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: user-canary
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/canary: "true"
+    nginx.ingress.kubernetes.io/canary-by-header: "name"
+    nginx.ingress.kubernetes.io/canary-by-header-value: "vip"
spec:
  rules:
  - http:
      paths: 
       - backend:
          serviceName: service-user-v2
          servicePort: 80
  backend:
     serviceName: service-user-v2
     servicePort: 80

  同樣的:

kubectl apply -f ingress-gray.yaml
curl --header "name:vip"  http://172.31.178.169:31234/user

4)基於權重切分流量 

  這種實現原理主要是根據使用者請求,通過根據灰度百分比決定是否轉發到灰度服務環境中

  • nginx.ingress.kubernetes.io/canary-weight:值是字串,為 0-100 的數字,代表灰度環境命中概率。如果值為 0,則表示不會走灰度。值越大命中概率越大。當值 = 100 時,代表全走灰度。

  一樣一樣的,修改下配置引數罷了:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: user-canary
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/canary: "true"
+   nginx.ingress.kubernetes.io/canary-weight: "50"
spec:
  rules:
  - http:
      paths: 
       - backend:
          serviceName: service-user-v2
          servicePort: 80
  backend:
     serviceName: service-user-v2
     servicePort: 80

  測試下:

kubectl apply -f ingress-gray.yaml
for ((i=1; i<=10; i++)); do curl http://172.31.178.169:31234/user; done

  k8s 會優先去匹配 header ,如果未匹配則去匹配 cookie ,最後是 weight。

二、滾動釋出

  滾動釋出,則是我們一般所說的無當機發布。其釋出方式如同名稱一樣,一次取出一臺/多臺伺服器(看策略配置)進行新版本更新。當取出的伺服器新版確保無問題後,接著採用同等方式更新後面的伺服器。k8s建立副本應用程式的最佳方法就是部署(Deployment),部署自動建立副本集(ReplicaSet),副本集可以精確地控制每次替換的Pod數量,從而可以很好的實現滾動更新。k8s每次使用一個新的副本控制器(replication controller)來替換已存在的副本控制器,從而始終使用一個新的Pod模板來替換舊的pod模板

  • 建立一個新的replication controller
  • 增加或減少pod副本數量,直到滿足當前批次期望的數量
  • 刪除舊的replication controller

  滾動釋出的優缺點如下:

  • 優點
    • 不需要停機更新,無感知平滑更新。
    • 版本更新成本小,不需要新舊版本共存
  • 缺點
    • 更新時間長:每次只更新一個/多個映象,需要頻繁連續等待服務啟動緩衝
    • 舊版本環境無法得到備份:始終只有一個環境存在
    • 回滾版本異常痛苦:如果滾動釋出到一半出了問題,回滾時需要使用同樣的滾動策略回滾舊版本

  我們下面來嘗試下,先擴容為10個副本:

kubectl get deploy
kubectl scale deployment user-v1  --replicas=10

  修改deployment-user-v1.yaml檔案:

apiVersion: apps/v1  #API 配置版本
kind: Deployment     #資源型別
metadata:
  name: user-v1     #資源名稱
spec:
  minReadySeconds: 1
+ strategy:
+   type: RollingUpdate
+   rollingUpdate:
+     maxSurge: 1
+     maxUnavailable: 0
+ selector:
+   matchLabels:
+     app: user-v1 #告訴deployment根據規則匹配相應的Pod進行控制和管理,matchLabels欄位匹配Pod的label值
  replicas: 10 #宣告一個 Pod,副本的數量
  template:
    metadata:
      labels:
        app: user-v1 #Pod的名稱
    spec:   #組內建立的 Pod 資訊
      containers:
      - name: nginx #容器的名稱
+       image: registry.cn-beijing.aliyuncs.com/zhangrenyang/nginx:user-v3 #使用哪個映象
        ports:
        - containerPort: 80 #容器內對映的埠
引數含義
minReadySeconds 容器接受流量延緩時間:單位為秒,預設為0。如果沒有設定的話,k8s會認為容器啟動成功後就可以用了。設定該值可以延緩容器流量切分
strategy.type = RollingUpdate ReplicaSet 釋出型別,宣告為滾動釋出,預設也為滾動釋出
strategy.rollingUpdate.maxSurge 最多Pod數量:為數字型別/百分比。如果 maxSurge 設定為1,replicas 設定為10,則在釋出過程中pod數量最多為10 + 1個(多出來的為舊版本pod,平滑期不可用狀態)。maxUnavailable 為 0 時,該值也不能設定為0
strategy.rollingUpdate.maxUnavailable 升級中最多不可用pod的數量:為數字型別/百分比。當 maxSurge 為 0 時,該值也不能設定為0

  啟動:

kubectl apply -f ./deployment-user-v1.yaml
deployment.apps/user-v1 configured

  然後檢視狀態:

kubectl rollout status deployment/user-v1

三、服務可用性探針

  當 Pod 的狀態為 Running 時,該 Pod 就可以被分配流量(可以訪問到)了。一個後端容器啟動成功,不一定不代表服務啟動成功。

3.2.1 存活探針 LivenessProbe

  第一種是存活探針。存活探針是對執行中的容器檢測的。如果想檢測你的服務在執行中有沒有發生崩潰,服務有沒有中途退出或無響應,可以使用這個探針。如果探針探測到錯誤, Kubernetes 就會殺掉這個 Pod;否則就不會進行處理。如果預設沒有配置這個探針, Pod 不會被殺死。

3.2.2 可用探針 ReadinessProbe

  第二種是可用探針。作用是用來檢測 Pod 是否允許被訪問到(是否準備好接受流量)。如果你的服務載入很多資料,或者有其他需求要求在特定情況下不被分配到流量,那麼可以用這個探針。如果探針檢測失敗,流量就不會分配給該 Pod。在沒有配置該探針的情況下,會一直將流量分配給 Pod。當然,探針檢測失敗,Pod 不會被殺死。

3.2.3 啟動探針 StartupProbe

  第三種是啟動探針。作用是用來檢測 Pod 是否已經啟動成功。如果你的服務啟動需要一些載入時長(例如初始化日誌,等待其他呼叫的服務啟動成功)才代表服務啟動成功,則可以用這個探針。如果探針檢測失敗,該 Pod 就會被殺死重啟。在沒有配置該探針的情況下,預設不會殺死 Pod 。在啟動探針執行時,其他所有的探針檢測都會失效。

探針名稱在哪個環節觸發作用檢測失敗對Pod的反應
啟動探針 Pod 執行時 檢測服務是否啟動成功 殺死 Pod 並重啟
存活探針 Pod 執行時 檢測服務是否崩潰,是否需要重啟服務 殺死 Pod 並重啟
可用探針 Pod 執行時 檢測服務是不是允許被訪問到 停止Pod的訪問排程,不會被殺死重啟

檢測方式

1、ExecAction

  通過在 Pod 的容器內執行預定的 Shell 指令碼命令。如果執行的命令沒有報錯退出(返回值為0),代表容器狀態健康。否則就是有問題的

  我們來新建一個檔案,vi shell-probe.yaml,內容如下:

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: shell-probe
  name: shell-probe
spec:
  containers:
  - name: shell-probe
    image: registry.aliyuncs.com/google_containers/busybox
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5
      periodSeconds: 5

  然後執行下面的命令,檢視情況:

kubectl apply -f liveness.yaml 
kubectl get pods | grep liveness-exec
kubectl describe pods liveness-exec

2、TCPSocketAction

  這種方式是使用 TCP 套接字檢測。 Kubernetes 會嘗試在 Pod 內與指定的埠進行連線。如果能建立連線(Pod的埠開啟了),這個容器就代表是健康的,如果不能,則代表這個 Pod 就是有問題的。

  建立檔案如下,tcp-probe.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: tcp-probe
  labels:
    app: tcp-probe
spec:
  containers:
  - name: tcp-probe
    image: nginx
    ports:
    - containerPort: 80
    readinessProbe:
      tcpSocket:
        port: 80
      initialDelaySeconds: 5
      periodSeconds: 10

  類似的:

kubectl apply -f tcp-probe.yaml 
kubectl get pods | grep tcp-probe
kubectl describe pods tcp-probe

  進入到容器內部:

kubectl exec -it tcp-probe  -- /bin/sh

  更新apt-get並安裝vim:

apt-get update
apt-get install vim -y
vi  /etc/nginx/conf.d/default.conf

  修改nginx檔案配置,把80埠修改為8080,然後過載一下nginx:

nginx -s reload

  看一下狀態:

kubectl describe pod tcp-probe

3、HTTPGetAction

  這種方式是使用 HTTP GET 請求。Kubernetes 會嘗試訪問 Pod 內指定的API路徑。如果返回200,代表容器就是健康的。如果不能,代表這個 Pod 是有問題的。

  新增http-probe.yaml檔案:

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: http-probe
  name: http-probe
spec:
  containers:
  - name: http-probe
    image: registry.cn-beijing.aliyuncs.com/zhangrenyang/http-probe:1.0.0
    livenessProbe:
      httpGet:
        path: /liveness
        port: 80
        httpHeaders:
        - name: source
          value: probe
      initialDelaySeconds: 3
      periodSeconds: 3

  然後,執行並檢視狀態:

kubectl apply -f ./http-probe.yaml
kubectl describe pods http-probe
kubectl replace --force -f http-probe.yaml 

  Dockerfile內容如下:

FROM node
COPY ./app /app
WORKDIR /app
EXPOSE 3000
CMD node index.js

  node服務檔案如下:

let http = require('http');
let start = Date.now();
http.createServer(function(req,res){
  if(req.url === '/liveness'){
    let value = req.headers['source'];
    if(value === 'probe'){
     let duration = Date.now()-start;
      if(duration>10*1000){
          res.statusCode=500;
          res.end('error');
      }else{
          res.statusCode=200;
          res.end('success');
      }
    }else{
     res.statusCode=200;
     res.end('liveness');
    }
  }else{
     res.statusCode=200;
     res.end('liveness');
  }
}).listen(3000,function(){console.log("http server started on 3000")});

  好了今天的內容就到這裡了。

 

相關文章