摘要:節(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 進(jìn)程停止
apiserver 進(jìn)程停止
etcd 進(jìn)程停止
kubernetes 管理網(wǎng)絡(luò) Down
當(dāng)出現(xiàn)這種情況的時候,會出現(xiàn)節(jié)點 NotReady,進(jìn)而當(dāng)kube-controller-manager 中的--pod-eviction-timeout定義的值,默認(rèn) 5 分鐘后,將觸發(fā) Pod eviction 動作。
對于不同類型的 workloads,其對應(yīng)的 pod 處理方式因為 controller-manager 中各個控制器的邏輯不通而不同。總結(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 類型資源,當(dāng)節(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)停止,不再進(jìn)一步刪除,剩下的交給 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)
當(dāng)節(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 為默認(rèn)值 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 標(biāo)記刪除,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ū)是不推薦的,因為可能會對唯一的標(biāo)志符(單調(diào)遞增的序列號)產(chǎn)生影響,如果發(fā)生,對 StatefulSet 是致命的,可能會導(dǎo)致數(shù)據(jù)丟失(可能是應(yīng)用集群腦裂,也可能是對 PV 多寫導(dǎo)致)。
kubectl delete pods
但是這樣刪除仍然需要一些保護(hù)措施,以 Ceph RBD 存儲插件為例,當(dāng)執(zhí)行force delete 前,根據(jù)經(jīng)驗,用戶應(yīng)該先設(shè)置 ceph osd blacklist,防止當(dāng)遷移過程中網(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ù),后果將是嚴(yán)重的
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/32992.html
摘要:核心概念是最小的調(diào)度單元,可以由一個或者多個容器組成。該模式會跟云服務(wù)商有關(guān),比如可以通過等創(chuàng)建一個外部的負(fù)載均衡器,將請求轉(zhuǎn)發(fā)到對應(yīng)的服務(wù)組。而可以提供外部服務(wù)可訪問的負(fù)載均衡器等。 概述 Kubernetes 有各類資源對象來描述整個集群的運行狀態(tài)。這些對象都需要通過調(diào)用 kubernetes api 來進(jìn)行創(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í)行等命令手動配置集群,嚴(yán)重限制了集群自動故障恢復(fù)擴容縮容的能力。 一. 概述 kubernetes通過statefulset為zookeeper、etcd等這類有狀態(tài)的應(yīng)用程序提供完善支持,statefulset具備以下特性: 為pod提供穩(wěn)定的唯一的...
摘要:二總結(jié)使用的和的,能夠很好的支持這樣的有狀態(tài)服務(wù)部署到集群上。部署方式有待優(yōu)化本次試驗中使用靜態(tài)方式部署集群,如果節(jié)點變遷時,需要執(zhí)行等命令手動配置集群,嚴(yán)重限制了集群自動故障恢復(fù)擴容縮容的能力。 一. 概述 kubernetes通過statefulset為zookeeper、etcd等這類有狀態(tài)的應(yīng)用程序提供完善支持,statefulset具備以下特性: 為pod提供穩(wěn)定的唯一的...
摘要:在中,被用來管理有狀態(tài)應(yīng)用的對象。并行管理并行管理告訴控制器以并行的方式啟動或者終止所有的。如果設(shè)置為,則控制器將會刪除和重建中的每一。在大部分的情況下,不會使用分隔當(dāng)希望進(jìn)行金絲雀發(fā)布,或者執(zhí)行階段發(fā)布時,分隔是很有用的。 在Kubernetes中,StatefulSet被用來管理有狀態(tài)應(yīng)用的API對象。StatefulSets在Kubernetes 1.9版本才穩(wěn)定。Statefu...
閱讀 787·2021-11-24 10:19
閱讀 1184·2021-09-13 10:23
閱讀 3506·2021-09-06 15:15
閱讀 1833·2019-08-30 14:09
閱讀 1763·2019-08-30 11:15
閱讀 1903·2019-08-29 18:44
閱讀 998·2019-08-29 16:34
閱讀 2517·2019-08-29 12:46