XiHan.Framework.Scheduling.Kubernetes
scheduling.leader@1 的 Kubernetes Provider:用 coordination.k8s.io/v1 的 Lease 对象在 Pod 之间选出领导,直接调用 API Server,不依赖 Kubernetes 客户端库。适用于部署在 Kubernetes 上、只要求集群唯一的作业在一个 Pod 上运行的应用。
| 项 | 内容 |
|---|---|
| 包 ID | XiHan.Framework.Scheduling.Kubernetes |
| 环层 | Ring 3(实现) |
| 能力 | 提供 scheduling.leader@1(role: provider);依赖 kernel.hosting@1 |
| 特性入口 | KubernetesSchedulingFeature(清单 ID xihan.scheduling.kubernetes) |
| 配置节 | XiHan:Scheduling:Kubernetes(清单 contributes.config 声明的也是这一节) |
| 依赖 | XiHan.Framework.Scheduling.Abstractions;无第三方依赖 |
| AOT | 兼容 |
| 稳定性 | Preview |
概述
本包只提供领导选举,不提供作业存储。它与 Scheduling.InMemory 一起使用:作业调度仍由抽象包的引擎加内存作业存储承担,KubernetesLeaderElection 顶替进程内选举。内存存储不共享,引擎因此只让持有 Lease 的 Pod 按计划运行集群唯一(RunOnEveryInstance = false)的作业,其他 Pod 只运行本地作业、手动触发与入队的运行。
选举的规则:
- 获取:Lease 不存在就创建;持有者为空,或
renewTime + leaseDurationSeconds已过,就接管并把leaseTransitions加一;否则返回SCHEDULING_CONFLICT_002。并发竞争由 resourceVersion 裁决,409 也视为被占用。 - 续约写
renewTime,释放清空holderIdentity。引擎每个轮询周期续约一次。 - 存活时间按秒向上取整写进 Lease(Kubernetes 只有整数秒),本地判断是否持有仍用精确值。
- API Server 不可达或超时(客户端超时 10 秒)→
SCHEDULING_UNAVAILABLE_002,引擎下个周期重试。
调度器的默认资源 scheduling.jobs 映射到配置的 Lease 名;其他资源映射到 <Lease>-<资源>,点与下划线换成连字符,截到 63 个字符(KubernetesSchedulingOptions.LeaseNameFor)。
命名空间、持有者与 API Server 按 Pod 内的约定解析:
| 项 | 解析顺序 |
|---|---|
| 命名空间 | 配置 Namespace → ServiceAccount 挂载的 namespace 文件 → 环境变量 POD_NAMESPACE → default |
| 持有者 | 配置 Holder → 环境变量 XIHAN_POD_NAME → HOSTNAME → 机器名 |
| API Server | 配置 ApiServer → KUBERNETES_SERVICE_HOST / KUBERNETES_SERVICE_PORT → https://kubernetes.default.svc |
令牌每次请求都从挂载文件读取,跟得上投射令牌的轮换;CA 证书文件存在时作为唯一的信任根,不存在时用系统信任。
何时使用
- Kubernetes 多副本,后台作业只需要在一个 Pod 上跑,运行时作业与历史不必在副本间共享。
- 需要共享存储、节点失联恢复或在副本间分发作业时用 Scheduling.Database;它同时提供领导选举,不必再装配本包。
- 本地开发没有 API Server,不装配本包,退回进程内选举。
安装与启用
dotnet add package XiHan.Framework.Scheduling.Kubernetes --prerelease当前是预览版,安装要带 --prerelease;从源码或本地包源引用见快速上手。示例应用 framework/samples/KubernetesApp 只在 Pod 内装配本包:
using XiHan.Framework.Hosting.AspNetCore;
using XiHan.Framework.Profiles.Full;
using XiHan.Framework.Scheduling.Kubernetes;
var builder = WebApplication.CreateBuilder(args);
// API Server 的环境变量存在即运行在 Pod 里
var inCluster = Environment.GetEnvironmentVariable("KUBERNETES_SERVICE_HOST") is not null;
builder.AddXiHan(xihan =>
{
xihan.UseFullProfile();
if (inCluster)
{
xihan.UseFeature<KubernetesSchedulingFeature>();
}
});Pod 的 ServiceAccount 需要的最小权限:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: orders-leader
rules:
- apiGroups: ["coordination.k8s.io"]
resources: ["leases"]
resourceNames: [orders-scheduler]
verbs: ["get", "update", "patch"]
- apiGroups: ["coordination.k8s.io"]
resources: ["leases"]
verbs: ["create"]xihan pack 把应用清单 xihan.app.json 里的 leader.lease 写进环境变量 XiHan__Scheduling__Kubernetes__Lease,并生成 ServiceAccount、只含该 Lease 的 Role 与 RoleBinding,见部署与打包。
配置
| 键 | 类型 | 缺省 | 说明 |
|---|---|---|---|
XiHan:Scheduling:Kubernetes:Lease | DNS 标签 | xihan-scheduler | 调度器租约的 Lease 名 |
XiHan:Scheduling:Kubernetes:Namespace | 字符串 | 按 Pod 约定解析 | 命名空间 |
XiHan:Scheduling:Kubernetes:Holder | 字符串 | 按 Pod 约定解析 | 持有者标识 |
XiHan:Scheduling:Kubernetes:ApiServer | 绝对 URI | 按 Pod 约定解析 | API Server 地址 |
XiHan:Scheduling:Kubernetes:TokenPath | 路径 | /var/run/secrets/kubernetes.io/serviceaccount/token | ServiceAccount 令牌文件 |
XiHan:Scheduling:Kubernetes:CertificateAuthorityPath | 路径 | /var/run/secrets/kubernetes.io/serviceaccount/ca.crt | CA 证书文件 |
{ "XiHan": { "Scheduling": { "Kubernetes": { "Lease": "orders-scheduler" } } } }租约的存活时间与资源名是引擎选项 XiHan:Scheduling:LeaseTimeToLiveSeconds(缺省 30)与 XiHan:Scheduling:LeaseResource(缺省 scheduling.jobs),见 Scheduling.Abstractions。Lease 不是 DNS 标签、ApiServer 不是绝对 URI 时启动抛 ArgumentException。
主要类型
| 类型 | 说明 |
|---|---|
KubernetesSchedulingFeature | 特性:注册选项、Lease 客户端与选举;被选中提供 scheduling.leader 时把 ILeaderElection 指向本包 |
KubernetesLeaderElection | ILeaderElection 实现;Holder 是本实例的持有者标识 |
KubernetesSchedulingOptions | 配置项,以及 ResolveNamespace()、ResolveHolder()、ResolveApiServer()、LeaseNameFor(resource) |
Leases.KubernetesLeaseClient | Lease 的 GET / POST / PUT 客户端;CreateInCluster(options) 按 Pod 约定创建,应用或测试可预先注册自己的实例替代它 |
使用示例
确认当前 Pod 是否是领导(取自示例应用的 /leader 端点):
using XiHan.Framework.Scheduling.Abstractions.Leadership;
using XiHan.Framework.Scheduling.Abstractions.Schedulers;
using XiHan.Framework.Scheduling.Kubernetes;
app.MapGet("/leader", (JobSchedulerEngine scheduler, ILeaderElection election) =>
new LeaderView(election is KubernetesLeaderElection kubernetes ? kubernetes.Holder : Environment.MachineName, scheduler.IsLeader));
public sealed record LeaderView(string Holder, bool IsLeader);对两个 Pod 各请求一次,只有 Lease 持有者返回 IsLeader 为真;kubectl get lease orders-scheduler -o yaml 能看到 holderIdentity 与 leaseTransitions。持有者 Pod 正常停止时引擎释放租约,另一个 Pod 下一个轮询周期接管;Pod 被强制杀掉时,要等租约过期(缺省 30 秒)。
行为与限制
- 只提供领导选举。运行时登记的作业、暂停状态与运行历史仍在各 Pod 的内存里,不在副本间共享,也没有失联恢复。
- Lease 存活时间以整秒写入,小于 1 秒的存活时间在 API Server 上按 1 秒处理。
- 与
Scheduling.Database同时装配时,两个显式 Provider 都提供scheduling.leader,需要用XiHan:Providers:scheduling.leader指定一个,规则见能力包与清单。 - 通过
Scheduling.Conformance的LeaderElectionConformance(以内存 API Server 桩验证),含应用生命周期用例:排空中的节点释放 Lease,另一个 Pod 在租约到期之前就接手(ADR-0094)。
相关
- 调度与后台作业
- Scheduling.Abstractions
- Scheduling.InMemory
- Scheduling.Database
- 部署与打包
- 规范:RFC-0013《调度与领导选举》
