ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Apache Mesos Quota 配额机制:按 Role 限制集群资源用量的原理与实操

Apache Mesos Quota 配额机制:按 Role 限制集群资源用量的原理与实操
  • 云原生
  • 集群管理
  • 容器编排
  • 任务调度

【免费下载链接】mesos

Apache Mesos

项目地址:https://gitcode.com/gh_mirrors/meso/mesos
点击查看免费下载

本篇围绕 Apache Mesos 的 Quota(配额)功能展开:在多个用户或团队共享同一个集群时,运维人员如何按 role 维度为每个角色设置资源用量上限(limit)、如何通过 v1 API 更新与查询配额、配额消耗量是如何计算的,以及配额在 master 内部(registry 持久化、allocator 生效、offer 回收)的完整执行链路。读完本文,你不仅能直接复制可用的UPDATE_QUOTA/GET_QUOTA操作命令,还能从源码层面理解限额的校验规则、force 标志的作用边界与 failover 恢复行为。

Quota 解决什么问题

当多个用户共享一个 Mesos 集群时,运维方往往希望限制"每个用户最多能用多少资源"。Quota 正是为此设计的:它允许 operator 以per-role(按角色)的粒度设置资源上限。Mesos 中的 role 天然适合作为租户边界——框架(framework)在注册时声明所订阅的 role,其分配到的资源都会计入该 role,从而形成清晰的配额归属。

配额的核心数据模型由 include/mesos/quota/quota.proto 定义,其中关键消息是:

message QuotaConfig { required string role = 1; map<string, Value.Scalar> guarantees = 2; // 已废弃的配额保证 map<string, Value.Scalar> limits = 3; // 资源上限 }

在 src/master/quota.cpp 中,Quota结构体把QuotaConfig转换为内存中的guarantees(ResourceQuantities)与limits(ResourceLimits)两个集合;对旧版 API 的QuotaInfo/QuotaRequest,guarantee 会同时被当作 limit 处理(源码注释明确写着 "For legacyQuotaInfo, guarantee also acts as limit"),这正是后文"Deprecated: Quota Guarantees"一节的由来。

支持配额的资源类型

支持Quota 的资源:

  • cpus
  • mem
  • disk
  • gpus
  • 任何类型为SCALAR的自定义资源

不支持Quota 的资源:

  • ports
  • 任何类型为RANGES或SET的自定义资源

这一约束在请求校验逻辑中有对应实现:src/master/quota.cpp 中的validation::quotaInfo会对每个资源检查resource.type() != Value::SCALAR时直接报错 "QuotaInfo must not include non-scalar resources",并拒绝携带ReservationInfo、DiskInfo、RevocableInfo等资源字段。

设置配额:UPDATE_QUOTA 调用

默认情况下,每个 role 都没有资源限制。要为单个或多个 role 修改资源限制,使用 v1 API 的UPDATE_QUOTA调用。注意该调用是原子性的(all-or-nothing):如果请求中某一个 role 的配额更新无效或未获授权,整个请求都不会生效。

完整示例:

curl --request POST \ --url http://<master-ip>:<master-port>/api/v1/ \ --header 'Content-Type: application/json' \ --data '{ "type": "UPDATE_QUOTA", "update_quota": { "force": false, "quota_configs": [ { "role": "dev", "limits": { "cpus": { "value": 10 }, "mem": { "value": 2048 }, "disk": { "value": 4096 } } }, { "role": "test", "limits": { "cpus": { "value": 1 }, "mem": { "value": 256 }, "disk": { "value": 512 } } } ] } }'

请求参数说明

字段类型说明
typestring固定为UPDATE_QUOTA
update_quota.forcebool是否强制覆盖。默认false
update_quota.quota_configs[]array一批 role 的配额配置,整体原子生效
quota_configs[].rolestring目标 role 名,必须存在于 role 白名单(若配置了--roles),且不支持嵌套 role(如eng/prod)
quota_configs[].limitsmap资源上限,键为资源名(cpus、mem、disk、gpus或自定义 SCALAR 资源),值为{ "value": <number> }
quota_configs[].guaranteesmap旧版保证语义,已废弃,新配置只应设置limits

关于force标志的两点行为(与 docs/quota.md 保持一致):

  • 如果 role当前已消耗的配额超过了所设 limit,请求会被拒绝;将force设为true可以绕过该检查。
  • master 会尝试回收(rescind)足够的 outstanding offers,确保该 role 不会超过其 limit。

源码中的校验链路

UPDATE_QUOTA的完整处理在 src/master/quota_handler.cpp 的Master::QuotaHandler::update中,校验顺序如下:

  1. role 白名单检查:master->isWhitelistedRole(config.role()),不在白名单中的 role 直接返回 400("is not on the roles whitelist")。
  2. 嵌套 role 拒绝:role 名包含/时返回 "Updating quota on nested role ... is not supported yet"。
  3. 配置合法性校验(src/master/quota.cpp 中的validate(const QuotaConfig&)),包含几个容易踩坑的硬限制:
    • 标量值必须非负且有限;
    • mem、disk的上限为1 Exabyte(以 MB 为基准单位),其他资源(如 cpus、自定义资源)的上限为1 万亿(1 trillion)——这是为了避免Value::Scalar底层定点逻辑在约 9.2e15 附近发生溢出;
    • 若同时设置了guarantees和limits,必须满足guarantees <= limits。
  4. 当前消耗检查:通过RoleResourceBreakdown(master, role).consumedQuota()计算 role 当前消耗,若limits不包含该消耗量且未提供force,返回 400 并提示 "use 'force' flag to bypass this check"。
  5. 配额树(QuotaTree)层级校验与集群容量超卖(overcommit)检查:所有 role 的 guarantees 总和不能超过集群已注册 agent 的标量资源总量(同样可被force覆盖)。
  6. 授权检查:逐个 config 调用 authorizer 的UPDATE_QUOTA_WITH_CONFIG动作,任一未授权则整个请求返回 403 Forbidden——这就是"all-or-nothing"在权限层面的体现。

校验全部通过后,_update会将配额写入 registry(见下文持久化一节),更新 master 内存态master->quotas并调用master->allocator->updateQuota(...)让分配器立即生效。

配额如何持久化

配额的持久化走的是 master 的 registry 事务机制。src/master/quota.hpp 定义了UpdateQuota这个RegistryOperation,其头文件注释解释了关键设计决策:

不对 quota registry 操作施加任何约束,quota 请求是否合法由 master 与 allocator 判断;因此perform()从不返回Error。

UpdateQuota::perform的实现(src/master/quota.cpp):

  • 先把整批QuotaConfig再校验一遍(防御性检查);
  • 逐个 role 在 registry 的quota_configs中查找:如果新配额等于DEFAULT_QUOTA(即"空配置",等同于删除配额),则删除该 role 的条目;否则更新或插入;
  • 顺带清理旧版quotas(QuotaInfo)字段中同 role 的遗留条目,保证新老实态不冲突;
  • 若 registry 中还存在任何配额,则在 registry 的minimum_capabilities中追加QUOTA_V2能力标记,否则移除该标记——这保证了 HA 集群中旧版本 master 在 failback 时能正确拒绝不支持的配置。

查看配额

Web UI

Web UI 的Roles标签页会展示所有已知 role 的资源核算信息,其中就包括已配置的配额(quota)与配额消耗(quota consumption)。

API:GET_QUOTA 返回配额配置

$ curl --request POST \ --url http://<master-ip>:<master-port>/api/v1/ \ --header 'Content-Type: application/json' \ --header 'Accept: application/json' \ --data '{ "type": "GET_QUOTA" }'

响应:

{ "type": "GET_QUOTA", "get_quota": { "status": { "infos": [ { "configs" : [ { "role": "dev", "limits": { "cpus": { "value": 10.0 }, "mem": { "value": 2048.0 }, "disk": { "value": 4096.0 } } }, { "role": "test", "limits": { "cpus": { "value": 1.0 }, "mem": { "value": 256.0 }, "disk": { "value": 512.0 } } } ] } ] } } }

从源码看,GET_QUOTA的响应由 src/master/quota_handler.cpp 中_status组装:它先快照当前master->quotas(避免响应构建期间配额被并发修改),再对每个 role 逐个执行authorizeGetQuota授权检查(action 为GET_QUOTA),未被授权的 role 会从结果中过滤掉。返回体同时填充了旧版QuotaInfo(仅含 guarantee,用于向后兼容)和新版QuotaConfig(含 guarantees 与 limits)两个视图。

/roles端点:同时查看配额与消耗

$ curl http://<master-ip>:<master-port>/roles

响应示例:

{ "roles": [ { "name": "dev", "weight": 1.0, "quota": { "role": "dev", "limit": { "cpus": 10.0, "mem": 2048.0, "disk": 4096.0 }, "consumed": { "cpus": 2.0, "mem": 1024.0, "disk": 2048.0 } }, "allocated": { "cpus": 2.0, "mem": 1024.0, "disk": 2048.0 }, "offered": {}, "reserved": { "cpus": 2.0, "mem": 1024.0, "disk": 2048.0 }, "frameworks": [] } ] }

Quota Consumption:消耗量到底包含什么

文档给出的口径是:一个 role 的配额消耗 = 其 allocations + reservations。也就是说,即使 reservation 尚未被分配,它同样计入配额消耗;而被 offer 出去但尚未接受分配的资源(offered)不计入配额消耗。

这个口径在 src/master/master.cpp 的Master::RoleResourceBreakdown::consumedQuota中有直接实现:

ResourceQuantities Master::RoleResourceBreakdown::consumedQuota() const { ResourceQuantities unallocatedReservation; foreachvalue (Slave* slave, master->slaves.registered) { ResourceQuantities totalReservation = ResourceQuantities::fromResources( slave->totalResources.reservedToRoleSubtree(role)); ResourceQuantities usedReservation; foreachvalue (const Resources& r, slave->usedResources) { usedReservation += ResourceQuantities::fromResources( r.reservedToRoleSubtree(role)); } unallocatedReservation += totalReservation - usedReservation; } return allocated() + unallocatedReservation; }

实现逻辑与文档描述一一对应:先对每个 agent 计算"总 reservation − 已使用的 reservation"得到未分配的部分,再加上allocated()(该 role 子树下所有 framework 的已用资源),即为消耗总量。offered()、allocated()、reserved()三个分解函数也在同一位置(src/master/master.cpp)实现,正是/roles端点输出中同名字段的来源。

此外,配额消耗还会沿 role 树的祖先节点向上聚合——src/master/allocator/mesos/hierarchical.cpp 中RoleTree每次 track/untrack 分配与 reservation 时都会调用applyToRoleAndAncestors更新每一层的消耗并同步指标。

Metrics

文档列出的配额相关指标键:

  • allocator/mesos/quota/roles/<role>/resources/<resource>/guarantee
  • allocator/mesos/quota/roles/<role>/resources/<resource>/limit
  • 配额消耗指标将经由 MESOS-9123 提供。

从当前仓库源码看,消耗指标实际上已经有对应实现:src/master/allocator/mesos/metrics.hpp 中定义了QuotaMetrics quotaConsumed {"/consumed"},Metrics::updateConsumed负责写入;src/master/allocator/mesos/metrics.cpp 的updateQuota还会为设置了配额的 role 注册allocator/mesos/quota/roles/<role>/resources/<name>/offered_or_allocated的 PullGauge。另外有一个实现细节值得注意:当 role 配额被重置为DEFAULT_QUOTA(即删除配额)时,updateQuota会提前返回,不再发布 offered_or_allocated 指标,以保持与旧版行为一致。

指标命名注释中也给出了官方示例:"Quota metrics should have a suffix in singular. (Example of a name: allocator/mesos/quota/roles/roleA/resources/cpu/limit.)"

Deprecated: Quota Guarantees(已废弃的配额保证)

Mesos 1.9 之前,配额 API 只暴露 "guarantees"(配额保证):保证某个 role 最少有若干资源可用,同时设置 guarantee 也会隐式地设置一个 limit。1.9 起,limit 作为独立概念直接暴露(即本文前述的limits字段),guarantees 则被废弃。

废弃的核心理由与 Mesos 向optimistic offer model(乐观 offer 模型)演进直接相关(见 JIRA MESOS-1607,其目标是改善多 role / 多 scheduler 场景下的可扩展性):

  • 强制保证 quota guarantee 要求 Mesos预留(hold back)足够资源来满足所有尚未满足的 guarantee——这在传统分配模型下是可行的;
  • 而乐观 offer 模型下,"扣下资源留白"的做法不再可行;limit 在这种模型下依然简单可执行,但 guarantee 将需要一套复杂的 "effective limit" 传播机制;
  • 因此 "limit + 基于优先级的抢占(priority based preemption)" 的组合会是更简单、更契合未来模型的方向。

guarantee 在 Mesos 1.9 中仍然可用,但已标记为废弃;旧版 guarantee 用法可参考 Apache Mesos 1.8.0 发布版中的 docs/quota.md(本仓库文档指向的版本分支,此处不给出外链)。

从源码可以印证 guarantee 仍被完整保留的现状:QuotaConfig消息中guarantees字段与limits并列存在,QuotaTree::validate(src/master/quota_handler.cpp)至今仍校验"父 role 的 guarantee 必须包含其所有子 role guarantee 之和"的层级关系,_update流程里也会为 guarantee 单独执行一轮 offer 回收。

实现细节与边界条件

嵌套 role 暂不支持配额

对eng/prod这类嵌套 role 设置配额目前不支持,update入口会直接返回 "Updating quota on nested role ... is not supported yet"(源码中标注待 MESOS-7402 修复后移除该检查)。

Failover 期间 allocator 的暂停与恢复

Master failover 后,为了正确执行配额限制,allocator 会被暂停,直到满足以下两个条件之一才重新发放 offer:

  • 至少80%的 agent 重新注册;
  • 或10 分钟超时。

这两个参数在默认值层面与源码完全对应:include/mesos/allocator/allocator.hpp 中

constexpr Duration DEFAULT_ALLOCATOR_RECOVERY_TIMEOUT = Minutes(10); constexpr double DEFAULT_ALLOCATOR_AGENT_RECOVERY_FACTOR = 0.80;

并且它们已通过 master 的启动 flag 暴露为可配置项(src/master/flags.cpp):

  • --allocator_agent_recovery_factor:领导者选举后,allocator 开始发放 offer 所需的已重新注册 agent 的最低比例;
  • --allocator_recovery_timeout:领导者重选后开始发放 offer 前最多等待的时间。

在 src/master/allocator/mesos/hierarchical.cpp 的恢复流程中可以看到具体机制:expectedAgentCount = _expectedAgentCount * options.agentRecoveryFactor,随后pause()暂停分配并delay(options.recoveryTimeout, self(), &Self::resume)注册超时恢复;每有一个 agent 重新注册便计数一次,达到expectedAgentCount即恢复。若集群内没有任何预期 agent,则直接跳过恢复等待。

默认 role(*)现在受支持

配额已支持默认的*role(对应 JIRA MESOS-3938)。

配额的强制执行:best-effort 的 offer 回收

文档提到"master 会尝试回收足够的 offer 以确保 role 不超过 limit"。这一行为的完整实现位于 src/master/quota_handler.cpp 的_update中,值得理解其设计取舍:

  1. 配额先行:registry 事务提交后,先更新master->quotas并 dispatchupdateQuota到 allocator,再执行回收。注释明确解释了顺序:若先回收再设配额,回收出来的资源可能在updateQuota生效前被重新分配出去(master 与 allocator 之间存在消息竞态)。
  2. 限额回收:计算该 role 的consumedQuota() + offered(),遍历所有 framework 的 outstanding offers,对分配给该 role 子树的 offer 逐个rescindOffer,直到consumedAndOffered落回 limit 之内。
  3. 保证回收(针对 guarantee):悲观地假设 allocator 中看似"可用"的资源都会消失,贪婪地回收其他 role 子树之外的 offer,直到rescinded + consumedAndOffered覆盖该 role 的全部 guarantees。

源码注释坦承这是一项best-effort工作:精确追踪 consumed/offered 的边界代价很高,且 master 与 allocator 之间存在竞态(可能多收,也可能少收);配额的意义在于收敛一个瞬时状态——资源持续流动后,配额最终会被强制执行。注释同时指出,未来若 Mesos 全面转向乐观 offer 模型(MESOS-1607),配额强制将移到准入控制(admission control)阶段完成,offer 回收将不再必要。

另外,MesosAllocator对updateQuota的调用(src/master/allocator/mesos/allocator.hpp)是通过process::dispatch异步派发到 allocator 进程处理的,HierarchicalAllocatorProcess::updateQuota会更新roleTree并同步刷新指标、记录日志(src/master/allocator/mesos/hierarchical.cpp),随后触发新一轮分配,使新的限额立即作用于后续 offer 的生成。

小结

能力入口关键点
设置/修改/删除配额v1 APIUPDATE_QUOTA原子生效;force可越过"已超消耗"与"超卖"检查;删除配额 = 提交空QuotaConfig
查询配额配置v1 APIGET_QUOTA逐 role 授权过滤;同时返回旧版infos与新版configs
查询配额消耗/rolesHTTP 端点consumed= allocated + 未分配 reservation;offered 不计入
监控指标/metricsallocator/mesos/quota/roles/<role>/resources/<resource>/{guarantee,limit,consumed,offered_or_allocated}
Failover 恢复--allocator_agent_recovery_factor/--allocator_recovery_timeout默认 80% agent 重注册或 10 分钟超时后恢复发放 offer

配合 src/master/quota.cpp、src/master/quota_handler.cpp 与 include/mesos/quota/quota.proto 这三个文件,可以完整追溯一条UPDATE_QUOTA请求从 HTTP 入口、参数校验、registry 事务到 allocator 生效的全链路,本文所有实现级结论均可在仓库中直接查证。

  • 云原生
  • 集群管理
  • 容器编排
  • 任务调度

【免费下载链接】mesos

Apache Mesos

项目地址:https://gitcode.com/gh_mirrors/meso/mesos
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进