天天看點

OpenKruise使用方法與測試文檔說明背景前期容器化改造關于OpenKruise使用和測試過程

文檔說明

 該文檔主要記錄了我在使用OpenKruise的操作,官方的文檔相對來說比較簡潔,對使用姿勢和API的示例并不是很直覺,是以我把整體測試流程記錄了下來,如果已經熟練使用OpenKruise的大神,則可以跳過該文檔。

背景

這兩年,随着雲計算的發展,雲原生的概念應運而生。為了讓應用能擁有雲一樣的特性,每個公司也開始了各自的雲原生實踐之路。

從今年上半年,我們公司也開始了自己的雲原生之路。概括的來說,雲原生 = DevOps+持續傳遞+服務網格+容器化

DevOps和持續傳遞,我們已經擁有了對應的釋出和工單等系統,已經在公司内部平穩維護和使用,目前需要解決的就是服務網格和容器化。由于曆史原因和技術債務考慮,我們決定先進行應用的容器化改造,然後再進行服務網格的支援。

容器化的改造也并不是一蹴而就的。對于那些有一定的發展時間和規模的公司,都會存在自身的技術債務,業務可能會依賴固定IP,應用可能依舊是有狀态的等等。是以,對我們而言,在盡量不影響業務開發的前提下,我們選擇使用statefulset且固定IP的方式進行前期的容器化遷移。

前期容器化改造

大部分網際網路公司都會有三個環境:線下開發環境、預發環境和正式生産環境

最開始,我們打算先将線下環境進行容器化改造,接下來伴随這k8s的環境準備,釋出系統的容器化改造,我們已經實作了應用通過釋出系統釋出後,從部署在原先的雲伺服器到部署在k8s的節點上。這裡的細節就不描述了,由于我們公司所有的系統都是自研的,原理基本就是應用建立對應的statefulset進行pod的管理。

當線下環境容器化改造完成以後,在推行的過程中,業務開發也回報了一些容器化後出現的問題,主要為以下幾點:

  • 釋出的時間變長,影響了業務開發的效率。

相較于原先雲伺服器的釋出方式,隻需要更新應用的建構物,然後重新啟動應用就完成了應用的釋出。而如果容器化以後,pod會經過Terminating銷毀後,再進行pending重新排程,最後到容器的running,相比較耗時長,另外如果線上上環境,pod數量較多,預計的pending時間會更長。

  • 釋出方式單一

對于使用statefulset的方式,如果對于某個應用存在多個pod執行個體,并不支援多個pod的并行釋出,隻能夠依次順序釋出,如果前一個pod沒有釋出成功,後一個pod不會開始釋出,這種場景線上上大批量的分批灰階釋出場景肯定是不滿足要求的

為了優化以上問題,我們查閱了相關資料,也聽了線下雲原生相關的meetUp,最後打算使用阿裡開源的Openkruise,對整體釋出流程進行優化。

關于OpenKruise

OpenKruise 是阿裡雲開源的雲原生應用自動化管理套件,也是目前托管在 Cloud Native Computing Foundation (CNCF) 下的 Sandbox 項目。

官方位址:

https://openkruise.io

對于openKruise,我們比較關注Advanced StatefulSet的原地更新和并行、灰階分批釋出功能等。

使用和測試過程

安裝OpenKruise

https://openkruise.io/zh-cn/docs/installation.html

比較簡單,照着文檔來就行了

測試原地更新功能

首先需要建立Advanced StatefulSet

這裡需要說明兩點:

  • 對目前已經建立了k8s原生的StatefulSet,不能通過修改yaml,轉換成Advanced StatefulSet,需要将原先的statefulset删除,然後重新建立
  • 建立完成以後,通過kubectl get statefulset 名稱 -n xxx 的方式無法看到建立Advanced StatefulSet資訊,是以如果使用的是其他雲廠商的容器服務,則在控制台對應的workload中檢視不到對應的資源資訊。例如我們公司使用的是騰訊雲的TKE服務,在控制台上就看不到建立的Advanced StatefulSet。但是可以通過kubectl get sts.apps.kruise.io檢視

建立的yaml如下:

apiVersion: apps.kruise.io/v1beta1 

kind: StatefulSet 

metadata: 

  annotations: 

    description: xxxx

    tke.cloud.tencent.com/enable-static-ip: "true" 

  labels: 

    appname: xxxx 

  name: xxxx

  namespace: xxxx 

spec: 

  podManagementPolicy: OrderedReady 

  replicas: 1 

  revisionHistoryLimit: 10 

  selector: 

    matchLabels: 

      appname: xxxx 

  template: 

    metadata: 

      annotations: 

        tke.cloud.tencent.com/vpc-ip-claim-delete-policy: Never 

      labels: 

        appname: xxxx 

    spec: 

      readinessGates: 

      - conditionType: InPlaceUpdateReady 

      containers: 

      - env: 

        - name: PATH 

          value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 

        image: xxxx

        imagePullPolicy: IfNotPresent 

        name: xxxx 

        readinessProbe: 

          failureThreshold: 3 

          httpGet: 

            path: /status 

            port: 80 

            scheme: HTTP 

          initialDelaySeconds: 60 

          periodSeconds: 10 

          successThreshold: 1 

          timeoutSeconds: 300 

        resources: 

          limits: 

            cpu: 2500m 

            memory: 4Gi 

          requests: 

            cpu: 1500m 

            memory: 2Gi 

        securityContext: 

          privileged: true 

        lifecycle: 

          preStop: 

            exec: 

              command: ["/bin/sh","-c","/home/mapp/bin/stop-app.sh"] 

        terminationMessagePath: /dev/termination-log 

        terminationMessagePolicy: File 

      dnsPolicy: ClusterFirst 

      imagePullSecrets: 

      - name: offline-test 

      restartPolicy: Always 

      schedulerName: default-scheduler 

      securityContext: {} 

      terminationGracePeriodSeconds: 30 

  updateStrategy: 

    type: RollingUpdate 

    rollingUpdate: 

      podUpdatePolicy: InPlaceIfPossible 

      inPlaceUpdateStrategy: 

        gracePeriodSeconds: 10 

​​​說明:已經将yaml中的敏感資訊去除。

建立以後,通過以下指令檢視:

​​kubectl get sts.apps.kruise.io 
           
OpenKruise使用方法與測試文檔說明背景前期容器化改造關于OpenKruise使用和測試過程

原地更新的條件:

InPlaceIfPossible: 控制器會優先嘗試原地更新 Pod,如果不行再采用重建更新。目前,隻有修改 spec.template.metadata.* 和 spec.template.spec.containers[x].image 這些字段才可以走原地更新。

是以我重新設定了yaml中的鏡像版本,然後執行​

kubectl apply -f .\statefulset.yaml
           

執行指令的同時,通過指令監聽:

kubectl get sts.apps.kruise.io  -w
kubectl get pod  pod的名稱  --watch
           
OpenKruise使用方法與測試文檔說明背景前期容器化改造關于OpenKruise使用和測試過程
OpenKruise使用方法與測試文檔說明背景前期容器化改造關于OpenKruise使用和測試過程

檢視pod的event事件

OpenKruise使用方法與測試文檔說明背景前期容器化改造關于OpenKruise使用和測試過程

總體從替換鏡像後到容器正常啟動,總共耗時40s

進入pod檢視業務啟動狀态,鏡像版本已經更新

結果對比

原地更新的方式與之前釋出的方式進行對比,差異就在于原地更新不需要進行pod的銷毀和排程過程。原地更新從更新鏡像到容器啟動成功,花費40s,而原先的方式,容器的銷毀和排程花費的時間為70s左右。

另外在配置上,我設定了gracePeriodSeconds

是以我們又在原地更新中提供了 graceful period 選項,作為優雅原地更新的政策。使用者如果配置了 gracePeriodSeconds 這個字段,控制器在原地更新的過程中會先把 Pod status 改為 not-ready,然後等一段時間(gracePeriodSeconds),最後再去修改 Pod spec 中的鏡像版本。 這樣,就為 endpoints-controller 這些控制器留出了充足的時間來将 Pod 從 endpoints 端點清單中去除。

如果将gracePeriodSeconds配置去除,原地更新的時間能夠縮短為30s

測試MaxUnavailable 政策

maxUnavailable 政策來支援并行 Pod 釋出,它會保證釋出過程中最多有多少個 Pod 處于不可用狀态。注意,maxUnavailable 隻能配合 podManagementPolicy 為 Parallel 來使用。

說明: 經測試,不允許在之前的Advanced StatefulSet上更新釋出政策

Resource: "apps.kruise.io/v1beta1, Resource=statefulsets", GroupVersionKind: "apps.kruise.io/v1beta1, Kind=StatefulSet" 
Name: "xxxx", Namespace: "xxx" 
for: ".\\statefulset.yaml": admission webhook "vstatefulset.kb.io" denied the request: spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'template', 'reserveOrdinals', 'lifecycle' and 'updateStrategy' are forbidden
           

是以将之前的Advanced StatefulSet進行删除,然後建立

yaml如下:

apiVersion: apps.kruise.io/v1beta1 
kind: StatefulSet 
metadata: 
  annotations: 
    description: xxxx
    tke.cloud.tencent.com/enable-static-ip: "true" 
  labels: 
    appname: xxxx 
  name: xxxx 
  namespace: xxx 
spec: 
  podManagementPolicy: Parallel 
  replicas: 3 
  revisionHistoryLimit: 10 
  selector: 
    matchLabels: 
      appname: xxxx 
  template: 
    metadata: 
      annotations: 
        tke.cloud.tencent.com/vpc-ip-claim-delete-policy: Never 
      labels: 
        appname: xxxx
    spec: 
      readinessGates: 
      - conditionType: InPlaceUpdateReady 
      containers: 
      - env: 
        - name: PATH 
          value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 
        image: xxxx
        imagePullPolicy: Always 
        name: flowplusdevhosttest 
        readinessProbe: 
          failureThreshold: 3 
          httpGet: 
            path: /status 
            port: 80 
            scheme: HTTP 
          initialDelaySeconds: 60 
          periodSeconds: 10 
          successThreshold: 1 
          timeoutSeconds: 300 
        resources: 
          limits: 
            cpu: 2500m 
            memory: 4Gi 
          requests: 
            cpu: 1500m 
            memory: 2Gi 
        securityContext: 
          privileged: true 
        lifecycle: 
          preStop: 
            exec: 
              command: ["/bin/sh","-c","/home/mapp/bin/stop-app.sh"] 
        terminationMessagePath: /dev/termination-log 
        terminationMessagePolicy: File 
      dnsPolicy: ClusterFirst 
      imagePullSecrets: 
      - name: offline-test 
      restartPolicy: Always 
      schedulerName: default-scheduler 
      securityContext: {} 
      terminationGracePeriodSeconds: 30 
  updateStrategy: 
    type: RollingUpdate 
    rollingUpdate: 
      podUpdatePolicy: InPlaceIfPossible 
      maxUnavailable: 100% 
           

說明:

建立了Advanced StatefulSet,副本數設定了3個,同時更新政策設定成了并行,最大不可用數為100%,說明在更新時,可以進行批量同時更新。

測試并行釋出

此時如果修改了鏡像版本後,觸發了3個pod的原地更新,觀察3個pod是否同時進行更新

修改了鏡像版本後,Advanced StatefulSet的确同時從Ready狀态數值轉換成了0

OpenKruise使用方法與測試文檔說明背景前期容器化改造關于OpenKruise使用和測試過程

最終釋出完成以後:

OpenKruise使用方法與測試文檔說明背景前期容器化改造關于OpenKruise使用和測試過程

檢視對應三個pod的event

pod-0:

OpenKruise使用方法與測試文檔說明背景前期容器化改造關于OpenKruise使用和測試過程

pod-1:

OpenKruise使用方法與測試文檔說明背景前期容器化改造關于OpenKruise使用和測試過程

pod-2:

OpenKruise使用方法與測試文檔說明背景前期容器化改造關于OpenKruise使用和測試過程

最終得出結論:能夠支援并行釋出,3個pod同時進行原地更新

測試MaxUnavailable

如果在并發釋出的過程中,還可以設定maxUnavailable的值。

比如說3個pod中,我需要保證服務不能同時挂,起碼有一個pod正常提供服務,那我可以設定maxUnavailable為2。

調整後進行釋出:

OpenKruise使用方法與測試文檔說明背景前期容器化改造關于OpenKruise使用和測試過程

Ready狀态的pod會保留一個,不會導緻所有的pod都停止服務

灰階分批釋出

灰階釋出通過partition字段進行控制

如果在釋出過程中設定了 partition:

如果是數字,控制器會将 (replicas - partition) 數量的 Pod 更新到最新版本。

如果是百分比,控制器會将 (replicas * (100% - partition)) 數量的 Pod 更新到最新版本。

以之前建立的Advanced StatefulSet為例,如果我設定了partition為2,則更新時,隻會更新一台機器

apiVersion: apps.kruise.io/v1beta1 
kind: StatefulSet 
metadata: 
  annotations: 
    description: xxxx
    tke.cloud.tencent.com/enable-static-ip: "true" 
  labels: 
    appname: xxxx 
  name: xxxx
  namespace: xxx 
spec: 
  podManagementPolicy: Parallel 
  replicas: 3 
  revisionHistoryLimit: 10 
  selector: 
    matchLabels: 
      appname: xxx 
  template: 
    metadata: 
      annotations: 
        tke.cloud.tencent.com/vpc-ip-claim-delete-policy: Never 
      labels: 
        appname: xxx 
    spec: 
      readinessGates: 
      - conditionType: InPlaceUpdateReady 
      containers: 
      - env: 
        - name: PATH 
          value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 
        image: xxxx
        imagePullPolicy: Always 
        name: xxxx 
        readinessProbe: 
          failureThreshold: 3 
          httpGet: 
            path: /status 
            port: 80 
            scheme: HTTP 
          initialDelaySeconds: 60 
          periodSeconds: 10 
          successThreshold: 1 
          timeoutSeconds: 300 
        resources: 
          limits: 
            cpu: 2500m 
            memory: 4Gi 
          requests: 
            cpu: 1500m 
            memory: 2Gi 
        securityContext: 
          privileged: true 
        lifecycle: 
          preStop: 
            exec: 
              command: ["/bin/sh","-c","/home/mapp/bin/stop-app.sh"] 
        terminationMessagePath: /dev/termination-log 
        terminationMessagePolicy: File 
      dnsPolicy: ClusterFirst 
      imagePullSecrets: 
      - name: offline-test 
      restartPolicy: Always 
      schedulerName: default-scheduler 
      securityContext: {} 
      terminationGracePeriodSeconds: 30 
  updateStrategy: 
    type: RollingUpdate 
    rollingUpdate: 
      podUpdatePolicy: InPlaceIfPossible 
      maxUnavailable: 2 
      partition: 2 
           

測試灰階釋出

接下來修改鏡像版本:

隻更新了一個pod的鏡像版本

OpenKruise使用方法與測試文檔說明背景前期容器化改造關于OpenKruise使用和測試過程

檢視3個pod的資訊

隻有第三個 pod-2 的鏡像更新了,其他兩個鏡像還是原來的鏡像

OpenKruise使用方法與測試文檔說明背景前期容器化改造關于OpenKruise使用和測試過程

如果此時partition設定為1,則會再更新一個pod的鏡像版本。測試結果如下:

OpenKruise使用方法與測試文檔說明背景前期容器化改造關于OpenKruise使用和測試過程

這個時候會繼續更新一台,此時pod-1的鏡像已經更新。

如果需要更新所有的鏡像,則把partition設定為0,則會對所有pod進行更新。

控制釋出順序

控制釋出順序隻是将原先statefulset的順序釋出,可以改成權重和序号釋出。

weight: Pod 優先級是由所有 weights 清單中的 term 來計算 match selector 得出

order: Pod 優先級是由 orderKey 的 value 決定,這裡要求對應的 value 的結尾能解析為 int 值

是以這裡的釋出順序隻是将原先的順序釋出通過selector進行自定義的排序,并不滿足我們釋出過程中,可以随意指定一台進行釋出的操作。

是以略過

釋出暫停

觸發方式:

spec:
# ...
  updateStrategy:
    rollingUpdate:
      paused: true
           

首先将maxUnavailable設定為2,則并行釋出時,會同時并行釋出兩個pod,等兩個pod釋出成功以後,再釋出另一台pod。測試在中途暫停釋出。

更改鏡像版本後,并行釋出了兩台,有一個pod依然保留,正常提供服務:

OpenKruise使用方法與測試文檔說明背景前期容器化改造關于OpenKruise使用和測試過程

此時暫停釋出:

OpenKruise使用方法與測試文檔說明背景前期容器化改造關于OpenKruise使用和測試過程

從UPDATES的數量可以看出,當最早并行的兩個pod已經釋出完成,但是剩餘的一個pod始終沒有釋出,是以釋出已經暫停了。

然後将暫停釋出關掉:

OpenKruise使用方法與測試文檔說明背景前期容器化改造關于OpenKruise使用和測試過程

另一個pod也開始進行釋出更新

序号保留

通過在 reserveOrdinals 字段中寫入需要保留的序号,Advanced StatefulSet 會自動跳過建立這些序号的 Pod。如果 Pod 已經存在,則會被删除。 注意,spec.replicas 是期望運作的 Pod 數量

沒有使用場景,略過

對于這點,我也比較好奇這個功能的實際場景是什麼

總結

OpenKruise的确能夠對目前釋出系統的流程進行優化,提高開發使用的效率:

  • 原地更新功能

通過原地更新的功能,能夠減少pod銷毀和排程的時間,能夠減少40s左右的釋出時間。另外線上上的節點數量規模,減少時間的效益應該更加明顯。

  • 并行釋出能夠

目前對于部署場景下有多個pod進行釋出,使用openKruise,可以将原先的串行釋出方式改變為并行釋出,尤其是線上上大批量機器的釋出,并行釋出能夠節約大量時間

  • 分批灰階釋出

目前openKruise可以通過partion的設定來進行機器的分批灰階釋出,開發可以指定目前灰階多少個pod,但是不能随意指定任意一台pod進行釋出

是以接下來,我們開始将原先的StatefulSet 轉換為Advanced StatefulSet,另外由于之前我釋出系統是Java + Python的應用,如果調用OpenKruise資源,有以下幾種方式:

  • 通過官方提供的golang用戶端
  • 通過官方提供的java api
  • 通過python調用shell指令

最後我們選擇使用官方的golang api:

https://github.com/openkruise/kruise-api

Golang用戶端測試代碼

官網的執行個體代碼比較簡潔,​下面貼下我的測試代碼:

go.mod:

module openKruiseApiTest

go 1.14

require github.com/openkruise/kruise-api v0.8.0-1.18
           

main.go:

package main

import (
    "context"
    "fmt"
    kruiseclientset "github.com/openkruise/kruise-api/client/clientset/versioned"
    "k8s.io/client-go/tools/clientcmd"
    "log"
    "path/filepath"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func main() {

    kubeconfig := filepath.Join("D:\\environment\\k8s\\", "xxxxx")  //config的位址
    config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
    if err != nil {
        log.Fatal(err)
    }

    kruiseClient := kruiseclientset.NewForConfigOrDie(config)
    statefulset, err := kruiseClient.AppsV1alpha1().StatefulSets("xxx").Get(context.TODO(),"xxxx",metav1.GetOptions{})

    fmt.Printf("Result: %s \n", statefulset)

}