
1. 先把这个场景想透为什么 kolla 镜像要绕道阿里云镜像仓库做过 OpenStack 私有云交付的基本都遇到过这个场面kolla-ansible 的 globals.yml 改好了inventory 也填完了信心满满地敲下 prechecks结果卡在拉镜像上一个 glance-api 镜像拉了四十分钟还在 60%最后 connection reset。等你好不容易把镜像拉完一算时间光拉镜像花了一整天。这个项目标题讲的就是这件事——把 kolla 的 docker 镜像从上游注册表同步到阿里云容器镜像服务ACR再让 kolla-ansible 从 ACR 拉取把原本不可控的跨国链路变成可控的国内链路。我的定位很明确这是一篇给OpenStack 实施工程师、私有云运维、以及正在用 kolla-ansible 部署环境的人看的实操记录。如果你只是想在单机上玩一下 all-in-one也适用但收益最大的还是那些要交付多套环境、或者要在内网隔离环境里部署的团队。前置知识要求不高会敲 docker 命令、能看懂 YAML 配置就够了但我会把每一步为什么这么做讲清楚避免你照着抄却不知道为什么出问题的时候完全抓瞎。先说清楚这个方案到底解决了什么。kolla 的镜像托管在海外注册表上单个镜像从几百 MB 到 1.5 GB 不等一个完整的 OpenStack 环境通常需要 60 到 120 个镜像总体积轻松超过 15 GB。这个体量在链路抖动面前很不友好断流重传的成本极高而且 kolla-ansible 的拉取是并发执行的多台节点同时拉同一个镜像时带宽被摊薄经常出现部分节点成功、部分节点失败的情况反复重试非常折磨人。把镜像先归拢到阿里云 ACR等于把一次性从远处搬一大批货改成先集中搬一次再从身边分发这个思路在所有离线交付场景里都通用。1.1 三条可选路线为什么最终选 ACR 中转在动手之前我把方案盘了一遍实际可走的路有三条各有各的适用边界。第一条是ACR 中转也就是这个项目的做法。中转机从上游拉镜像打 tag 后推到阿里云 ACR部署节点把docker_registry指向 ACR 地址直接拉。优点是部署节点完全不需要直连上游拉取速度取决于 ACR 到你机房的链路通常比直连海外快一个数量级缺点是需要在阿里云上有账号和实例并且镜像在云端有一份额外的存储。第二条是内网自建 Harbor。适合完全隔离的内网环境或者对镜像资产有强管控要求的场景。做法和中转一样只是终点换成自己机房里的 Harbor。这个方案我没有选原因是部署节点如果不在同一个内网里Harbor 反而成了新的瓶颈而且 Harbor 本身要维护证书、存储、备份都得操心。但如果你交付的是纯内网环境Harbor 基本是唯一解。第三条是docker save / load 离线包。在中转机把镜像导出成 tar用移动硬盘或者内网文件服务器拷到目标机器再 load。这个方案最原始也最可靠完全不依赖网络但镜像总量 20 GB 起步十几个 tar 包来回倒腾版本一升级就得重来一遍只适合一次性交付或者气隙环境。三条路线的对比如下方案适用场景首次准备耗时后续更新成本主要风险ACR 中转部署节点能访问公网或同地域 VPC中取决于上行带宽低重新推变更镜像即可云上配额、带宽限制内网 Harbor完全隔离内网、强管控高要搭一套服务中自维护成本、证书管理save/load 离线包气隙环境、一次性交付高极高几乎全量重做介质损坏、版本错配选 ACR 的核心理由其实就一句话把不确定性前置并集中处理。上游拉取失败是可以重试的重试成本只发生在一台中转机上而如果让十台部署节点各自去拉任何一台失败都得单独排查排查成本被放大了十倍。这个集中失败、集中重试的思路在批量运维场景里几乎是万能钥匙。1.2 版本与标签的对齐最容易翻车的地方这块必须先讲因为它是整个流程里最容易出错、出错之后最难查的环节。kolla 的镜像标签不是随便起的它由openstack_release和kolla_base_distro两个变量拼出来形如2024.1-ubuntu部分版本还会带上发行版的小版本号比如2024.1-ubuntu-22.04。你在中转机上推的标签必须和部署节点上 kolla-ansible 算出来的标签完全一致差一个字符都拉不到。我踩过一次坑中转机上按记忆推了2024.1-ubuntu结果部署节点上 kolla-ansible 因为kolla_base_distro配的是ubuntu、但内部模板拼出来的是带小版本的形式prechecks 直接报镜像不存在。当时排查了半天以为是仓库认证问题最后发现是标签对不上。所以我的建议是不要凭记忆写标签先在一台能跑 kolla-ansible 的机器上把标签打印出来或者直接看一次 pull 日志里实际请求的镜像全名照着抄。另一个容易忽略的点是docker_namespace。不同版本的 kolla-ansible 默认值不一样早期版本走 Docker Hub 的kolla命名空间较新版本默认走quay.io的openstack.kolla。这个值决定了中转机从哪儿拉必须先确认清楚。确认方法很简单看 kolla-ansible 安装目录下角色的默认变量或者直接kolla-ansible -i inventory all -e globals.yml -e passwords.yml pull -vvv跑一遍日志里第一次出现的完整镜像地址就是标准答案。1.3 中转机的定位它不是部署节点很多人图省事直接拿一台部署节点当中转机拉完推完再删镜像。我不推荐这么干原因有三个。第一中转过程中本地磁盘会短暂堆积大量镜像docker 的存储目录会膨胀如果和部署节点的根分区共用很容易把磁盘吃满导致 kolla 服务起不来。第二中转机需要稳定的上行带宽而部署节点的带宽通常是要留给业务流量的。第三职责分离之后下次版本升级你只需要重新跑一遍中转机的脚本不会影响正在运行的环境。我的做法是单独准备一台机器做中转配置不用很高2 核 4G 就够因为瓶颈在网络上不在 CPU。磁盘要给足至少 100 GB 空闲因为这个流程里本地会同时存在从上游拉的和已打 tag 待推的两份镜像虽然它们共享层但 docker 的层管理在频繁 tag 之后磁盘占用会比想象中大。如果条件允许中转机放在阿里云同地域的 ECS 上是最优解走 VPC 内网推送到 ACR速度快、不消耗公网带宽成本也低。这一点后面讲网络配置时会再展开。2. 开工前的环境准备与关键参数确认准备工作看着琐碎但每一步都直接影响后面的成功率。我的习惯是在动手前先把所有参数列在一张纸上确认无误后再执行避免执行到一半发现某个值不对前面白做。这一节把中转机、ACR 实例、本地 docker 三块的前置条件过一遍顺带把镜像清单的获取方法讲透因为清单是后面所有批量操作的输入。先明确一下这个流程里会涉及到的四类角色上游注册表提供源镜像中转机负责搬运阿里云 ACR作为落点部署节点kolla-ansible 管理的那批机器作为最终消费者。四者的关系理清楚了配置就不会乱。2.1 中转机的系统与 docker 环境操作系统我一般用 Ubuntu 22.04 或者 Rocky Linux 9和 kolla 的基础镜像发行版保持一致不是必须的但能省掉一些奇奇怪怪的兼容问题。docker 版本建议 24.x 以上主要是为了更好的 manifest 处理和更稳定的 push 行为。安装流程不展开说几个容易忽略的点。/etc/docker/daemon.json里我通常会调整两个参数。一个是max-concurrent-downloads和max-concurrent-uploads默认是 3在带宽充足的情况下可以调到 5但不建议更高因为并发太高反而容易触发注册表的限流导致部分请求返回 429。另一个是>{ data-root: /data/docker, max-concurrent-downloads: 5, max-concurrent-uploads: 5, log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 } }改完systemctl daemon-reload systemctl restart docker生效。这里有个细节如果/data/docker是新建目录要先确认它有足够的 inodedocker 的层文件数量很多小文件的 inode 消耗不小df -i看一眼比较保险。注意如果中转机上原本已经有别的容器在跑改>docker login --username你的账号 registry.cn-hangzhou.aliyuncs.com密码不是账号密码而是 ACR 控制台里设置的固定密码或者临时密码。固定密码更适合自动化场景临时密码有效期短脚本跑到一半过期了会很尴尬。登录成功后凭据会写到/root/.docker/config.json如果用 root 执行。这里有个坑kolla-ansible 是通过 ssh 到各节点执行的默认用户很多时候是 root所以凭据要落在 root 的家目录下。如果你用普通用户执行 ansible凭据就在那个用户的家目录两者不通用。我一般统一用 root 或者统一用带 sudo 的普通用户不要混着来。凭据文件长这样里面是 base64 编码的用户名密码{ auths: { registry.cn-hangzhou.aliyuncs.com: { auth: dXNlcm5hbWU6cGFzc3dvcmQ } } }注意这个文件等同于明文密码权限必须是 600也不要提交到任何代码仓库里。团队协作时用 ansible-vault 或者环境变量注入别图省事直接写在脚本里。验证登录是否生效不需要真的推镜像用docker pull拉一个公开的小镜像试试就行。如果返回unauthorized通常是密码错了或者命名空间权限没配好。2.4 镜像清单怎么来三种获取方式的实际取舍清单是整个批量操作的输入获取方式我试过三种各有各的适用场景。方式一跑一次 pull 之后从本地导出。这是最准的因为它是 kolla-ansible 真实请求过的镜像集合包含了所有enable_*开关生效后的结果。kolla-ansible -i ./inventory all -e globals.yml -e passwords.yml pull docker images --format {{.Repository}}:{{.Tag}} \ | grep -E openstack\.kolla|quay\.io \ | sed -E s#^[^/]/[^/]/## \ | sort -u images.txt缺点是必须先能拉成功如果链路本身就不通这个方法就卡死了。所以我一般先用它在一台链路好的机器上跑一次把清单沉淀下来后面就复用了。方式二从角色默认变量里 grep。适合完全拉不动、但想先知道要同步哪些镜像的情况。grep -rhE ^[a-z0-9_]_image:\s /usr/share/kolla-ansible/ansible/roles/*/defaults/main.yml \ | awk -F {print $2} \ | sort -u images.txt拿到的是全集不管enable_*有没有开。多推几个不影响使用只是浪费仓库配额和推送时间。如果你只启用核心服务可以手动裁掉大部分能省不少时间。方式三从 pull 的调试日志里抓。加上-vvv跑日志里会出现完整的镜像地址kolla-ansible -i ./inventory all -e globals.yml -e passwords.yml pull -vvv 21 \ | grep -oE quay\.io/openstack\.kolla/[a-z0-9-]:[a-zA-Z0-9.\-] \ | sort -u images.txt这个方法适合链路部分可用、想边拉边抓清单的场景但日志量大跑起来比较慢。三种方式我都用过实际生产里我倾向于方式二打底 方式一校准。先用方式二拿到全集清单人工过一遍去掉确定不用的服务再用方式一跑一次真实 pull对比两边差异把漏掉的补上。这个双向校验能抓出很多我以为不用但其实必需的基础镜像。顺便把常见服务对应的镜像列一下方便你裁剪清单时对照。这里只列核心组合以你的globals.yml实际启用项为准globals.yml 开关通常需要的镜像短名基础组件始终需要kolla-toolbox、cron、fluentd、haproxy、keepalived、mariadb、memcached、rabbitmqenable_keystonekeystone、keystone-fernet、keystone-sshenable_glanceglance-apienable_novanova-api、nova-conductor、nova-scheduler、nova-compute、nova-libvirt、nova-novncproxy、nova-sshenable_neutronneutron-server、neutron-openvswitch-agent、neutron-l3-agent、neutron-dhcp-agent、neutron-metadata-agentenable_horizonhorizonenable_cindercinder-api、cinder-scheduler、cinder-volume、cinder-backup基础组件那一行是最容易被漏的。我见过有人只推了 OpenStack 服务镜像部署时 prechecks 报mariadb镜像找不到一查才发现基础组件的镜像也是从同一个命名空间拉的。3. 拉取与推送的核心实操准备工作做完进入正题。这一节我会按先手工打通一个镜像、再上批量脚本、最后用 skopeo 做优化的顺序讲这个顺序很重要因为手工验证能提前暴露认证、命名、标签这些基础问题一旦批量脚本跑起来才发现问题排查成本会高很多。3.1 先手工走一遍单镜像把链路打通不要一上来就写循环。先挑一个体积中等的镜像比如kolla-toolbox几百 MB把 pull、tag、push 三个动作走一遍确认整条链路是通的。SRCquay.io/openstack.kolla/kolla-toolbox:2024.1-ubuntu DSTregistry.cn-hangzhou.aliyuncs.com/kolla-mirror/kolla-toolbox:2024.1-ubuntu docker pull $SRC docker tag $SRC $DST docker push $DST这三条命令看着简单但每一步都可能出问题。docker pull失败通常是链路问题或者镜像名拼错docker tag不会失败但如果目标地址格式不对会在 push 时暴露docker push失败最常见的是没登录、命名空间不存在、或者仓库名不合法。推送成功后去 ACR 控制台的镜像仓库列表里看一眼能看到刚推上去的镜像和它的标签说明链路完全打通。这一步的验证价值很高后面批量跑的时候如果出现大面积失败基本可以排除基础配置问题直接从网络和配额方向排查。这里解释一下docker tag到底做了什么。它不会复制镜像数据只是在本地给同一个镜像 ID 多加一个名字标签指向同一份层数据。所以 tag 操作是瞬间完成的不占额外空间。真正消耗时间和带宽的是 pull 和 push。理解这一点很重要因为它意味着打 tag 失败从来不是磁盘问题只可能是名字格式问题。3.2 批量脚本串行、重试、日志三件套批量操作的脚本我改过很多版最后沉淀下来的版本核心就三个特征串行执行、失败重试、全量日志。串行的理由是并发推送到同一个 ACR 实例时容易出现个别请求被限流的情况而且并发下的错误日志会交织在一起排查很痛苦。串行的代价是总耗时变长但换来的是过程可控、失败可定位我认为值得。如果你镜像数量很多又赶时间可以把并发控制在 3但要确保每个镜像的日志单独落文件。重试是必须的因为网络抖动是常态。我的策略是每个镜像最多重试 3 次间隔递增10 秒、20 秒、30 秒超过就记为失败最后统一汇总。#!/usr/bin/env bash set -uo pipefail SRC_REGISTRYquay.io SRC_NSopenstack.kolla DST_REGISTRYregistry.cn-hangzhou.aliyuncs.com DST_NSkolla-mirror TAG2024.1-ubuntu LIST./images.txt LOG./sync-$(date %Y%m%d-%H%M).log FAILED./failed.txt : $FAILED log() { echo [$(date %F %T)] $* | tee -a $LOG; } retry() { local n0 max3 delay10 until $; do n$((n 1)) if [ $n -ge $max ]; then return 1 fi log 第 ${n} 次失败${delay}s 后重试 sleep $delay delay$((delay 10)) done } while IFS read -r name; do [ -z $name ] continue src${SRC_REGISTRY}/${SRC_NS}/${name}:${TAG} dst${DST_REGISTRY}/${DST_NS}/${name}:${TAG} log ${name} if ! retry docker pull $src $LOG 21; then log PULL FAILED: ${src} echo $name $FAILED continue fi docker tag $src $dst if ! retry docker push $dst $LOG 21; then log PUSH FAILED: ${dst} echo $name $FAILED continue fi log OK done $LIST log 完成失败 $(wc -l $FAILED) 个用法就是./sync.sh脚本会读同目录的images.txt一行一个镜像短名。跑完之后failed.txt里就是需要补推的重新跑一遍脚本先把失败的写回 images.txt即可。这个脚本有几个细节值得说。set -uo pipefail里没有加-e是因为我们要让循环在单个镜像失败时继续往下走如果加了-e第一个失败就整个脚本退出了。日志用tee同时输出到屏幕和文件方便你实时观察进度也能事后回溯。retry函数用的是命令作为参数传入的写法这样 pull 和 push 都能复用同一套重试逻辑不用写两遍。注意脚本里把凭据写进 globals.yml 或者脚本本身都是不安全的。ACR 密码我一般用环境变量或者docker login的持久化凭据脚本只负责 pull/tag/push不管认证。实测下来在一条稳定的链路上100 个镜像、平均 350 MB串行跑完大概 3 到 5 小时主要时间花在上行推送。如果你在阿里云同地域的 ECS 上走 VPC 内网这个时间能压缩到 1 小时以内差距非常明显。3.3 用 skopeo 直接跨仓库复制省掉落地磁盘docker pull/tag/push 的问题在于镜像要在本地完整落一遍盘磁盘占用大、IO 开销也大。skopeo 能直接在两个注册表之间复制数据不落地本地镜像库对中转机磁盘的要求大幅降低。单镜像复制skopeo copy --all --retry-times 3 \ --dest-creds ${ACR_USER}:${ACR_PASS} \ docker://quay.io/openstack.kolla/nova-api:2024.1-ubuntu \ docker://registry.cn-hangzhou.aliyuncs.com/kolla-mirror/nova-api:2024.1-ubuntu这里的--all是关键它会把多架构的 manifest list 完整复制过去包括 amd64 和 arm64。这一点非常重要因为docker pull/tag/push的组合默认只会保留当前机器架构的那一份 manifest如果以后要在 arm 架构的节点上部署从 ACR 拉的时候就会报no matching manifest。我早期用 docker 脚本踩过这个坑后来统一改成 skopeo就是为了避免这个隐患。批量复制可以用skopeo syncwhile IFS read -r name; do [ -z $name ] continue skopeo sync --src docker --dest docker --all --retry-times 3 \ --dest-creds ${ACR_USER}:${ACR_PASS} \ quay.io/openstack.kolla/${name}:2024.1-ubuntu \ registry.cn-hangzhou.aliyuncs.com/kolla-mirror \ || echo $name failed.txt done images.txtskopeo sync的目标参数只写到命名空间层级源参数的仓库名和标签会自动带过去所以镜像名和标签都保持原样不用手工拼。skopeo 的缺点是可观测性差一些进度不直观出问题时的报错信息不如 docker 友好。我的做法是日志里记录每个镜像的开始和结束时间跑完之后用耗时数据反推哪些镜像偏大或者哪些链路不稳定。如果不想装 skopeo只用 docker 也能保证多架构方法是先docker manifest inspect看源镜像有没有 manifest list如果只有单架构那用什么都一样如果是多架构就需要用docker buildx imagetools create在推送端重新组装流程会复杂不少不如直接用 skopeo 省事。3.4 验证别只看推送成功要做清单比对推送完成不等于可以收工。我见过推上去的镜像标签对、但内容是旧的上一次推送的缓存也见过 manifest 缺失导致只能在一种架构上拉取。所以推送完成后必须做一次验证。第一层验证是计数比对源清单有多少个ACR 上就有多少个仓库# 本地清单 wc -l images.txtACR 侧可以通过控制台看仓库总数或者用 API。数量对不上说明有遗漏去 failed.txt 里找。第二层验证是摘要比对随机抽几个镜像对比 digestskopeo inspect --raw docker://quay.io/openstack.kolla/nova-api:2024.1-ubuntu | sha256sum skopeo inspect --raw docker://registry.cn-hangzhou.aliyuncs.com/kolla-mirror/nova-api:2024.1-ubuntu | sha256sum两个 sha256 一致说明 manifest 内容完全相同镜像层也就完全一致。这个方法比对比镜像大小可靠得多因为大小相同不代表内容相同。第三层验证是实际拉取在一台部署节点上把docker_registry配好跑一次kolla-ansible pull看到全部成功才算真正闭环。这一步要放在部署前的准备阶段做不要等 prechecks 了才发现问题。提示如果 ACR 上开了镜像安全扫描推送完成到扫描出结果之间有个时间差扫描过程中拉取是正常的不用等扫描完。4. 让 kolla-ansible 从阿里云仓库拉镜像镜像推到 ACR 只是第一步真正让部署流程用上它要在 kolla-ansible 的配置侧做三处调整。这一节把配置项、认证方式、拉取策略、多版本共存四块讲清楚这些都是部署阶段必踩的点。先理解 kolla-ansible 是怎么拼镜像地址的。它在角色里把镜像地址拆成几个变量docker_registry是注册表域名docker_namespace是命名空间后面接镜像短名和标签。所以要改的就是前两个变量把它们从默认值改成你的 ACR 地址。这个设计的好处是你不需要逐个改镜像名改两个全局变量就全部生效了。4.1 globals.yml 里必须改的几个变量打开/etc/kolla/globals.yml找到或者新增这几行# 注册表域名不带 https:// 前缀 docker_registry: registry.cn-hangzhou.aliyuncs.com # 你创建的 ACR 命名空间 docker_namespace: kolla-mirror # 基础发行版必须和推送时用的标签一致 kolla_base_distro: ubuntu # OpenStack 版本必须和推送时用的标签一致 openstack_release: 2024.1这四个值决定了 kolla-ansible 拼出来的镜像地址。前两个决定从哪儿拉后两个决定拉哪个标签。四个值里任何一个和实际推送的不一致都会导致镜像找不到。docker_registry这里有个常见的低级错误写成https://registry.cn-hangzhou.aliyuncs.com。带了协议前缀之后拼出来的地址会变成https://registry.cn-hangzhou.aliyuncs.com/...docker 解析的时候会报错或者当成非法镜像名。只写域名不写协议不写结尾斜杠。docker_namespace的坑在于它和注册表的关系。默认值在不同版本里可能是kolla或openstack.kolla你改成自己的命名空间之后拼出来的地址就变成registry.cn-hangzhou.aliyuncs.com/kolla-mirror/nova-api正好对上你推送的目标地址。所以推送时用的命名空间必须和这里的值完全一致包括大小写虽然 ACR 只允许小写但还是提一句。kolla_base_distro和openstack_release这两个值必须和中转机推送时用的标签组合严格对应。我在 1.2 节强调过这一点这里再强调一次因为它是最高频的失败原因。改完之后建议先用一个命令验证一下拼出来的地址ansible -i ./inventory all -m debug \ -a msg{{ docker_registry }}/{{ docker_namespace }}/nova-api:{{ openstack_release }}-{{ kolla_base_distro }} \ -e /etc/kolla/globals.yml输出的地址和你在 ACR 上看到的一致配置就对了。4.2 私有仓库认证三种注入方式的选择ACR 的仓库默认是私有的部署节点拉取时必须带认证。kolla-ansible 提供了几种处理方式我用过两种各有适用场景。方式一每台节点提前 docker login。最直接但节点多了之后重复劳动而且凭据过期需要重新登录不适合频繁重建的环境。docker login --username账号 registry.cn-hangzhou.aliyuncs.com方式二通过 kolla-ansible 变量注入。在 globals.yml 里加docker_registry_username: {{ vault_acr_username }} docker_registry_password: {{ vault_acr_password }}配套的vault_acr_username/vault_acr_password放在 vault 加密文件里。kolla-ansible 在 prechecks 阶段会拿这组凭据去各节点登录一次配置全节点生效这是我推荐的方式。关于凭据存放我的做法是单独搞一个secrets.yml用 ansible-vault 加密跑的时候-e secrets.yml --ask-vault-pass。这样配置文件可以放心进代码仓库密码不会泄漏。团队里多人协作时vault 密码通过内部渠道传递不走代码仓库。有一点要注意docker_registry_password里如果含特殊字符比如、#、$在 YAML 里要用引号包起来否则解析会出问题。我遇到过一次密码里带#没加引号YAML 把它当注释了密码后半截直接被截断报了个莫名的认证失败查了很久。还有第三方式是 ACR 支持匿名拉取把仓库设成公开。这个方式省事但不推荐镜像里有 OpenStack 的各类配置和基础组件暴露在公网不合适。如果部署节点在内网且对安全要求不高倒是可以考虑权衡一下。4.3 拉取策略与部署前的验证顺序配置改完后建议按这个顺序验证每一步都能把问题挡在更早的阶段单节点手工拉取。在任意一台部署节点上直接docker pull一个镜像验证认证和网络。这一步失败的话问题在凭据或 VPC 域名不在 kolla-ansible。跑kolla-ansible pull。这个命令只拉镜像不做别的是验证配置最安全的方式。跑之前建议加-vvv日志会显示每个镜像的完整地址和拉取结果出问题好定位。跑kolla-ansible prechecks。这一步会检查镜像是否存在、版本是否匹配比 pull 更严格。正式 deploy。kolla-ansible -i ./inventory all \ -e /etc/kolla/globals.yml \ -e /etc/kolla/passwords.yml \ -e /etc/kolla/secrets.yml \ --ask-vault-pass \ pull关于拉取策略kolla-ansible 默认的行为是本地没有才拉不会每次都去检查远端有没有更新。这个行为对生产环境是合理的避免无意中把镜像换掉。但如果你在中转机上重新推了同名同标签的镜像比如修复了某个问题部署节点上是感知不到的需要手动清掉本地镜像再拉docker rmi registry.cn-hangzhou.aliyuncs.com/kolla-mirror/nova-api:2024.1-ubuntu注意清镜像之前确认对应容器已经停止否则会报image is being used by running container。生产环境慎用批量清理建议按服务逐个来清完立刻 pull别留空窗期。还有一个容易忽略的点pull是并发到所有节点的如果你有 10 台节点同时拉同一个 1.5 GB 的镜像ACR 侧的出口带宽会被打满部分节点可能超时。缓解办法是在 ACR 侧确认带宽规格或者分批执行用--limit限制节点组。4.4 多版本共存时的命名策略实际工作中经常要在同一套环境里保留多个 OpenStack 版本或者同时维护测试环境和生产环境。这时候命名空间怎么规划就很关键。我的做法是按用途分命名空间按版本分标签。比如新建kolla-mirror-prod和kolla-mirror-test两个命名空间同一个命名空间里放不同版本的镜像标签。这样部署时只需要改docker_namespace标签由openstack_release自动带出来配置文件改动最小。另一种做法是按版本分命名空间比如kolla-2024-1、kolla-2023-2。好处是删除旧版本时整个命名空间一起清干净利落坏处是随着版本增多命名空间数量膨胀个人版的配额可能不够。两种我都用过现在倾向于第一种因为切换环境的操作更轻量。但要提醒一点不同 OpenStack 版本的镜像绝对不能混用即使镜像短名相同内部的代码和依赖也完全不同。标签是唯一的区分依据推送和拉取两侧必须严格对齐。如果 ACR 个人版的仓库配额不够还有个折中方案只保留当前正在用的版本旧版本在确认不再需要回滚之后清理掉。清理可以在控制台按仓库批量删除也可以配置生命周期规则自动清理未使用的标签这个功能企业版支持得更好。5. 常见问题与排查速查表这一节是我在实际操作中攒下来的问题清单。有些是一次性的低级错误有些是反复出现的环境问题我把它们按阶段分类整理方便你对照排查。排查的基本原则是先分层再定位认证层能不能登录、网络层能不能连通注册表、命名层镜像地址对不对、内容层镜像本身完不完整。大部分问题都能通过这四层的顺序检查快速收敛。5.1 拉取阶段的问题现象docker pull卡在某一层不动最后超时。这是上游链路抖动不是配置问题。换个时间重试或者用 skopeo 的--retry-times参数自动重试。如果某个大镜像反复失败可以先单独拉它不要和其他镜像一起跑。现象manifest unknown。标签错了或者镜像名错了。用skopeo list-tags docker://quay.io/openstack.kolla/nova-api看一下这个仓库到底有哪些标签照着改。我遇到过一次是把2024.1-ubuntu写成了2024.1-ubuntu22.04少了个中划线。现象toomanyrequests: You have reached your pull rate limit。上游注册表对匿名拉取有限流。这种情况下不要并发拉改成串行或者分批拉中间留点间隔。现象拉取到 99% 卡住很久。大概率是磁盘满了docker 在等待写入。df -h看一眼如果是/var/lib/docker所在分区满了清理一下没用的镜像或者换>