如何限制每个租户的最大镜像数量
解读
在国内私有云或行业云场景里,**“租户”**通常对应一个命名空间(Namespace)或一个 Harbor 项目(Project)。
面试官想确认你是否理解:
- Docker 本身没有“租户”概念,必须借助 镜像仓库级(Registry) 的配额机制;
- 配额需要与 身份认证(LDAP/OIDC)+ RBAC 打通,防止租户绕过;
- 配额超限后,push 阶段必须强制拒绝并给出清晰错误码,否则就是“假限制”;
- 还要考虑 镜像 GC 策略,防止租户“删标签不释放空间”导致配额统计失真。
知识点
- Harbor 配额模型:项目级配额(Project Quota)支持“镜像数量”与“存储容量”双维度,基于 Redis 计数,数据库事务保证一致性。
- OCI Distribution Spec:Docker Registry v2 协议在 push manifest 阶段返回 429/403 即可阻断上传,Harbor 中间件 quota.go 正是利用该扩展点。
- 配额计算口径:数量=同一 Repository 下不同 Digest 的 Manifest 个数,标签(tag)重复覆盖不会增加计数,但多架构索引(index)每张子 manifest 都会计入。
- 软限制与硬限制:国内金融云常要求“软 80% 邮件、硬 100% 拒绝”,Harbor 通过 webhook 触发企业微信或钉钉告警。
- 多租户隔离:若使用 Harbor 复制模式,需在主实例上开启“只读非管理员”,防止租户在子实例绕过配额。
- Jenkins CI 适配:在 pipeline 里捕获 429 错误,自动触发 镜像清理脚本(保留最近 N 个版本),实现无人值守。
答案
生产环境最成熟的方案是 Harbor 项目级配额,步骤如下:
- 安装 Harbor ≥ v2.2,启用“配额管理”特性(helm 安装时 quota.enabled=true)。
- 为每个租户创建独立 Project,在“项目配置→镜像数量”字段填写上限值,例如 200。
- 通过 OIDC 绑定企业微信或 LDAP 组,把用户加入该项目 Developer 角色,确保只能 push 到本项目。
- 当租户 CI 执行 docker push 时,Harbor core 服务会在 PUT /v2/<name>/manifests/<tag> 阶段检查计数;若超限立即返回
DENIED: The image count exceeds the quota of 200 for project tenant-a
HTTP 403,Docker CLI 直接退出非 0,CI 任务失败。 - 若需自动化清理,可调用 Harbor API
GET /projects/{project_id}/repositories/{repo}/artifacts?with_tag=true
按 created_time 排序,保留最近 30 个 digest,其余执行 DELETE,删除后立即触发 GC 释放配额计数。
若环境未部署 Harbor,也可临时用 Registry 插件方案:
- 官方 Distribution 没有计数接口,需自研 authz 插件(Go 实现)拦截 manifest PUT,把计数写入 Redis Hash:
HINCRBY tenant:<name>:count并设置 TTL 与事务锁; - 插件返回 429 即可阻断;
- 该方案需自己处理 并发锁、计数回滚、GC 同步,维护成本高,国内仅少数大厂自研,面试时建议优先回答 Harbor。
拓展思考
- 镜像数量与存储容量双维度配额如何权衡?
数量配额防止“小镜像大数量”拖慢数据库索引;容量配额防止“超大镜像”占满磁盘。国内运营商通常 数量≤500 且容量≤500 GiB,双触发任一即拒绝。 - 如何防止租户利用“多架构索引”绕过数量限制?
Harbor 2.5+ 已把 index 里的子 manifest 全部计入;若自研插件,需在 manifest list 展开阶段递归统计。 - Serverless 场景下函数镜像生命周期极短,配额是否还有意义?
可改为 TTL 策略:镜像 24 h 内无 pull 访问即自动回收,配额只用于防止瞬时 burst,降低 Redis 压力。 - 国产信创环境(ARM+openEuler)如何移植?
Harbor 官方镜像已支持 arm64,只需替换基础镜像为 openEuler 20.03 LTS,重新编译 quota 插件即可通过鲲鹏 900 测试。