摘要:節(jié)點對不會有影響,查詢處于狀態(tài)并一直保持。根據(jù)上一節(jié)描述,此時已經(jīng)有正確的在其他節(jié)點,此時故障節(jié)點恢復(fù)后,執(zhí)行優(yōu)雅刪除,刪除舊的。會從狀態(tài)變?yōu)闋顟B(tài),執(zhí)行優(yōu)雅刪除,,然后執(zhí)行重新調(diào)度與重建操作。會從狀態(tài)直接變成狀態(tài),不涉及重建。
節(jié)點離線后的 pod 狀態(tài)
在 kubernetes 使用過程中,根據(jù)集群的配置不同,往往會因為如下情況的一種或幾種導(dǎo)致節(jié)點 NotReady:
kubelet 進程停止
apiserver 進程停止
etcd 進程停止
kubernetes 管理網(wǎng)絡(luò) Down
當出現(xiàn)這種情況的時候,會出現(xiàn)節(jié)點 NotReady,進而當kube-controller-manager 中的--pod-eviction-timeout定義的值,默認 5 分鐘后,將觸發(fā) Pod eviction 動作。
對于不同類型的 workloads,其對應(yīng)的 pod 處理方式因為 controller-manager 中各個控制器的邏輯不通而不同??偨Y(jié)如下:
deployment: 節(jié)點 NotReady 觸發(fā) eviction 后,pod 將會在新節(jié)點重建(如果有 nodeSelector 或者親和性要求,會處于 Pending 狀態(tài)),故障節(jié)點的 Pod 仍然會保留處于 Unknown 狀態(tài),所以此時看到的 pod 數(shù)多于副本數(shù)。
statefulset: 節(jié)點 NotReady 同樣會對 StatefulSet 觸發(fā) eviction 操作,但是用戶看到的 Pod 會一直處于 Unknown 狀態(tài)沒有變化。
daemonSet: 節(jié)點 NotReady 對 DaemonSet 不會有影響,查詢 pod 處于 NodeLost 狀態(tài)并一直保持。
這里說到,對于 deployment 和 statefulSet 類型資源,當節(jié)點 NotReady 后顯示的 pod 狀態(tài)為 Unknown。 這里實際上 etcd 保存的狀態(tài)為 NodeLost,只是顯示時做了處理,與 daemonSet 做了區(qū)分。對應(yīng)代碼中的邏輯為:
### node controller // 觸發(fā) NodeEviction 操作時會 DeletePods,這個刪除為 GracefulDelete, // apiserver rest 接口對 PodObj 添加了 DeletionTimestamp func DeletePods(kubeClient clientset.Interface, recorder record.EventRecorder, nodeName, nodeUID string, daemonStore extensionslisters.DaemonSetLister) (bool, error) { ... for _, pod := range pods.Items { ... // Set reason and message in the pod object. if _, err = SetPodTerminationReason(kubeClient, &pod, nodeName); err != nil { if apierrors.IsConflict(err) { updateErrList = append(updateErrList, fmt.Errorf("update status failed for pod %q: %v", format.Pod(&pod), err)) continue } } // if the pod has already been marked for deletion, we still return true that there are remaining pods. if pod.DeletionGracePeriodSeconds != nil { remaining = true continue } // if the pod is managed by a daemonset, ignore it _, err := daemonStore.GetPodDaemonSets(&pod) if err == nil { // No error means at least one daemonset was found continue } glog.V(2).Infof("Starting deletion of pod %v/%v", pod.Namespace, pod.Name) recorder.Eventf(&pod, v1.EventTypeNormal, "NodeControllerEviction", "Marking for deletion Pod %s from Node %s", pod.Name, nodeName) if err := kubeClient.CoreV1().Pods(pod.Namespace).Delete(pod.Name, nil); err != nil { return false, err } remaining = true } ... } ### staging apiserver REST 接口 // 對于優(yōu)雅刪除,到這里其實已經(jīng)停止,不再進一步刪除,剩下的交給 kubelet watch 到變化后去做 delete func (e *Store) Delete(ctx genericapirequest.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error) { ... if graceful || pendingFinalizers || shouldUpdateFinalizers { err, ignoreNotFound, deleteImmediately, out, lastExisting = e.updateForGracefulDeletionAndFinalizers(ctx, name, key, options, preconditions, obj) } // !deleteImmediately covers all cases where err != nil. We keep both to be future-proof. if !deleteImmediately || err != nil { return out, false, err } ... } // stagging/apiserver中的 rest 接口調(diào)用,設(shè)置了 DeletionTimestamp 和 DeletionGracePeriodSeconds func (e *Store) updateForGracefulDeletionAndFinalizers(ctx genericapirequest.Context, name, key string, options *metav1.DeleteOptions, preconditions storage.Preconditions, in runtime.Object) (err error, ignoreNotFound, deleteImmediately bool, out, lastExisting runtime.Object) { ... if options.GracePeriodSeconds != nil { period := int64(*options.GracePeriodSeconds) if period >= *objectMeta.GetDeletionGracePeriodSeconds() { return false, true, nil } newDeletionTimestamp := metav1.NewTime( objectMeta.GetDeletionTimestamp().Add(-time.Second * time.Duration(*objectMeta.GetDeletionGracePeriodSeconds())). Add(time.Second * time.Duration(*options.GracePeriodSeconds))) objectMeta.SetDeletionTimestamp(&newDeletionTimestamp) objectMeta.SetDeletionGracePeriodSeconds(&period) return true, false, nil } ... } ### node controller // SetPodTerminationReason 嘗試設(shè)置 Pod狀態(tài)和原因到 Pod 對象中 func SetPodTerminationReason(kubeClient clientset.Interface, pod *v1.Pod, nodeName string) (*v1.Pod, error) { if pod.Status.Reason == nodepkg.NodeUnreachablePodReason { return pod, nil } pod.Status.Reason = nodepkg.NodeUnreachablePodReason pod.Status.Message = fmt.Sprintf(nodepkg.NodeUnreachablePodMessage, nodeName, pod.Name) var updatedPod *v1.Pod var err error if updatedPod, err = kubeClient.CoreV1().Pods(pod.Namespace).UpdateStatus(pod); err != nil { return nil, err } return updatedPod, nil } ### 命令行輸出 // 打印輸出時狀態(tài)的切換,如果 "DeletionTimestamp 不為空" 且 "podStatus 為 NodeLost 狀態(tài)"時, // 顯示的狀態(tài)為 Unknown func printPod(pod *api.Pod, options printers.PrintOptions) ([]metav1alpha1.TableRow, error) { ... if pod.DeletionTimestamp != nil && pod.Status.Reason == node.NodeUnreachablePodReason { reason = "Unknown" } else if pod.DeletionTimestamp != nil { reason = "Terminating" } ... }節(jié)點恢復(fù) Ready 后 pod 狀態(tài)
當節(jié)點恢復(fù)后,不同的 workload 對應(yīng)的 pod 狀態(tài)變化也是不同的。
deployment: 根據(jù)上一節(jié)描述,此時 pod 已經(jīng)有正確的 pod 在其他節(jié)點 running,此時故障節(jié)點恢復(fù)后,kubelet 執(zhí)行優(yōu)雅刪除,刪除舊的 PodObj。
statefulset: statefulset 會從Unknown 狀態(tài)變?yōu)?Terminating 狀態(tài),執(zhí)行優(yōu)雅刪除,detach PV,然后執(zhí)行重新調(diào)度與重建操作。
daemonset: daemonset 會從 NodeLost 狀態(tài)直接變成 Running 狀態(tài),不涉及重建。
我們往往會考慮下面兩個問題,statefulset 為什么沒有重建? 如何保持單副本 statefulset 的高可用呢?
關(guān)于為什么沒重建
首先簡單介紹下 statefulset 控制器的邏輯。
Statefulset 控制器通過 StatefulSetControl 以及 StatefulPodControl 2個模塊協(xié)調(diào)完成對 statefulSet 類型 workload 的狀態(tài)管理(StatefulSetStatusUpdater)和擴縮控制(StatefulPodControl)。實際上,StatefulsetControl是對 StatefulPodControl 的調(diào)用來增刪改 Pod。
StatefulSet 在 podManagementPolicy 為默認值 OrderedReady 時,會按照整數(shù)順序單調(diào)遞增的依次創(chuàng)建 Pod,否則在 Parallel時,雖然是按整數(shù),但是 Pod 是同時調(diào)度與創(chuàng)建。
具體的邏輯在核心方法 UpdateStatefulSet 中,見圖:
我們看到的 Stateful Pod 一直處于 Unknown 狀態(tài)的原因就是因為這個控制器屏蔽了對該 Pod 的操作。因為在第一節(jié)介紹了,NodeController 的 Pod Eviction 機制已經(jīng)把 Pod 標記刪除,PodObj 中包含的 DeletionTimestamp 被設(shè)置,StatefulSet Controller 代碼檢查 IsTerminating 符合條件,便直接 return 了。
// updateStatefulSet performs the update function for a StatefulSet. This method creates, updates, and deletes Pods in // the set in order to conform the system to the target state for the set. The target state always contains // set.Spec.Replicas Pods with a Ready Condition. If the UpdateStrategy.Type for the set is // RollingUpdateStatefulSetStrategyType then all Pods in the set must be at set.Status.CurrentRevision. // If the UpdateStrategy.Type for the set is OnDeleteStatefulSetStrategyType, the target state implies nothing about // the revisions of Pods in the set. If the UpdateStrategy.Type for the set is PartitionStatefulSetStrategyType, then // all Pods with ordinal less than UpdateStrategy.Partition.Ordinal must be at Status.CurrentRevision and all other // Pods must be at Status.UpdateRevision. If the returned error is nil, the returned StatefulSetStatus is valid and the // update must be recorded. If the error is not nil, the method should be retried until successful. func (ssc *defaultStatefulSetControl) updateStatefulSet( ... for i := range replicas { ... // If we find a Pod that is currently terminating, we must wait until graceful deletion // completes before we continue to make progress. if isTerminating(replicas[i]) && monotonic { glog.V(4).Infof( "StatefulSet %s/%s is waiting for Pod %s to Terminate", set.Namespace, set.Name, replicas[i].Name) return &status, nil } ... } } // isTerminating returns true if pod"s DeletionTimestamp has been set func isTerminating(pod *v1.Pod) bool { return pod.DeletionTimestamp != nil }
那么如何保證單副本高可用?
往往應(yīng)用中有一些 pod 沒法實現(xiàn)多副本,但是又要保證集群能夠自愈,那么這種某個節(jié)點 Down 掉或者網(wǎng)卡壞掉等情況,就會有很大影響,要如何能夠?qū)崿F(xiàn)自愈呢?
對于這種 Unknown 狀態(tài)的 Stateful Pod ,可以通過 force delete 方式去刪除。關(guān)于 ForceDelete,社區(qū)是不推薦的,因為可能會對唯一的標志符(單調(diào)遞增的序列號)產(chǎn)生影響,如果發(fā)生,對 StatefulSet 是致命的,可能會導(dǎo)致數(shù)據(jù)丟失(可能是應(yīng)用集群腦裂,也可能是對 PV 多寫導(dǎo)致)。
kubectl delete pods
但是這樣刪除仍然需要一些保護措施,以 Ceph RBD 存儲插件為例,當執(zhí)行force delete 前,根據(jù)經(jīng)驗,用戶應(yīng)該先設(shè)置 ceph osd blacklist,防止當遷移過程中網(wǎng)絡(luò)恢復(fù)后,容器繼續(xù)向 PV 寫入數(shù)據(jù)將文件系統(tǒng)弄壞。因為 force delete 是將 PodObj 直接從 ETCD 強制清理,這樣 StatefulSet Controller 將會新建新的 Pod 在其他節(jié)點, 但是故障節(jié)點的 Kubelet 清理這個舊容器需要時間,此時勢必存在 2 個容器mount 了同一塊 PV(故障節(jié)點Pod 對應(yīng)的容器與新遷移Pod 創(chuàng)建的容器),但是如果此時網(wǎng)絡(luò)恢復(fù),那么2 個容器可能同時寫入數(shù)據(jù),后果將是嚴重的
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/32992.html
摘要:核心概念是最小的調(diào)度單元,可以由一個或者多個容器組成。該模式會跟云服務(wù)商有關(guān),比如可以通過等創(chuàng)建一個外部的負載均衡器,將請求轉(zhuǎn)發(fā)到對應(yīng)的服務(wù)組。而可以提供外部服務(wù)可訪問的負載均衡器等。 概述 Kubernetes 有各類資源對象來描述整個集群的運行狀態(tài)。這些對象都需要通過調(diào)用 kubernetes api 來進行創(chuàng)建、修改、刪除,可以通過 kubectl 命令工具,也可以直接調(diào)用 k8...
摘要:此文已由作者劉超授權(quán)網(wǎng)易云社區(qū)發(fā)布。五更加適合微服務(wù)和的設(shè)計好了,說了本身,接下來說說的理念設(shè)計,為什么這么適合微服務(wù)。相關(guān)閱讀為什么天然適合微服務(wù)為什么天然適合微服務(wù)為什么天然適合微服務(wù)文章來源網(wǎng)易云社區(qū) 此文已由作者劉超授權(quán)網(wǎng)易云社區(qū)發(fā)布。 歡迎訪問網(wǎng)易云社區(qū),了解更多網(wǎng)易技術(shù)產(chǎn)品運營經(jīng)驗 四、Kubernetes 本身就是微服務(wù)架構(gòu) 基于上面這十個設(shè)計要點,我們再回來看 Kube...
摘要:二總結(jié)使用的和的,能夠很好的支持這樣的有狀態(tài)服務(wù)部署到集群上。部署方式有待優(yōu)化本次試驗中使用靜態(tài)方式部署集群,如果節(jié)點變遷時,需要執(zhí)行等命令手動配置集群,嚴重限制了集群自動故障恢復(fù)擴容縮容的能力。 一. 概述 kubernetes通過statefulset為zookeeper、etcd等這類有狀態(tài)的應(yīng)用程序提供完善支持,statefulset具備以下特性: 為pod提供穩(wěn)定的唯一的...
摘要:二總結(jié)使用的和的,能夠很好的支持這樣的有狀態(tài)服務(wù)部署到集群上。部署方式有待優(yōu)化本次試驗中使用靜態(tài)方式部署集群,如果節(jié)點變遷時,需要執(zhí)行等命令手動配置集群,嚴重限制了集群自動故障恢復(fù)擴容縮容的能力。 一. 概述 kubernetes通過statefulset為zookeeper、etcd等這類有狀態(tài)的應(yīng)用程序提供完善支持,statefulset具備以下特性: 為pod提供穩(wěn)定的唯一的...
摘要:在中,被用來管理有狀態(tài)應(yīng)用的對象。并行管理并行管理告訴控制器以并行的方式啟動或者終止所有的。如果設(shè)置為,則控制器將會刪除和重建中的每一。在大部分的情況下,不會使用分隔當希望進行金絲雀發(fā)布,或者執(zhí)行階段發(fā)布時,分隔是很有用的。 在Kubernetes中,StatefulSet被用來管理有狀態(tài)應(yīng)用的API對象。StatefulSets在Kubernetes 1.9版本才穩(wěn)定。Statefu...
閱讀 740·2021-11-24 10:19
閱讀 1126·2021-09-13 10:23
閱讀 3445·2021-09-06 15:15
閱讀 1788·2019-08-30 14:09
閱讀 1702·2019-08-30 11:15
閱讀 1850·2019-08-29 18:44
閱讀 948·2019-08-29 16:34
閱讀 2470·2019-08-29 12:46