<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          OpenKruise 源碼分析之 ContainerRecreateRequest

          共 14877字,需瀏覽 30分鐘

           ·

          2022-07-11 12:25

          OpenKruise 是基于 CRD 的拓展,包含了很多應用工作負載和運維增強能力,本系列文章會從源碼和底層原理上解讀各個組件,以幫助大家更好地使用和理解 OpenKruise。讓我們開始 OpenKruise 的源碼之旅吧!

          前言

          上一篇文章[1]中我們解讀了 OpenKruise 原地升級的原理和相關代碼,在此基礎上我們來研究一個基于原地升級能力的組件 - ContainerRecreateRequest[2]ContainerRecreateRequest(下文簡稱 CRR) 能夠重建 Pod 中一個或多個容器。該功能和 Kruise 提供的原地升級類似,當一個容器重建的時候,Pod 中的其他容器還保持正常運行。重建完成后,Pod 中除了該容器的 restartCount 增加以外不會有什么其他變化。如果掛載了 volume mount 掛載卷,卷中的數(shù)據(jù)不會丟失也不需要重新掛載。這個功能實現(xiàn)了運維容器與業(yè)務容器的管理分離,比如一個 Pod 中會有主容器中運行核心業(yè)務,sidecar 中運行運維容器,比如日志收集等.當業(yè)務容器需要重啟的時候,傳統(tǒng)的更新方式會讓整個 Pod 重啟從而導致運維容器無故被重啟從而中斷服務,而使用 ContainerRecreateRequest 可以實現(xiàn)只讓特定的容器重啟,高效的同時更加安全。

          今天就讓我們從源碼的角度來看一下 ContainerRecreateRequest 的實現(xiàn)原理。

          源碼解讀

          我們先來看一下整個 CRR 的代碼流程概覽,可以看到整個過程主要有三個組件參與,包括 CRR 的 admission webhook, controller manager,以及我們上一篇就提到過的原地升級中的重要組件 - kruise-daemon 中的 crr daemon controller

          然后我們再逐步拆開講解每一步的內容。

          1. create CRR

          先看一下 CRR 這個自定義資源的 schema 定義:

          apiVersion: apps.kruise.io/v1alpha1
          kind: ContainerRecreateRequest
          metadata:
            namespace: pod-namespace
            name: xxx
          spec:
            podName: pod-name
            containers:       # 要重建的容器名字列表,至少要有 1 個
            - name: app
            - name: sidecar
            strategy:
              failurePolicy: Fail                 # 'Fail' 或 'Ignore',表示一旦有某個容器停止或重建失敗, CRR 立即結束
              orderedRecreate: false              # 'true' 表示要等前一個容器重建完成了,再開始重建下一個
              terminationGracePeriodSeconds: 30   # 等待容器優(yōu)雅退出的時間,不填默認用 Pod 中定義的
              unreadyGracePeriodSeconds: 3        # 在重建之前先把 Pod 設為 not ready,并等待這段時間后再開始執(zhí)行重建
              minStartedSeconds: 10               # 重建后新容器至少保持運行這段時間,才認為該容器重建成功
            activeDeadlineSeconds: 300        # 如果 CRR 執(zhí)行超過這個時間,則直接標記為結束(未結束的容器標記為失敗)
            ttlSecondsAfterFinished: 1800     # CRR 結束后,過了這段時間自動被刪除掉

          然后開始走讀代碼流程。

          1.1 檢查 feature-gate

          當我們創(chuàng)建一個 CRR 的時候,會最先經(jīng)過 adminssion webhook,webhook 中會最先檢查當前 feature gates 中是否開啟了 kruise-daemon ,因為這個功能依賴于 kruise-daemon 組件來停止 Pod 容器,如果 KruiseDaemon feature-gate 被關閉了,ContainerRecreateRequest 也將無法使用。

          func (h *ContainerRecreateRequestHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
           if !utilfeature.DefaultFeatureGate.Enabled(features.KruiseDaemon) {
            return admission.Errored(http.StatusForbidden, fmt.Errorf("feature-gate %s is not enabled", features.KruiseDaemon))
           }
              ...
          }

          1.2 注入默認值并檢查 Pod

          創(chuàng)建 CRR 的時候要為其注入一些特定的標簽,為后面控制啟動容器的流程做準備,比如打上 ContainerRecreateRequestPodNameKeyContainerRecreateRequestActiveKey的標簽:

          obj.Labels[appsv1alpha1.ContainerRecreateRequestPodNameKey] = obj.Spec.PodName
          obj.Labels[appsv1alpha1.ContainerRecreateRequestActiveKey] = "true"

          檢查當前處理的 Pod 是否符合更新條件,比如 Pod 是否是 active 的:

          func IsPodActive(p *v1.Pod) bool {
           return v1.PodSucceeded != p.Status.Phase &&
            v1.PodFailed != p.Status.Phase &&
            p.DeletionTimestamp == nil
          }

          以及 Pod 是否已經(jīng)完成調度,如果未完成調度的話就無法完成原地重啟(無法使用部署到節(jié)點上的 kruise-daemon):

          if !kubecontroller.IsPodActive(pod) {
            return admission.Errored(http.StatusBadRequest, fmt.Errorf("not allowed to recreate containers in an inactive Pod"))
           } else if pod.Spec.NodeName == "" {
            return admission.Errored(http.StatusBadRequest, fmt.Errorf("not allowed to recreate containers in a pending Pod"))
           }

          1.3 將 Pod 中的信息注入到 CRR

          CRR 的運行需要獲取 Pod 的信息,比如獲取 Pod 中的 Lifecycle.PreStop 讓 kruise-daemon 執(zhí)行 preStop hook 后把容器停掉,獲取指定容器的 containerID 來判斷重啟后 containerID 的變化等。

          err = injectPodIntoContainerRecreateRequest(obj, pod)
           if err != nil {
            return admission.Errored(http.StatusBadRequest, err)
           }
          ...
          if podContainer.Lifecycle != nil && podContainer.Lifecycle.PreStop != nil {
             c.PreStop = &appsv1alpha1.ProbeHandler{
              Exec:      podContainer.Lifecycle.PreStop.Exec,
              HTTPGet:   podContainer.Lifecycle.PreStop.HTTPGet,
              TCPSocket: podContainer.Lifecycle.PreStop.TCPSocket,
             }
            }
                  ......

          2. CRR controller

          創(chuàng)建 CRR 并為其注入相關信息后,CRR 的 controller manager 接管 CRR 的更新。

          2.1 同步 container status

          CRR 的 status 中包含所要重啟的 container 的相關狀態(tài)信息:

          type ContainerRecreateRequestStatus struct {
           // Phase of this ContainerRecreateRequest, e.g. Pending, Recreating, Completed
           Phase ContainerRecreateRequestPhase `json:"phase"`
           // Represents time when the ContainerRecreateRequest was completed. It is not guaranteed to
           // be set in happens-before order across separate operations.
           // It is represented in RFC3339 form and is in UTC.
           CompletionTime *metav1.Time `json:"completionTime,omitempty"`
           // A human readable message indicating details about this ContainerRecreateRequest.
           Message string `json:"message,omitempty"`
           // ContainerRecreateStates contains the recreation states of the containers.
           ContainerRecreateStates []ContainerRecreateRequestContainerRecreateState `json:"containerRecreateStates,omitempty"`
          }
          type ContainerRecreateRequestContainerRecreateState struct {
           // Name of the container.
           Name string `json:"name"`
           // Phase indicates the recreation phase of the container.
           Phase ContainerRecreateRequestPhase `json:"phase"`
           // A human readable message indicating details about this state.
           Message string `json:"message,omitempty"`
          }

          CRR controller 不斷更新 container 的重啟信息到 status 中。

          func (r *ReconcileContainerRecreateRequest) syncContainerStatuses(crr *appsv1alpha1.ContainerRecreateRequest, pod *v1.Pod) error {
              ...
          }

          controller 同步 container status 的邏輯非常重要,在這里筆者曾經(jīng)遇到一個詭異的問題,就是創(chuàng)建了好幾個 CRR 后,其中幾個 CRR 一直卡在 Recreating 的狀態(tài),即使 container 已經(jīng)重啟完成或者 TTL 到期也不會發(fā)生變化,詳情可以見這個 issue[3]。原因就是同步 container status 的邏輯跟時鐘同步有關:

          containerStatus := util.GetContainerStatus(c.Name, pod)
            if containerStatus == nil {
             klog.Warningf("Not found %s container in Pod Status for CRR %s/%s", c.Name, crr.Namespace, crr.Name)
             continue
            } else if containerStatus.State.Running == nil || containerStatus.State.Running.StartedAt.Before(&crr.CreationTimestamp) {
             // 只有 container 的創(chuàng)建時間晚于 crr 的創(chuàng)建時間,才認為 crr 重啟了 container,假如此時 CRR 所處節(jié)點或者 Pod 所在節(jié)點的時鐘發(fā)生漂移,那有可能出現(xiàn) container 創(chuàng)建的時間早于 crr 創(chuàng)建時間,即使該 container 是由 crr 控制重啟。
             continue
            }
                  ...

          經(jīng)過排查后發(fā)現(xiàn)確實是好多 k8s Node 的 NTP server 出現(xiàn)問題導致時鐘漂移,再加上上述的邏輯,就不難解釋為何 CRR 會卡住不動了。

          2.2 make pod not ready

          CRR 在重啟 container 之前會給 Pod 注入一個 v1.PodConditionType - KruisePodReadyConditionType 并置為 false, 使 Pod 進入 not ready 狀態(tài),從 service 的 Endpoint 上摘掉流量。

          condition := GetReadinessCondition(newPod) // 獲取 KruisePodReadyConditionType condition
            if condition == nil { // 如果沒有設置,就新建一個
             _, messages := addMessage("", msg)
             newPod.Status.Conditions = append(newPod.Status.Conditions, v1.PodCondition{
              Type:               appspub.KruisePodReadyConditionType,
              Message:            messages.dump(),
              LastTransitionTime: metav1.Now(),
             })
            } else {// 如果存在該 condition,就置為 false
             changed, messages := addMessage(condition.Message, msg)
             if !changed {
              return nil
             }
             condition.Status = v1.ConditionFalse
             condition.Message = messages.dump()
             condition.LastTransitionTime = metav1.Now()
            }

          3. kruise daemon controller

          CRR kruise daemon controller 會監(jiān)聽 CRR 資源的 create, update, delete 事件,然后在 manage 函數(shù)中更新 CRR。

          3.1 watch CRR

          CRR controller 將 update 和 create 事件都加入到 process 隊列中,等待處理。

          informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
            AddFunc: func(obj interface{}) {
             crr, ok := obj.(*appsv1alpha1.ContainerRecreateRequest)
             if ok {
              enqueue(queue, crr)
             }
            },
            UpdateFunc: func(oldObj, newObj interface{}) {
             crr, ok := newObj.(*appsv1alpha1.ContainerRecreateRequest)
             if ok {
              enqueue(queue, crr)
             }
            },
            DeleteFunc: func(obj interface{}) {
             crr, ok := obj.(*appsv1alpha1.ContainerRecreateRequest)
             if ok {
              resourceVersionExpectation.Delete(crr)
             }
            },
           })

          3.2 CRR phase to recreating

          daemon controller 的代碼入口處先把 CRR 的 phase 設置為 ContainerRecreateRequestRecreating

           // once first update its phase to recreating
           if crr.Status.Phase != appsv1alpha1.ContainerRecreateRequestRecreating {
            return c.updateCRRPhase(crr, appsv1alpha1.ContainerRecreateRequestRecreating)
           }

          3.3 wait for unready grace period

          CRR 中的 unreadyGracePeriodSeconds 表示在 2.2 步驟中將 Pod 設置為 not ready 后等待多久再執(zhí)行 restart container。

          // crr_daemon_controller.go

          leftTime := time.Duration(*crr.Spec.Strategy.UnreadyGracePeriodSeconds)*time.Second - time.Since(unreadyTime)
            if leftTime > 0 {
             klog.Infof("CRR %s/%s is waiting for unready grace period %v left time.", crr.Namespace, crr.Name, leftTime)
             c.queue.AddAfter(crr.Namespace+"/"+crr.Spec.PodName, leftTime+100*time.Millisecond)
             return nil
            }

          3.4 KillContainer

          kruise-daemon 會執(zhí)行 preStop hook 后把容器停掉,然后 kubelet 感知到容器退出,則會新建一個容器并啟動。最后 kruise-daemon 看到新容器已經(jīng)啟動成功超過 minStartedSeconds 時間后,會上報這個容器的 phase 狀態(tài)為 Succeeded。

          // crr_daemon_controller.go
           err := runtimeManager.KillContainer(pod, kubeContainerStatus.ID, state.Name, msg, nil)

          3.5 更新 CRRContainerRecreateStates

          不斷更新 CRR status 中關于 container 的狀態(tài)信息 - containerRecreateStates

           c.patchCRRContainerRecreateStates(crr, newCRRContainerRecreateStates)

          4. 完成 CRR

          4.1 CRR 置為 completed

          這部分邏輯在 controller managerkruise daemon 都有,而且判定 CRR completed 的方式比較多,這里舉幾個典型的例子:

          4.1.1

          當完成重啟 container 的數(shù)量跟 CRR 中 ContainerRecreateStates 的數(shù)組長度一致的時候認為已經(jīng)完成所有容器的重啟工作,可以標記 CRR 為完成。

          if completedCount == len(newCRRContainerRecreateStates) {
            return c.completeCRRStatus(crr, "")
           }
          4.1.2

          當發(fā)現(xiàn)有容器重啟失敗了,并且策略是 ignore 就直接標記本次 CRR 為 completed。

           case appsv1alpha1.ContainerRecreateRequestFailed:
             completedCount++
             if crr.Spec.Strategy.FailurePolicy == appsv1alpha1.ContainerRecreateRequestFailurePolicyIgnore {
              continue
             }
             return c.completeCRRStatus(crr, "")
          4.1.3

          上面兩個例子都是在 crr_daemon_controller.go 中的,這里列一個 crr_controller 判定完成的例子:

          if crr.Spec.ActiveDeadlineSeconds != nil {
            leftTime := time.Duration(*crr.Spec.ActiveDeadlineSeconds)*time.Second - time.Since(crr.CreationTimestamp.Time)
            if leftTime <= 0 {
             klog.Warningf("Complete CRR %s/%s as failure for recreating has exceeded the activeDeadlineSeconds", crr.Namespace, crr.Name)
             return reconcile.Result{}, r.completeCRR(crr, "recreating has exceeded the activeDeadlineSeconds")
            }
            duration.Update(leftTime)
           }

          CRR 在規(guī)定的 TTL 時間里沒有完成任務,會被在這里標記為完成,但是會標記一個含有失敗信息的 message。

          4.2 到期刪除 CRR

          如果 CRR 設置了 TTLSecondsAfterFinished 字段,達到該時間后,系統(tǒng)就會將 CRR 刪除,這對定期清理已經(jīng)完成的 CRR 很有幫助。

          if crr.Spec.TTLSecondsAfterFinished != nil {
             leftTime = time.Duration(*crr.Spec.TTLSecondsAfterFinished)*time.Second - time.Since(crr.Status.CompletionTime.Time)
             if leftTime <= 0 {
              klog.Infof("Deleting CRR %s/%s for ttlSecondsAfterFinished", crr.Namespace, crr.Name)
              if err = r.Delete(context.TODO(), crr); err != nil {
               return reconcile.Result{}, fmt.Errorf("delete CRR error: %v", err)
              }
              return reconcile.Result{}, nil
             }
            }

          結語

          文章的結尾再來回顧一下 CRR 是如何在幾個組件協(xié)作之下工作的:

          傳統(tǒng)的 Pod 重啟就是將原有的 Pod 刪除,等待重建新的 Pod,而 CRR 的出現(xiàn)為我們提供了一種全新的重啟服務的方式。

          參考資料

          [1]

          OpenKruise 源碼分析之原地升級: https://cloudsjhan.github.io/2022/06/19/OpenKruise-源碼解讀之原地升級/

          [2]

          kruise CRR: https://openkruise.io/zh/docs/user-manuals/containerrecreaterequest

          [3]

          kruise issue 895: https://github.com/openkruise/kruise/issues/895


          瀏覽 64
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  免费啪啪网 | 欧美国产精品一区婷婷五月天 | 豆花视频成人版www满18 | 青青草原精品 | 精品乱伦无码 |