什么是 Lease?
Lease 对象的核心作用是表示某个实体(通常是一个 Pod 或进程)对某项资源或角色的“持有权”,并且这种持有权是有时间限制的。通过定期续约(renew),持有者可以保持其控制权。如果持有者未能续约,租约到期后,其他实体可以接管。 常见的应用场景包括:
- 领导选举:在分布式系统中,确保只有一个实例(Leader)执行特定任务,其他实例作为 Follower。
- 资源协调:跟踪和管理资源的临时所有权。
- 心跳机制:通过续约时间(RenewTime)检测持有者是否仍然活跃。 Kubernetes 内部的一些组件(如 kube-controller-manager 和 kube-scheduler)就使用 Lease 来实现高可用性和领导选举。
Lease 的工作原理
- 创建租约:
- 一个进程(如你的代码)创建一个 Lease 对象,并声明自己为持有者(HolderIdentity)。
- 续约:
- 持有者需要定期更新 RenewTime,证明自己仍然活跃。通常通过客户端(如 kubectl 或 Go 客户端)调用 Kubernetes API 来更新。
- 失效与接管:
- 如果持有者未能及时续约(例如进程崩溃),其他进程可以通过检查 RenewTime 和 LeaseDurationSeconds 判断租约是否过期,并尝试接管。
- 领导选举:
- 多个实例竞争同一 Lease 对象时,只有成功创建或更新它的实例成为 Leader。
示例场景
假设你用这个 Lease 来实现领导选举:
- 你有一个分布式应用,有 3 个 Pod:pod-1、pod-2、pod-3。
- 它们都尝试创建或更新同一个 Lease 对象(例如 my-leader-lease)。
- pod-1 成功创建,设置 HolderIdentity: “pod-1”,成为 Leader。
- pod-1 每 10 秒更新 RenewTime,保持领导地位。
- 如果 pod-1 崩溃,RenewTime 未更新,pod-2 检测到租约过期,接管并更新 HolderIdentity: “pod-2”。
现有逻辑
// Copyright 2018 The Operator-SDK Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"time"
"github.com/operator-framework/operator-sdk/pkg/k8sutil" // Operator-SDK 提供的工具包,用于获取运行环境信息(如命名空间、Pod 信息)
corev1 "k8s.io/api/core/v1" // Kubernetes Core v1 API,包含 Pod、ConfigMap 等资源定义
apierrors "k8s.io/apimachinery/pkg/api/errors" // Kubernetes API 错误处理包
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" // Kubernetes 元数据定义包,包含 ObjectMeta 等
"k8s.io/apimachinery/pkg/util/wait" // 提供等待和退避逻辑的工具
crclient "sigs.k8s.io/controller-runtime/pkg/client" // controller-runtime 提供的通用客户端,用于操作 Kubernetes 资源
"sigs.k8s.io/controller-runtime/pkg/client/config" // 获取 Kubernetes 配置(如 kubeconfig)
)
// maxBackoffInterval 定义了尝试成为 Leader 时,最大退避等待时间(16秒)。
// 在多次尝试失败后,等待时间会逐渐增加,但不会超过这个值。
const maxBackoffInterval = time.Second * 16
// Become 函数确保当前 Pod 成为其命名空间内的 Leader。
// 如果代码运行在集群外(本地),会跳过领导选举直接返回 nil。
// 它通过创建一个带有当前 Pod 作为 OwnerReference 的 ConfigMap 来实现领导选举。
// ConfigMap 的名称是 lockName,同一时间只能有一个同名 ConfigMap 存在,成功创建者即为 Leader。
// 当 Leader Pod 终止时,垃圾回收机制会删除 ConfigMap,其他 Pod 可以接管。
func leaderBecome(ctx context.Context, lockName string) error {
log.Info("Trying to become the leader.") // 日志:尝试成为 Leader
// 获取当前 Operator 运行的命名空间
ns, err := k8sutil.GetOperatorNamespace()
if err != nil {
// 如果无法获取命名空间(可能是本地运行或无权限)
if err == k8sutil.ErrNoNamespace || err == k8sutil.ErrRunLocal {
log.Info("Skipping leader election; not running in a cluster.") // 日志:不在集群中,跳过选举
return nil
}
return err // 其他错误,返回
}
// 获取 Kubernetes 配置(通常来自 kubeconfig 或 ServiceAccount)
config, err := config.GetConfig()
if err != nil {
return err // 配置获取失败,返回错误
}
// 创建 controller-runtime 的客户端,用于操作 Kubernetes 资源
client, err := crclient.New(config, crclient.Options{})
if err != nil {
return err // 客户端创建失败,返回错误
}
// 获取当前 Pod 的 OwnerReference,表示当前 Pod 是谁
owner, err := myOwnerRef(ctx, client, ns)
if err != nil {
return err // 获取 OwnerReference 失败,返回错误
}
// 检查是否已存在由当前 Pod 拥有的 ConfigMap(可能是进程重启的情况)
existing := &corev1.ConfigMap{}
key := crclient.ObjectKey{Namespace: ns, Name: lockName} // ConfigMap 的键(命名空间+名称)
err = client.Get(ctx, key, existing)
switch {
case err == nil: // ConfigMap 已存在
// 检查现有 ConfigMap 的 OwnerReferences
for _, existingOwner := range existing.GetOwnerReferences() {
if existingOwner.Name == owner.Name { // 如果 Owner 是当前 Pod
log.Info("Found existing lock with my name. I was likely restarted.") // 日志:发现已有锁,可能是重启
log.Info("Continuing as the leader.") // 日志:继续作为 Leader
return nil // 直接返回,当前 Pod 已是 Leader
}
log.Info("Found existing lock", "LockOwner", existingOwner.Name) // 日志:发现其他 Pod 持有的锁
}
case apierrors.IsNotFound(err): // ConfigMap 不存在
log.Info("No pre-existing lock was found.") // 日志:没有预先存在的锁
default: // 其他错误
log.Error(err, "Unknown error trying to get ConfigMap") // 日志:获取 ConfigMap 时发生未知错误
return err
}
// 创建一个新的 ConfigMap,用作领导锁
cm := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: lockName, // ConfigMap 名称,由 lockName 指定
Namespace: ns, // 命名空间
OwnerReferences: []metav1.OwnerReference{*owner}, // 设置当前 Pod 为 Owner
},
}
// 尝试创建 ConfigMap,成为 Leader
backoff := time.Second // 初始退避时间为 1 秒
for { // 无限循环,直到成功或上下文取消
err := client.Create(ctx, cm) // 尝试创建 ConfigMap
switch {
case err == nil: // 创建成功
log.Info("Became the leader.") // 日志:成功成为 Leader
return nil // 返回,当前 Pod 成为 Leader
case apierrors.IsAlreadyExists(err): // ConfigMap 已存在(其他 Pod 是 Leader)
existingOwners := existing.GetOwnerReferences() // 获取当前 ConfigMap 的 Owner
switch {
case len(existingOwners) != 1: // ConfigMap 应该只有一个 Owner
log.Info("Leader lock configmap must have exactly one owner reference.", "ConfigMap", existing)
case existingOwners[0].Kind != "Pod": // Owner 必须是 Pod
log.Info("Leader lock configmap owner reference must be a pod.", "OwnerReference", existingOwners[0])
default: // 正常情况:ConfigMap 由一个 Pod 拥有
leaderPod := &corev1.Pod{}
key = crclient.ObjectKey{Namespace: ns, Name: existingOwners[0].Name} // 当前 Leader Pod 的键
err = client.Get(ctx, key, leaderPod) // 获取 Leader Pod 信息
switch {
case apierrors.IsNotFound(err): // Leader Pod 已删除
log.Info("Leader pod has been deleted, waiting for garbage collection do remove the lock.") // 日志:等待垃圾回收删除 ConfigMap
case err != nil: // 获取 Pod 失败
return err
case isPodEvicted(*leaderPod) && leaderPod.GetDeletionTimestamp() == nil: // Leader Pod 被驱逐但未标记删除
log.Info("Operator pod with leader lock has been evicted.", "leader", leaderPod.Name) // 日志:Leader 被驱逐
log.Info("Deleting evicted leader.") // 日志:删除被驱逐的 Leader
err := client.Delete(ctx, leaderPod) // 删除 Leader Pod
if err != nil {
log.Error(err, "Leader pod could not be deleted.") // 日志:删除失败
}
case leaderPod.GetDeletionTimestamp() != nil: // Leader Pod 已被标记删除(新增逻辑)
log.Info("Operator pod with leader lock has been terminating/completed.", "leader", leaderPod.Name) // 日志:Leader Pod 正在终止
log.Info("Deleting a-lock cm.") // 日志:删除 ConfigMap
errCm := client.Delete(ctx, cm) // 删除 ConfigMap
if errCm != nil {
log.Error(errCm, "Leader pod could not be deleted.") // 日志:删除 ConfigMap 失败
}
err := client.Delete(ctx, leaderPod) // 删除 Leader Pod
if err != nil {
log.Error(err, "Leader pod could not be deleted.") // 日志:删除 Pod 失败
}
default: // Leader Pod 仍然活跃
log.Info("Not the leader. Waiting.") // 日志:当前不是 Leader,继续等待
}
}
// 等待一段时间后重试,带有随机抖动(Jitter)避免所有 Follower 同时竞争
select {
case <-time.After(wait.Jitter(backoff, .2)): // 等待 backoff 时间(带 20% 随机抖动)
if backoff < maxBackoffInterval { // 如果退避时间未达到最大值
backoff *= 2 // 退避时间翻倍
}
continue // 继续下一次循环
case <-ctx.Done(): // 上下文取消
return ctx.Err() // 返回上下文错误
}
default: // 创建 ConfigMap 时发生其他错误
log.Error(err, "Unknown error creating ConfigMap") // 日志:未知错误
return err
}
}
}
// myOwnerRef 返回当前运行 Pod 的 OwnerReference。
// 它依赖环境变量 POD_NAME(通过 downwards API 设置)来识别当前 Pod。
func myOwnerRef(ctx context.Context, client crclient.Client, ns string) (*metav1.OwnerReference, error) {
// 获取当前 Pod 的信息
myPod, err := k8sutil.GetPod(ctx, client, ns)
if err != nil {
return nil, err // 获取失败,返回错误
}
// 构造 OwnerReference
owner := &metav1.OwnerReference{
APIVersion: "v1", // API 版本
Kind: "Pod", // 资源类型
Name: myPod.ObjectMeta.Name, // Pod 名称
UID: myPod.ObjectMeta.UID, // Pod 的唯一标识符
}
return owner, nil
}
// isPodEvicted 检查 Pod 是否被驱逐(Evicted)。
// Pod 被驱逐时,Phase 为 Failed,且 Reason 为 "Evicted"。
func isPodEvicted(pod corev1.Pod) bool {
podFailed := pod.Status.Phase == corev1.PodFailed // Pod 状态为 Failed
podEvicted := pod.Status.Reason == "Evicted" // 原因是 Evicted
return podFailed && podEvicted // 两者都满足则为被驱逐
}
现有逻辑总结
- 目标:
- 通过创建一个 ConfigMap(名称为 a-lock)来实现领导选举,成功创建者成为 Leader。
- ConfigMap 的 OwnerReference 指向当前 Pod,当 Pod 删除时,ConfigMap 会被垃圾回收。
- 流程:
- 检查环境:如果不在集群中运行,跳过领导选举。
- 检查现有锁:如果已有由当前 Pod 拥有的 ConfigMap,直接恢复为 Leader。
- 创建锁:尝试创建新的 ConfigMap,如果失败(已有其他 Leader),检查当前 Leader 状态。
- 处理失效:
- 如果 Leader Pod 已删除,等待垃圾回收。
- 如果 Leader Pod 被驱逐或标记删除,主动删除 Pod 和 ConfigMap。
- 否则,等待并重试(带有退避机制)。
与 Lease 的对比
- 相似点:都用于领导选举,依赖 Kubernetes API 的原子性操作。
- 不同点:
- Lease 是专门为领导选举设计的轻量资源,内置续约机制(RenewTime)。
- ConfigMap 是通用资源,通过 OwnerReference 和垃圾回收间接实现接管,逻辑更复杂。
- 请求开销:
- Lease 需要定期续约和查询。
- 此代码使用轮询和退避,Follower 需反复尝试创建 ConfigMap,也会产生多次请求。
为什么基于 ConfigMap 的方案在主机房断电后表现不佳?(另一个Operator没能及时成为Leader)
1. ConfigMap 依赖垃圾回收机制
- 机制:
- 在现有代码中,ConfigMap 的 OwnerReference 指向 Leader Pod。当 Leader Pod 被删除时,Kubernetes 的垃圾回收器(Garbage Collector, GC)会自动删除 ConfigMap,从而允许新的 Pod 成为 Leader。
- 主机房断电的影响:
- 如果主机房断电,运行 Leader Pod 的节点会突然下线。此时,Kubernetes 集群需要检测到节点不可用,并将该节点上的 Pod 标记为删除(DeletionTimestamp)。
- 问题:节点失联后,Kubernetes 不会立即认为 Pod 已删除,而是依赖 Node Controller 和 Taint-based Eviction 等机制来更新状态,这个过程可能需要几分钟甚至更久。
- 延迟来源:
- Node NotReady 检测:Master 检测节点失联通常依赖 node.kubernetes.io/not-ready 和 node.kubernetes.io/unreachable 污点,默认超时(node-monitor-grace-period)是 40 秒。
- Pod Eviction 超时:Pod 被驱逐的默认超时(eviction-hard 或 eviction-soft)可能配置为几分钟(常见默认值如 5 分钟)。
- 垃圾回收延迟:即使 Pod 被标记删除,GC 删除 ConfigMap 也有一定延迟(通常几秒到几分钟,取决于集群负载和 GC 调度)。
2. 代码中的轮询和退避逻辑
- 机制:
- 现有代码使用轮询方式尝试创建 ConfigMap,如果失败(IsAlreadyExists),会检查现有 Leader Pod 状态,并以指数退避(backoff)等待。
- 退避时间从 1 秒开始,每次翻倍,直到最大值 maxBackoffInterval = 16 秒。
- 主机房断电的影响:
- 当 Leader Pod 的节点断电后,Follower Pod 检测到 ConfigMap 仍存在,但无法立即接管。它会进入等待循环,每次等待时间逐渐增加(1s → 2s → 4s → 8s → 16s)。
- 问题:如果 GC 迟迟未删除 ConfigMap,Follower 的等待时间会累积。例如,等待 5 次退避的总时间是 1 + 2 + 4 + 8 + 16 = 31 秒,但如果需要更多次(因为 GC 延迟),时间会更长。
- 延迟来源:
- 退避累积:退避逻辑本身会拖慢接管速度。
- 未优化检测:代码依赖主动轮询,而非实时监听(Watch),无法立即感知 ConfigMap 删除。
3. Kubernetes 集群的容错机制
- 机制:
- Kubernetes 在设计时倾向于“谨慎”处理节点失联,避免误判(false positive)。这意味着在确认节点和 Pod 真正失效前,会有较长的宽限期。
- 主机房断电的影响:
- 如果整个主机房断电,多个节点可能同时失联,Master 的 Node Controller 和 API Server 负载会激增,导致状态更新变慢。
- 问题:Pod 和 ConfigMap 的状态更新可能被推迟,进一步延长新 Leader 上任时间。
- 延迟来源:
- Node Controller 过载:大规模节点失效可能导致状态同步延迟。
- API Server 压力:大量资源状态变更可能使 API 调用变慢。
4. 代码中的改进逻辑(DeletionTimestamp)
- 机制:
- 现有代码新增了检查 leaderPod.GetDeletionTimestamp() != nil,当 Leader Pod 被标记删除时,主动删除 ConfigMap 和 Pod。
- 主机房断电的影响:
- 这个逻辑依赖 Pod 被标记删除,但断电后 Pod 状态更新依赖 Node Controller,可能需要几分钟。
- 问题:如果节点失联时间过长,DeletionTimestamp 不会立即设置,主动删除 ConfigMap 的逻辑无法触发。
- 延迟来源:
- 状态更新延迟:节点失联到 Pod 被标记删除的时间(默认可能 5-10 分钟)。
断电场景:15 分钟的延迟虽然不常见,但并非完全不合理,尤其在以下条件下:
- Node Eviction 超时较长:如果集群配置了较长的宽限期(如 kube-controller-manager 的 –pod-eviction-timeout=5m 或更高),Pod 状态更新可能延迟 5-10 分钟。
- GC 延迟:大规模断电后,GC 处理大量资源,调度延迟可能累加到几分钟。
- 退避累积:Follower 的退避逻辑在极端情况下可能执行多次(比如 10-20 次),每次 16 秒,总计 160-320 秒(约 3-5 分钟)。
- 集群负载:如果 Master 或 API Server 因断电事件过载,状态同步可能进一步推迟。
- 估算:
- Node 失联检测:5 分钟(默认宽限期)。
- Pod 驱逐 + GC:2-5 分钟。
- Follower 退避:3-5 分钟。
与 Lease 方案的对比
- Lease 的优势:
- 主动续约:Leader 定期更新 RenewTime,Follower 可通过时间戳判断租约是否过期,无需依赖 GC。
- 更快接管:租约过期后(通常 15-30 秒),Follower 可立即尝试接管。
- 典型延迟:在断电场景下,接管时间通常在几十秒到 1-2 分钟(取决于 LeaseDurationSeconds 和网络恢复)。
- 现有 ConfigMap 方案:
- 接管时间受限于 Node 状态更新和 GC,异常场景下可能高达数十分钟。
Lease 资源在 Kubernetes 中的版本局限性分析
背景
Kubernetes 中的 Lease 资源(隶属 coordination.k8s.io API 组)用于节点心跳和领导选举等协调机制,是一个不算核心但也不可忽视的功能。然而,官方文档(https://kubernetes.io/docs/reference/)对Lease 的版本演进记录极为有限:
- 未明确说明何时引入 v1beta1(Beta)。
- 未记录 v1 何时成为稳定版(GA)。
- 仅在 API 参考中提供当前版本(比如 1.29)的字段说明,历史信息几乎为零。 这种文档缺失迫使我们通过自行测试和源码分析来确定 Lease 的版本支持情况。下文记录了这一过程,包括使用的命令、每步的意义,以及最终结论。
查找过程与命令记录
1. 检查集群支持的 API 版本
kubectl get --raw /apis/coordination.k8s.io | python -m json.tool
- 意义:通过 Kubernetes API Server 的 /apis/coordination.k8s.io 端点,获取当前集群支持的 coordination.k8s.io API 版本(比如 v1beta1 或 v1)。python -m json.tool 美化 JSON 输出,便于阅读。
- 目的:验证运行时环境是否支持 Lease 的某个版本,但无法揭示历史。
2. 查看客户端和服务器版本
kubectl version --short
- 意义:显示 kubectl 客户端和 API Server 的版本(如 v1.16.15 或 v1.20.11),确保测试环境明确。
- 目的:为后续测试提供版本上下文,避免混淆。
3. 克隆 Kubernetes 源码
git clone https://github.com/kubernetes/kubernetes.git
- 意义:获取 Kubernetes 完整源码仓库,包含所有版本的历史记录。
- 目的:由于文档不足,直接从源码追溯 Lease 的引入时间。
4. 搜索包含 “lease” 的文件
grep -lER '\blease' ./* | grep -vE '(docs|test|e2e|vendor)'
- 意义:
- grep -lER ‘\blease’ ./*:递归搜索当前目录下所有包含单词 “lease” 的文件,-l 只输出文件名,\b 确保匹配完整单词。
- grep -vE ‘(docs|test|e2e|vendor)’:排除文档、测试和依赖目录,聚焦核心代码。
- 目的:定位 Lease 定义的关键文件(如 staging/src/k8s.io/api/coordination/v1beta1/types.go 和 v1/types.go)。
5. 切换到最新分支
git checkout master
- 意义:确保从最新代码开始分析,包含所有历史提交。
- 目的:避免从某个旧提交开始遗漏后续变更。
6. 查找 v1beta1 的首次出现
git log --follow --name-only -- staging/src/k8s.io/api/coordination/v1beta1/types.go | tail
- 意义:
- --follow:追踪文件重命名历史。
- --name-only:只显示提交涉及的文件名。
- | tail:取最后几行,找到最早提交。
- 结果:
commit f38e952f4eb3a5f4996a2a41bd0eedc98b6637de
Author: wojtekt <wojtekt@google.com>
Date: Tue May 22 16:34:25 2018 +0200
Add coordination API group with Lease type
staging/src/k8s.io/api/coordination/v1beta1/types.go
- 目的:确定 v1beta1 的代码引入点。
7. 检查 f38e952f 的版本
git tag --contains f38e952f4eb3a5f4996a2a41bd0eedc98b6637de
- 意义:列出包含此提交的所有 Tag,揭示它首次出现在哪个版本。
- 结果:包含 v1.12.0 及后续版本。
- 目的:确认 v1beta1 的代码从 1.12.0 开始存在。
8. 验证 f38e952f 的文件状态
git checkout f38e952f4eb3a5f4996a2a41bd0eedc98b6637de
ls -l staging/src/k8s.io/api/coordination/v1beta1/types.go
ls -l staging/src/k8s.io/api/coordination/v1/types.go
- 意义:
- git checkout:切换到该提交。
- ls -l:检查文件是否存在。
- 结果:
- v1beta1/types.go 存在。
- v1/types.go 不存在。
- 目的:确认 f38e952f 只引入了 v1beta1,无 v1。
9. 检查 1.12.0 的文件状态
git checkout v1.12.0
ls -l staging/src/k8s.io/api/coordination/v1/types.go
ls -l staging/src/k8s.io/api/coordination/v1beta1/types.go
- 意义:验证 1.12.0 版本的文件状态。
- 结果:同上,v1beta1 有,v1 无。
- 目的:进一步确认 1.12.0 只包含 v1beta1。
10. 查找 v1 的首次出现(尝试 1)
git log --follow --diff-filter=A --name-only -- staging/src/k8s.io/api/coordination/v1/types.go | tail
- 意义:
- --diff-filter=A:只显示文件添加的提交。
- --follow:追踪重命名。
- 结果:错误输出 v1beta1/types.go(f38e952f)。
- 问题:–follow 误将 v1 历史追溯到 v1beta1。
11. 查找 v1 的首次出现(尝试 2)
git log --follow --diff-filter=A --name-only -- staging/src/k8s.io/api/coordination/v1/types.go
- 意义:完整输出,避免 tail 截断。
- 结果:仍错误指向 v1beta1。
- 问题:同上,–follow 干扰。
12. 查找 v1 的首次出现(修正)
git log --diff-filter=A --name-only -- staging/src/k8s.io/api/coordination/v1/types.go
- 意义:去掉 –follow,直接找 v1/types.go 的添加。
- 结果:
commit 73d14dede6bd6915b608f5fd86c20c9a322847be
Author: wojtekt <wojtekt@google.com>
Date: Wed Dec 19 16:22:05 2018 +0100
Promote Lease API to v1
staging/src/k8s.io/api/coordination/v1/types.go
- 目的:准确找到 v1 的代码引入点。
13. 检查 73d14dede 的版本
git tag --contains 73d14dede6bd6915b608f5fd86c20c9a322847be
- 意义:确认 v1 引入的版本。
- 结果:包含 v1.14.0 及后续版本。
- 目的:锁定 v1 从 1.14.0 开始存在。
结论
通过源码分析和命令追溯,Lease 资源的版本支持情况如下:
- coordination.k8s.io/v1beta1:
- 代码引入:1.12.0(f38e952f,2018-05-22)。
- 运行支持:1.13.0(Alpha,PR #64503,默认关闭,需 feature gate)。
- 说明:1.12.0 仅定义代码,未启用;1.13.0 通过 KEP-589 实现 Alpha 支持。
- coordination.k8s.io/v1:
- 代码引入:1.14.0(73d14dede,2018-12-19)。
- Beta:1.14.0(默认启用)。
- 稳定(GA):1.17.0(官方文档和社区确认)。
- 说明:1.14.0 添加 v1 代码并提升为 Beta,1.17.0 正式稳定。
- 局限性:
- 官方文档未明确记录版本演进(Alpha、Beta、GA 时间点)。
- 需通过源码(f38e952f 和 73d14dede)和 PR(#64503、#71735)推断。
- 早期版本(如 1.13)需手动测试(kind 或 minikube)确认运行支持。
补充说明
- 文档不足:Kubernetes 文档对 Lease 的版本历史几乎无记录,CHANGELOG 提及零散,KEP 和 PR 信息分散,导致用户需自行挖掘。
- 建议:可向 Kubernetes 社区提交 PR(如改进 website 仓库的 API 参考),补充版本演进细节。
- 验证方法:使用 kind 创建历史版本集群(如 kindest/node:v1.13.12),测试 kubectl apply 是否支持 v1beta1 或 v1。