Karmada karmadactl uncordon 命令详解:将集群重新标记为可调度
【免费下载链接】karmadaOpen, Multi-Cloud, Multi-Cluster Kubernetes Orchestration项目地址: https://gitcode.com/GitHub_Trending/ka/karmada
karmadactl uncordon是 Karmada 多集群编排平台中用于"解除集群封锁"的命令,它通过移除集群上的cluster.karmada.io/unschedulable污点(taint),将处于维护/封锁状态的成员集群重新标记为可调度(schedulable),从而让 Karmada 调度器可以再次向该集群分发工作负载。本文以 karmadactl uncordon 官方命令参考 为骨架,结合 Karmada 仓库源码(cordon 命令实现、污点常量定义、调度器 TaintToleration 插件)深入讲解其用法、参数、底层原理与完整运维流程。读完本文,你将掌握:如何用一条命令安全地恢复一个被 cordon 的集群、--dry-run的预演用法、以及 uncordon 背后的"污点-容忍"调度机制。
命令概述:uncordon 与 cordon 的对称关系
在 Karmada 中,cordon与uncordon是一对互补的集群管理命令,用于在不删除成员集群的前提下,临时控制该集群是否接收新的调度任务:
| 命令 | 作用 | 默认调度行为 |
|---|---|---|
karmadactl cordon CLUSTER | 将集群标记为不可调度(unschedulable),防止新资源被调度到该集群 | 调度器不再向其分配新工作负载 |
karmadactl uncordon CLUSTER | 将集群重新标记为可调度(schedulable),恢复其接收调度任务的能力 | 调度器恢复向其分配工作负载 |
从源码注释可以确认二者的语义定位(cordon.go):
cordonLong = templates.LongDesc(` Mark cluster as unschedulable.`) uncordonLong = templates.LongDesc(` Mark cluster as schedulable.`)以及两个内部状态常量:
const ( // DesiredCordon a flag indicate karmadactl.RunCordonOrUncordon cordon a cluster, // cordon prevent new resource scheduler to cordoned cluster. DesiredCordon = iota // DesiredUnCordon a flag indicate karmadactl.RunCordonOrUncordon uncordon a cluster. DesiredUnCordon )也就是说,uncordon与cordon在实现上共享同一套核心逻辑RunCordonOrUncordon,仅通过desired参数区分"加污点"与"去污点"两种动作。在 karmadactl 主命令注册处 中,二者都被归入 "Cluster Management Commands"(集群管理命令组),与taint命令并列:
{ Message: "Cluster Management Commands:", Commands: []*cobra.Command{ cordon.NewCmdCordon(f, parentCommand), cordon.NewCmdUncordon(f, parentCommand), taint.NewCmdTaint(f, parentCommand), }, },命令语法(Synopsis)
uncordon命令的用法非常简洁,只接收一个集群名作为位置参数:
karmadactl uncordon CLUSTER官方文档给出的标准示例是(karmadactl_uncordon.md):
# Mark cluster "foo" as schedulable. karmadactl uncordon foo即:执行karmadactl uncordon foo后,名为foo的成员集群将被标记为可调度。
参数约束从 cordon.go 的 Complete 方法 中可以明确看到:
func (o *CommandCordonOption) Complete(args []string) error { // Get cluster name from the command args. if len(args) == 0 { return errors.New("cluster name is required") } if len(args) > 1 { return errors.New("more than one cluster name is not supported") } o.ClusterName = args[0] return nil }因此:
- 必须提供集群名,缺省会直接报错
cluster name is required; - 一次只能操作一个集群,传入多个名称会报错
more than one cluster name is not supported; - 集群名不支持通配符或批量操作,需要逐个 uncordon。
另外,命令定义中通过ValidArgsFunction: utilcomp.SpecifiedResourceTypeAndNameCompletionFunc(f, []string{"cluster"})(cordon.go)为CLUSTER参数注册了基于集群资源的 shell 自动补全,交互式终端下输入 Tab 可以提示当前已注册的集群名。
选项(Options)详解
uncordon自身的选项(不含继承项)如下表,与 karmadactl_uncordon.md 完全一致:
| 选项 | 类型 | 说明 |
|---|---|---|
--dry-run | bool | 以 dry-run 模式运行命令,不发起任何服务器请求,仅模拟执行 |
-h, --help | bool | 显示uncordon命令的帮助信息 |
--karmada-context string | string | 要使用的 kubeconfig 中的 context 名称 |
--kubeconfig string | string | CLI 请求所使用的 kubeconfig 文件路径 |
这些 flag 的来源可从源码确认:NewCmdUncordon中通过options.AddKubeConfigFlags(flags)注册--kubeconfig与--karmada-context,通过flags.BoolVar(&opts.DryRun, "dry-run", false, ...)注册--dry-run(cordon.go)。其中 kubeconfig 相关 flag 的具体实现在 pkg/karmadactl/options/global.go:
// AddKubeConfigFlags adds flags to the specified FlagSet. func AddKubeConfigFlags(flags *pflag.FlagSet) { flags.StringVar(DefaultConfigFlags.KubeConfig, "kubeconfig", *DefaultConfigFlags.KubeConfig, "Path to the kubeconfig file to use for CLI requests.") flags.StringVar(DefaultConfigFlags.Context, "karmada-context", *DefaultConfigFlags.Context, "The name of the kubeconfig context to use") }使用要点:
--kubeconfig:默认取~/.kube/config(即默认 kubeconfig 路径)。uncordon操作的是 Karmada 控制面(karmada-apiserver)上的 Cluster 对象,因此这里的 kubeconfig 必须指向包含 Karmada 控制面访问凭据的配置文件,而不是成员集群的 kubeconfig;--karmada-context:当 kubeconfig 中包含多个 context(例如同时有 Karmada 控制面与多个成员集群的 context)时,用它指定使用名为该值的 context 来发起请求;--dry-run:预演模式,不真正修改集群状态,适合在维护窗口前核对命令参数是否正确。关于 dry-run 的语义,命令选项结构体注释 写得很清楚:"DryRun tells if run the command in dry-run mode, without making any server requests."(dry-run 模式下完全不发起服务器请求)。
继承自父命令的选项(Options inherited from parent commands)
与 Karmada 其他 CLI 命令一致,uncordon还会继承一组来自 klog 的日志控制选项,它们与命令本身的业务逻辑无关,仅在排查 CLI 行为时需要关注:
| 选项 | 说明 |
|---|---|
--add-dir-header | 为日志消息头附加文件目录(true 时生效) |
--alsologtostderr | 同时将日志输出到 stderr 和文件(当-logtostderr=true时无效果) |
--alsologtostderrthreshold severity | 当日志级别达到或超过该阈值时输出到 stderr(--alsologtostderr=true时生效) |
--legacy-stderr-threshold-behavior | 为 true 时,logtostderr=true情况下忽略stderrthreshold(默认 true) |
--log-backtrace-at traceLocation | 当日志命中file:N时输出堆栈跟踪(默认:0) |
--log-dir string | 非空时,日志文件写入该目录(-logtostderr=true时无效果) |
--log-file string | 非空时,使用该文件作为日志文件(-logtostderr=true时无效果) |
--log-file-max-size uint | 日志文件最大大小(MB),0 表示不限制(默认 1800) |
--logtostderr | 将日志输出到 stderr 而非文件(默认 true) |
--one-output | 为 true 时只向日志自身的原生级别写入,不再向更低级别写入(-logtostderr=true时无效果) |
--skip-headers | 为 true 时,日志消息不添加头部前缀 |
--skip-log-headers | 为 true 时,打开日志文件时不写头部(-logtostderr=true时无效果) |
--stderrthreshold severity | 日志级别达到或超过该阈值时输出到 stderr(默认 2) |
-v, --v Level | 日志详细程度级别 |
--vmodule moduleSpec | 以pattern=N逗号分隔列表形式,按文件过滤日志级别 |
这些继承选项与karmadactl主命令共享,完整清单见 karmadactl_uncordon.md。
实战用法:完整运维流程示例
典型场景:集群维护与恢复
Karmada 中 uncordon 最常见的应用场景是集群维护窗口管理:
# 1. 查看当前已加入 Karmada 的成员集群 karmadactl get clusters # 2. 进入维护前,封锁目标集群,停止接收新的调度任务 karmadactl cordon member-cluster-1 # 3. 确认封锁生效(可观察到集群状态或结合 scheduler 日志确认) karmadactl get cluster member-cluster-1 -o yaml # 此时 member-cluster-1 的 spec.taints 中应包含: # - effect: NoSchedule # key: cluster.karmada.io/unschedulable # 4. 维护完成后,解除封锁,恢复调度能力 karmadactl uncordon member-cluster-1 # 5. 结果确认(控制台输出 "member-cluster-1 cluster uncordoned")预演(Dry-run)用法
在对生产集群执行 uncordon 前,可以先预演一遍:
karmadactl uncordon member-cluster-1 --dry-rundry-run 模式下命令不会向 karmada-apiserver 发起任何写请求,输出结果与真实执行一致(member-cluster-1 cluster uncordoned),可用于核对集群名、context 等参数是否配置正确。
指定 kubeconfig 与 context
当你的 kubeconfig 同时包含多个 Karmada 控制面(如多套环境)时:
# 使用指定 kubeconfig 文件 karmadactl uncordon foo --kubeconfig /path/to/karmada.config # 使用 kubeconfig 中的指定 context karmadactl uncordon foo --karmada-context karmada-prod底层原理:污点(Taint)机制
uncordon并不是直接修改集群的某个"调度开关"字段,而是通过 Kubernetes 标准的污点(Taint)机制实现的。命令内部操作的关键对象是集群spec.taints中的一条不可调度污点。
不可调度污点的定义
污点的键名定义在 pkg/apis/cluster/v1alpha1/well_known_constants.go:
const ( // TaintClusterUnscheduler will be added when cluster becomes unschedulable // and removed when cluster becomes schedulable. TaintClusterUnscheduler = "cluster.karmada.io/unschedulable" // TaintClusterNotReady will be added when cluster is not ready // and removed when cluster becomes ready. TaintClusterNotReady = "cluster.karmada.io/not-ready" // TaintClusterUnreachable will be added when cluster becomes unreachable // (corresponding to ClusterConditionReady status ConditionUnknown) // and removed when cluster becomes reachable (ClusterConditionReady status ConditionTrue). TaintClusterUnreachable = "cluster.karmada.io/unreachable" )其中TaintClusterUnscheduler(cluster.karmada.io/unschedulable)是 cordon/uncordon 命令专门操作的污点,其 Effect 为NoSchedule,由命令内部构造:
unschedulerTaint := corev1.Taint{ Key: clusterv1alpha1.TaintClusterUnscheduler, Effect: corev1.TaintEffectNoSchedule, }对应地,TaintClusterNotReady(cluster.karmada.io/not-ready)与TaintClusterUnreachable(cluster.karmada.io/unreachable)是由集群控制器根据集群健康状态自动添加的污点,与手动运维的 uncordon 命令无关。
uncordon 的执行链路
uncordon的完整执行链路在 cordon.go 的 RunCordonOrUncordon 中:
func RunCordonOrUncordon(desired int, f util.Factory, opts CommandCordonOption) error { cordonOrUncordon := "cordon" if desired == DesiredUnCordon { cordonOrUncordon = "un" + cordonOrUncordon } karmadaClient, err := f.KarmadaClientSet() if err != nil { return err } cluster, err := karmadaClient.ClusterV1alpha1().Clusters().Get(context.TODO(), opts.ClusterName, metav1.GetOptions{}) if err != nil { return err } cordonHelper := newCordonHelper(cluster) if !cordonHelper.updateIfRequired(desired) { fmt.Printf("%s cluster %s\n", cluster.Name, alreadyStr(desired)) return nil } if !opts.DryRun { err := cordonHelper.patchOrReplace(karmadaClient) if err != nil { return err } } fmt.Printf("%s cluster %sed\n", cluster.Name, cordonOrUncordon) return nil }流程可以拆解为四步:
- 获取集群对象:通过
KarmadaClientSet().ClusterV1alpha1().Clusters().Get()从 karmada-apiserver 读取目标 Cluster 的完整对象; - 判断是否真的需要变更:调用
updateIfRequired检查目标状态。若集群"本来就没有/本来就有"不可调度污点,则直接输出already uncordoned/already cordoned并返回,不产生任何写操作; - 非 dry-run 时写回:通过
patchOrReplace将变更后的污点列表写回 karmada-apiserver; - 输出结果:成功执行后输出
xxx cluster uncordoned。
幂等性判断逻辑
updateIfRequired(cordon.go)保证了命令的幂等性:
func (c *cordonHelper) updateIfRequired(desired int) bool { c.desired = desired if desired == DesiredCordon && !c.hasUnschedulerTaint() { return true } if desired == DesiredUnCordon && c.hasUnschedulerTaint() { return true } return false } func (c *cordonHelper) hasUnschedulerTaint() bool { unschedulerTaint := corev1.Taint{ Key: clusterv1alpha1.TaintClusterUnscheduler, Effect: corev1.TaintEffectNoSchedule, } for _, taint := range c.cluster.Spec.Taints { if taint.MatchTaint(&unschedulerTaint) { return true } } return false }- 执行 uncordon 时,若集群存在
cluster.karmada.io/unschedulable(NoSchedule)污点 → 执行移除; - 若集群不存在该污点 → 输出
<cluster> cluster already uncordoned,直接结束。
这意味着重复执行 uncordon 是安全的,不会报错也不会产生多余的 API 请求。
写回方式:优先补丁,回退更新
patchOrReplace(cordon.go)采用"优先 patch、失败则 update"的策略:
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, c.cluster) if err == nil { _, err = client.Patch(context.TODO(), c.cluster.Name, types.MergePatchType, patchBytes, metav1.PatchOptions{}) } else { _, err = client.Update(context.TODO(), c.cluster, metav1.UpdateOptions{}) }- 先将修改前后的 Cluster 对象分别序列化为 JSON;
- 尝试生成strategic merge patch,生成成功则走
Patch(MergePatchType)请求,避免覆盖其他字段的并发修改; - 若 patch 构造失败(例如对象无法编码为 JSON),则退化为整对象
Update。
对于 uncordon(DesiredUnCordon),移除污点的实现是典型的"swap-and-truncate"技巧,从 taints 切片中定位匹配项后与最后一个元素交换,再截断切片:
if c.desired == DesiredUnCordon { for i, n := 0, len(c.cluster.Spec.Taints); i < n; i++ { if c.cluster.Spec.Taints[i].MatchTaint(&unschedulerTaint) { c.cluster.Spec.Taints[i] = c.cluster.Spec.Taints[n-1] c.cluster.Spec.Taints = c.cluster.Spec.Taints[:n-1] break } } }注意这里只移除第一个匹配的不可调度污点;同时,由于只匹配Key+Effect(MatchTaint不校验Value),即使污点带任意 Value 也会被匹配移除。
调度器如何响应:TaintToleration 插件
了解 uncordon 如何生效,还需要理解调度器侧对污点的处理。Karmada 调度器的TaintToleration过滤插件(pkg/scheduler/framework/plugins/tainttoleration/taint_toleration.go)会在调度每个 ResourceBinding 时检查目标集群的污点是否被传播策略的clusterTolerations容忍:
filterPredicate := func(t *corev1.Taint) bool { return t.Effect == corev1.TaintEffectNoSchedule || t.Effect == corev1.TaintEffectNoExecute } taint, isUntolerated := v1helper.FindMatchingUntoleratedTaint(klog.Background(), cluster.Spec.Taints, bindingSpec.Placement.ClusterTolerations, filterPredicate, false) if !isUntolerated { return framework.NewResult(framework.Success) } return framework.NewResult(framework.Unschedulable, fmt.Sprintf("cluster(s) had untolerated taint {%s}", taint.ToString()))核心结论:
- 当集群带有
cluster.karmada.io/unschedulable(Effect=NoSchedule)污点,且对应传播策略的 Placement 中没有配置能容忍该污点的clusterTolerations时,该集群在调度过滤阶段会被判定为Unschedulable,调度器不会向它分发新的工作负载; - 执行
karmadactl uncordon移除该污点后,集群重新通过 TaintToleration 过滤,恢复接收新调度任务的能力。
因此uncordon的完整生效链路是:命令移除集群污点 → 调度器过滤插件重新评估 → 集群恢复可调度。
相关字段:Placement 中的 clusterTolerations
与污点机制配套的ClusterTolerations字段定义在传播策略 API 中(pkg/apis/policy/v1alpha1/propagation_types.go):
// ClusterTolerations represents the tolerations. // +optional ClusterTolerations []corev1.Toleration `json:"clusterTolerations,omitempty"`它是一个corev1.Toleration切片,语义与 Kubernetes Pod 的 tolerations 一致。运维上这意味着:即使集群被 cordon(带有 NoSchedule 污点),只要某个 PropagationPolicy/ClusterPropagationPolicy 在 Placement 中显式配置了匹配该污点的容忍(例如key: cluster.karmada.io/unschedulable,effect: NoSchedule),调度器仍可能将特定工作负载调度到该集群——这是"强制调度"的合法通道,与 uncordon 的"全局恢复调度"形成互补。
测试验证:行为由单元测试保障
uncordon的行为有完整的单元测试覆盖,见 pkg/karmadactl/cordon/cordon_test.go。TestRunCordonOrUncordon使用 fake clientset 覆盖了四种组合场景:
| 测试用例 | 前置状态 | 操作 | 期望输出 |
|---|---|---|---|
| CordonUncordonedCluster_ClusterCordoned | 未封锁 | cordon | <name> cluster cordoned |
| CordonCordonedCluster_ClusterAlreadyCordoned | 已封锁 | cordon | <name> cluster already cordoned |
| UncordonCordonedCluster_ClusterUncordoned | 已封锁 | uncordon | <name> cluster uncordoned |
| UncordonUncordonedCluster_ClusterAlreadyUncordoned | 未封锁 | uncordon | <name> cluster already uncordoned |
测试中的状态校验函数(checkClusterCordonedCondition/checkClusterUncordonedCondition)直接断言集群spec.taints中是否存在cluster.karmada.io/unschedulable+NoSchedule组合:
var checkClusterUncordonedCondition = func(cluster *clusterv1alpha1.Cluster) error { for _, taint := range cluster.Spec.Taints { if taint.Key == clusterv1alpha1.TaintClusterUnscheduler && taint.Effect == corev1.TaintEffectNoSchedule { return fmt.Errorf("expected no noschedule taint for cluster %s, but got %v", cluster.GetName(), taint) } } return nil }这从测试层面再次印证:uncordon 的本质就是"确保集群 spec.taints 中不存在不可调度污点",且对已处于可调度状态的集群重复执行会输出already uncordoned而不是报错。
关联命令与进一步阅读
- karmadactl cordon:与 uncordon 配套的封锁命令,二者共享同一实现(cordon.go);
- karmadactl taint:手动为集群添加/移除任意污点,比 cordon 更灵活,适用于自定义调度约束;
- karmadactl 命令总览:查看全部 karmadactl 子命令;
- 调度器侧污点容忍过滤插件实现:pkg/scheduler/framework/plugins/tainttoleration/taint_toleration.go;
- 污点常量定义:pkg/apis/cluster/v1alpha1/well_known_constants.go。
小结
karmadactl uncordon是 Karmada 集群日常运维中"恢复调度"的标准动作,核心要点可归纳为:
- 用法极简:
karmadactl uncordon CLUSTER,一次只能操作一个集群; - 本质是污点操作:命令移除集群上的
cluster.karmada.io/unschedulable(NoSchedule)污点,而非修改其他调度字段; - 幂等安全:对已可调度的集群重复执行输出
already uncordoned,不会产生副作用; - 预演友好:
--dry-run模式下不发起任何服务器请求,可在维护窗口前安全验证; - 与调度器联动:污点移除后,调度器的 TaintToleration 过滤插件才会允许新工作负载调度到该集群,配合 Placement 的
clusterTolerations可实现"强制调度"等高级策略。
掌握cordon/uncordon/taint这三个命令的组合使用,即可在不删除集群的前提下,对多集群调度行为进行精细的维护控制。
【免费下载链接】karmadaOpen, Multi-Cloud, Multi-Cluster Kubernetes Orchestration项目地址: https://gitcode.com/GitHub_Trending/ka/karmada
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考