如果你是DigitalOcean的老用户,对Container Registry这个产品应该不陌生。之前它最大的限制就是“一个账号只能有一个容器注册表”,所有项目的镜像都得挤在同一个命名空间里,生产、测试、不同团队的镜像全部堆在一起,时间一长基本靠猜。最近DO正式推出了多注册表支持,允许在同一个账号下创建和独立管理多个容器注册表。这篇文章就从这个新功能展开,聊聊它到底解决什么问题、怎么配置、权限怎么分、成本怎么算,以及实际踩过哪些坑。适合正在用DO部署服务、做CI/CD流水线,或者想给团队做镜像权限隔离的开发者参考。
1. 多注册表支持到底解决什么问题
先说结论:多注册表支持把DO容器镜像管理的粒度从“账号级”降到了“注册表级”。以前不管你有多少项目、多少环境,所有镜像都要挤在一个Registry里。现在你可以按项目、按团队、按环境拆成多个独立注册表,每个都有自己的命名空间、访问凭证和资源用量。
1.1 旧版单注册表模式有什么别扭
旧版单注册表模式最难受的是镜像命名和权限没法分开。比如你有两个项目A和B,每个项目又有dev和prod两套环境,老玩法只能在同一个Registry里用路径前缀区分:
registry.digitalocean.com/my-account/project-a-dev/api:v1.2.0 registry.digitalocean.com/my-account/project-a-prod/api:v1.2.0 registry.digitalocean.com/my-account/project-b-dev/web:v2.1.0前缀区分在镜像少的时候还能忍,一旦项目多了就全是问题。第一是权限无法细粒度控制,给CI一个读写Token,它就能对注册表里所有镜像做删除操作,哪天流水线脚本写错,可能把生产镜像一起清了。第二是配额和账单没法归因,存储费用是整单算的,你只知道这个月超了,但不知道是A项目刷了大量镜像,还是B项目的测试环境一直在堆积垃圾镜像。第三是控制台体验混乱,所有仓库平铺在一起,没有项目边界,运维排查时别说新人了,老手也得翻半天。
1.2 多注册表模式带来的核心变化
多注册表支持上线后,上面的场景直接换了一种玩法。你可以创建两个注册表:
registry.digitalocean.com/project-a-dev/api:v1.2.0 registry.digitalocean.com/project-a-prod/api:v1.2.0 registry.digitalocean.com/project-b-dev/web:v2.1.0每个注册表是独立实例,拥有独立的仓库列表、独立的访问凭证和独立的资源统计。打个生活化的比方:以前所有食物都放在一个大冰箱里,甜点和生肉串味,谁都能开冰箱。现在换成了几个独立储物柜,每个柜子单独上锁,钥匙分开配,哪个柜子用了多少电也单独能查到。
权限模型也随之清晰了。给开发环境注册表配读写Token、给生产环境注册表只配只读Token,就算开发那边Token泄露,也最多影响dev仓库,动不了生产镜像。这个变化对多团队协作场景来说是质的提升。
1.3 适合哪些场景使用
根据我的实际使用经验,下面几类场景特别适合拆成多个注册表:
| 场景 | 推荐做法 | 原因 |
|---|---|---|
| 多环境隔离 | dev、staging、prod各建一个注册表 | 环境之间镜像彻底隔离,误操作影响面最小 |
| 多团队协作 | 每个团队一个注册表 | 权限边界清晰,A团队不能覆盖B团队镜像 |
| 多产品线 | 每个产品线一个注册表 | 镜像命名不冲突,账单消耗能按产品分开统计 |
| 安全合规 | 数据敏感业务单独一个注册表 | 审计采集、访问控制、清理策略都能单独配置 |
不过也要提醒一句:不是注册表越多越好。如果你只有一个小项目、一个人维护,拆出五六个注册表纯属给自己添乱。这个功能的价值在于“边界管理”,而不是“数量越多越酷”,后面我还会专门讲到底怎么规划才合理。
2. 实操:五分钟创建并使用多个注册表
功能吹得再好,落地才是关键。这一节直接带你创建两个注册表,把推送、拉取、多注册表并存这些基本操作全部走一遍。我的演示环境是macOS + doctl命令行工具,你换成Linux也一样,思路完全相同。
2.1 准备工作
开始之前,先把工具链准备齐。你需要一个DigitalOcean账号,并且已经完成了账号验证。然后安装doctl,Homebrew用户一条命令搞定:
brew install doctl装完以后初始化认证,需要先去控制台生成一个API Token(需要read和write权限),然后执行:
doctl auth init它会提示你粘贴Access Token,粘贴进去回车,看到“Validating token... OK”就说明认证通过了。这一步建议顺手给Token取个清晰的名字,别叫什么“my-token”,我见过太多人半年后看着一堆Token完全想不起是干嘛用的,建议命名成类似 do-token-terraform、do-token-cicd 这种带用途的名字。
2.2 创建第一个注册表
先演示控制台方式。登录DigitalOcean控制台,左侧菜单找到“Container Registry”,点击“Create Registry”,填名称、选择区域、选择订阅档位。区域这里我多说一句:一定要选离你运行工作负载最近的数据中心,比如你的Droplet在sfo3,注册表也选sfo3,镜像拉取延迟最低,流量费用也更划算——跨区域拉镜像虽然也能用,但每一层都得走跨区网络,慢且贵。
CLI方式等价的操作是这样:
doctl registry create demo-dev --region sfo3 --subscription-tier basic参数简单解释一下:demo-dev是注册表名称,创建后这个名字会出现在镜像地址前缀里,所以命名要能一眼看出归属;--subscription-tier指定订阅档,基础档适合个人和小团队,具体配额参考官网定价页。创建成功后,用列表命令确认:
doctl registry list输出里会显示名称、区域、创建时间和订阅档位。到这里第一个注册表就建好了。
2.3 创建第二个注册表
既然是多注册表,光一个不行。我们再创建一个生产用的注册表,命令行操作完全一样:
doctl registry create demo-prod --region nyc1 --subscription-tier basic这里故意选了和demo-dev不同的区域,是为了演示跨区管理。实际生产环境我建议还是保持所有资源同区域,我见过有人图省事把dev放在sfo3、prod放在nyc1,结果每次跨区同步镜像都慢得抓狂。除非你有明确的多区域容灾需求,否则不要主动制造跨区域网络开销。
再次执行doctl registry list,你应该能看到两条记录:
Name Region Subscription Created At demo-dev sfo3 basic ... demo-prod nyc1 basic ...到这一步,你已经拥有了两个完全独立的容器注册表。注意注册表名称在账号内必须唯一,创建时如果提示重名,换个词就行。
2.4 登录、打标签、推送和拉取
注册表建好后,先登录。DO官方推荐用doctl代劳,它会自动帮你处理认证信息,不用手搓docker login:
doctl registry login登录成功后,随便拿一个本地镜像做测试。比如我本地有个demo镜像,先给镜像打上目标注册表的标签:
docker tag demo:latest registry.digitalocean.com/demo-dev/api:v1.0.0注意标签格式:路径前缀必须和你的注册表完全匹配。这里是demo-dev,如果你手滑写成了demo-prod,推送就会进生产仓库,这种低级错误在多注册表模式下特别容易犯。打好标签后推送:
docker push registry.digitalocean.com/demo-dev/api:v1.0.0推送完成后,你可以用doctl registry repository list-demo-dev之类的命令查看仓库内容,也可以直接在控制台页面看到。拉取镜像就是在部署服务器上执行:
docker pull registry.digitalocean.com/demo-dev/api:v1.0.0同样,如果你要操作demo-prod,先doctl registry login切换对应注册表,或者手动docker login到对应的registry地址,然后再拉再推。
2.5 多注册表如何共存使用
实际工作中你不会只在单台机器上操作,比如同一台构建机可能要往两个注册表推镜像。Docker本身支持在~/.docker/config.json里保存多个registry的认证态,只要不是同一registry地址互相覆盖,多个注册表的登录信息可以共存。
我的建议是脚本写到哪一步就明确登录哪一步,不要迷恋“全局登录”。比如打包脚本开头:
doctl registry login --registry demo-dev docker push registry.digitalocean.com/demo-dev/...这个“先指定注册表再操作”的习惯,能避免90%的镜像推错位置事故。哪怕牺牲一点执行时间,也要把上下文的确定性拉满。
3. 权限模型与团队协作配置
多注册表最大的价值其实不在“多建几个仓库”,而是每个注册表都可以有独立的一套令牌和权限。这一节重点讲怎么利用这个能力搭团队协作模型,以及CI/CD里怎么接。
3.1 注册表级别的访问令牌划分
DigitalOcean的容器注册表支持按注册表生成访问令牌。你可以为demo-dev生成一个读写令牌,为demo-prod只生成只读令牌。开发和CI用demo-dev的读写令牌,部署流水线用demo-prod的只读令牌。这样开发人员可以自由推送和删除dev镜像,但到了生产注册表就只剩拉取权限。
这样划分之后,即便某个开发者的Token泄露,攻击者能碰到的只有dev仓库,生产镜像的完整性和可用性完全不受影响。这种“最小权限”的思路,是任何镜像托管方案的核心安全实践,多注册表只是把这种思路变成了开箱即用的能力。
实操上,在控制台的每个注册表详情页里,都有独立的Token管理入口,创建令牌时可以勾选Read或Read/Write权限。CLI也可以用doctl的registry token相关命令来管理。
3.2 团队协作时注册表怎么分
给团队划分注册表时,我总结了一个比较稳的公式:注册表 = 业务边界 × 安全等级。不要只按环境分,也不要只按项目分,两个维度都要看。
比如你们有三个团队:支付团队、用户团队、数据团队,每个团队又有dev和prod两套环境。理论上拆出六个注册表是可行的,但我个人不推荐一上来就拆这么细。我先建议按团队拆三个核心注册表,team-pay、team-user、team-data,环境之间用镜像tag区分,等团队的镜像量和自动化程度都上来了,再在需要严格环境隔离的团队内部拆dev和prod。
为什么?因为注册表多了以后,管理成本不是线性增长,而是指数增长的。每个表都要维护Token、设置清理策略、排查权限问题。小团队用tag区分环境,代价低得多;大团队用独立注册表隔离环境,安全收益更明确。先问自己“有没有隔离的必要”,再决定要不要多开一个表,而不是为了开而开。
3.3 CI/CD流水线集成实操
把多注册表接到GitHub Actions里非常方便,DO官方维护了doctl action。下面是一段我实际在用的工作流片段:
- name: Login to DigitalOcean Registry uses: digitalocean/action-doctl@v2 with: token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} - name: Login Registry run: doctl registry login --registry demo-dev - name: Build and Push run: | docker build -t registry.digitalocean.com/demo-dev/api:${{ github.sha }} . docker push registry.digitalocean.com/demo-dev/api:${{ github.sha }}关键点有两个:一是DIGITALOCEAN_ACCESS_TOKEN要单独存成GitHub Secret,不要把Token明文写在仓库里;二是doctl registry login后面显式指定了--registry demo-dev,这样登录的就是正确的注册表,不会发生流水线把镜像推到dev表却想部署到prod表的情况。
生产部署的流水线同理,把登录的目标换成demo-prod,并且让doctl使用只读Token。我见过有的团队一套Token走天下,开发环境Token直接拿去推送生产镜像,这是典型的“一把钥匙开所有锁”,为的就是少配一个Secret,结果反而成了事故高发点。
3.4 Kubernetes拉取私有镜像的配置方式
如果你的服务跑在DO的Kubernetes上,拉取私有注册表镜像需要提前创建imagePullSecret。多注册表模式下,每个注册表都要创建一个独立的Secret:
kubectl create secret docker-registry regcred-dev \ --docker-server=registry.digitalocean.com \ --docker-username=<你的Token> \ --docker-password=<你的Token>然后在Deployment的spec里指定:
spec: template: spec: imagePullSecrets: - name: regcred-dev注意一个很容易踩的坑:生产环境的Pod如果在配置里引用的是regcred-prod,那么image字段的镜像地址前缀必须也是demo-prod,两边必须严格对应。跨注册表引用会导致拉取失败,而且报错信息不会直接告诉你“地址和Secret不匹配”,只会提示你pull access denied,排查时要先想到这里。
4. 配额、计费与清理策略
多注册表模式带来了便利,也带来了成本管理的新问题。很多人以为拆出多个表,存储配额也会相应翻倍,其实不是这样。这一节把计费和清理的关键点讲清楚,避免你月底收到账单之后肉疼。
4.1 订阅与存储计费逻辑
DigitalOcean的容器注册表采用订阅制,每个订阅档位对应一定的存储容量和出站流量额度。多注册表支持并不会让你每个表都获得独立的一份免费额度——所有注册表共享账号当前订阅的总配额。
举个例子:假设你的订阅档位包含500GB存储,你创建了两个注册表,那么是两个表合计使用这500GB,而不是每个表各500GB。控制台和CLI可以按注册表分别查看消耗量,但最终账单是按账号整体的资源消耗来结算的。这种计费方式对绝大多数场景是合理的,但要求你在规划阶段就心里有数:拆多个表的主要目的是权限和边界管控,不是变相扩容。
4.2 多注册表带来的存储冗余问题
这是最容易被忽视的一个成本坑。Docker镜像由多个layer组成,同一个注册表内部,多个镜像可以共享相同的layer,比如两个服务都基于同一个基础镜像,基础镜像的layer在存储上只有一份。
但注册表和注册表之间是隔离的,一个注册表里已经存过的layer,不会因为另一个注册表也有同名layer就被自动复用。也就是说,如果你把同一套服务镜像分别推到demo-dev和demo-prod两个注册表,存储就要付两份。dev环境天天构建新镜像,每个新镜像都重复往prod表再推一份,成本很快就上来了。
我见过一个项目,把dev和生产完整复制到两个注册表,一个月存储量翻了三倍。你要用多注册表,就要明白它是用存储换隔离,不是无成本的。
4.3 清理策略和垃圾回收实操
注册表里最占空间的往往不是正式版本,而是那些tag为latest、开发过程中反复推送的中间产物。多注册表模式下,每个注册表都可以单独配置清理策略,我建议这样设:
| 注册表 | 保留策略 | 清理频率 |
|---|---|---|
| demo-dev | 只保留最近3天构建的镜像,历史tag全部清理 | 每天一次GC |
| demo-prod | 保留所有正式release tag,未打tag的镜像立即清理 | 每周一次GC |
doctl提供了镜像删除和垃圾回收命令,比如开发环境可以批量删除指定时间之前的tag。删除镜像后存储空间不会立即释放,还需要对注册表执行垃圾回收,把那些无引用的layer真正清掉。执行前记得确认保留策略,否则可能把还在用的镜像layer一并回收掉。
我在生产环境吃过一次亏:GC之前没有确认某个旧release还在被回滚流程使用,回收完以后回滚时才发现镜像没了,只能重新构建。血的教训告诉我,生产注册表GC之前一定要拉一遍当前部署镜像清单,确认没有在用的tag。
5. 常见踩坑与排查速查
多注册表功能本身很稳,但它带来的新问题基本都集中在“认错表、配错权限、算不清用量”这三类。下面整理一份我实际遇到的排查实录,直接对照查。
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| docker login 报 unauthorized | Token无权访问该注册表,或注册表名称写错 | 确认登录的是目标注册表,重新生成Token并确认权限 |
| push 时提示 denied | 使用的Token只有只读权限 | 换用读写Token,或在控制台给Token加写权限 |
| pull 时提示 not found | 镜像根本不在这个注册表,或者你拉错了路径前缀 | 用doctl查看目标注册表的仓库列表,核对镜像完整地址 |
| 推完镜像控制台看不到 | 推送时tag前缀打到了别的注册表 | 检查本地镜像的Repository字段,推错表的重新打tag再推 |
| 删除镜像后存储用量没下降 | 需要执行垃圾回收 | 对对应注册表执行GC,确认没有其他层被引用 |
| 多个节点同时拉取镜像变慢 | 并发拉取触发限流或带宽配额不足 | 错峰发布、增加订阅档位、考虑配置镜像缓存 |
| 生产环境Pod启动时ImagePullBackOff | imagePullSecret与镜像地址不在同一个注册表 | 核对Secret的docker-server,确认镜像前缀和Secret一一对应 |
5.1 镜像推错注册表怎么抢救
推错注册表这种事,多注册表模式下特别容易发生。我自己的经验是:发现推错后,第一时间用完整路径确认事实,然后决定是删还是复制。
如果是dev镜像推到了prod注册表,问题不大,直接删除prod表里那个错误tag,再重新推正确的。删除时注意别误删别人刚推的有效tag,先列出该仓库所有的tag,确认删除目标只包含错误镜像。如果是生产镜像推到了dev表,情况稍微麻烦一点,dev表大家都有写权限,镜像可能已经被覆盖或混入大量中间版本,先拉取出来重新打tag再推,不要直接在dev表上做修改。
抢救完之后,我建议从机制上防止第二次发生:把每个注册表的缩写名和完整地址贴在构建机终端上方,或者在脚本里加一道“注册表名称校验”,推送前自动比较目标前缀是否和当前登录的注册表一致,不一致直接终止构建。多用一分钟做防护,真出事故时能省一小时。
5.2 Token权限边界不清晰怎么排查
多注册表模式下Token一多,很容易弄混哪个Token能操作哪个表。我踩过的坑是:给两个注册表各建了一个Token,结果手动登录时把prod的Token配到了dev的doctl登录环境里,后续所有dev推送都报权限错误,排查了半天才发现是Token和注册表不匹配。
现在的做法是给每个Token命名时带上注册表名和权限级别,比如demo-prod-readonly、demo-dev-rw。控制台里Token列表一下子就清楚了。万一实在搞不清哪个Token对应哪个注册表,也不要急着猜,直接在控制台创建新Token,然后新旧分批替换,彻底清掉混乱状态。
5.3 跨注册表拉取镜像的性能优化
多注册表并存时,有些同学会图方便跨区或跨表拉镜像。虽然功能上没有限制,但性能和费用上会有明显差异。镜像的每一层都是远程拉取,跨区域网络延迟会体现在每一次pod调度、每一次滚动更新上。
如果你的工作负载在sfo3,就老老实实从sfo3的注册表拉镜像。如果确实有多区域需求,优先考虑把镜像推到每个区域自己的注册表,然后用自动化脚本同步,而不是每次都在运行时跨区拉。同步脚本的编写不复杂,本质就是docker pull再docker push,但可以用doctl的API把流程自动化,GitHub Actions定时任务就能跑。
这套多注册表能力上线之后,我最大的体会是:它不是一个“多建几个仓库”的花哨功能,而是把镜像管理的边界主动权交还给了使用者。早年间单注册表时代的那种“所有镜像一锅炖、权限一把抓、账单一团麻”的日子,终于有了正经的解法。如果你正准备从单注册表迁移到多注册表,别急着把所有项目全拆开,先按我前面说的那个公式——业务边界乘安全等级——做一次规划,把dev和prod的权限边界、Token命名规范、各注册表的清理策略都定好,再让团队分批切换。做过一次之后你会发现,多注册表给运维带来的确定感,比多出来的几个仓库名值钱得多。