news 2026/11/3 0:35:10

信创环境无外网离线部署实践:Docker镜像导出与Harbor私有仓库搭建

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
信创环境无外网离线部署实践:Docker镜像导出与Harbor私有仓库搭建

先在脑子里把整个链路过一次:CubeStudio 这种企业级 AI 平台要私有化部署到完全无外网的信创环境,核心就是“把外网的所有依赖提前打包,再通过隔离介质送进去”。镜像导出、Harbor 离线仓库、内网编排启动,每一步都不难,但每一步都有坑。这篇文章我会把自己实际操作中的完整流程、配置、排错都放进来,给同样在做信创离线部署的朋友做个参考。

1. 先把整套部署思路理清楚

1.1 完全无外网环境的典型部署链路

很多人在接到“内网私有化部署”需求时,第一反应是“不就是 docker pull 吗,放到内网跑一下”。等你真正站到一台没有任何外网连接的服务器前,就会发现连docker pull nginx都执行不了,更别提拉一套 CubeStudio 的镜像了。

所以,必须把部署拆成三个物理阶段来准备:

  • 外网同步机:这台机器可以访问外网,负责从镜像仓库拉取 CubeStudio 所有服务镜像、Harbor 离线安装包、Docker 与 Compose 组件包,统一打成离线介质包。
  • 隔离介质:通常是一块移动硬盘或 U 盘,也可以是企业内部允许的数据摆渡系统。介质上存放同步机整理好的所有 tar 包、rpm 包、安装脚本。
  • 内网资源区:包括一台 Harbor 镜像仓库服务器和多台 CubeStudio 业务节点。所有业务节点都从 Harbor 拉镜像,这样后续扩容、升级、节点重建都不需要重新插 U 盘。

这个链路看似多绕了一步,实际上是最稳的。尤其信创环境里,机器可能是鲲鹏、飞腾、海光、兆芯等多种 CPU 架构,操作系统可能是麒麟、统信 UOS,如果只靠一台机器离线 load,后续其他节点没法复用一套镜像,那才是灾难。

1.2 为什么离线部署一定要上 Harbor

有人会问,内网机器不多的话,直接把镜像 tar 包拷到每一台机器、执行docker load不就行了吗?短期看确实可以,但问题出在“私有化交付”这件事上:

  • 镜像没有统一版本管理:CubeStudio 前后端、中间件、算法引擎加起来十几个镜像,散落在各节点上,没法确认哪台跑的是哪个版本。
  • 升级与回滚困难:想升级某个服务版本,你得重新打包 tar,再逐台 load、逐台重启,出了问题还没法快速回退。
  • 多节点拉取不可控:一旦后续要加一台计算节点做 GPU 推理,新节点必须从头对接离线介质。

Harbor 的价值就是把这些镜像集中到内网一个标准仓库里,所有节点通过192.168.x.x/cubestudio/xxx:tag这种地址拉取。它本身也提供镜像复制、回收策略、安全扫描这些能力,很多信创项目验收时也会把“是否具备统一镜像仓库”作为一项指标。

所以,我这边最终定的架构是:隔离网内一台 Harbor + 多台应用节点,应用节点上通过 Docker Compose 编排 CubeStudio,所有镜像统一从 Harbor 获取。

2. 外网侧准备:把所有能搬的东西提前打成包

2.1 先理清 CubeStudio 有哪些镜像

这一步最容易被忽略,但恰恰是最关键的。我一般拿到交付包后,第一件事是去读 CubeStudio 官方提供的 compose 文件或离线清单,把镜像逐个列出来,保存成一个images.txt。

以生产环境为例,通常包含以下几类:

  • 前端网关:负责 Web 入口、权限校验、反向路由,一般是一个 nginx 类镜像。
  • 后端服务:负责任务编排、用户管理、模型仓库逻辑,这是核心 API 服务。
  • 数据库:PostgreSQL,保存元数据和任务状态。
  • 对象存储:MinIO,存放数据集、模型权重、日志文件。
  • 缓存队列:Redis,用于会话和异步任务。
  • 算子引擎:部分场景需要 Notebook 或训练执行器,可能还需要独立镜像。

我列一下常见的清单格式:

cubestudio/frontend:2.1.0 cubestudio/backend:2.1.0 cubestudio/notebook:2.1.0 cubestudio/postgres:15.2 cubestudio/minio:RELEASE.2024-08-03T04-33-05Z cubestudio/redis:7.2-alpine

这个文件必须和最终内网部署用的docker-compose.yml对齐,千万别漏。漏一个镜像,内网里就得多跑一次摆渡。

2.2 镜像导出命令与多架构踩坑记录

清单整理好以后,在同步机上执行批量拉取和导出。我直接给脚本,按顺序操作:

mkdir -p /opt/offline/images cd /opt/offline # 逐个拉取 while read -r img; do docker pull "$img" done < images.txt # 逐个导出为独立 tar,方便按需搬运 while read -r img; do name=$(echo "$img" | tr '/:' '_') docker save -o "images/${name}.tar" "$img" done < images.txt # 统一打一个大包,方便一次性拷走 tar -czf cubestudio-images.tar.gz images/

如果单个 tar 太大,可以考虑分卷压缩:

split -b 4G -d cubestudio-images.tar.gz cubestudio-images.tar.gz.

为什么我建议导出成独立 tar 而不是一个大包?因为信创内网经常存在“先部署基础环境、后部署业务应用”的节奏。先送基础镜像,后送业务镜像,独立 tar 更方便。当然,如果摆渡介质空间够,直接一个大包也行。

这里必须提一个容易翻车的地方:CPU 架构匹配。同步机如果是 x86 机器,而内网信创服务器是鲲鹏或者飞腾这类 ARM64 芯片,直接导出的镜像 load 进去大概率报exec format error,因为镜像里的程序二进制是 x86 指令集。

处理办法有两个:

  • 在同步机上用docker pull --platform linux/arm64拉取对应架构镜像再导出;
  • 更稳妥的方式是,找一台与内网目标环境相同 CPU 架构的同步机,或者先确认镜像仓库是否提供多架构 manifest,再拉取对应平台镜像。

建议在镜像 tar 包命名中明确标记架构,比如加上x86_64或aarch64后缀,否则内网同事看到一堆 tar 根本分不清哪个能用。

2.3 配套安装包清单:Docker、Compose、Harbor

很多离线部署失败,不是败在业务镜像,而是败在内网机器连 Docker 都没装好,或者 Harbor 离线安装包里的 Docker Compose 插件版本不兼容。所以同步机上要准备一整套“环境安装包”。

我整理的标准清单是这样的:

/opt/offline/ ├── images/ │ ├── cubestudio_frontend_2.1.0.tar │ ├── cubestudio_backend_2.1.0.tar │ └── ... ├── docker/ │ ├── docker-ce-24.0.7.rpm │ ├── docker-ce-cli-24.0.7.rpm │ ├── containerd.io-1.6.28.rpm │ ├── docker-compose-plugin-2.24.0.rpm │ └── docker-buildx-plugin-0.14.0.rpm ├── harbor/ │ └── harbor-offline-installer-v2.11.1.tgz ├── gpu/ │ ├── nvidia-container-toolkit-*.rpm │ └── libnvidia-container-*.rpm └── images.txt

这里有几个点要提前确认:

第一,Docker rpm 包必须按内网操作系统的发行版来准备。麒麟 V10 和统信 UOS 大都基于 RHEL8/CentOS8 内核,一般可以用 el8 系列的 rpm 包,但最好还是拿一台同版本的机器先验证。

第二,Harbor 离线安装包自带了 Harbor 所有组件镜像,不需要额外拉镜像。但 Harbor 2.x 的离线安装脚本依赖 Docker Compose v2 插件,如果内网机器没有,会直接报docker compose command not found。

第三,如果 CubeStudio 需要 GPU 推理,还要准备 NVIDIA Container Toolkit 的离线 rpm,或者对应信创算力卡的容器运行时组件。这些提前准备好,能省掉后面至少一天的时间。

3. 内网 Harbor 离线搭建实战

3.1 先把 Docker 和 Compose 插件补齐

内网服务器第一次通电后,通常只有一个干净的操作系统。我先手动挂载介质,把 docker 安装包解压出来:

mkdir -p /mnt/usb mount /dev/sdb1 /mnt/usb cd /mnt/usb/docker rpm -Uvh *.rpm systemctl enable --now docker docker version

如果是 Debian/Ubuntu 内核的信创系统,rpm 包就不能用了,需要提前准备 deb 包,用dpkg -i安装。这就是为什么我强调“按内网系统准备安装包”。

接着确认 Docker Compose 插件是否生效:

docker compose version

如果报命令找不到,就把同步机上准备好的 compose 插件手动拷贝到对应目录:

mkdir -p /usr/local/lib/docker/cli-plugins cp /mnt/usb/docker/docker-compose /usr/local/lib/docker/cli-plugins/ docker-compose chmod +x /usr/local/lib/docker/cli-plugins/docker-compose docker compose version

这一步看似基础,但很多 Harbor 安装失败就是因为它。Harbor 的install.sh会调用 docker compose,没有这个插件,脚本跑到一半就停住了。

3.2 Harbor 离线安装与 HTTPS 证书配置

安装完基础环境后,开始装 Harbor。我用的是 Harbor 离线安装包,里面包含了 Harbor 自身的全部镜像,所以不需要内网访问外网。

mkdir -p /data/harbor tar xzf /mnt/usb/harbor/harbor-offline-installer-v2.11.1.tgz -C /data/harbor cd /data/harbor cp harbor.yml.tmpl harbor.yml

编辑harbor.yml,核心配置如下:

hostname: 192.168.209.133 http: port: 80 https: port: 443 certificate: /data/cert/harbor.crt private_key: /data/cert/harbor.key harbor_admin_password: YourStrongPassword data_volume: /data/harbor/data

生产环境我建议直接启用 HTTPS。离线环境没有正规 CA,可以自建一套内部 CA 和证书:

mkdir -p /data/cert openssl genrsa -out /data/cert/harbor.key 2048 openssl req -new -x509 \ -key /data/cert/harbor.key \ -out /data/cert/harbor.crt \ -days 3650 \ -subj "/CN=192.168.209.133"

自签名证书在浏览器里会有告警,但内网私有化部署无所谓。真正要注意的是后面 Docker 节点信任证书那一步,我放在 3.3 讲。

然后执行安装:

./install.sh --with-trivy

加上--with-trivy是为了启用镜像漏洞扫描,很多信创安全验收会要求这个能力。安装结束后,检查容器状态:

docker ps | grep harbor

看到 harbor-core、harbor-portal、harbor-registry 等容器都在Up状态,基本就成功了。再用 curl 验证 API:

curl -k https://192.168.209.133/v2/

返回一个空的{}或者版本信息都算正常。

3.3 让内网所有业务节点信任 Harbor

Harbor 起来只是第一步,真正会卡人的是 Docker 客户端推送镜像时的信任问题。默认情况下,Docker 只信任 HTTPS 且证书受系统信任的仓库。内网自签名证书默认不被信任,所以要在每个业务节点的 Docker 配置里做两件事:

mkdir -p /etc/docker/certs.d/192.168.209.133 cp /data/cert/harbor.crt /etc/docker/certs.d/192.168.209.133/ca.crt

这是最规范的证书信任方式。我实际操作中还会同时配置insecure-registries,因为有些脚本拉镜像时不一定走 443 端口:

{ "insecure-registries": ["192.168.209.133"] }

修改后重启 Docker:

systemctl restart docker

注意,重启 Docker 会中断当前节点上所有运行中的容器,所以这个操作一定要放在业务低峰期,或者先把已有容器 stop 再操作。

这里也顺手说明一个后面常见的问题来源:很多“harbor 推送失败”的第一行日志是get "https://192.168.209.133/v2/": dial tcp 192.168.209.133:xxx: connect: connection refused,十有八九就是 Harbor 服务没起来,或者节点没有信任这个地址。后面我会专门列排查表。

4. 镜像导入与推送

4.1 把离线导出的镜像包导进内网

Harbor 装好后,接下来就是把 CubeStudio 的镜像从 tar 包里 load 进来,再推送到 Harbor。首先在内网机器的介质目录找到镜像包:

docker load -i images/cubestudio_frontend_2.1.0.tar docker load -i images/cubestudio_backend_2.1.0.tar

docker load会把镜像的层和元数据导入本地,然后可以在本地镜像列表看到它们:

docker images | grep cubestudio

这里会遇到一个典型问题:导出的镜像保存了原始仓库名和 tag,比如cubestudio/backend:2.1.0,但这个地址在内网里不存在,必须重新打上 Harbor 的仓库地址,否则内网节点没法从这个地址拉取。

4.2 批量重打标签并推送到 Harbor

手动一条条 retag 太慢了,项目交付时我一般写一个批量脚本:

registry=192.168.209.133 project=cubestudio while read -r img; do tag=${img##*:} repo=${img%%:*} docker tag "$img" "$registry/$project/$repo:$tag" docker push "$registry/$project/$repo:$tag" done < /opt/offline/images.txt

执行完后,到 Harbor 的 Web 界面里应该能看到cubestudio项目下面所有镜像和 tag。再随便挑一个拉取测试:

docker pull 192.168.209.133/cubestudio/backend:2.1.0

如果拉取成功并显示Digest信息,说明内网 Harbor 本身已经正常工作了。

我要特别提醒一下:推送到 Harbor 的 tag 一定要和后续 docker-compose.yml 里的镜像地址完全一致,包括仓库 IP、项目名、镜像名、版本号。线上我见过太多人因为漏写了项目名cubestudio,导致 CubeStudio 启动时一直从默认 Docker Hub 拉镜像,最后报manifest unknown。

4.3 常见推送失败问题排查

红框里最经典的一条报错:harbor 推送失败 get "https://192.168.209.133/v2/": dial tcp 192.168.209.133:xxx: connect: connection refused,基本可以按下面这个顺序排查:

  • 先看 Harbor 容器状态:docker ps | grep harbor,如果 harbor-core、harbor-registry 没起来,先看日志。
  • 再看端口监听:ss -tlnp | grep 443,如果 443 没有监听,说明 Harbor 的 nginx 没起来。
  • 然后看业务节点是否信任:curl -k https://192.168.209.133/v2/,如果在业务节点上都不通,那就是网络权限或防火墙问题。
  • 最后看 daemon.json:cat /etc/docker/daemon.json,确认insecure-registries或证书信任配置没有拼错,改完记得重启 docker。

这个排查顺序我基本固定下来了,每次遇到类似问题都能快速定位,而不是盲改一堆配置。

5. CubeStudio 服务编排与启动

5.1 docker-compose 编排文件解析

CubeStudio 一般提供官方 compose 编排文件,但既然我们离线部署,镜像地址要从原始的 Docker Hub 改成内网 Harbor 地址。下面给一个精简版示例,实际以交付版本为准:

version: "3.8" services: postgres: image: 192.168.209.133/cubestudio/postgres:15.2 restart: always environment: POSTGRES_DB: cubestudio POSTGRES_USER: cubestudio POSTGRES_PASSWORD: ChangeMe volumes: - /data/cubestudio/postgres:/var/lib/postgresql/data redis: image: 192.168.209.133/cubestudio/redis:7.2-alpine restart: always minio: image: 192.168.209.133/cubestudio/minio:RELEASE.2024-xx restart: always command: server /data --console-address ":9001" volumes: - /data/cubestudio/minio:/data backend: image: 192.168.209.133/cubestudio/backend:2.1.0 restart: always depends_on: - postgres - redis - minio environment: DB_HOST: postgres DB_PORT: "5432" REDIS_HOST: redis MINIO_ENDPOINT: http://minio:9000 LICENSE_FILE: /etc/cubestudio/license.lic volumes: - /data/cubestudio/models:/data/models - /data/cubestudio/datasets:/data/datasets - /opt/cubestudio/license.lic:/etc/cubestudio/license.lic:ro frontend: image: 192.168.209.133/cubestudio/frontend:2.1.0 restart: always depends_on: - backend ports: - "8080:80"

熟悉 Docker Compose 的人可能注意到了,这里把 license 文件也挂载进去了。企业私有化部署经常需要离线授权,license 文件要么放在数据盘,要么以只读方式挂进容器,这个必须提前规划。

5.2 数据目录规划与启动顺序

内网机器的数据落到哪里,一开始就要想好。我习惯把所有有状态数据统一放在/data/cubestudio/下,目录结构这样分:

/data/cubestudio/ ├── postgres/ # 元数据库 ├── minio/ # 对象存储数据 ├── models/ # 模型权重 ├── datasets/ # 数据集 └── logs/ # 业务日志

启动服务不要一股脑docker compose up -d,建议分步启动,先启动依赖组件,再启动业务服务:

docker compose up -d postgres docker compose up -d redis minio docker compose up -d backend docker compose up -d frontend

依赖服务起来后,可以用一个简单的循环等 backend 健康检查通过:

until curl -sf http://localhost:8080/api/health; do sleep 2 echo "waiting for backend..." done

健康检查通过后再访问前端页面,看是否能正常登录。

5.3 信创环境里的 AI 算力适配

CubeStudio 如果承担大模型微调或推理任务,内网节点一般要挂 GPU 或国产加速卡。以 NVIDIA GPU 为例,离线环境需要提前准备:

rpm -ivh libnvidia-container*.rpm rpm -ivh nvidia-container-toolkit*.rpm nvidia-ctk runtime configure --runtime=docker systemctl restart docker

重启后检查:

docker info | grep -i runtime

能看到nvidia运行时说明配置成功。这时候在 compose 文件里给训练/推理服务加上资源限制:

trainer: image: 192.168.209.133/cubestudio/trainer:2.1.0 runtime: nvidia deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu]

国产加速卡方面,比如 DCU、昇腾这类,通常不支持标准的nvidiaruntime,更多是通过设备映射/dev/davinci0、/dev/hisi_hdc等方式挂进容器,还要设置对应的LD_LIBRARY_PATH。这些适配强烈建议在投标前或项目初期就拿真机验证,不要在部署当天才试。

6. 常见问题速查与排错实录

6.1 Harbor 与镜像推送问题

现象可能原因处理方式
get https://192.168.209.133/v2/: dial tcp ... connection refusedHarbor 容器没启动docker ps检查,看docker logs harbor-core
同样的报错,但 Harbor 在运行业务节点没有信任仓库地址配置insecure-registries或证书信任,重启 docker
x509: certificate signed by unknown authorityCA 证书未放到节点复制harbor.crt到/etc/docker/certs.d/192.168.209.133/ca.crt
unauthorized: authentication required推送时没登录先docker login 192.168.209.133
manifest unknowntag 不存在或名称不一致对比 compose 文件与 Harbor 中的镜像 tag

Harbor 版本升级时也要小心。老版本升级到新版本,官方有upgrade.sh,但升级前一定先备份harbor.yml和data_volume指向的数据目录,最好在测试环境先跑一遍数据库迁移,再动生产。

6.2 信创系统与容器运行时问题

现象可能原因处理方式
exec format error镜像架构与 CPU 不匹配重新在目标架构同步机导出镜像
docker compose command not foundCompose 插件未安装手动拷贝插件到/usr/local/lib/docker/cli-plugins
系统自带 podman 与 docker 冲突端口或 socket 占用停用 podman socket,重新指定 docker socket
Windows Server 2022 上离线起 WSL 容器失败WSL 发行版没有离线包使用wsl --install --from-file 离线包,仅适合开发验证,生产建议 Linux 信创环境

补充一点:信创机器的内核版本往往比公版 CentOS 低,Docker 版本不要追新,建议选 20.10 或 24.0 这种经过大量国产系统验证的稳定版本。我在麒麟 V10 上见过 Docker 25 和系统内核网络栈的兼容性问题,后来降到 24.0 就正常了。

6.3 CubeStudio 业务层常见问题

现象可能原因处理方式
前端页面可以打开,但登录请求 502backend 容器没起来看docker logs backend,往往和数据库连接有关
上传数据集失败MinIO 对象存储无法访问检查 compose 里MINIO_ENDPOINT是否用服务名而不是 localhost
模型训练任务卡死GPU 运行时未生效docker exec进容器跑nvidia-smi,确认设备可见
磁盘写满镜像和模型权重占用过大提前规划大容量数据盘,Harbor 设置镜像回收策略

实际项目里,数据库连接失败和 MinIO 地址配置错误是出现频率最高的两个问题。内网里容器之间通信一定要用 compose 中的服务名,千万别改成 IP,否则服务重启后 IP 一旦变化,整套服务就断了。

整个流程走下来,我的体会是:离线部署真正考验的不是操作能力,而是“提前规划”的能力。哪些镜像需要、哪些 rpm 需要、哪些架构需要,在同步机阶段全部想清楚,内网就是按部就班。如果等到内网里发现缺包,再安排介质摆渡,一次至少折腾半天。按照上面这套“镜像导出 + Harbor + 编排启动”的组合,基本能把离线部署从“玄学”变成“流水线”。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/27 19:44:10

RPC核心机制与BRPC实战:用C++从零构建高性能分布式服务

RPC是分布式系统开发里绕不开的一块硬骨头。我刚接触那会儿&#xff0c;啃完一堆文档&#xff0c;代码还是写不利索&#xff0c;直到上手BRPC才真正明白“远程调用”到底是怎么回事。这篇东西我不打算做成API手册&#xff0c;而是想把RPC的核心机制拆开揉碎&#xff0c;再用C和…

作者头像 李华
网站建设 2026/10/26 23:34:45

文氏桥正弦波振荡电路:起振条件与Multisim稳幅优化实操

文氏桥正弦波振荡电路是我这几年带学生做模拟电子技术实验时&#xff0c;最常拿来当范例的一块电路。原因很直接&#xff1a;它结构紧凑&#xff0c;一个运放加几个阻容元件就能输出比较标准的正弦波&#xff0c;而且从原理到调试再到稳幅优化&#xff0c;每一步都能讲出实打实…

作者头像 李华
网站建设 2026/10/26 16:06:35

Redis为什么快?内存、IO多路复用与数据结构全解析

"Redis为什么快"这个问题&#xff0c;我一年至少要回答几十遍。面试的时候被问到&#xff0c;带团队做技术分享的时候要讲&#xff0c;排查线上慢查询的时候也要重新过一遍。网上能搜到的答案高度一致——基于内存、单线程、IO多路复用。这九个字背下来不难&#xff…

作者头像 李华
网站建设 2026/10/27 20:55:01

算法备案安全自评估报告写作指南:结构拆解与实操经验

算法备案这个事&#xff0c;做推荐算法、生成式服务、搜索排序的同学这两年应该都不陌生。我自己从第一批备案开始&#xff0c;前后写过近十份安全自评估报告&#xff0c;从最开始反复被退回&#xff0c;到后来基本一次过审&#xff0c;中间踩过的坑确实不少。在这个合规流程里…

作者头像 李华
网站建设 2026/10/28 14:16:11

用claude-mem给Claude装上跨会话记忆,告别重复沟通

如果你跟我一样&#xff0c;几乎天天在用Claude写代码、整理方案、处理文档&#xff0c;大概率撞到过同一堵墙&#xff1a;昨天还在一个会话里聊得清清楚楚的技术选型&#xff0c;今天新建一个会话&#xff0c;Claude完全像失忆了一样&#xff0c;开口又问“你这个项目用什么框…

作者头像 李华