☰
Kolla镜像迁移到私有仓库与kolla-ansible对接
2026/11/3 12:48:57 网站建设 项目流程

1. 先搞清楚 kolla 镜像的“一整套”到底有多少个、叫什么名

把 OpenStack 的 kolla 镜像搬到自家云厂商的托管镜像仓库,第一件要干的事不是敲docker pull,而是把镜像清单弄清楚。我见过太多人上来就凭记忆拉nova-api、keystone、glance-api这几个“看起来最重要”的,结果kolla-ansible一跑 prechecks 就报一堆manifest unknown,回头补镜像补到怀疑人生。kolla 的镜像不是零散的一堆独立服务,它是一个版本强耦合的集合:所有镜像的 tag 必须完全一致,缺一个都可能让部署卡在中间。

这一章先把镜像的命名规则、数量和 registry 前缀的生效位置讲清楚,后面所有操作都建立在这三个认知上。

1.1 镜像全名的四段拼装规则

kolla 生成镜像全名的逻辑其实很简单,就是四段拼接:<registry>/<namespace>/<container_name>:<tag>。四段里最容易出错的是后两段之外的 registry 和 namespace,因为很多人只盯着 tag。

container_name是 kolla 内部定义的容器名,比如nova_api、nova_libvirt、keystone、haproxy、mariadb、rabbitmq,注意 kolla 内部用的是下划线风格,虽然仓库里实际的镜像路径往往是中划线风格(nova-api),这个转换由 kolla 的 filter 完成,你手工拉取时需要按仓库里的真实路径来。tag 则是<release>-<distro>-<arch>三段式,典型值如2024.1-ubuntu-22.04,也有2023.2-rocky-9、2024.1-debian-bookworm这类组合。

这里有个非常关键的细节:arch 段在 x86_64 机器上通常省略不写,只在 ARM 等平台才需要显式带-aarch64。所以你在 x86 环境下看到2024.1-ubuntu-22.04是对的,不要自己脑补补一个-x86_64上去,那样一定拉不到。

用一张表把四段的取值来源和常见错误对照一下,比翻文档快得多:

段位典型取值常见错误
registry上游公共仓库地址混用了旧版 docker.io 与新版 quay 前缀,导致一半镜像拉不到
namespacekolla 的命名空间写成kolla但实际应为openstack.kolla,路径 404
container_namenova-api、keystone用了 kolla 内部的下划线名,仓库里不存在该路径
tag2024.1-ubuntu-22.04自行为 x86 补-x86_64,或版本号与本次部署不一致

记住一句口诀:tag 从头到尾一个字符都不许改,能改的只有 registry 和 namespace。

1.2 一个最小可部署集合的镜像数量盘点

“到底要拉多少个”这个问题,答案取决于你要部署哪些服务。一个只开核心组件(keystone、glance、nova、neutron、placement、horizon、mariadb、rabbitmq、memcached、haproxy、keepalived)的全能型单节点,镜像数量大概在55 到 70 个之间;如果打开 cinder、swift、heat、octavia、barbican、manila、设计相关的 telemetry,很容易冲到120 个以上。全量拉一遍 kolla 的镜像清单,规模在两百多个。

这些镜像的体积分布很不均匀:nova-libvirt、neutron-server、cinder-volume、horizon这类包含大量 Python 依赖和系统包的镜像,单个解压后两三个 GB 很常见;kolla-toolbox、cron、fluentd这类小镜像可能只有几百 MB。以 2024.1 的实际经验值来说,一个中等规模部署的全套镜像,压缩体积合计在 15GB 到 25GB 区间,落到本地磁盘解压后大约 40GB 到 60GB。

这个数字一定要提前算,因为它直接决定你的/var/lib/docker所在分区会不会被撑爆。我在第一次干这件事的时候就吃了亏:临时找的跳板机根分区只有 40G,拉到第七十个镜像时 dockerd 直接无响应,docker images卡住不动,最后只能清空了重来。

我一般会按下面的顺序确定清单,而不是凭感觉:

  1. 先定openstack_release和distro,这两个决定了 tag 的唯一取值;
  2. 从kolla-ansible的ansible/group_vars/all.yml里找到本次要部署的服务集合,或者干脆用kolla-ansible的enable_*开关反推出跑得起来的组件集;
  3. 拿一份官方镜像清单文件做基线,按实际启用的服务做裁剪,宁可多留两三个用不上的,也不要漏;
  4. 把清单写成一个纯文本文件,一行一个镜像路径,后面所有脚本都读这个文件。

清单写成文件的好处是:拉取、改 tag、推送、校验四个环节全部复用同一份数据源,不会出现“拉的时候有、推的时候忘了”的情况。

1.3 registry 前缀在 kolla-ansible 里的生效位置

弄清了镜像名,还要知道部署端是怎么用这个前缀的。kolla-ansible拼镜像全名的模板大致是{{ docker_registry }}/{{ docker_namespace }}/{{ container_name }}:{{ tag }},也就是说registry 和 namespace 是两个独立变量,这一点非常容易被忽略。

很多人习惯性地把docker_registry填成一个完整路径,比如my-registry.example.com/kolla,然后把docker_namespace留空或者保持默认。这种写法在部分版本上能跑,但一旦docker_namespace默认值不为空,拼出来的路径就会多出一段,变成my-registry.example.com/kolla/openstack.kolla/nova-api:...,直接 404。

我的做法是:docker_registry只填域名加端口,docker_namespace填你要用的仓库命名空间。这样两个变量各司其职,拼出来的路径和你手工 push 时用的路径能完全对上:

# /etc/kolla/globals.yml 中的相关片段 docker_registry: "registry.internal.example.com:5000" docker_namespace: "kolla"

对应到手工操作,你推上去的镜像路径就应该是registry.internal.example.com:5000/kolla/nova-api:2024.1-ubuntu-22.04。两边一致,部署时才能精准命中。

还有一个隐藏点:kolla-ansible里有个kolla_docker_registry变量(注意多了个前缀)在某些版本里用于指定 kolla 工具容器自己的来源,和docker_registry不是一回事。改配置前先grep -rn "docker_registry" /path/to/kolla-ansible/ansible/group_vars/看一眼当前版本的定义,能省掉一次排查。

2. 拉取前必须算清楚的三笔账:容量、时间、失败率

老老实实说,这件事的难点根本不在“会不会用 docker 命令”,而在资源规划。几十上百个 GB 的镜像搬运,本质是一次长时间、高并发、易中断的数据迁移。这一章讲的三笔账,是我每次动手前必算的,算完再决定用哪套方案、开多少并发、分几批。

2.1 磁盘容量估算与 /var/lib/docker 的位置问题

容量估算不能只看“镜像总大小”,要按三层来算:

  • 压缩层:上游仓库里存储的体积,也就是你在网页上看到的数字,全套 15~25GB;
  • 解压层:docker 拉下来之后本地存储的体积,通常是压缩层的 2~2.5 倍,40~60GB;
  • 中间层:如果你用docker tag+docker push的方式,中间还会产生一份同样大小的临时对象,虽然是硬链接为主,但 docker 的存储驱动会额外占用一部分元数据和层索引,实际可能再多出 10% 左右。

所以最低准备 100GB 可用空间是底线,想稳一点直接给 150GB。这个数字我在多个环境里验证过,不是拍脑袋。

第二个问题更隐蔽:/var/lib/docker默认在根分区。哪怕你的数据盘挂了 500G,只要没把 docker 的数据目录挪过去,一样会被根分区卡死。迁移数据目录的步骤如下,注意每一步都不要跳:

# 1. 确认当前 docker 数据目录位置 docker info | grep -i "docker root dir" # 2. 停止 docker 和容器 systemctl stop docker.socket docker # 确认没有残留进程 pgrep -a dockerd # 3. 迁移数据(用 rsync 保留属性,便于回滚) rsync -aHAX --progress /var/lib/docker/ /data/docker/ # 4. 修改 daemon 配置 mkdir -p /etc/docker cat > /etc/docker/daemon.json <<'EOF' { "data-root": "/data/docker", "max-concurrent-downloads": 6, "max-concurrent-uploads": 4, "storage-driver": "overlay2" } EOF # 5. 启动并验证 systemctl start docker docker info | grep -i "docker root dir"

max-concurrent-downloads默认是 3,调高确实能提升总吞吐,但要注意别和max-concurrent-uploads一起无脑拉满。上传并发过高时,某些自建 registry 会被打满连接数,反而出现大量 500 错误。我一般下载开 6、上传开 4,是比较稳的组合。

注意:改完># 用 time 和 docker 的进度输出做一次小批量试拉 time docker pull <上游 registry>/<namespace>/nova-api:<tag> # 观察拉完后 docker 自己打印的总大小和耗时

拉 3 到 5 个有代表性的大镜像,把“大小÷耗时”平均一下,得到的就是有效吞吐。用这个数字去算全量,误差能控制在 20% 以内。

这里还要提一个容易忽略的成本:时间窗口。如果你是在办公网里干这件事,白天带宽被同事分走,晚上会快很多。我一般把大批量拉取安排在夜里跑,用nohup挂后台,第二天早上看日志。别在周五下午开始拉两百个镜像,那基本等于给自己找麻烦。

2.3 拉取失败率与重试策略

几十上百个镜像串行拉取,中途出现几个失败是大概率事件,不是意外。原因五花八门:某一层传输超时、上游偶发 429 限流、本地磁盘瞬时写满、dockerd 内存不足……指望“一次拉完”是不现实的,正确的心态是“设计一套能失败重试的流程”。

我的做法是把拉取包装成一个带重试的脚本,核心逻辑是:先检查本地是否已有同名同 tag 的镜像,有就跳过;没有就拉,失败后 sleep 若干秒重试,最多重试 3 次;单个镜像 3 次都失败就记进失败清单,继续下一个。整批跑完之后再针对失败清单单独跑一轮。

#!/usr/bin/env bash # pull_kolla.sh - 按清单批量拉取,带幂等与重试 set -uo pipefail UPSTREAM="${UPSTREAM:-<上游 registry>/<上游 namespace>}" TAG="${TAG:?请设置 TAG,例如 2024.1-ubuntu-22.04}" LIST="${LIST:-images.txt}" FAILED="failed.txt" : > "$FAILED" while read -r name; do [ -z "$name" ] && continue ref="${UPSTREAM}/${name}:${TAG}" if docker image inspect "$ref" >/dev/null 2>&1; then echo "[skip] $ref 已存在" continue fi ok=0 for i in 1 2 3; do echo "[pull] 第 ${i} 次尝试 $ref" if docker pull "$ref"; then ok=1 break fi sleep $((i * 15)) done if [ "$ok" -eq 0 ]; then echo "[fail] $ref" | tee -a "$FAILED" fi done < "$LIST" echo "---- 失败清单 ----" cat "$FAILED"

这个脚本有几个刻意的设计,值得说明理由:

  • 幂等检查放在最前:批量任务中断后重跑是常态,没有幂等检查就要从头再来一遍,浪费的是几个小时;
  • set -uo pipefail但不加-e:因为单个镜像失败不应该让整个脚本退出,这就是“预期内失败”的处理方式;
  • 重试间隔递增:15 秒、30 秒、45 秒,给上游一点缓冲时间,比固定 5 秒硬刚的成功率高不少;
  • 失败清单落盘:这是整批任务的“断点续传”依据,比记在脑子里靠谱一万倍。

3. 两条落地路线:本地中转 与 skopeo 直连复制

把镜像从上游搬到自家仓库,技术上就两条路:本地中转(pull 到本地再 push 上去)和工具直连复制(registry 到 registry,不落地)。两条路我都用过多轮,各有明确适用场景,不是“新工具一定更好”。

3.1 路线A:docker pull + tag + push 的完整脚本

这条路最朴素,也最好理解:本地拿一份,改个名字,推上去。优点是所有步骤都可见、可中断、可单独排查;缺点是本地要占一份完整空间,网络流量走两遍(下游一遍、上游一遍)。

执行顺序上有个小优化:不要“全拉完再全推”,而是按批拉、按批推,每批 10 到 15 个。这样做的好处是磁盘峰值占用只有一批的量,而且中途失败影响面小。下面这个脚本做了拉取和推送一体的处理:

#!/usr/bin/env bash # migrate_kolla.sh - pull + retag + push 一体化 set -uo pipefail UPSTREAM="<上游 registry>/<上游 namespace>" TARGET="registry.internal.example.com:5000/kolla" TAG="${TAG:?请设置 TAG}" LIST="${LIST:-images.txt}" BATCH="${BATCH:-10}" LOGDIR="./logs" mkdir -p "$LOGDIR" : > "$LOGDIR/failed_pull.txt" : > "$LOGDIR/failed_push.txt" pull_one() { local name="$1" local src="${UPSTREAM}/${name}:${TAG}" local dst="${TARGET}/${name}:${TAG}" if ! docker image inspect "$src" >/dev/null 2>&1; then docker pull "$src" || { echo "$name" >> "$LOGDIR/failed_pull.txt"; return 1; } fi docker tag "$src" "$dst" || { echo "$name" >> "$LOGDIR/failed_push.txt"; return 1; } docker push "$dst" || { echo "$name" >> "$LOGDIR/failed_push.txt"; return 1; } echo "[ok] $name" return 0 } # 分批处理,控制磁盘峰值 split -l "$BATCH" "$LIST" "$LOGDIR/batch_" for f in "$LOGDIR"/batch_*; do echo "==== 处理批次 $f ====" while read -r name; do [ -z "$name" ] && continue pull_one "$name" done < "$f" # 每批结束后清理本地镜像,释放空间 while read -r name; do [ -z "$name" ] && continue docker rmi "${UPSTREAM}/${name}:${TAG}" "${TARGET}/${name}:${TAG}" >/dev/null 2>&1 done < "$f" done echo "---- pull 失败 ----"; cat "$LOGDIR/failed_pull.txt" echo "---- push 失败 ----"; cat "$LOGDIR/failed_push.txt"

关键点在于每批结束后docker rmi清掉本地镜像。这一步很多人不愿意做,觉得“万一要重推呢”,但不清的话磁盘峰值就是全量,前面算的容量账全部白费。清理之后如果推失败了要重来,代价只是重新 pull 一次,比重开一台机器划算。

docker tag这一步的意义在于给镜像打上目标仓库的完整路径。docker 的 push 行为完全由镜像名里的 registry 段决定,你不能在 push 时临时指定地址,所以 tag 是必须的中间步骤,不能省。

3.2 路线B:skopeo copy 免落盘的取舍

skopeo这类工具能直接从一个 registry 复制到另一个 registry,本地不落地。它的核心命令形态是:

skopeo copy \ --all \ --src-creds "<src_user>:<src_pass>" \ --dest-creds "<dst_user>:<dst_pass>" \ docker://<上游 registry>/<上游 namespace>/<name>:<tag> \ docker://registry.internal.example.com:5000/kolla/<name>:<tag>

--all的含义是把所有架构的 manifest 一起复制。这个参数对于 kolla 这种多架构镜像来说很关键:不加--all只复制当前平台那一个 manifest,如果后续有人在 ARM 机器上从你的仓库拉,就会报no matching manifest。

这条路的最大优势是省磁盘。本地不落地,峰值占用接近零,一台小规格的机器就能跑,不用担心/var/lib/docker撑爆。第二个优势是可以做校验:skopeo inspect --raw能直接读到远端 manifest,复制完立刻比对 digest,比docker pull之后再看 ID 更干净。

麻烦的地方在于:需要给工具单独配认证,而且要确认目标仓库支持 manifest 直接上传。极少数私有 registry 在 API 兼容性上有瑕疵,表现为大层上传到 99% 时断掉,而同样的层用 docker push 就没问题。我遇到过两次,最后都是退回路线 A 解决的。

3.3 两条路线的对比与选型建议

把两条路线的关键差异列成表,选型时对着看:

维度路线A:本地中转路线B:skopeo 直连复制
本地磁盘峰值全量或一批的量(40G+)接近 0
网络流量走两遍(下+上)走一遍直连
失败可观测性强,docker 日志直观中,需要读工具输出
多架构支持需额外处理--all一把过
对机器规格要求高(磁盘、内存)低
出问题时的兜底直接手动docker pull排查需要熟悉工具命令
适合场景镜像数少、要逐个排查、已有大机器镜像上百、磁盘紧张、追求效率

我目前的习惯是:首次迁移用路线 B 打底,跑完之后用路线 A 做补漏。因为首批总有几个镜像会因为各种原因复制失败,补漏的时候量很小,用 docker 命令逐个处理最快,排查也最清楚。反过来,一上来就用路线 A 拉两百个镜像,中间随便来个磁盘告警就能让你忙一下午。

提示:不管走哪条路线,先把目标仓库里的两个测试镜像推上去跑通,再开始全量。认证、网络策略、仓库命名空间权限这些问题,用一个小镜像验证只要五分钟,用十个 GB 的镜像验证要半小时。

4. 推上去之后的命名约定:仓库路径与 kolla 期望的前缀怎么对齐

很多人以为“推上去就完事了”,其实推上去的路径和 tag才是决定部署能不能跑起来的关键。这一章讲三个必须守住的约定,全是踩过坑之后总结的。

4.1 不要把 tag 改掉

我见过一种“聪明”的做法:把2024.1-ubuntu-22.04这种长 tag 简化成2024.1,觉得省事好看。这个操作在 kolla 里是致命的。

原因在于kolla-ansible是按变量拼出完整 tag 再去拉的,它拼出来的 tag 一定带 distro 段。你把它改成短 tag,部署时就找不到镜像,报manifest unknown或者pull access denied,而且报错信息里不会告诉你“其实是 tag 不对”,只会说拉不到。

更麻烦的是,如果部署端配置的是openstack_release: 2024.1、kolla_base_distro: ubuntu这类分散变量,你手工改 tag 之后两边就永远对不上。所以规矩很简单:上游 tag 是什么,你推上去就是什么,一个字符都不动。想区分环境,改 registry 或 namespace,不要动 tag。

4.2 namespace 用一级还是两级

namespace 的层级直接影响你后面维护的复杂度。kolla 上游用的是两级形式(域名 + 命名空间),如果你在自建仓库里也用两级,路径结构就是:

registry.internal.example.com:5000/kolla/nova-api:2024.1-ubuntu-22.04

如果你用了带项目层级的两级 namespace,比如registry.internal.example.com:5000/infra/kolla/nova-api,那就变成三级路径了。这在技术上完全可行,但要注意kolla-ansible的docker_namespace变量能不能接受带斜杠的值。有些版本的变量处理会做转义,带斜杠可能出问题。

我的建议是保持一级 namespace,也就是docker_registry填域名端口,docker_namespace填kolla。想区分环境就用不同的 registry 地址或者不同的 namespace 名(kolla-prod、kolla-test),不要往路径里塞层级。

4.3 私有仓库的认证与 insecure 配置

自家仓库基本都要认证。这里有两个配置层次,都要做:

第一层是操作端,也就是你做 push 的那台机器,要先登录:

docker login registry.internal.example.com:5000 \ -u "<用户名>" -p "<密码>" # 凭据写入 ~/.docker/config.json

如果要批量在多台机器上操作,可以用--password-stdin从文件读,避免密码出现在 shell history 里:

cat /path/to/pass.txt | docker login registry.internal.example.com:5000 \ -u "<用户名>" --password-stdin

第二层是部署端,也就是跑kolla-ansible的所有节点。这里需要在globals.yml里给出凭据,kolla 会在拉镜像时使用:

docker_registry: "registry.internal.example.com:5000" docker_namespace: "kolla" docker_registry_username: "<用户名>" docker_registry_password: "<密码>" docker_registry_insecure: "no"

如果仓库走的是自签证书,docker_registry_insecure需要设成"yes",同时每个节点的 dockerd 都要把这个地址加进insecure-registries,否则 docker 层就已经拒绝了,kolla 配置再对也没用:

{ "insecure-registries": ["registry.internal.example.com:5000"] }

改完daemon.json记得systemctl daemon-reload && systemctl restart docker。这一步在每个节点都要做,包括控制节点、计算节点、存储节点。我吃过一次亏:只改了控制节点,kolla-ansible deploy跑到 nova_libvirt 就卡住,日志里是一句含糊的error pulling image,翻了半小时才想起来计算节点没配。

5. kolla-ansible 侧对接:让部署真的从你的仓库拉

前面所有工作都是为了这一步。配置改动量其实很小,但每一项都不能错。这一章按“必改项 → 验证动作 → 报错对照”的顺序说。

5.1 globals.yml 五个必改项

/etc/kolla/globals.yml里和镜像来源相关的,主要是下面这几项。我一般用grep先确认现有值,再统一改:

grep -nE "docker_registry|docker_namespace|openstack_release|kolla_base_distro|network_interface" /etc/kolla/globals.yml

需要关注的五项:

配置项作用常见取值
docker_registry镜像仓库地址(域名+端口)registry.internal.example.com:5000
docker_namespace仓库命名空间kolla
openstack_release决定 tag 的前半段2024.1
kolla_base_distro决定 tag 的中段ubuntu
docker_registry_username/password仓库认证按实际填

改完之后强烈建议做一次配置验证,让 kolla 自己把镜像全名拼出来,比你口算靠谱:

kolla-ansible -i ./inventory/all-in-one \ -e @/etc/kolla/globals.yml \ prechecks --tags <某个服务名> 2>&1 | tee /tmp/prechecks.log

prechecks阶段会做镜像存在性检查,如果前缀或 tag 不对,这里就会暴露出来,比跑到deploy一半再失败要省时间得多。这是我每次必做的一步,哪怕前面已经验证过。

5.2 验证镜像来源的三个动作

配置改完了,怎么确认部署时真的从你的仓库拉,而不是悄悄回退到上游?三个动作,从粗到细:

动作一:看部署日志里的镜像全名。kolla-ansible deploy的输出里会打印TASK [service : Pulling image xxx],把这一行抓出来看一眼前缀对不对。这是最直接的方式。

动作二:在目标节点上docker image inspect看 RepoDigests。部署完成后找一个容器对应的镜像,docker image inspect <image> --format '{{.RepoDigests}}',Digest 里的域名应该指向你的仓库。如果是上游地址,说明某处配置没生效。

动作三:临时断掉对上游的访问做一次反向验证。这个动作最狠也最准:把节点上对上游仓库的出站访问暂时阻断,然后删掉一个非关键镜像,重新执行该服务的部署。如果还能拉下来,说明确实走的是你的仓库;如果报错说拉不到,说明有地方还在指向上游。

第三个动作我不建议在正式环境常做,但在测试环境验证首次迁移时非常值得。它能把“配置看起来对但其实没生效”这种隐蔽问题一次性揪出来。

5.3 报错对照表

把迁移过程中实际遇到过的报错整理成表,遇到问题时先对号入座:

报错关键字大概率原因处理方向
manifest unknowntag 不对,或该镜像没推上去到仓库里查这个路径和 tag 是否存在
pull access denied认证未配置,或凭据错了检查docker_registry_username/password
http: server gave HTTP response to HTTPS client仓库是 HTTP 但被当 HTTPS 处理加insecure-registries并重启 docker
x509: certificate signed by unknown authority自签证书未被信任分发 CA 证书到/etc/docker/certs.d/<registry>/
no space left on device磁盘满清本地镜像、扩盘、迁># 逐个校验目标仓库里是否存在对应镜像 while read -r name; do [ -z "$name" ] && continue ref="registry.internal.example.com:5000/kolla/${name}:${TAG}" if skopeo inspect --raw "docker://${ref}" >/dev/null 2>&1; then echo "[has] $ref" else echo "[miss] $ref" | tee -a missing.txt fi done < images.txt

这一步的价值在于它绕开了 docker 的本地状态,直接问仓库。本地有镜像不代表远端有,远端有也不代表 manifest 完整。只有skopeo inspect --raw能拿到完整 manifest 的,才是部署时一定能拉到的。

我现在的固定流程是:拉取 → 推送 →闭环校验→ 只对缺失项补推 → 再校验一次。补推的量通常很小,五六个以内,手工处理比跑全量脚本快。

最后分享一个我个人觉得最有用的习惯:把每次迁移的镜像清单、仓库地址、tag、执行时间记成一个文本文件,放在项目目录里。过两周再回来看,或者换个同事接手,这份记录的价值远超你想象。镜像迁移这种事,做一次就忘一次,唯一靠得住的是写下来的东西。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询