阿凯是“云栖优选”超市集团的 IT 工程师——当然,这家“超市”卖的不是蔬菜水果,而是 AI 应用。每个应用都被打包成一个个标准货箱(容器镜像),里面装着代码、运行环境和模型权重。过去,团队像散兵游勇一样从公共批发市场(Docker Hub)各自进货,货箱乱堆在个人电脑上,版本对不上、下载又慢,生产环境一部署就“货不对板”。 老板拍板:建一座自己的中央仓库!于是阿凯从零开始:先搞清仓库和货箱的关系,接着在后院搭了个简易库房(Registry),再升级成带门禁、监控、质检的现代化仓储系统(Harbor);学会给货箱贴条码(tag)、入库出库(push/pull),给员工发不同级别工牌(权限),给货物盖质检章(签名),定期盘点清理过期货物(垃圾回收),最后在别的城市开分仓、双向同步(异地多活)。 这座仓库建完之后,“云栖优选”的 AI 应用上线速度翻了好几倍。这篇文章,就是阿凯的建仓笔记。
对 AI 从业者来说,镜像仓库是模型服务的“弹药库”——掌握它的搭建与管理,才能让训练、部署流水线跑得又快又稳,这是 AI 工程化落地的核心硬技能。
可视化演示
展示Docker镜像仓库的客户端-仓库-存储架构、镜像推送拉取流程及跨仓库复制拓扑,拖动滑块调节速度观察数据流转。
本节配有交互动效(进入视口后自动加载,约 3KB)。
仓库架构 客户端通过Registry HTTP API与仓库交互 Pull/Push = RESTful HTTP V2 存储后端支持文件系统和对象存储
仓库基础概念
“云栖优选”要先搞清楚:到底往仓库里存什么?超市里,货架上摆的是拆开的一件件商品;而在中央仓库,货物是整箱整箱的标准货箱。镜像仓库里的“货箱”就是容器镜像——一个运行时所需的一切:代码、依赖库、Python 解释器、模型权重,全被打包成只读的“整数箱”。仓库(registry)则是专门存放和分发这些货箱的中心化服务,而货架上一个个格子(repository)存放某一类镜像的不同版本。
概念上要区分三层:镜像(image)是那个货箱;仓库路径(repository)是货架上的分类标签,比如“零食区/薯片”;镜像仓库服务(registry)则是整栋仓储大楼。Docker Hub 就是一个面向全世界的公共批发市场,任何人可以免费领货,但公共市场的缺点很明显:下载慢、可能断供、不能保证货物没被动过手脚。这就是私有仓库的价值——自家仓库,随时补货、速度快、能上锁。
一个货箱要能准确找到它的位置,靠的是一串地址:
阿凯把地址结构贴在工位上:以后任何人要货,只要报上完整的镜像地址,仓管员就能在三秒钟内告诉他货在哪个区、哪排货架——这就是规范的力量。
- 仓库服务地址(hostport):镜像来自哪台“中央仓库”服务器,写清域名和端口才能找到货源地;省略时 Docker 默认去 Docker Hub 公共批发市场。
- 命名空间(namespace)类似超市按“生鲜区/日化区”划分通道,通常对应团队或项目名,比如 ai-team。
- 仓库名(repo)具体某一类镜像的名称,比如 trainer 训练镜像,对应货架上的某一类货箱。
- 标签(tag)版本号,如 v1.2、latest,相当于货箱上的批次标签,方便精确取用。
搭建私有仓库
阿凯决定先在自家后院搭一个简易库房试试水,用的就是 Docker 官方的 registry 镜像。这就像先去批发市场买一套标准货架,足够把每天的货先归拢起来。命令极其简单:把 registry:2 容器跑起来,映射 5000 端口,再挂一个数据卷用于持久化——货箱可以随存随取,但库房本身不能一重启就“失忆”。
从概念上讲,这个简易仓库就是一台轻量级的镜像存储服务:客户端通过 HTTP API 与它对话,push 时上传货箱,pull 时按需下载。但它也意味着三个“裸奔”问题:没有登录验证、没有管理界面、默认只认 HTTPS。生产环境通常会在前面加一个 Nginx 反向代理来终结 TLS 加密,并把请求转发给 registry 后端,相当于在库房门口装一道保安岗亭和卷帘门。
搭建前要先估算库房多大,核心公式是“去重后的真实占用”:
简易库房跑通了,阿凯很满意——但他知道,真正的考验是当仓库里涌进几十个团队、上千个货箱时,怎么管住质量和安全。
- 右端所有标签的原始大小之和:如果给每个货箱单独算体积,把全部镜像标注的容量加起来,这就是“看似要占”的空间。
- 左端去重后的唯一层体积:镜像由一层层文件系统叠加而成,公共层(如基础操作系统、Python 环境)被多个镜像复用,只存一份。
- 中间的不等号因为“合用的零件只占一个货位”,所以实际占用永远不超过标注总量,这也是仓库能省磁盘的根本原因。
Harbor企业仓库
库房升级的契机发生在一次“货损”事故:有人手滑 push 了一个没经过测试的镜像,生产环境直接拉取部署,线上服务崩了半小时。阿凯意识到,光有货架不够,还需要门禁、监控、质检和分拣流程——这正是 Harbor 的用武之地。Harbor 是云原生计算基金会(CNCF)的毕业项目,一个开源的企业级镜像仓库平台。
Harbor 并不是一个全新的仓库,而是把官方 Registry 这排“货架”搬进一栋现代化仓储大楼,外面套上各种管理系统:Portal 提供 Web 管理界面(监控大屏)、Core 负责认证授权与 API(门禁系统)、JobService 负责复制和扫描等异步任务(分拣传送带)、数据库和 Redis 保存台账与缓存。安装时下载离线安装包、解压后执行 prepare 和 install 脚本,几分钟就能起一栋“楼”。
Harbor 的组成可以浓缩成一句话:
仓库大楼落成那天,阿凯在日志里看到一条记录:某位同事凌晨三点 push 了一个镜像,三分钟后生产环境拉走——他知道,以后每一次“进货出货”都有据可查了。
- Registry存储货架:负责镜像数据本身的读写,是整栋楼的“库房主体”。
- Core 与 JobService门禁与调度:Core 管登录、项目和权限;JobService 负责复制、扫描等后台任务。
- Portal监控大屏:提供图形化界面,让阿凯不用敲命令就能看到仓库全貌、配置策略。
镜像推送与拉取
有了现代化仓库,接下来就是日常的搬运作业。阿凯把流程总结成“一卡、一签、一入、一出”:登录(login)相当于到门禁处刷卡领临时工牌;打标签(tag)相当于给货箱贴上正确的目的地和批次信息;推送(push)是送货入库;拉取(pull)是提货出库。
具体操作上:先用 docker login harbor.example.com 输入凭证,再通过 docker tag 把本地镜像的名字改成仓库规定的完整格式,然后用 docker push 上传。到了部署环节,生产服务器用 docker pull 拉取对应标签的镜像即可启动容器。初学者最容易踩的坑是镜像名不规范——就像寄快递地址写错,仓库会直接拒收。
拉取时有一个让阿凯惊喜的机制:镜像分层共享让“只搬差异”成为可能。本地已有的层可以直接复用,只下载缺失的部分:
阿凯给团队立了规矩:每次发布必须打上清晰标签,比如 ai-team/trainer:v2.0.1,禁止裸用 latest 上生产——因为“最新”这个标签没有承诺,随时可能变。
- 缺失层集合客户端先和仓库比对 manifest(货箱清单),找出本地没有的层。
- 逐层求大小每一层都是一个不可变的文件系统快照,有自己的 sha256 摘要作“身份证号”。
- 求和得差量把所有缺失层的大小加起来,就是这次 pull 实际需要传输的数据量。
仓库权限控制
仓库正式运营后,阿凯发现不是所有人都该有全部权限:实习生可以看看货架上有什么(访客),算法工程师能往里放货(开发者),运维同事要能清理和删除镜像(维护者),而只有项目负责人能管理整个项目(项目管理员)。Harbor 用“项目”来划分库区,每个项目可以设为公开或私有,公开项目任何人可拉取,私有项目须经过认证。
权限控制的本质是一个判定函数:用户 u 想对某个资源执行某个操作(比如删除镜像),系统看的是 u 拥有的所有角色中,是否有任何一个角色被授予了这个操作。这就是经典的 RBAC 模型:
除了人,阿凯还配置了机器人账号给 CI/CD 流水线用——就像给自动叉车发一张专用工牌,只允许它往指定项目推送镜像,丢不了也越不了权。
- 用户角色集合和现实一样,一个人可以身兼数职,比如既是“开发者”又是某项目的“维护者”。
- 遍历判定逐个检查每个角色是否被授予目标操作(pull、push、delete、scan 等)。
- 存在性逻辑或把结果用“或”连接起来,任一为真即为真;全部为假则默认拒绝。
镜像安全与签名
货损事故让阿凯心有余悸:如果有人往仓库里混入一个同名同标签却藏了恶意代码的镜像,生产环境照单全收怎么办?这就需要“质检章”。阿凯开启 Docker Content Trust(DCT):发布镜像前,发布者用自己的私钥对镜像摘要(sha256 值)签名,相当于给货箱盖上防伪印章;拉取时客户端用公钥验证印章是否有效。
签名验证的数学过程并不复杂,核心是三步:先对镜像清单(manifest)计算摘要,再用私钥签名,最后用公钥验签:
同时在 Harbor 中开启漏洞扫描(基于 Trivy 等引擎),每次 push 后自动给镜像做“抽检”并打分。阿凯还设了安全策略:漏洞等级高于中危的镜像禁止 pull 到生产项目。从此,仓库里的每个货箱都是“盖章且抽检合格”的。
- 计算清单摘要对 manifest 做 SHA256 哈希,得到一串固定长度的“指纹”。
- 私钥签名发布者用自己保管的私钥对指纹加密生成签名,盖上独一无二的印章。
- 公钥验签任何拉取者用发布者公开的公钥验证签名指纹是否与镜像一致,一致才放行。
垃圾回收与清理
运营三个月后,阿凯发现磁盘空间快要爆炸了。查了一圈才明白:在仓库里“删除”镜像,只是从台账上划掉了这个标签,货箱里的零件还占着货位。就比如超市把过期商品从货架撤下,库房里那堆箱子并没有被清运走。必须手动运行垃圾回收(GC),把那些“没有任何标签引用”的层真正删掉。
垃圾回收的判定标准就是引用计数:镜像层被多少个标签引用着?打标签时引用加一,删除标签时引用减一,当引用计数归零,说明没有任何镜像需要这层数据了,就可以物理删除:
GC 运行时要对仓库加只读锁,否则边清运边进货会乱套。所以阿凯把 GC 安排在每周日凌晨的维护窗口,用脚本定时执行并记录释放的空间量——就像超市打烊后才开始盘点和清仓。
- 引用计数管理每个镜像层都有一个计数器,记录当前有多少个标签指向它。
- 推送时加一每次 push 一个包含该层的新镜像标签,计数器加一。
- 删除时减一并判定删除标签后计数器减一;归零时该层变成无主垃圾,GC 扫描后物理清除。
异地复制与多活
“云栖优选”在上海开了分公司,部署了独立的 GPU 集群。如果把镜像从总部仓库远程拉取,跨地域传输慢还占用专线带宽。阿凯的做法是:在 Harbor 里配置“镜像复制”策略,总部仓库每收到新镜像,后台任务就自动把它同步到上海的分仓——就像把热销货物提前配送到外地分仓,让当地门店就近补货。
复制策略有两种模式:push 模式由源仓库主动推送到目标仓库,pull 模式则由目标仓库定时从源仓库拉取,适合网络隔离场景。更进一步,两个城市的仓库可以互相复制、双向同步,形成“多活”架构:任何一侧都能独立提供拉取服务,一台挂掉另一台无缝顶上。多副本最直接的价值是提升整体可用性:
阿凯还额外强调一句话:复制是为了“配送快”,不等于“有备份”。备份要基于对象存储快照或 Harbor 离线导出单独做——分仓模型解决的是吞吐和延迟,账本还是要单独锁进保险柜。
- 单一节点的可用率假设每个仓库节点可用率达到 99%,不可用率就是 1%。
- 所有节点同时失效只有所有副本同时不可用,服务才真正不可用,所以把各节点不可用率相乘。
- 用 1 减去失效概率得到“至少有一个节点可用”的概率,也就是系统整体可用率。
AI 应用场景
AI 训练镜像的 CI/CD 发布流水线
阶段:ML 工程化 / 持续集成与持续部署(CI/CD)
解决的问题:训练和推理镜像散落在各成员机器上,版本混乱、回滚困难。通过 GitLab CI 在代码合并时自动构建镜像并 push 到 Harbor,打上 commit 短哈希标签;部署时按标签拉取,事故时一键回滚到上一个稳定版本。
具体案例:某自动驾驶团队每天产生一个新的训练镜像,CI 流水线自动推送;某次模型精度回退时,运维直接执行 docker pull harbor.internal/adas/trainer:2024-05-01 回滚到前一日版本,十分钟内恢复服务。
多区域推理集群的镜像分发
阶段:模型部署与推理服务
解决的问题:模型上线时,多个区域的 GPU 集群都要拉取同一个推理镜像,跨地域拉取延迟高、带宽成本高。用 Harbor 的 push 模式复制策略,镜像上传后自动同步到各区域分仓,拉取从分钟级降到秒级。
具体案例:一家视频审核公司在北京、上海、新加坡部署推理集群,主 Harbor 在北京;配置复制策略后,三个区域同时拉取新模型镜像,无需再走跨区公网传输。
金融合规下的镜像安全管控
阶段:安全合规与审计
解决的问题:金融监管要求交付物可追溯、防篡改,未签名镜像禁止上生产。在 Harbor 开启内容信任(DCT)与漏洞扫描,只允许签名镜像部署;每次发布用私钥签名,Harbor 审计日志留痕,安全评审时直接导出操作记录。
具体案例:某券商 AI 团队把训练、风控、推荐三类镜像分别放入独立私有项目,配合角色权限和漏洞扫描,顺利通过外部安全审计。
Python 演示
import hashlib class Layer: # 镜像层: 一段不可变数据, 可被多个镜像共享 def __init__(self, data): self.data = data self.digest = "sha256:" + hashlib.sha256(data.encode()).hexdigest() self.refs = 0 class Registry: # 极简镜像仓库: 支持 push / pull / delete / gc def __init__(self): self.layers = {} # digest -> Layer self.tags = {} # (repo, tag) -> [digest] def push(self, repo, tag, layer_datas): digests = [] for data in layer_datas: d = "sha256:" + hashlib.sha256(data.encode()).hexdigest() if d not in self.layers: self.layers[d] = Layer(data) self.layers[d].refs += 1 digests.append(d) self.tags[(repo, tag)] = digests def pull(self, repo, tag): digests = self.tags.get((repo, tag)) if not digests: return None return [self.layers[d].data for d in digests] def delete(self, repo, tag): key = (repo, tag) if key not in self.tags: return False for d in self.tags.pop(key): self.layers[d].refs -= 1 return True def gc(self): removed = sum(1 for l in self.layers.values() if l.refs == 0) self.layers = {d: l for d, l in self.layers.items() if l.refs > 0} return removed def saved_layers(self): return sum(l.refs - 1 for l in self.layers.values()) if __name__ == "__main__": r = Registry() base = ["basefs", "python3.11", "cuda12"] v101 = ["basefs", "python3.11", "cuda12", "train.py"] v200 = ["basefs", "python3.11", "cuda12", "train.py", "model.bin"] r.push("ai/trainer", "v1.0", base) r.push("ai/trainer", "v1.1", v101) r.push("ai/trainer", "v2.0", v200) print("v2.0 拉取层数:", len(r.pull("ai/trainer", "v2.0"))) print("去重后实际层数:", len(r.layers), "共享节省层:", r.saved_layers()) r.push("ai/tmp", "tmp", ["basefs", "tempfile"]) r.delete("ai/tmp", "tmp") print("GC 回收垃圾层:", r.gc(), "剩余层:", len(r.layers))
if d not in self.layers层去重:相同的层数据只存一份,多个镜像共享同一 Layer 对象。self.layers[d].refs += 1push 时给每个被引用的层加一,记录“还有谁在用这个层”。for d in self.tags.pop(key): self.layers[d].refs -= 1删除标签时遍历所有层并减一,这就是“从台账划掉”。removed = sum(1 for l in self.layers.values() if l.refs == 0)GC 统计所有引用归零的层,它们才是真正要清走的垃圾。self.layers = {d: l for d, l in self.layers.items() if l.refs > 0}用字典推导式过滤掉无主层,物理释放空间。
常见误区
误区以为 Harbor 和 Docker Registry 是两家公司的竞品,二选一即可,装了 Harbor 就不需要 Registry。
正解Harbor 是构建在官方 Docker Registry 之上的企业级开源项目(CNCF 毕业项目)。它复用 Registry 做底层镜像存储,上面套了 Web UI、认证、项目、复制、扫描等模块。就像现代化仓储管理系统离不开库房货架——Harbor 的底层货架依然是 Registry。
为何常见:Docker 官方仓库镜像名叫 registry,Harbor 又有独立的安装包和界面,初学者容易把两者当成并列产品。
误区在 Harbor 删除镜像标签或执行 `docker rmi` 后,马上检查磁盘,以为空间已经腾出来了。
正解删除操作只移除了标签索引,镜像的层数据还躺在磁盘上;必须运行垃圾回收(registry garbage-collect / Harbor 的 GC),把引用计数归零的层物理清除,空间才算真正释放。就像超市把过期商品从货架撤下,但库存里的箱子还在仓库存着。
为何常见:日常删除文件立即释放空间的直觉容易误导;删除后界面里看不到镜像,更让人误以为数据已经没了。
误区内网搭好 registry 后直接 `docker push`,看到 "server gave HTTP response to HTTPS client" 报错,于是怀疑是镜像或网络问题。
正解Docker 客户端默认走 HTTPS,私有仓库要么配置 TLS 证书,要么在 daemon.json 中把该地址加入 insecure-registries 白名单。加白名单等于告诉客户端“这个仓库虽然不加密,但我信任它”,仅限可信内网环境使用。
为何常见:本地开发常用 HTTP 访问内部服务,容易忽略 Docker 的安全默认策略。
误区只要配置了 Harbor 复制策略把镜像推到另一个机房,数据就万无一失。
正解复制是分发机制,目的是让目标区域就近拉取、加速部署;如果源仓库误删或损坏,删除操作也会按策略同步传播。真正的备份要依赖 Harbor 离线备份或对象存储快照。分仓解决的是配送快不快,备份解决的是数据丢不丢,是两件事。
为何常见:“多副本”和“备份”在分布式语境下经常被混为一谈,忽略了复制会同步传播删除操作。
误区反正仓库在内网,所有人登录后都能 push/pull,省去项目角色配置的麻烦。
正解内网同样存在“生产”与“开发”的边界。Harbor 提供项目级角色(访客/开发者/维护者/项目管理员),配合机器人账号和审计日志,能防止误删线上镜像、追踪异常操作。好比仓库没门禁,进出全靠自觉,规模一大必出乱子。
为何常见:小团队初期人少、信任度高,忽视最小权限原则;等到人多手杂,一次误删才发现为时已晚。
面试题
Q1. Docker 镜像完整名称 docker.io/library/nginx:1.25 中,docker.io 部分代表什么?
- A.镜像的仓库名
- B.镜像仓库服务地址← 正确答案
- C.镜像的版本标签
- D.镜像的命名空间
答案:B。按 host/namespace/repo:tag 的地址结构,docker.io 是 registry 服务地址(公共批发市场),library 是命名空间,nginx 是仓库名,1.25 是标签。选 B。
Q2. 向自建私有仓库推送镜像前,通常必须先执行哪两条命令?
- A.docker build 和 docker run
- B.docker login 和 docker tag← 正确答案
- C.docker pull 和 docker push
- D.docker save 和 docker load
答案:B。docker login 完成仓库认证(刷卡领工牌),docker tag 把镜像名改成仓库规定格式(贴地址标签),之后才能 docker push 成功。其余组合都不是“推送前”的固定环节。
Q3. 默认情况下,Docker 客户端访问私有仓库时要求使用什么协议?
- A.使用 HTTP 明文
- B.使用 HTTPS 加密← 正确答案
- C.使用 FTP 直连
- D.使用 UDP 组播
答案:B。Docker 安全默认要求 HTTPS,否则报 "server gave HTTP response to HTTPS client";除非在 daemon.json 中把该地址加入 insecure-registries 白名单。这相当于门禁默认只认加密通道。
Q4. Harbor 与官方 Docker Registry 的主要区别是什么?
- A.Harbor 体积更小巧
- B.Harbor 内置权限和扫描← 正确答案
- C.Registry 不支持存储
- D.Registry 是收费商业软件
答案:B。Harbor 是在 Registry 之上封装的企业级平台,增加项目、RBAC 权限、漏洞扫描、镜像复制和审计等能力;Registry 本身就是开源轻量存储服务。所以核心区别是 Harbor 多了一整套企业级管理功能。
Q5. 在 Harbor 中删除一个镜像标签后,其底层镜像层何时会被真正删除?
- A.删除立即生效
- B.重启服务之后
- C.运行垃圾回收之后← 正确答案
- D.永远不会被删除
答案:C。删除标签只是让该层引用计数减一,类似从台账划掉;只有引用计数归零的层,在运行 GC 时才会被物理清除。这与“货架撤下货但仓库还没清运”是同一道理。
Q6. 在 Harbor 项目中,“开发者(Developer)”角色默认拥有以下哪种权限?
- A.只能拉取镜像
- B.可以拉取和推送镜像← 正确答案
- C.可以删除任意镜像
- D.可以管理项目成员
答案:B。Harbor 角色模型中,访客只能拉取,开发者可拉取和推送,维护者额外支持删除和撤销推送,项目管理员才能管理成员和策略。选 B。
Q7. Docker Content Trust 中,一个镜像被判定为“可信”的依据是什么?
- A.镜像体积足够小
- B.私钥签名验证通过← 正确答案
- C.名字带有 trust 标记
- D.由管理员负责上传
答案:B。DCT 基于 Notary 签名体系:发布者用私钥对镜像 manifest 摘要签名,拉取者用公钥验证签名是否匹配。验签通过才视为可信,防止同名同标签的篡改镜像混入生产。
Q8. 镜像仓库配置异地复制后,可以改善以下哪些问题?
- A.跨区域拉取延迟高← 正确答案
- B.单点故障影响大← 正确答案
- C.本地磁盘空间不足
- D.镜像签名验证失败
答案:AB。复制把镜像分发到各区域分仓,让用户就近拉取,同时多副本提升可用性、降低单点故障影响。但它不解决磁盘容量问题,也不参与签名验证逻辑——那是 DCT 的职责。
Q9. Harbor 支持的用户认证方式包括以下哪些?
- A.本地数据库认证← 正确答案
- B.LDAP 目录服务认证← 正确答案
- C.OIDC 单点登录认证← 正确答案
- D.明文 HTTP 认证
答案:ABC。Harbor 支持本地用户、LDAP/AD 和企业 OIDC 集成,便于与公司统一认证打通;明文 HTTP 只是传输层的弱点,并非一种“认证方式”,且在安全配置中应被禁用。
Q10. 关于镜像仓库垃圾回收(GC),下列说法正确的有哪些?
- A.仅清理引用计数为 0 的层← 正确答案
- B.建议在只读维护窗口执行← 正确答案
- C.会删除所有历史镜像
- D.可替代每日全量备份
答案:AB。GC 只回收没有任何标签引用的层,执行时仓库进入只读模式,所以应安排在维护窗口。它不会删除仍有引用的历史镜像,更不能替代备份——备份是独立的数据保全手段。