DigitalOcean容器注册表多表支持:权限隔离与CI/CD实践
如果你是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账号并且已经完成了账号验证。然后安装doctlHomebrew用户一条命令搞定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 logindoctl 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-doctlv2 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。多注册表模式下每个注册表都要创建一个独立的Secretkubectl create secret docker-registry regcred-dev \ --docker-serverregistry.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全部清理每天一次GCdemo-prod保留所有正式release tag未打tag的镜像立即清理每周一次GCdoctl提供了镜像删除和垃圾回收命令比如开发环境可以批量删除指定时间之前的tag。删除镜像后存储空间不会立即释放还需要对注册表执行垃圾回收把那些无引用的layer真正清掉。执行前记得确认保留策略否则可能把还在用的镜像layer一并回收掉。我在生产环境吃过一次亏GC之前没有确认某个旧release还在被回滚流程使用回收完以后回滚时才发现镜像没了只能重新构建。血的教训告诉我生产注册表GC之前一定要拉一遍当前部署镜像清单确认没有在用的tag。5. 常见踩坑与排查速查多注册表功能本身很稳但它带来的新问题基本都集中在“认错表、配错权限、算不清用量”这三类。下面整理一份我实际遇到的排查实录直接对照查。现象可能原因处理方式docker login 报 unauthorizedToken无权访问该注册表或注册表名称写错确认登录的是目标注册表重新生成Token并确认权限push 时提示 denied使用的Token只有只读权限换用读写Token或在控制台给Token加写权限pull 时提示 not found镜像根本不在这个注册表或者你拉错了路径前缀用doctl查看目标注册表的仓库列表核对镜像完整地址推完镜像控制台看不到推送时tag前缀打到了别的注册表检查本地镜像的Repository字段推错表的重新打tag再推删除镜像后存储用量没下降需要执行垃圾回收对对应注册表执行GC确认没有其他层被引用多个节点同时拉取镜像变慢并发拉取触发限流或带宽配额不足错峰发布、增加订阅档位、考虑配置镜像缓存生产环境Pod启动时ImagePullBackOffimagePullSecret与镜像地址不在同一个注册表核对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命名规范、各注册表的清理策略都定好再让团队分批切换。做过一次之后你会发现多注册表给运维带来的确定感比多出来的几个仓库名值钱得多。