client-go: k8s選主

快速上手

下面這個代碼就是一個選主的大概邏輯

package mainimport ("context""flag""fmt"_ "net/http/pprof""os""path/filepath""time""golang.org/x/exp/rand"v1 "k8s.io/api/core/v1"metav1 "k8s.io/apimachinery/pkg/apis/meta/v1""k8s.io/apimachinery/pkg/util/uuid""k8s.io/client-go/kubernetes""k8s.io/client-go/kubernetes/scheme"corev1 "k8s.io/client-go/kubernetes/typed/core/v1""k8s.io/client-go/tools/clientcmd""k8s.io/client-go/tools/leaderelection""k8s.io/client-go/tools/leaderelection/resourcelock""k8s.io/client-go/tools/record""k8s.io/client-go/util/homedir"
)func main() {ctx := context.Background()var kubeconfig *stringif home := homedir.HomeDir(); home != "" {kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "")}config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)if err != nil {panic(err)}clientset, err := kubernetes.NewForConfig(config)if err != nil {panic(err)}broadcaster := record.NewBroadcaster()broadcaster.StartRecordingToSink(&corev1.EventSinkImpl{Interface: clientset.CoreV1().Events("default"),})eventRecorder := broadcaster.NewRecorder(scheme.Scheme,v1.EventSource{Component: "hello-word"})createIdentity := func() string {hostname, err := os.Hostname()if err != nil {hostname = fmt.Sprintf("rand%d", rand.Intn(10000))}return fmt.Sprintf("%s_%s", hostname, string(uuid.NewUUID()))}lock := &resourcelock.LeaseLock{LeaseMeta: metav1.ObjectMeta{Namespace: "default",Name:      "hello-world",},Client: clientset.CoordinationV1(),LockConfig: resourcelock.ResourceLockConfig{Identity:      createIdentity(),EventRecorder: eventRecorder,},}leaderelection.RunOrDie(ctx, leaderelection.LeaderElectionConfig{Lock:          lock,LeaseDuration: 5 * time.Second,RenewDeadline: 4 * time.Second,RetryPeriod:   2 * time.Second,Callbacks: leaderelection.LeaderCallbacks{OnStartedLeading: func(ctx context.Context) {fmt.Println("start leading")},OnStoppedLeading: func() {fmt.Println("stop leading")},OnNewLeader: func(identity string) {fmt.Printf("new leader: %s\n", identity)},},Coordinated: false,})
}

我們同時啟動多個終端來運行這個程序,并且殺掉主節點來模擬節點掛掉,觀察是否會重新進行選舉出新的master
在這里插入圖片描述

從圖中可以看到,第一個程序選為主節點之后,第二三個程序自動成為slave,我們 kill 掉第一個程序之后,第二個程序搶到了鎖成為了 master

租約 Lease

k8s 內置很多種資源,其中 lease 也是k8s的一種資源,顧名思義表達的是租戶對某種資源的

占有的信息表示

?  ~ k get lease -A                                                                 
NAMESPACE         NAME                      HOLDER                                                      AGE
default           hello-world               VM-221-245-tencentos_bada2219-3a27-4b19-8b80-23fc05604391   6d7h
kube-node-lease   vm-221-245-tencentos      vm-221-245-tencentos                                        36d
kube-system       kube-controller-manager   VM-221-245-tencentos_8f5a4f85-ca0c-4b5f-ac81-8b0ff5ff2e49   36d
kube-system       kube-scheduler            VM-221-245-tencentos_4fab96c4-156b-4a77-b862-87224be44cb2   36d

比如我們查看一個 k8s 的 node 的 lease

?  ~ k get lease vm-221-245-tencentos -n kube-node-lease -oyaml                   
apiVersion: coordination.k8s.io/v1
kind: Lease                                    # 資源的種類
metadata:creationTimestamp: "2024-09-27T12:24:07Z"    # 這個資源的創建時間戳name: vm-221-245-tencentos                   # 名稱namespace: kube-node-lease                   # 命名空間ownerReferences:- apiVersion: v1kind: Nodename: vm-221-245-tencentos                 # 這個資源的占有名稱uid: 5df61ad6-cc1a-4669-8b0e-d48a5b0ffb91resourceVersion: "3811080"uid: 411c6d4e-5afb-4eba-a1a0-8a56d00b75db
spec:holderIdentity: vm-221-245-tencentosleaseDurationSeconds: 40                    # 租約的時常40srenewTime: "2024-11-03T01:35:05.171458Z"    # 租約的更新時間

而實際上 leader 選舉中的資源 lock 其實就是一種 lease,表明 master 主節點持有對某個資源
的唯一性

查看 https://github.com/kubernetes/client-go/tree/master/tools/leaderelection 的源文件

可以看到 leaderElection 的目錄結構,主要分為 resourcelock 和 leaderelection 的主文件,

文件內容不是很多

?  leaderelection git:(master) tree                                                                                                  jinchaozhu@VM-221-245-tencentos leaderelection %
.
├── healthzadaptor.go
├── healthzadaptor_test.go
├── leaderelection.go
├── leaderelection_test.go
├── leasecandidate.go
├── leasecandidate_test.go
├── metrics.go
├── OWNERS
└── resourcelock├── interface.go├── leaselock.go└── multilock.go

數據結構

存儲 Lease 相關的信息

// LeaderElectionRecord is the record that is stored in the leader election annotation.
// This information should be used for observational purposes only and could be replaced
// with a random string (e.g. UUID) with only slight modification of this code.
// TODO(mikedanese): this should potentially be versioned
type LeaderElectionRecord struct {// leader的標識HolderIdentity       string                      `json:"holderIdentity"`// 選舉間隔LeaseDurationSeconds int                         `json:"leaseDurationSeconds"`// 選舉成為leader的時間AcquireTime          metav1.Time                 `json:"acquireTime"`// 續任時間RenewTime            metav1.Time                 `json:"renewTime"`// leader位置的轉讓次數LeaderTransitions    int                         `json:"leaderTransitions"`// 選舉策略Strategy             v1.CoordinatedLeaseStrategy `json:"strategy"`PreferredHolder      string                      `json:"preferredHolder"`
}

Elector 相關的配置文件

type LeaderElectionConfig struct {// 鎖,用來保證時序競態Lock rl.Interface// 非leader候選者嘗試獲取leadership的間隔時間// Core clients default this value to 15 seconds.LeaseDuration time.Duration// leade 放棄leadership角色之前的確認時間// Core clients default this value to 10 seconds.RenewDeadline time.Duration// 候選者應該獲取leader角色的重試時間// Core clients default this value to 2 seconds.RetryPeriod time.Duration// 回掉函數// 比如開始leader選舉觸發什么、成為leader觸發什么、放棄leader觸發什么Callbacks LeaderCallbacks// WatchDog is the associated health checker// WatchDog may be null if it's not needed/configured.WatchDog *HealthzAdaptor// ReleaseOnCancel should be set true if the lock should be released// when the run context is cancelled. If you set this to true, you must// ensure all code guarded by this lease has successfully completed// prior to cancelling the context, or you may have two processes// simultaneously acting on the critical path.ReleaseOnCancel bool// Name is the name of the resource lock for debuggingName string// Coordinated will use the Coordinated Leader Election feature// WARNING: Coordinated leader election is ALPHA.Coordinated bool
}

主要邏輯

選舉的邏輯大概如下:

  1. 剛開始實例啟動的時候,各個實例都是一個 LeaderElector 的角色,最先開始選舉的就成

為 leader;成為 leader 之后便會維護一個 LeaseLock 供每個 LeaderElector 進行訪問查詢

  1. 其余的 LeaderElector 進入候選狀態,hang 住監控 leader 的狀態,必要時異常會再次參與選舉
  2. Leader 獲取到 Leadership 之后會持續性的刷新自己的 leader 狀態
func (le *LeaderElector) Run(ctx context.Context) {defer runtime.HandleCrash()defer le.config.Callbacks.OnStoppedLeading()  // StoppedLeading 函數每個節點都會執行// 未獲得leadership的節點這里就會返回// acquire 就是各個節點來爭搶 leadershipif !le.acquire(ctx) {return // ctx signalled done}ctx, cancel := context.WithCancel(ctx)defer cancel()// 這里只有獲取到leadership的角色的節點才會執行 StartedLeadinggo le.config.Callbacks.OnStartedLeading(ctx)// 獲取到 leadership 之后不停的刷新當前的狀態信息le.renew(ctx)
}func (le *LeaderElector) acquire(ctx context.Context) bool {...klog.Infof("attempting to acquire leader lease %v...", desc)wait.JitterUntil(func() {if !le.config.Coordinated {succeeded = le.tryAcquireOrRenew(ctx)    // 嘗試競爭} else {succeeded = le.tryCoordinatedRenew(ctx)}....klog.Infof("successfully acquired lease %v", desc)cancel().....}, le.config.RetryPeriod, JitterFactor, true, ctx.Done())return succeeded
}

核心邏輯 tryAcquireOrRenew

// tryAcquireOrRenew tries to acquire a leader lease if it is not already acquired,
// else it tries to renew the lease if it has already been acquired. Returns true
// on success else returns false.
func (le *LeaderElector) tryAcquireOrRenew(ctx context.Context) bool {now := metav1.NewTime(le.clock.Now())leaderElectionRecord := rl.LeaderElectionRecord{// 這里 identity 為當前競選者的標識HolderIdentity:       le.config.Lock.Identity(),    LeaseDurationSeconds: int(le.config.LeaseDuration / time.Second),RenewTime:            now,AcquireTime:          now,}// 1.判斷是否是Leader,如果是Leader并且Lease有效,則進行 Lock 的信息更新if le.IsLeader() && le.isLeaseValid(now.Time) {oldObservedRecord := le.getObservedRecord()leaderElectionRecord.AcquireTime = oldObservedRecord.AcquireTimeleaderElectionRecord.LeaderTransitions = oldObservedRecord.LeaderTransitionserr := le.config.Lock.Update(ctx, leaderElectionRecord)........}// 2.不是Leader,則進行鎖的獲取oldLeaderElectionRecord, oldLeaderElectionRawRecord, err := le.config.Lock.Get(ctx)if err != nil {........}// 3.對比檢查是否 Elctection 的 Record 信息// 需要更新則刷新本地的緩存信息if !bytes.Equal(le.observedRawRecord, oldLeaderElectionRawRecord) {le.setObservedRecord(oldLeaderElectionRecord)le.observedRawRecord = oldLeaderElectionRawRecord}if len(oldLeaderElectionRecord.HolderIdentity) > 0 && le.isLeaseValid(now.Time) && !le.IsLeader() {klog.V(4).Infof("lock is held by %v and has not yet expired", oldLeaderElectionRecord.HolderIdentity)return false}// 4. 按照是否Leader判斷是否進行更新 ElectionRecordif le.IsLeader() {leaderElectionRecord.AcquireTime = oldLeaderElectionRecord.AcquireTimeleaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitionsle.metrics.slowpathExercised(le.config.Name)} else {leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions + 1}// 5.更新鎖本身的信息if err = le.config.Lock.Update(ctx, leaderElectionRecord); err != nil {klog.Errorf("Failed to update lock: %v", err)return false}// 更新鎖成功則說明當前節點持有鎖:搶鎖成功/續期成功le.setObservedRecord(&leaderElectionRecord)return true
}

上面的 Lock 其實是自實現的一種 LeaseLock

// Interface offers a common interface for locking on arbitrary
// resources used in leader election.  The Interface is used
// to hide the details on specific implementations in order to allow
// them to change over time.  This interface is strictly for use
// by the leaderelection code.
type Interface interface {// Get returns the LeaderElectionRecordGet(ctx context.Context) (*LeaderElectionRecord, []byte, error)// Create attempts to create a LeaderElectionRecordCreate(ctx context.Context, ler LeaderElectionRecord) error// Update will update and existing LeaderElectionRecordUpdate(ctx context.Context, ler LeaderElectionRecord) error.....
}// Get returns the election record from a Lease spec
func (ll *LeaseLock) Get(ctx context.Context) (*LeaderElectionRecord, []byte, error) {lease, err := ll.Client.Leases(ll.LeaseMeta.Namespace).Get(ctx, ll.LeaseMeta.Name, metav1.GetOptions{})if err != nil {return nil, nil, err}ll.lease = leaserecord := LeaseSpecToLeaderElectionRecord(&ll.lease.Spec)recordByte, err := json.Marshal(*record)if err != nil {return nil, nil, err}return record, recordByte, nil
}// Create attempts to create a Lease
func (ll *LeaseLock) Create(ctx context.Context, ler LeaderElectionRecord) error {var err errorll.lease, err = ll.Client.Leases(ll.LeaseMeta.Namespace).Create(ctx, &coordinationv1.Lease{ObjectMeta: metav1.ObjectMeta{Name:      ll.LeaseMeta.Name,Namespace: ll.LeaseMeta.Namespace,},Spec: LeaderElectionRecordToLeaseSpec(&ler),}, metav1.CreateOptions{})return err
}

怎么判斷當前leader是持有租約的呢?

func (le *LeaderElector) isLeaseValid(now time.Time) bool {return le.observedTime.Add(time.Second * time.Duration(le.getObservedRecord().LeaseDurationSeconds)).After(now)
}

其實就是判斷上次觀察到的時間與當前之間的差是否在 LeaseDurationSeconds 的范圍內,在
范圍內就代表是有效的

那這個選主的主到底是怎么判斷的呢?

我們查看下面的判斷邏輯

// IsLeader returns true if the last observed leader was this client else returns false.
func (le *LeaderElector) IsLeader() bool {return le.getObservedRecord().HolderIdentity == le.config.Lock.Identity()
}

其實就是拿當前的 ElectionRecord 和每個實例啟動時的配置文件里面的 Identity 來進行比較
判斷是否一致即可

而這個 ObservedRecord 的信息是從 k8s 里面進行獲取的,這就保證了唯一性

Identity 是每個實例啟動的唯一標識,這個字段千萬不能重復,否則選舉一定失敗,報錯如下

E1103 15:44:21.019391 3024650 leaderelection.go:429] Failed to update lock optimitically: Operation cannot be fulfilled on leases.coordination.k8s.io "hello-world": the object has been modified; please apply your changes to the latest version and try again, falling back to slow path

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/90306.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/90306.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/90306.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

為什么Java的String不可變?

為什么Java的String不可變? 場景: 你在開發多線程用戶系統時,發現用戶密碼作為String傳遞后,竟被其他線程修改。這種安全隱患源于對String可變性的誤解。Java將String設計為不可變類,正是為了解決這類核心問題。 1??…

在Ubuntu上使用QEMU學習RISC-V程序(1)起步第一個程序

文章目錄一、 引言二、 環境準備三、編寫簡單的RISC-V程序四、 編譯步驟詳解五、使用QEMU運行程序六、程序詳解七、退出QEMU八、總結附錄:QEMU中通過UTRA顯示字符工作原理1、內存映射I/O原理2、add.s程序工作流程3、關鍵指令解析4、QEMU模擬的UART控制器5、為什么不…

R擬合 | 一個分布能看到三個峰,怎么擬合出這三個正態分布的參數? | 高斯混合模型 與 EM算法

1. 效果已知數據符合上圖分布,怎么求下圖的三個分布的參數mu, sigma,及每個分布的權重 lambda? 2. 代碼: 高斯混合模型(Gaussian Mixture Model,簡稱GMM) library(mixtools) set.seed(123) # 確保結果可重復…

Excel自動分列開票工具推薦

軟件介紹 本文介紹一款基于Excel VBA開發的自動分列開票工具,可高效處理客戶對賬單并生成符合要求的發票清單。 軟件功能概述 該工具能夠將客戶對賬單按照訂單號自動拆分為獨立文件,并生成可直接導入發票清單系統的標準化格式。 軟件特點 這是一款體…

【自用】JavaSE--Stream流

概述獲取Stream流集合的stream流集合名.stream( );collection集合List集合與Set集合都屬于Collection集合,因此可以直接調用stream方法獲取stream流,示例如下結果>map集合map集合存在鍵值對,因此無法使用該方法直接獲取stream流&#xff0…

【Elasticsearch】快照與恢復功能詳解

《Elasticsearch 集群》系列,共包含以下文章: 1?? 冷熱集群架構2?? 合適的鍋炒合適的菜:性能與成本平衡原理公式解析3?? ILM(Index Lifecycle Management)策略詳解4?? Elasticsearch 跨機房部署5?? 快照與恢…

技嘉z370主板開啟vtx

技嘉z370vtx應該默認就是開啟狀態,雖然主板開啟的vtx但是系統默認設置會導致vtx不能使用 1. 關閉hyper-V,Windows虛擬機監控程序平臺,虛擬機平臺 控制面板->程序->啟用或關閉windows功能 2.以管理員身份運行CMD bcdedit /set hypervisorlaunchtype off 3.…

Springmvc的自動解管理

中央轉發器&#xff08;DispatcherServlet&#xff09;控制器視圖解析器靜態資源訪問消息轉換器格式化靜態資源管理一、中央轉發器Xml無需配置<servlet><servlet-name>chapter2</servlet-name><servlet-class>org.springframework.web.servlet.Dispatc…

C#_定時器_解析

問題一:這里加lock是啥意思?它的原理是, 為什么可以鎖住? private readonly Timer _timer;/// <summary>/// 構造函數中初始化定時器/// </summary>public FtpTransferService(){// 初始化定時器_timer new Timer(_intervalMinutes * 60 * 1000);_timer.Elapsed…

Trae開發uni-app+Vue3+TS項目飄紅踩坑

前情 Trae IDE上線后我是第一時間去使用體驗的&#xff0c;但是因為一直排隊問題不得轉戰Cursor&#xff0c;等到Trae出付費模式的時候&#xff0c;我已經辦了Cursor的會員&#xff0c;本來是想等會員過期了再轉戰Trae的&#xff0c;但是最近Cursor開始做妖了 網上有一堆怎么…

低代碼中的統計模型是什么?有什么作用?

低代碼開發平臺中的統計模型是指通過可視化配置、拖拽操作或少量代碼即可應用的數據分析工具&#xff0c;旨在幫助技術人員及非技術人員快速實現數據描述、趨勢預測和業務決策。其核心價值在于降低數據分析門檻&#xff0c;使業務人員無需深入掌握統計原理或編程技能&#xff0…

Linux 下在線安裝啟動VNC

描述 Linux中的VNC就類似于Windows中的遠程桌面系統 本文只記錄在Cent OS 7的系統下在線安裝VNC。 安裝VNC 1、安裝VNC yum install tigervnc-server2、配置VNC的密碼 為用戶設置 VNC 密碼&#xff08;首次運行會提示輸入&#xff0c;也可以提前輸入&#xff09; vncpasswd密碼…

支持OCR和AI解釋的Web PDF閱讀器:解決大文檔閱讀難題

支持OCR和AI解釋的Web PDF閱讀器&#xff1a;解決大文檔閱讀難題一、背景&#xff1a;為什么需要這個工具&#xff1f;問題場景解決方案二、技術原理&#xff1a;如何實現這些功能&#xff1f;1、核心技術組件2、工作流程3、關鍵點三、操作指南1、環境準備2、生成Html代碼3、We…

研發過程都有哪些

產品規劃與定義 (Product Planning & Definition) 在詳細的需求調研之前&#xff0c;通常會進行市場分析、競品分析、確立產品目標和核心價值。這個階段決定了“我們要做什么”以及“為什么要做”。 系統設計與架構 (System & Architectural Design) 這是開發的“藍圖”…

舊物回收小程序系統開發——開啟綠色生活新篇章

在當今社會&#xff0c;環保已經成為全球關注的焦點話題。隨著人們生活水平的提高&#xff0c;消費能力不斷增強&#xff0c;各類物品的更新換代速度日益加快&#xff0c;大量舊物被隨意丟棄&#xff0c;不僅造成了資源的巨大浪費&#xff0c;還對環境產生了嚴重的污染。在這樣…

UE5 UI 水平框

文章目錄slot區分尺寸和對齊方式尺寸&#xff1a;自動模式尺寸&#xff1a;填充模式對齊常用設置所有按鈕大小一致&#xff0c;不受文本影響靠右排列和unity的HorizontalLayout不太一樣slot 以在水平框中放入帶文字的按鈕為例 UI如下布置 按鈕的大小受slot的尺寸、對齊和內部…

【Golang】Go語言變量

Go語言變量 文章目錄Go語言變量一、Go語言變量二、變量聲明2.1、第一種聲明方式2.2、第二種聲明方式2.3、第三種聲明方式2.4、多變量聲明2.5、打印變量占用字節一、Go語言變量 變量來源于數學&#xff0c;是計算機語言中能存儲計算結果或能表示值抽象的概念變量可以通過變量名…

Qt WebEngine Widgets的使用

一、Qt WebEngine基本概念Qt WebEngine中主要分為三個模塊&#xff1a;Qt WebEngine Widgets模塊&#xff0c;主要用于創建基于C Widgets部件的Web程序&#xff1b;Qt WebEngine模塊用來創建基于Qt Quick的Web程序&#xff1b;Qt WebEngine Core模塊用來與Chromeium交互。網頁玄…

【C++】標準模板庫(STL)—— 學習算法的利器

【C】標準模板庫&#xff08;STL&#xff09;—— 學習算法的利器學習 STL 需要注意的幾點及 STL 簡介一、什么是 STL&#xff1f;二、學習 STL 前的先修知識三、STL 常見容器特點對比四、學習 STL 的關鍵注意點五、STL 學習路線建議六、總結七、下一章 vector容器快速上手學習…

YOLO算法演進綜述:從YOLOv1到YOLOv13的技術突破與應用實踐,一文掌握YOLO家族全部算法!

引言&#xff1a;介紹目標檢測技術背景和YOLO算法的演進意義。YOLO算法發展歷程&#xff1a;使用階段劃分方式系統梳理各代YOLO的技術演進&#xff0c;包含早期奠基、效率優化、注意力機制和高階建模四個階段。YOLOv13的核心技術創新&#xff1a;詳細解析HyperACE機制、FullPAD…