Harbor离线安装实战:企业级容器镜像仓库零网络部署指南
发布时间:2026/10/22 16:14:54 锦皓数字建站

1. 项目概述为什么离线安装Harbor是生产环境的刚需Harbor不是玩具是企业级容器镜像仓库的基石。我第一次在某金融行业客户的灾备中心部署Harbor时整个机房物理隔离——没有外网、没有代理、连yum源都得手动拷贝。当时运维同事盯着我问“你确定不联网也能把Harbor跑起来”我点点头心里却清楚这不只是“能不能”的问题而是“必须能”。离线安装Harbor本质不是技术炫技而是对生产环境真实约束的尊重。它直面三类典型场景军工或政务类单位的网络强隔离要求海外分支机构受限于跨境带宽与合规审查无法稳定拉取Docker Hub镜像还有那些部署在工业控制网、电力调度网等OT网络中的边缘节点网络策略比防火墙规则还硬。这些地方docker pull命令一敲下去返回的不是镜像ID而是超时错误和运维同事沉默的注视。所以“离线安装”四个字背后是镜像预打包、证书链自包含、依赖项全闭环、服务启动零外联这一整套工程化能力。它要求你彻底放弃“联网即万能”的思维惯性转而用“所有资源皆可携带”的视角重新设计部署流程。关键词里没有“最新热词”因为这不是追热点的活儿——这是每个在真实企业环境中落地容器化的工程师迟早要亲手趟过的河。适合谁不是刚学完docker run hello-world的新手而是已经搭过K8s集群、配过Ingress、调过Prometheus告警阈值正准备把镜像管理纳入CI/CD流水线的中阶实践者。你不需要懂Go语言源码但得清楚docker-compose.yml里每个service的启动顺序意味着什么你不必手写TLS证书脚本但得明白harbor.cfg中hostname字段填错一个字符会导致所有push操作静默失败。这篇文章就是我把过去三年在六个不同离线环境里踩过的坑、记下的参数、验证过的路径原原本本摊开给你看。2. 整体设计思路离线≠简单打包而是构建可移植的运行时单元2.1 离线安装的本质是“运行时环境镜像化”很多人误以为离线安装Harbor 下载离线包 解压 运行install.sh。这种理解停留在表面。真正的离线部署核心目标是构建一个脱离外部网络依赖的自洽运行时单元。这个单元必须同时满足三个硬性条件二进制完备性所有可执行文件harbor-core、harbor-jobservice、nginx、redis、postgresql等及其动态链接库.so文件必须全部内置不能在启动时动态加载系统级共享库镜像完整性Harbor自身依赖的7个官方镜像goharbor/harbor-core:v2.10.0、goharbor/registry-photon:v2.10.0等必须提前下载、重命名、打标签并确保docker images列表中能直接看到配置原子性harbor.yml中所有路径如data_volume、certificate、端口、域名、数据库连接字符串必须在离线环境下绝对有效不能依赖DNS解析或外部CA签发。我见过最典型的失败案例是某制造企业将在线安装包直接拷贝到内网服务器后执行./install.sh。脚本运行到一半卡住日志显示failed to pull registry:2.7——原来Harbor安装脚本内部会尝试拉取一个临时registry镜像用于初始化而这个动作被防火墙无声拦截。解决方案不是加代理而是提前用docker save导出所有必需镜像再用docker load在目标机导入并修改install.sh脚本跳过pull步骤。这背后体现的设计哲学是把网络IO操作从“运行时”前移到“准备时”让部署过程变成纯粹的本地文件操作。2.2 Harbor离线包的三层结构拆解官方提供的离线安装包如harbor-offline-installer-v2.10.0.tgz并非简单压缩包而是经过精密编排的三层结构层级内容关键作用我的实操发现基础层harbor.v2.10.0.tar.gz包含所有Harbor组件的Docker镜像core、jobservice、portal等已按v2.10.0版本固化这个tar包体积约1.2GB解压后实际占用磁盘3.5GB以上因Docker镜像层叠加存储机制配置层harbor.yml.tmpl、install.sh、prepare脚本提供模板化配置生成逻辑prepare脚本负责根据harbor.yml生成最终的docker-compose.ymlprepare脚本默认启用HTTPS若内网环境强制用HTTP需手动注释harbor.yml中https段并删除certificate相关配置否则启动失败工具层docker-compose-linux-x86_64、docker二进制部分版本提供解决目标服务器无Docker/Compose环境的问题但仅限x86_64架构某次在ARM64服务器部署失败就是因为离线包只含x86_64版docker-compose后来改用curl -L https://github.com/docker/compose/releases/download/v2.20.0/docker-compose-linux-aarch64 -o /usr/local/bin/docker-compose手动补全提示离线包版本必须与目标环境Docker Engine版本严格匹配。Harbor v2.10.0要求Docker 20.10.0若目标机为Docker 19.03.15则必须降级使用Harbor v2.8.0离线包否则docker-compose up -d会报version not supported错误。2.3 为什么必须放弃“在线生成配置”的惯性思维在线安装时我们习惯运行./install.sh --with-notary --with-clair让脚本自动处理依赖。但在离线环境这种模式会暴露致命缺陷--with-notary选项会触发Notary服务镜像下载而该镜像不在标准离线包中--with-clair会尝试拉取Clair扫描器镜像同样缺失更隐蔽的是install.sh内部调用python3执行prepare脚本若目标机未安装Python3.7脚本直接退出且错误提示模糊只显示command not found。我的解决方案是完全绕过install.sh采用“配置先行、镜像预置、compose直启”三步法在有网环境用./prepare --with-notary --with-clair生成完整docker-compose.yml然后手动提取其中所有image:字段用docker pull逐个拉取这些镜像再用docker save -o harbor-images.tar $(cat image-list.txt)打包将docker-compose.yml、证书文件、数据目录挂载点一并拷贝至离线机执行docker-compose up -d。这套流程看似繁琐但换来的是100%可控性——你知道每一个字节从哪来、到哪去没有任何黑盒操作。3. 核心细节解析从证书生成到镜像预置的实操铁律3.1 证书离线环境里最不该省略的安全底线Harbor强制HTTPS这是硬性要求。很多团队为图省事在内网用自签名证书结果导致CI/CD流水线中docker login失败。根本原因在于自签名证书必须被客户端操作系统信任而Docker daemon默认不读取系统证书库。正确做法是双证书策略Harbor服务端证书用OpenSSL生成存放在/data/cert/目录harbor.yml中配置https: port: 443 certificate: /data/cert/harbor.crt private_key: /data/cert/harbor.key客户端信任证书将harbor.crt复制到每台需要push/pull的机器的Docker证书目录# Ubuntu/Debian sudo mkdir -p /etc/docker/certs.d/your-harbor-domain.com:443 sudo cp harbor.crt /etc/docker/certs.d/your-harbor-domain.com:443/ca.crt sudo systemctl restart docker注意your-harbor-domain.com必须与harbor.yml中hostname字段完全一致包括端口号若非443则需显式写出如harbor.internal:8443。我曾因hostname填了IP地址而docker login始终返回x509: certificate is valid for xxx, not yyy排查3小时才发现证书SANSubject Alternative Name未包含该IP。3.2 镜像预置如何确保100%覆盖Harbor所有依赖Harbor v2.10.0实际依赖的镜像远超官方文档列出的7个。通过docker-compose config命令展开docker-compose.yml可精确获取所有镜像名# 在有网环境生成配置后执行 docker-compose config | grep image: | sort -u输出结果包含image: goharbor/harbor-core:v2.10.0 image: goharbor/harbor-db:v2.10.0 image: goharbor/harbor-jobservice:v2.10.0 image: goharbor/harbor-portal:v2.10.0 image: goharbor/harbor-registryctl:v2.10.0 image: goharbor/registry-photon:v2.10.0 image: goharbor/redis-photon:v2.10.0 image: goharbor/trivy-adapter-photon:v2.10.0 image: goharbor/clair-adapter-photon:v2.10.0 image: goharbor/notation-signer-photon:v2.10.0共10个镜像。关键细节registry-photon和redis-photon是Photon OS定制版不能用通用redis:alpine替代否则启动时报exec format error架构不匹配trivy-adapter和clair-adapter仅在启用漏洞扫描时加载若离线环境无需扫描可在harbor.yml中设置scan_all_policy: disabled并删除对应service块减少镜像体积300MB所有镜像tag必须统一为v2.10.0混用v2.10.0和v2.10.0-build.1234会导致docker-compose up时版本冲突。实操步骤创建image-list.txt每行一个镜像名执行批量拉取while read image; do docker pull $image; done image-list.txt导出为单个tar包docker save $(cat image-list.txt) -o harbor-all-images.tar拷贝至离线机后一次性加载docker load -i harbor-all-images.tar实测心得docker save导出的tar包用tar -tvf harbor-all-images.tar | wc -l查看文件数正常应为1000个layer文件。若只有几十个说明docker save未包含所有镜像层需检查image-list.txt是否遗漏。3.3 数据持久化避开/var/lib/docker的隐形陷阱Harbor默认将数据存放在/data目录但新手常犯的错误是直接挂载宿主机/data到容器/data却忽略Docker自身存储位置。问题在于若宿主机Docker Root Dir为/var/lib/docker而/data目录恰好位于同一块磁盘当Harbor日志和镜像增长过快会挤占Docker运行空间导致docker ps命令卡死更严重的是/data目录权限若为root:root 755Harbor容器内进程以harbor用户运行无法写入启动后core服务反复重启。正确方案是分离存储路径并预设权限# 创建专用数据目录建议SSD盘 sudo mkdir -p /opt/harbor/data /opt/harbor/secretkey # 设置权限harbor容器内UID为10000GID为10000 sudo chown 10000:10000 /opt/harbor/data /opt/harbor/secretkey sudo chmod 750 /opt/harbor/data /opt/harbor/secretkey # 修改harbor.yml中路径 data_volume: /opt/harbor/data secretkey_path: /opt/harbor/secretkey注意chown 10000:10000是Harbor v2.10.0的硬编码UID/GID不可随意修改。若用id -u harbor查到不同值说明版本不匹配必须核对离线包版本。4. 实操全流程从零开始的离线部署手记4.1 准备阶段在联网环境完成所有前置工作这一步必须在有互联网的开发机或跳板机上完成耗时约25分钟步骤1下载离线包并校验完整性# 下载官方离线包以v2.10.0为例 wget https://github.com/goharbor/harbor/releases/download/v2.10.0/harbor-offline-installer-v2.10.0.tgz # 校验SHA256官网Release页面提供 echo a1b2c3... harbor-offline-installer-v2.10.0.tgz | sha256sum -c # 输出OK表示校验通过提示校验失败90%原因是下载中断。建议用wget -c断点续传或改用curl -C - -O。步骤2生成并精简docker-compose.yml# 解压离线包 tar -xzf harbor-offline-installer-v2.10.0.tgz cd harbor # 复制模板配置 cp harbor.yml.tmpl harbor.yml # 编辑harbor.yml关键修改项 vim harbor.yml # 修改以下字段 hostname: harbor.internal # 必须是域名不能是IP http: port: 80 https: # 启用HTTPS port: 443 certificate: /data/cert/harbor.crt private_key: /data/cert/harbor.key # 注释掉notary和clair相关段若无需 # notary: # enabled: true # clair: # enabled: true # 运行prepare生成docker-compose.yml ./prepare # 检查生成的docker-compose.yml是否包含所有service grep service: docker-compose.yml | wc -l # 应为8core, db, jobservice, nginx, portal, registry, registryctl, redis步骤3证书与镜像打包# 生成自签名证书有效期10年 mkdir -p /tmp/harbor-cert openssl req -newkey rsa:4096 -nodes -sha256 -keyout /tmp/harbor-cert/harbor.key \ -x509 -days 3650 -out /tmp/harbor-cert/harbor.crt \ -subj /CCN/STBeijing/LBeijing/OHarbor/CNharbor.internal # 提取所有镜像名 grep image: docker-compose.yml | awk {print $2} | sort -u /tmp/image-list.txt # 拉取并打包镜像 while read image; do docker pull $image; done /tmp/image-list.txt docker save $(cat /tmp/image-list.txt) -o /tmp/harbor-images.tar # 打包所有离线物料 tar -czf harbor-offline-bundle.tgz \ harbor.yml \ docker-compose.yml \ /tmp/harbor-cert/harbor.crt \ /tmp/harbor-cert/harbor.key \ /tmp/harbor-images.tar最终得到harbor-offline-bundle.tgz大小约1.8GB这就是交付给离线环境的唯一介质。4.2 部署阶段在目标服务器执行零网络依赖安装将harbor-offline-bundle.tgz拷贝至目标服务器如通过U盘或内网FTP执行以下命令步骤1解压并初始化目录结构# 创建部署目录 sudo mkdir -p /opt/harbor # 解压离线包 sudo tar -xzf harbor-offline-bundle.tgz -C /opt/harbor # 进入目录 cd /opt/harbor # 创建证书目录并复制证书 sudo mkdir -p /data/cert sudo cp harbor.crt /data/cert/ sudo cp harbor.key /data/cert/ # 设置证书权限Harbor要求私钥600 sudo chmod 600 /data/cert/harbor.key sudo chmod 644 /data/cert/harbor.crt步骤2加载镜像并验证# 加载所有镜像 sudo docker load -i harbor-images.tar # 验证镜像是否完整加载 sudo docker images | grep goharbor/ | wc -l # 应输出10 sudo docker images | grep photon | wc -l # 应输出6registry, redis, trivy等 # 关键检查是否存在重复tag sudo docker images | awk {print $1:$2} | sort | uniq -d # 若有输出说明镜像重复需docker rmi清理步骤3启动Harbor服务# 启动后台运行 sudo docker-compose up -d # 检查服务状态等待2分钟 sudo docker-compose ps # 正常输出应为8个service状态均为Up如Up 2 minutes # 查看核心服务日志 sudo docker logs -f harbor-core # 成功启动标志出现API server is serving at http://[::]:8080和Starting health check server注意首次启动可能耗时3-5分钟因PostgreSQL需初始化数据库。若docker-compose ps显示harbor-db状态为Restarting检查/data/database目录权限是否为10000:10000。步骤4验证访问与基础功能# 测试HTTPS访问需提前在本地hosts添加解析 curl -k https://harbor.internal/api/v2.0/ping # 返回pong表示API正常 # 创建测试项目 curl -k -X POST https://harbor.internal/api/v2.0/projects \ -H Content-Type: application/json \ -u admin:Harbor12345 \ -d {project_name:test,public:true} # 推送测试镜像需先配置Docker客户端信任证书 docker tag alpine:latest harbor.internal/test/alpine:latest docker push harbor.internal/test/alpine:latest # 成功标志输出Pushed且Harbor Web界面可见该镜像4.3 配置固化让离线环境具备自我维护能力离线部署完成后必须固化三项配置避免后续维护失联Docker Daemon配置编辑/etc/docker/daemon.json添加insecure-registries若强制用HTTP{ insecure-registries: [harbor.internal:80] }注意仅当harbor.yml中禁用HTTPS时才需此配置否则会与证书冲突。Harbor管理员密码重置若忘记密码可通过数据库直接修改需进入harbor-db容器sudo docker exec -it harbor-db psql -U postgres registry # 执行SQL UPDATE harbor_user SET salt12345678901234567890123456789012, passworde7d8...bcrypt哈希值 WHERE usernameadmin;提示bcrypt哈希值可用htpasswd -B -n admin生成但需确保与Harbor使用的bcrypt版本兼容。日志轮转配置防止/var/log/harbor/目录撑爆磁盘编辑/opt/harbor/common/config/core/env# 添加以下行 LOG_LEVELinfo LOG_ROTATE_SIZE100M LOG_ROTATE_COUNT105. 常见问题与排查技巧实录那些文档不会写的血泪教训5.1 启动失败类问题速查表现象根本原因排查命令解决方案docker-compose ps显示harbor-db为RestartingPostgreSQL数据目录权限错误ls -ld /data/databasesudo chown -R 10000:10000 /data/databasedocker logs harbor-core报failed to connect to databaseharbor.yml中database.password为空或错误grep password: harbor.yml检查database段确保password字段有值且与harbor-db容器环境变量一致curl -k https://harbor.internal/api/v2.0/ping返回502 Bad GatewayNginx未启动或配置错误sudo docker logs harbor-nginx检查/opt/harbor/common/config/nginx/nginx.conf中upstream core指向是否为core:8080docker push报unauthorized: authentication requiredHarbor未启用token认证或secretkey_path错误sudo docker logs harbor-core | grep token确认harbor.yml中token段未被注释且secretkey_path目录存在且可读docker-compose up -d报ERROR: failed to create endpointDocker网络驱动冲突如已存在bridge网络docker network lsdocker network rm harbor_harbor删除旧网络后重试5.2 镜像推送失败的深度排查链当docker push harbor.internal/test/alpine:latest失败时不要只看客户端错误要沿请求链路逐层检查第1层客户端Docker配置# 检查是否已登录 docker info \| grep Registry # 应显示harbor.internal # 检查证书是否生效 openssl s_client -connect harbor.internal:443 -servername harbor.internal 2/dev/null \| openssl x509 -noout -text \| grep Subject: # 输出应包含CNharbor.internal第2层Nginx反向代理# 进入nginx容器查看实时访问日志 sudo docker exec -it harbor-nginx tail -f /var/log/nginx/access.log # 正常推送应看到类似 # 10.0.0.1 - - [10/Jan/2024:10:00:00 0000] POST /v2/test/alpine/blobs/uploads/ HTTP/1.1 202 0 - docker/20.10.21 go/go1.18.10 git-commit/xxxxxx kernel/5.15.0-91-generic os/linux arch/amd64 UpstreamClient(Docker-Client/20.10.21 \\(linux\\)) # 若返回401/403说明认证失败返回502说明后端core服务未响应第3层Core服务健康状态# 检查core服务是否收到请求 sudo docker logs harbor-core \| grep POST /v2 # 正常应有类似 # time2024-01-10T10:00:00Z levelinfo msgreceived request methodPOST url/v2/test/alpine/blobs/uploads/ # 若无此日志说明Nginx未将请求转发给core检查nginx.conf中location /v2/块的proxy_pass配置5.3 性能瓶颈预警与优化实操离线环境往往硬件资源有限需主动规避性能陷阱陷阱1Redis内存溢出Harbor的jobservice依赖Redis存储任务队列若未限制内存Redis会吃光所有RAM。解决方案# 编辑docker-compose.yml中redis service redis: image: goharbor/redis-photon:v2.10.0 # 添加内存限制 mem_limit: 1g # 添加Redis配置 command: redis-server /etc/redis.conf --maxmemory 800mb --maxmemory-policy allkeys-lru陷阱2Registry存储缓慢当/data/registry目录位于机械硬盘时镜像push速度可能低于1MB/s。优化方法将/data/registry挂载到SSD分区在harbor.yml中启用registry缓存registry: storage_cache: max_entries: 10000 ttl_seconds: 300陷阱3UI界面加载超时Harbor Portal默认从CDN加载Bootstrap等前端资源离线环境必然失败。解决方案# 进入portal容器替换CDN链接为本地路径 sudo docker exec -it harbor-portal sed -i s|https://cdn.jsdelivr.net/npm/bootstrap.*|/static/bootstrap.min.css|g /usr/share/nginx/html/index.html # 并将bootstrap.min.css文件放入/opt/harbor/common/config/portal/static/最后分享一个小技巧每次更新Harbor版本前先在离线环境用docker-compose down -v彻底清理再重新加载新版本镜像。切勿直接docker-compose up -d覆盖启动否则旧镜像层残留会导致core服务因数据库schema不兼容而崩溃。我曾在一次v2.9.0升级v2.10.0时跳过此步结果修复数据库花了整整一个下午——那之后我的离线部署checklist第一条就是“down -v绝不手软”。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。