k8s leases 详解
Leases 是 k8s 内部提供的一种分布式锁机制,用于锁定共享资源、协调活动。
Lease 翻译成中文是“租约”或“契约”。当某个组件或进程获得了这个 Lease,就相当于它获得了某种特权或责任,可以执行特定的任务。
用途
领导者选举
在分布式系统中,我们经常需要选出一个"领导者"来协调工作。使用 Lease,多个候选者可以竞争获得这个"租约"。谁获得了 Lease,谁就成为领导者。
举个例子:假设有三个工人 A、B、C 要协同工作,但需要一个人来分配任务。他们可以竞争一个 Lease,谁获得了 Lease,谁就成为"领导",负责分配任务。
防止重复操作
Lease 可以确保在一段时间内只有一个组件在执行某项操作,避免重复工作或冲突。
例如:有多个清洁工要打扫一个房间,我们可以用 Lease 来确保同一时间只有一个人在打扫,避免互相干扰。
心跳机制
Lease 可以用作"心跳"信号,证明某个组件还在正常工作。如果一个组件无法续租 Lease,就可能表明它出了问题。
想象一下:工人们每隔一段时间需要在考勤表上签到。如果某个工人长时间没有签到,可能就是遇到了问题。
工作方式
- 获取:组件可以尝试获取一个 Lease。
- 持有:成功获取 Lease 的组件可以在一定时间内持有它。
- 续租:为了继续保持 Lease,组件需要定期更新或"续租"。
- 释放:当不再需要 Lease 时,组件可以主动释放它。
在 k8s 中的应用
在 Kubernetes 中,Lease 对象被广泛用于系统的各个部分:
- 节点心跳:kubelet 使用 Lease 来报告节点的健康状态。
- 控制平面组件的领导者选举:例如 kube-controller-manager 和 kube-scheduler 使用 Lease 来确保同一时间只有一个实例在工作。
- 自定义控制器的协调:开发者可以在自己的控制器中使用 Lease 来实现分布式锁或领导者选举。
实现细节
数据结构
Lease 在 Kubernetes 中是一个自定义资源(Custom Resource),定义在 coordination.k8s.io/v1 API 组中。它的主要字段包括:
- spec.holderIdentity:持有 Lease 的对象的标识符
- spec.leaseDurationSeconds:Lease 的有效期
- spec.acquireTime:获取 Lease 的时间
- spec.renewTime:最后一次更新 Lease 的时间
API
Kubernetes 提供了标准的 API 操作来管理 Lease 对象:
- CREATE:创建新的 Lease
- GET:获取 Lease 信息
- UPDATE:更新 Lease(用于续租)
- DELETE:删除 Lease
存储
Lease 对象存储在 etcd 中,与其他 Kubernetes 资源一样。这保证了高可用性和一致性。
实现细节
a) 获取 Lease:
组件尝试创建一个 Lease 对象。如果创建成功,说明获得了 Lease。如果对象已存在,则尝试更新它。
b) 续租:
持有 Lease 的组件需要周期性地更新 Lease 对象的 renewTime 字段。通常,更新间隔会小于 leaseDurationSeconds,以确保 Lease 不会过期。
c) 释放 Lease:
组件可以通过删除 Lease 对象或更新 holderIdentity 为空来释放 Lease。
d) 处理失败:
如果持有 Lease 的组件崩溃或无法续租,其他组件可以在 leaseDurationSeconds 过后尝试获取 Lease。
示例
以心跳为例:
func (nl *NodeLease) renewLease() error {
lease, err := nl.client.CoordinationV1().Leases(nl.namespace).Get(context.TODO(), nl.leaseName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
// Lease doesn't exist, create a new one
lease = &coordinationv1.Lease{
ObjectMeta: metav1.ObjectMeta{
Name: nl.leaseName,
Namespace: nl.namespace,
},
Spec: coordinationv1.LeaseSpec{
HolderIdentity: &nl.holderIdentity,
LeaseDurationSeconds: &nl.leaseDurationSeconds,
},
}
_, err = nl.client.CoordinationV1().Leases(nl.namespace).Create(context.TODO(), lease, metav1.CreateOptions{})
return err
}
return err
}
// Lease exists, update it
lease.Spec.RenewTime = &metav1.MicroTime{Time: time.Now()}
_, err = nl.client.CoordinationV1().Leases(nl.namespace).Update(context.TODO(), lease, metav1.UpdateOptions{})
return err
}
参考
[1] leases