ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

物理隔离内网离线部署容器化应用:镜像搬运、Harbor搭建与代理排障实战

物理隔离内网离线部署容器化应用:镜像搬运、Harbor搭建与代理排障实战 前阵子接了一个信创项目交付环境是物理隔离的内网外网访问被完全掐断整个机房只有一台出口机还只开放白名单代理端口。CubeStudio 这套产品本身是容器化交付的按理说装起来不算太难但真正进到那种完全无外网的环境里第一个问题就足够劝退一批人镜像到底怎么进去这篇文章就围绕这个场景把我实操过的一整套离线部署流程完整写下来。核心只有三件事镜像导出、内网 Harbor 仓库搭建、出口机代理补缺。捎带手把过程中踩过的几个坑——尤其是harbor 推送失败 get https://192.168.209.133/v2/: dial tcp ...这种报错的完整排查思路——也一并交代清楚。适合正在做企业私有化交付、信创环境适配、或者被内网网络策略折磨的容器运维同学参考。1. 先想清楚完全无外网的内网到底难在哪以及三条可走的路1.1 物理隔离环境的真实约束很多人在能上网的电脑上做测试时很难体会到完全离线环境的别扭程度。真正到了客户现场你会面对这些现实约束没有外网 DNS更别提直接docker pull。宿主机连基础的 yum/apt 软件源都不可用缺一个依赖都得折腾半天。内网通常按 VLAN 或安全域划分部署机和 Harbor 仓库机之间可能有额外的防火墙策略。唯一的外网通道是出口机也叫代理机往往只开放特定端口而且用起来有严格的审批流程。也就是说你不能假设部署机连不上外网但通过代理可以。代理是真有但能不能用、能用多久、放行哪些域名都是网络管理部门说了算。这就是为什么离线部署的核心工作不是装软件而是在受限通道下把镜像和依赖搬运进内网。1.2 三条路线对比我梳理下来完全无外网环境下主要有三条路方案适用场景优点缺点纯离线镜像导出严格物理隔离无任何代理通道最安全不依赖网络策略一旦漏了镜像补一次成本极高出口机代理拉取有代理窗口但不想长期开放能临时补缺灵活需要网络部门配合边界管理要谨慎本地软件源 镜像仓库大量服务器需要初始化长期收益高团队都受益前期搭建成本高维护复杂1.3 我的组合选择这个项目我最终采用的是镜像导出为主、出口机代理兜底的组合方案。原因很简单。CubeStudio 这类产品是典型的多组件容器化架构Web 控制台、调度服务、模型推理组件加在一起镜像数量通常在十个以上。第一次离线部署时几乎不可能一次把镜像清单整理得绝对完整——总有那么一两个依赖镜像是在启动服务后才暴露出来的。这时候出口机代理的价值就体现出来了它能让你在部署窗口期内临时补拉缺失的镜像而不必为了一个 200MB 的基础镜像专门再跑一趟现场。但反过来代理通道是救急用的所有核心镜像仍然要提前导出好否则一旦代理窗口关闭整个部署就得停工。2. 镜像导出这关架构、版本、压缩和校验一个都不能少2.1 先确定目标环境的 CPU 架构别想当然信创环境的坑第一个就藏在 CPU 架构里。飞腾、鲲鹏基本都是 ARM64海光、兆芯是 x86_64。你要先在部署目标机上执行uname -m或者lscpu确认架构再回到有外网的导出机上操作。千万别觉得镜像不都是通用的吗——同一个镜像 tag在 x86 机器上 pull 下来的是 amd64 格式推到内网 Harbor 没问题但部署到 ARM64 机器上就会直接报exec format error起都起不来。我自己会先在有网机器上批量确认一遍架构信息docker images --format {{.Repository}}:{{.Tag}} {{.ID}} | while read img id; do echo $img - $(docker inspect --format {{.Architecture}} $id) done如果是多架构镜像docker manifest inspect也能看到所有支持的平台列表。这一步花十分钟能省掉到现场后一天的折腾。2.2 镜像清单整理用表格管住版本导出之前我会先建一份镜像清单类似下面这样镜像名Tag架构来源备注cstudio-web2.1.0amd64交付离线包前端控制台cstudio-scheduler2.1.0amd64交付离线包任务调度python3.11-slimarm64Docker Hub模型服务基础镜像这个清单不只是给自己看的也是给客户运维留档用的。信创项目经常要过验收镜像来源、版本、架构都是审计材料的一部分。2.3 docker save 的正确姿势分组打别一把梭很多人习惯把一堆镜像塞进一个大 tar 里docker save $(docker images -q) all.tar我强烈不建议这么干。第一文件太大传输和恢复都麻烦第二一旦 tar 文件在传输中损坏整个包都废了排查起来想死的心都有。正确做法是按组件分组导出docker save cstudio-web:2.1.0 | gzip cstudio-web.tar.gz docker save cstudio-scheduler:2.1.0 | gzip cstudio-scheduler.tar.gz docker save python:3.11-slim | gzip python-3.11-slim.tar.gz每条镜像单独打一个包每组生成对应的 SHA256 校验值sha256sum cstudio-web.tar.gz cstudio-web.tar.gz.sha256这样传输到内网后每导出一个包就能立刻校验一次哪个文件坏了就单独重传哪个不用推倒重来。2.4 skopeo 处理多架构镜像如果你的目标环境是 ARM64但有外网的导出机是 x86_64直接用docker pull拉下来的可能就是 x86 版。这时候建议用 skopeo 直接按目标架构拉取。举例我要拉一个 ARM64 的 Python 基础镜像skopeo copy --override-archarm64 --override-oslinux \ docker://docker.io/library/python:3.11-slim \ docker://192.168.209.133/library/python:3.11-slimskopeo 的优势是它直接操作 registry不经过 docker daemon 缓存而且可以指定架构。如果你提前已经搭好了 Harbor或者用任意一台临时 registryskopeo 甚至可以做到从外网 registry 直接中转推入内网 Harbor连 tar 包都不用落盘。2.5 传输与校验别让文件在路上坏掉离线部署现场最恶心的问题不是镜像缺了而是镜像包看着在、load 的时候报错。archive/tar: invalid tar header这种情况十有八九是 tar 包在传输中断过、被截断或者磁盘写坏了。尤其用 U 盘拷数据时FAT32 格式单文件 4GB 的限制也容易出问题。所以我的固定流程是传输前生成.sha256校验文件。传到内网后逐包执行sha256sum -c校验。校验通过后再docker load绝不跳步。这一步虽然啰嗦但它是后续所有环节能不能顺利进行的基础。3. 出口机代理给部署机开一条临时外网通道配置好立刻关3.1 网络拓扑与前置条件这个项目的出口机和部署机是分处两个网段的。出口机同时接了外网和运维网网络部门只放行了几个仓库域名的 443 端口。拓扑上看就是外网 - 出口机(正向代理) - 运维网 - 部署机 / Harbor在动手之前先跟网络管理确认三件事代理的监听地址和端口是多少放行了哪些目标域名docker.io、ghcr.io 这些允许使用的时间窗口有多长这三个问题没问清楚后面配置全白做。3.2 squid 正向代理配置白名单比什么都重要出口机上我用的是 squid理由就一个字稳。配置文件核心就两块ACL 加 http_access。apt install squid -y vim /etc/squid/squid.confacl proxy_net src 192.168.0.0/16 acl allowed_domains dstdomain .docker.io .ghcr.io .quay.io http_access allow proxy_net http_access allow allowed_domains http_access deny all注意http_access deny all放在最后效果是除了白名单内的域名其他一切请求全部拒绝。这和白名单的语义一致也方便事后审计——squid 的 access.log 会记录每一笔请求。配置完成后systemctl restart squid ss -lntp | grep 3128看到 3128 端口在监听代理就绪。3.3 docker/containerd 走代理的配置部署机和 Harbor 机器上Docker 要拉外网镜像需要让 dockerd 进程走代理而不是在 shell 里export http_proxy——shell 的环境变量对 dockerd 后台进程不生效。正确做法是给 Docker 配置 systemd 环境变量mkdir -p /etc/systemd/system/docker.service.d cat /etc/systemd/system/docker.service.d/http-proxy.conf EOF [Service] EnvironmentHTTP_PROXYhttp://出口机IP:3128/ EnvironmentHTTPS_PROXYhttp://出口机IP:3128/ EnvironmentNO_PROXYlocalhost,127.0.0.1,192.168.0.0/16,192.168.209.133 EOF然后systemctl daemon-reload systemctl restart docker这里NO_PROXY是重中之重。我最初就是因为没把内网 IP 段加进NO_PROXY导致后面docker push到内网 Harbor 时数据走了代理直接报dial tcp超时。这个坑后面会展开细讲。如果你用的是 containerd配置方式也类似mkdir -p /etc/systemd/system/containerd.service.d cat /etc/systemd/system/containerd.service.d/proxy.conf EOF [Service] EnvironmentHTTP_PROXYhttp://出口机IP:3128/ EnvironmentHTTPS_PROXYhttp://出口机IP:3128/ EnvironmentNO_PROXYlocalhost,127.0.0.1,192.168.0.0/16,192.168.209.133 EOF3.4 用完即断的边界管理代理通道是网络部门给出的临时窗口不是给内网长期开的后门。我的习惯是在拉取所有补缺镜像后立刻把部署机和 Harbor 机器的 systemd 代理配置删除或注释掉再重启 Docker同时通知网络部门关闭出口机的代理端口。整个过程记录时间点和操作人。这样做既是配合网络安全策略也是避免后续内网流量意外绕行出口机——一旦代理端口长期开放内网机器访问任何外网资源都会走它既慢又不合规。4. Harbor 离线仓库搭建步骤、TLS 证书配置与 dial tcp 报错排查4.1 为什么用 Harbor而不是裸 registry内网镜像仓库理论上docker run -p 5000:5000 registry:2一条命令就能起。但实际交付场景里我从来不用裸 registry原因有三需要项目隔离。不同团队、不同组件放在不同 project 下权限才分得清。需要审计。信创项目验收时要能查谁在什么时间推送、拉取了什么镜像。需要 GC 和配额管理。内网磁盘有限Harbor 提供镜像清理策略裸 registry 什么都没有。所以这个项目直接用 Harbor上生产一点毛病没有。4.2 离线安装 HarborHarbor 官方提供离线安装包在有外网的机器上下载后导入内网# 在有网机器下载 wget https://github.com/goharbor/harbor/releases/download/v2.10.1/harbor-offline-installer-v2.10.1.tgz # 传到内网后解压 tar xzvf harbor-offline-installer-v2.10.1.tgz cd harbor cp harbor.yml.tmpl harbor.ymlharbor.yml 里改几个关键配置hostname: 192.168.209.133 http: port: 80 https: port: 443 certificate: /data/harbor/cert/server.crt private_key: /data/harbor/cert/server.key harbor_admin_password: Harbor12345 data_volume: /data/harbor然后执行./install.sh整个过程会自动拉取本地的 harbor 镜像并启动组件。安装完成后浏览器访问https://192.168.209.133应该能看到 Harbor 登录页。4.3 自签证书给内网 IP 签发一张能用的 HTTPS 证书Harbor 默认走 HTTPS内网环境没有 DNS我们要直接给 IP 签证书。这里有个关键点证书的 Subject Alternative NameSAN必须包含 IP 地址否则 Docker 客户端会报证书校验失败。很多人在自签证书上栽跟头就是因为只写了 Common NameCN没写 SAN。我的做法分三步。第一步生成 CA 根证书openssl req -newkey rsa:4096 -nodes -keyout ca.key -x509 -days 3650 -out ca.crt \ -subj /CNHarbor-CA第二步生成 Harbor 服务器证书重点在-addext这行openssl req -newkey rsa:4096 -nodes -keyout server.key -out server.csr \ -subj /CN192.168.209.133 \ -addext subjectAltNameIP:192.168.209.133第三步用 CA 签发服务器证书openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \ -out server.crt -days 3650 -extfile extfile.cnf其中 extfile.cnf 内容subjectAltNameIP:192.168.209.133注意证书有效期我直接签了 10 年。信创项目周期普遍长你要是只签一年明年所有节点都会突然拉不了镜像那才是真正的定时炸弹。4.4 客户端信任配置比 insecure-registries 更稳的方案Harbor 部署完成后所有需要拉取镜像的节点都要信任这个自签证书。大部分教程会让你在daemon.json里加insecure-registries省事是真省事但我更推荐正规证书分发方案在每台部署机上mkdir -p /etc/docker/certs.d/192.168.209.133 cp ca.crt /etc/docker/certs.d/192.168.209.133/ca.crt systemctl restart docker这样做的好处是传输镜像时走的是正常 TLS 加密不会被中间设备干扰而且后续另一套内网服务如果也用了这个 CA同一个 ca.crt 还能复用不用每个服务都开 insecure。对于 containerd 节点配置方式不同。需要在/etc/containerd/certs.d/192.168.209.133/hosts.toml里写明server https://192.168.209.133 [host.https://192.168.209.133] ca /etc/containerd/certs.d/192.168.209.133/ca.crt如果你用的是 Windows Server 2022 WSL 跑容器注意代理和证书配置要落到 WSL 发行版里的 Docker/containerd 上而不是 Windows 侧的 Docker Desktop。Docker Desktop 的证书路径不一样很容易出现Windows 能看、WSL 里拉不动的诡异现象。4.5 harbor 推送失败 dial tcp 的完整排查链路这个报错我在现场遇到过不止一次很多人一看到dial tcp就以为是网络不通但其实原因有四五种可能。下面我把完整的排查链路写出来按顺序走基本都能定位。报错长这样Error response from daemon: Get https://192.168.209.133/v2/: dial tcp 192.168.209.133:443: connect: connection refused第一步确认 Harbor 服务本身活着没有。在 Harbor 机器上执行docker ps | grep harbor如果harbor-core一直在重启看日志docker logs harbor-core --tail 100常见问题是数据库没初始化成功、证书路径配错、或者磁盘权限不对。第二步在 Harbor 本机验证接口。curl -k https://127.0.0.1/v2/返回{}说明 Harbor 核心服务正常。如果这里就失败问题在 Harbor 自身或证书不在网络。第三步从部署机上测试端口连通性。curl -kv https://192.168.209.133/v2/观察报错类型connection refused端口没监听或者防火墙主动拒绝。no route to host路由不通或者安全组直接丢弃。Connection timed out通常是被防火墙静默丢弃重点查网络策略。x509: certificate/TLS handshake error证书信任问题回到 4.4 节配证书。第四步查防火墙。Harbor 机器上查看firewall-cmd --list-all iptables -L -n | grep 443如果有防火墙策略挡住了 443 端口添加放行规则firewall-cmd --add-port443/tcp --permanent firewall-cmd --reload第五步检查代理环境变量。这一步最容易忽略。env | grep -i proxy systemctl show docker --propertyEnvironment如果 Docker 配置了HTTP_PROXY但NO_PROXY没包含192.168.209.133那么docker push内网 Harbor 时会先进代理——而代理的白名单通常只放行外网仓库域名内网 IP 直接被拒绝或绕错路表现就是 dial tcp 失败。解决方法是把 Harbor 的 IP 和整个内网网段加进NO_PROXY然后重启 Dockersystemctl daemon-reload systemctl restart docker这一步做完90% 的假网络不通都能解决。4.6 镜像导入与推送Harbor 就绪后把之前导出的镜像包逐一导入并推送docker load cstudio-web.tar.gz docker tag cstudio-web:2.1.0 192.168.209.133/library/cstudio-web:2.1.0 docker push 192.168.209.133/library/cstudio-web:2.1.0这里建议把镜像统一放到一个 project比如library下后面部署机上统一改前缀也方便。docker login 192.168.209.133推送完成后在 Harbor Web 页面能看到镜像列表和对应的 digest。digest 一定要记下来后面部署时有地方需要核对。5. CubeStudio 部署落地把镜像仓库地址接进容器运行时然后起服务5.1 部署前环境检查清单在真正拉起 CubeStudio 之前我习惯先在目标机上过一遍环境检查操作系统版本和内核cat /etc/os-release、uname -rCPU 架构uname -m容器运行时版本docker version或containerd -v数据盘挂载df -h确认数据目录所在分区有充足空间端口占用情况ss -lntp防止 80/443/8080 等端口被占资源规划上CubeStudio 这类多组件平台的建议是 16 核 64G 起步数据盘单独挂一块 500G 以上的 SSD。别低估镜像和运行时产生的日志量信创机器配置普遍不高磁盘规划要留足余量。5.2 配置容器运行时指向内网 Harbor如果用的是 Docker确保daemon.json里有内网 Harbor 的证书配置或 insecure-registries 配置然后重启{ insecure-registries: [192.168.209.133] }如果用的是 containerd按前面 4.4 节配置hosts.toml。配置完成后先在命令行验证拉取docker pull 192.168.209.133/library/python:3.11-slim或 containerd 节点crictl pull 192.168.209.133/library/python:3.11-slim能拉下来说明镜像链路通了。5.3 用编排文件拉起 CubeStudioCubeStudio 的交付物通常会带一套 docker-compose 或 Kubernetes 编排清单。不管哪种核心操作都是把里面的镜像地址从前缀改为内网 Harbor 地址。举个例子原始编排文件里可能是image: cstudio-web:2.1.0要批量替换成image: 192.168.209.133/library/cstudio-web:2.1.0用 sed 批量处理sed -i s#image: cstudio-#image: 192.168.209.133/library/cstudio-#g docker-compose.yml替换完成并确认无误后docker login 192.168.209.133 docker compose pull docker compose up -d如果编排文件是 Kubernetes 的同理改 image 前缀后kubelet会从内网 Harbor 拉取注意提前在每个节点上配置好 containerd 的 hosts.toml并且把imagePullPolicy改成IfNotPresent或Always避免因本地没有镜像而意外去外网拉取。5.4 实际部署中容易翻车的几个点镜像 digest 校验失败。这类报错多半是导入的镜像包不完整或者传输损坏。回到第 2.5 节重新校验 sha256重新 load。组件初始化访问外部源。CubeStudio 某些初始化任务会尝试连接 pip 源、Maven 源或系统软件源这在无外网环境里会直接卡死。处理方式是提前准备好离线依赖包或者在部署机上临时借助出口机代理拉取初始化完成后再断开。我一般建议和网络部门协调一个代理白名单窗口期把所有外部依赖一次拉完。日志里出现 i/o timeout。如果所有配置都正确但还是偶发超时重点看是不是多个容器同时拉取大镜像导致内网带宽拥堵。可以分批启动服务或者调大net.ipv4.tcp_keepalive_time等内核参数。数据库初始化失败。这类问题最常见原因是数据目录权限不对。Harbor 和 CubeStudio 的数据卷都要确保属主正确一般用 1000:1000 或 root 均可但不要混着来。错误日志里如果有Permission denied先chown再重启。6. 回看整条链路我记下的几条硬道理这一趟跑下来真正值得沉淀的其实不是具体命令而是几个容易在过程中被忽略的判断。镜像版本和架构一定要提前核对到位。现场最贵的资源是时间。一旦发现某个 arm64 镜像在 x86 机器上导出整个进度都得停下。用 skopeo 按架构拉取、用清单文件管版本是我现在做离线交付的固定动作。NO_PROXY 和代理的边界一定要画清楚。配置了代理不等于所有流量都走代理NO_PROXY里的内网网段才是保护伞。我见过太多人在这一步吃亏——内网 Harbor 推送失败排了半天防火墙最后发现是代理环境变量搞的鬼。自签证书有效期直接签 10 年。一年真不够用。信创项目从部署到验收跨越两三个季度很正常中间如果证书过期所有节点同一时间拉取失败那画面太美不想再经历。Harbor 的磁盘规划不要只看今天。离线环境里镜像只会越来越多Harbor 数据盘要单独挂载并且在部署完成后就配置好镜像清理策略比如保留最近 N 个版本。否则半年后再看数据盘满了GC 都跑不动。最后说一点个人体会这类离线部署项目技术上最难的往往不是装软件而是和网络管理部门的沟通节奏。代理窗口期、白名单域名、端口放行这些前置条件没确认清楚再熟练的部署流程也会卡在第一步。我的建议是进入现场第一天就拉一份网络资源清单——哪些域名放行、哪些端口通、代理窗口多长——把这几个问题确认清楚了再动工效率会翻倍。
返回列表