ARTICLE DETAIL

资讯详情

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

Harbor企业级镜像仓库从零部署与排错实战

Harbor企业级镜像仓库从零部署与排错实战 1. 为什么需要自己搭一个 Harbor——从“Docker Hub 被限速”说起Harbor 不是 Docker 的替代品而是 Docker 生态里真正能让你把镜像“管起来”的那把锁。我第一次在客户现场踩坑就是因为没提前搭 Harbor开发团队用docker push往 Docker Hub 推一个 800MB 的 Spring Boot 镜像推到 92% 卡住重试三次全失败运维同事查日志发现是 Docker Hub 对未认证账户的匿名推送限速到了 100KB/s而他们用的是公司统一账号但账号下绑了 17 个子团队触发了速率熔断机制。这时候没人再关心“Docker 怎么安装”所有人盯着屏幕问“有没有办法让镜像不走公网能不能本地存、本地验、本地删”——答案就是 Harbor。它不是个玩具而是一套企业级镜像生命周期管理平台。你看到的“上传/下载镜像”背后其实是四层能力在协同存储层底层用的是 Registry v2 协议 权限层项目级 RBAC支持 LDAP/AD 同步 安全层镜像扫描、内容信任签名、漏洞报告 治理层镜像保留策略、GC 自动清理、审计日志归档。很多教程只教“docker-compose up -d启起来就行”结果上线三天就出事某开发误删了生产环境的nginx:alpine镜像因为 Harbor 默认项目权限是“开发可读写”没人配过只读策略还有一次安全团队要求所有镜像必须通过 Clair 扫描后才能部署但 Harbor 里没开扫描器开关CI 流水线照常发布最后被红队打穿。所以这篇不是“Docker Desktop 上点几下就能跑的 Demo 教程”。我会带你从 CentOS 7.9 物理机开始完整复现一个生产可用、权限可控、日志可溯、扫描可配的 Harbor 实例。重点讲清楚三个别人不说透的细节为什么 Harbor 必须用 HTTPSHTTP 模式下docker login会报x509: certificate signed by unknown authority这不是证书问题而是 Docker 客户端强制策略为什么docker push harbor.example.com/project/app:v1.2里的域名必须和 Harbor 配置的hostname完全一致少一个字符push 就返回unauthorized: authentication required为什么用docker-compose启动后harbor-core容器总在重启大概率是/data目录权限不对或者 PostgreSQL 初始化失败但日志被logrotate切走了。你不需要会写 Go 或 Python但得懂 Linux 文件权限、SSL 证书链、Docker daemon.json 配置逻辑。如果你正卡在“镜像上传 401”或“pull 报错 no basic auth credentials”这篇文章会直接给你定位路径和修复命令。2. 安装前必须确认的五件事——别跳过检查清单Harbor 看似一键部署实则对环境极其挑剔。我见过太多人卡在第 3 步反复重装三次才发现是 SELinux 没关。下面这五件事每一条都对应一个真实故障场景必须逐项验证2.1 确认系统版本与内核参数CentOS 7.9 是黄金组合Harbor 官方明确支持CentOS 7.6 / Ubuntu 18.04 / RHEL 7.6但实际生产中CentOS 7.9 是最稳的选择。原因有三Docker CE 20.10.x 在 CentOS 7.9 上的 cgroup v1 兼容性最好而 Harbor 的registry组件重度依赖 cgroup 内存限制firewalld规则模板在 7.9 中最全harbor.yml里配置的80/443/4443端口能自动映射systemd版本 219 支持RestartSec10这类精细重启控制避免harbor-jobservice崩溃后无限重启拖垮宿主机。提示执行cat /etc/redhat-release uname -r输出必须是CentOS Linux release 7.9.2009和3.10.0-1160.el7.x86_64。如果内核低于3.10.0-1127请先yum update -y reboot否则 Harbor 启动后registry容器会因memcg参数不识别而退出。2.2 关闭 SELinux 与 firewalld不是“建议”是硬性要求Harbor 的clair漏洞扫描器和notary内容信任服务组件需要访问/data目录下的 socket 文件而 SELinux 默认禁止跨域 socket 连接。我曾在一个金融客户环境里SELinux 处于permissive模式harbor-core日志里疯狂刷avc: denied { connectto } for pid123 commcore path/data/clair.sock但容器就是不报错直到用ausearch -m avc -ts recent才抓到这条拒绝日志。正确操作只有两种彻底关闭setenforce 0 sed -i s/SELINUXenforcing/SELINUXdisabled/g /etc/selinux/config或者临时放行仅测试用semanage port -a -t http_port_t -p tcp 8080但 Harbor 默认不用 8080firewalld 同理。Harbor 依赖80HTTP 重定向、443HTTPS 主服务、4443内部 API三个端口。firewall-cmd --list-ports输出必须包含80/tcp 443/tcp 4443/tcp否则docker login会超时。别信“用 iptables 替代”Harbor 的prepare脚本会主动调用firewall-cmdiptables 规则会被覆盖。2.3 磁盘空间与挂载点规划/data 目录必须独立Harbor 默认将所有数据镜像、数据库、日志、证书存放在/data目录。很多人直接用df -h看根分区还有 20GB 就开干结果推入 5 个 Java 镜像后磁盘爆满harbor-db容器因 PostgreSQL WAL 日志写满而崩溃。真实需求测算如下每个 500MB 的应用镜像实际占用约 750MB含 layer 压缩、metadata、scan 结果缓存PostgreSQL 数据库每万条镜像记录占 1.2GB日志默认保留 7 天每天约 80MB所以 1TB 磁盘起步且/data必须是独立挂载点如xvdb1不能是/下的子目录。验证命令# 查看 /data 是否独立挂载 findmnt /data | grep -q xvdb echo OK || echo ERROR: /data not on separate disk # 检查可用空间需 500GB df -h /data | awk NR2 {print $4} | grep -E ^[5-9][0-9]{2}G|[1-9][0-9]{3}G /dev/null echo Space OK || echo Insufficient space2.4 Docker 版本与 daemon.json 配置20.10.17 是兼容阈值Harbor 2.5 要求 Docker Engine ≥ 20.10.10但实测 20.10.17 最稳。低于此版本docker push时registry组件会因OCI manifest解析异常返回500 Internal Server Error。更重要的是daemon.json配置。很多教程忽略这点导致docker login成功但push失败。关键参数只有两个insecure-registries: [harbor.example.com]—— 如果用 HTTP 模式不推荐必须加此项registry-mirrors: [https://mirror.gcr.io]—— 这个镜像源只影响docker pull不影响 Harbor 上传但能加速prepare脚本拉取基础镜像。验证方法# 检查 Docker 版本 docker version --format {{.Server.Version}} | grep -E ^20\.10\.(1[7-9]|[2-9][0-9])$ /dev/null echo Docker OK || echo Upgrade Docker # 检查 daemon.json 是否存在且语法正确 jq empty /etc/docker/daemon.json 2/dev/null echo daemon.json valid || echo daemon.json invalid2.5 DNS 与 hostname 解析harbor.example.com 必须能 ping 通这是最隐蔽的坑。Harbor 安装脚本./install.sh会读取harbor.yml中的hostname字段并生成对应证书。如果hostname设为harbor.example.com但宿主机/etc/hosts里没配127.0.0.1 harbor.example.com那么docker login harbor.example.com时客户端会尝试解析 DNS而内网 DNS 通常没有这条记录最终超时。正确做法分两步修改/etc/hostsecho 127.0.0.1 harbor.example.com /etc/hosts在harbor.yml中设置hostname: harbor.example.com注意不能写localhost或127.0.0.1Docker 客户端会拒绝连接验证命令# 必须返回 0 ping -c1 harbor.example.com /dev/null echo DNS OK || echo Fix /etc/hosts # 检查 hostname 是否匹配 grep ^hostname: harbor.yml | grep -q harbor.example.com echo Config OK || echo Edit harbor.yml3. 安装过程详解从下载到启动的每一步意图Harbor 安装本质是三阶段流水线准备环境 → 生成配置 → 启动容器。网上很多教程把./install.sh当黑盒但一旦失败你连日志都找不到。下面我拆解每个命令背后的意图告诉你该看什么日志、该改什么参数。3.1 下载离线安装包并校验为什么必须用 offline 包Harbor 官方提供 online 和 offline 两种安装包。online 包只有 1.2MB启动时动态拉取 1.2GB 的镜像offline 包 1.2GB自带全部镜像。生产环境必须用 offline 包理由有二网络不可靠prepare脚本拉取goharbor/harbor-core:v2.5.3时若中断不会重试直接报错failed to pull image安全合规金融客户要求所有镜像必须经内部 Nexus 代理online 包的docker-compose.yml里写死image: goharbor/xxx无法替换 registry 地址。下载命令# 从官网下载注意版本号 wget https://github.com/goharbor/harbor/releases/download/v2.5.3/harbor-offline-installer-v2.5.3.tgz # 校验 SHA256官网页面有 checksum echo e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 harbor-offline-installer-v2.5.3.tgz | sha256sum -c解压后目录结构必须包含harbor.yml.tmpl模板文件install.sh主安装脚本common.sh环境检测函数prepare核心配置生成器注意不要mv harbor /opt/harbor必须保持解压后目录名为harbor。install.sh里硬编码了./prepare路径改名会导致command not found。3.2 配置 harbor.yml七个必改参数与三个隐藏陷阱harbor.yml是 Harbor 的心脏。官方模板有 200 行但生产环境只需改 7 个参数。其余参数要么有默认值要么留空即可。下面列出必须修改项并解释每个值的业务含义参数示例值为什么必须改关联风险hostnameharbor.example.comDocker 客户端认证域名必须和docker login命令一致改成harbor.local会导致x509: certificate is valid for harbor.local, not harbor.example.comhttp.port80HTTP 端口用于重定向到 HTTPS设为8080时docker login必须带端口harbor.example.com:8080但 Docker 默认不支持带端口的 registryhttps.port443HTTPS 主端口所有 API 和 Web 访问走这里若设为8443浏览器访问https://harbor.example.com:8443可打开但docker login仍会连443data_volume/data数据根目录所有子目录database、redis、jobservice都在此下若指向/home/harbor/datachown -R 10000:10000 /home/harbor/data会失败因为/home分区通常是noexecdatabase.passwordHarbor12345PostgreSQL 密码长度必须 ≥ 8 且含大小写字母数字密码含符号会导致prepare脚本解析 YAML 失败报yaml: line X: did not find expected keyharbor_admin_passwordHarbor12345Web 管理后台密码同上规则密码太简单登录后会被强制跳转到密码修改页API 调用返回401 Unauthorizedclair.updater_interval12Clarity 漏洞库更新间隔小时设为0表示禁用设为1会导致每小时拉取 2GB 的 NVD 数据库拖慢整个 Harbor 响应三个隐藏陷阱陷阱一certificate和private_key路径必须是绝对路径。模板里写./cert/harbor.crt但prepare脚本会把它拼成/root/harbor/cert/harbor.crt而证书实际在/data/cert/。正确写法是/data/cert/harbor.crt。陷阱二trivy.ignore_unfixed设为true时扫描结果里不显示未修复漏洞。金融客户要求必须显示所有 CVE所以必须设为false。陷阱三notification.endpoint如果配了企业微信 webhookURL 必须以https://开头。配http://会导致harbor-core容器启动失败日志报invalid URL scheme。3.3 执行 prepare 脚本生成配置文件的底层逻辑./prepare不是魔法它只是个 Python 脚本./common/prepare作用是把harbor.yml里的参数注入到 12 个模板文件中。最关键的三个输出文件是common/config/registry/config.ymlRegistry 服务配置定义 storage backend默认 filesystem、auth指向 core 服务、middlewareblob cachecommon/config/core/app.confCore 服务配置含数据库连接串、Redis 地址、Clair 扫描器地址docker-compose.yml最终的容器编排文件定义harbor-core、harbor-registry、harbor-db等 8 个服务。执行时加-f参数可指定 yml 文件./prepare -f harbor.yml成功标志控制台输出Generated configuration file: ./common/config/registry/config.yml等 12 行ls -l common/config/下所有文件时间戳都是当前时间docker-compose.yml里services.harbor-core.environment.HARBOR_ADMIN_PASSWORD的值是加密后的字符串不是明文。失败常见原因harbor.yml语法错误yaml.parser.ParserError: while parsing a block mapping用python -c import yaml; print(yaml.load(open(harbor.yml), Loaderyaml.FullLoader))检查/data目录不存在或无写权限OSError: [Errno 13] Permission denied: /data/secret执行mkdir -p /data chown 10000:10000 /data证书文件路径错误IOError: [Errno 2] No such file or directory: /data/cert/harbor.crt确认证书已放入/data/cert/且权限为644。3.4 启动 Harbordocker-compose up -d 的真相docker-compose up -d启动的是docker-compose.yml定义的 8 个服务。但它们的启动顺序有强依赖harbor-dbPostgreSQL→ 2.redis→ 3.harbor-registry→ 4.harbor-core→ 5.harbor-jobservice→ 6.harbor-portal→ 7.harbor-trivy→ 8.nginxdocker-compose本身不保证顺序Harbor 用healthcheck解决每个服务的docker-compose.yml里都有healthcheck.test字段例如harbor-core的健康检查是curl -f http://localhost:8080/api/v2.0/ping || exit 1。只有前一个服务健康后一个才启动。启动后验证命令# 查看所有容器状态必须全是 Up docker-compose ps | awk NR1 {print $1,$4} | grep -v Up # 检查 harbor-core 是否监听 8080 docker exec harbor-core ss -tlnp | grep :8080 # 检查 nginx 是否转发 443 到 8080 docker exec nginx ss -tlnp | grep :443如果harbor-core一直 restarting看日志docker logs harbor-core 21 | tail -20高频错误failed to connect to database: dial tcp 172.19.0.2:5432: connect: connection refused→harbor-db没起来查docker logs harbor-dbfailed to initialize database: pq: password authentication failed for user harbor→harbor.yml里database.password和harbor_db_password不一致failed to create table: pq: relation project does not exist→ PostgreSQL 数据库初始化失败删掉/data/database重来。4. Docker 上传与下载镜像从 login 到 push 的全流程实操安装完成只是起点。真正的价值在于让开发能顺畅地push和pull。这一节我用一个真实 Spring Boot 应用为例从代码打包到镜像上传全程演示并标注每个命令背后的网络请求和权限校验点。4.1 准备测试镜像构建一个带标签的 Spring Boot 镜像假设你有一个 Maven 项目pom.xml里已配好spring-boot-maven-plugin。构建命令# 打包成 fat jar mvn clean package -DskipTests # 构建 Docker 镜像注意tag 必须含 Harbor 域名 docker build -t harbor.example.com/library/springboot-demo:v1.0 . # Dockerfile 内容关键点FROM 必须用 public 镜像不能用 private registry FROM openjdk:11-jre-slim VOLUME /tmp ARG JAR_FILEtarget/*.jar COPY ${JAR_FILE} app.jar ENTRYPOINT [java,-Djava.security.egdfile:/dev/./urandom,-jar,/app.jar]提示docker build时-t参数的格式必须是harbor.example.com/项目名/镜像名:标签。harbor.example.com是 registry 地址library是 Harbor 里的项目名需提前在 Web 界面创建springboot-demo是镜像名v1.0是标签。少任何一部分push都会失败。4.2 Docker login认证流程的三次握手docker login不是简单发个 token而是标准的 Docker Registry V2 认证协议客户端向https://harbor.example.com/service/token?accountadminserviceharbor-registry发 GET 请求Harbor 返回 JWT token 和 realm认证域客户端用该 token 作为 Bearer Auth向https://harbor.example.com/v2/发 HEAD 请求验证权限。执行命令docker login harbor.example.com -u admin -p Harbor12345成功标志输出Login Succeeded~/.docker/config.json里新增harbor.example.com: {auth: YWRtaW46SGFyYm9yMTIzNDU}base64 编码的admin:Harbor12345docker info | grep -A5 Insecure Registries显示harbor.example.com如果用了 HTTP 模式。失败排查Error response from daemon: Get https://harbor.example.com/v2/: x509: certificate signed by unknown authority→ 客户端没信任 Harbor 的 CA 证书。解决把/data/cert/harbor.crt复制到/etc/docker/certs.d/harbor.example.com/ca.crt然后systemctl restart dockerError response from daemon: Get https://harbor.example.com/v2/: unauthorized: authentication required→ 用户密码错或项目library的成员里没加admin用户Web 界面检查。4.3 Push 镜像四步校验与失败定位docker push是最易出错的环节。它实际执行四步校验Registry 可达性校验curl -I https://harbor.example.com/v2/返回200 OK项目存在性校验curl -X GET https://harbor.example.com/api/v2.0/projects/library返回200用户权限校验curl -X GET https://harbor.example.com/api/v2.0/projects/library/members检查admin是否在role_id: 1项目管理员列表中Layer 上传校验对每个 layer 发POST /v2/library/springboot-demo/blobs/uploads/返回202 Accepted。执行命令docker push harbor.example.com/library/springboot-demo:v1.0进度条显示The push refers to repository [harbor.example.com/library/springboot-demo]后会依次上传layer、config、manifest。如果卡在某个 layer看日志# 在 Harbor 宿主机上实时监控 registry 日志 docker logs -f harbor-registry 21 | grep -E (PUSH|Blob|manifest)高频错误denied: requested access to the resource is denied→ 用户没library项目的Developer或更高权限unauthorized: authentication required→docker login的 token 过期默认 24 小时重新docker loginreceived unexpected HTTP status: 500 Internal Server Error→harbor-registry存储层失败检查/data/registry目录权限是否为10000:10000。4.4 Pull 镜像从 Harbor 下载到本地的完整链路docker pull比 push 简单但仍有陷阱。命令# 先登出其他 registry避免 credential 冲突 docker logout # 登录 Harbor docker login harbor.example.com -u admin -p Harbor12345 # 拉取镜像 docker pull harbor.example.com/library/springboot-demo:v1.0拉取过程分三步获取 manifestGET /v2/library/springboot-demo/manifests/v1.0返回 JSON 描述所有 layer下载 configGET /v2/library/springboot-demo/blobs/sha256:xxx下载 layers并发GET /v2/library/springboot-demo/blobs/sha256:yyy。验证是否成功# 查看本地镜像 docker images | grep springboot-demo # 运行测试 docker run -d -p 8080:8080 harbor.example.com/library/springboot-demo:v1.0 curl http://localhost:8080/actuator/health注意docker pull时如果本地已有同名镜像如springboot-demo:v1.0它不会覆盖而是创建新镜像 ID。要确保用harbor.example.com/前缀否则会从 Docker Hub 拉取。5. 常见问题与排查技巧实录那些文档里不会写的坑Harbor 的报错信息向来以“优雅的模糊”著称。下面是我三年运维中整理的 7 个高频问题每个都附带 root cause、日志定位命令和一行修复命令。5.1 问题harbor-core 容器反复重启日志显示failed to connect to redis现象docker-compose ps显示harbor-core状态为Restartingdocker logs harbor-core最后一行是failed to connect to redis: dial tcp 172.19.0.3:6379: connect: connection refused。Root Causeharbor-redis容器启动失败但docker-compose ps里状态还是Up因为 healthcheck 没配。真实原因是 Redis 配置内存不足/data/redis目录权限不对或redis.conf里maxmemory设太高。日志定位# 查看 redis 容器日志 docker logs harbor-redis # 如果输出为空说明容器根本没启动 docker inspect harbor-redis | grep -A5 Status修复命令# 删除旧数据谨慎 rm -rf /data/redis # 重启 redis docker-compose restart harbor-redis # 等 30 秒后检查 docker exec harbor-redis redis-cli ping # 应返回 PONG5.2 问题Web 界面能打开但登录后 404或点击“Projects”空白现象浏览器访问https://harbor.example.com显示登录页输入 admin 密码后跳转到https://harbor.example.com/harbor/projects页面空白F12 看 Network 标签页/api/v2.0/projects返回401 Unauthorized。Root Causeharbor-core服务的 JWT token 签名密钥不一致。harbor.yml里secretkey_path默认是/data但prepare脚本生成的密钥文件在/data/secret/下而容器里挂载的是/data导致core读不到密钥。日志定位# 查看 core 日志中的 token 错误 docker logs harbor-core 21 | grep -i token | tail -5修复命令# 重建 secret 目录 mkdir -p /data/secret chown 10000:10000 /data/secret # 重新运行 prepare ./prepare -f harbor.yml # 重启 docker-compose down docker-compose up -d5.3 问题docker push 时卡在 “waiting for layer upload”数小时不动现象docker push harbor.example.com/library/app:v1后进度条停在waiting for layer uploaddocker logs harbor-registry无新日志。Root CauseNginx 反向代理超时。Harbor 的nginx.conf里proxy_read_timeout默认 600 秒10 分钟但大镜像上传可能超时。Nginx 先断开连接registry还在等数据造成僵死。日志定位# 查看 nginx 错误日志 docker logs harbor-nginx 21 | grep -i timeout修复命令# 修改 nginx 配置在宿主机上 sed -i s/proxy_read_timeout 600;/proxy_read_timeout 3600;/ /compose_location/nginx.conf # 重启 nginx docker-compose restart harbor-nginx5.4 问题Clair 扫描器一直显示 “Pending”never start现象Web 界面点击镜像的 “Scan” 按钮状态变成 “Pending”10 分钟后还是 Pendingdocker logs harbor-trivy无输出。Root CauseTrivy 数据库初始化失败。harbor.yml里trivy.skip_update设为true时Trivy 不会下载漏洞库但harbor-core仍会发扫描请求导致 pending。日志定位# 查看 trivy 日志 docker logs harbor-trivy # 如果输出 “DB schema migration failed”说明数据库损坏修复命令# 清空 trivy 数据 rm -rf /data/trivy # 设置 skip_update 为 false首次启动必须下载 sed -i s/skip_update: true/skip_update: false/ harbor.yml # 重新 prepare ./prepare -f harbor.yml # 重启 docker-compose restart harbor-trivy5.5 问题上传镜像后Web 界面看不到但 docker pull 能成功现象docker push返回Pusheddocker pull成功但 Harbor Web 界面的library项目里没有这个镜像。Root Cause项目设置了“机器人账户”Robot Account但docker login用的是admin用户而admin不在该项目的成员列表中。Harbor 的权限模型是项目级权限 用户全局权限。日志定位# 查看 core 日志中的权限拒绝 docker logs harbor-core 21 | grep -i permission denied | tail -3修复命令# 在 Web 界面操作Projects → library → Members → Add Member → User: admin → Role: Project Admin # 或用 API需 token curl -X POST https://harbor.example.com/api/v2.0/projects/1/members \ -H Authorization: Basic $(echo -n admin:Harbor12345 | base64) \ -H Content-Type: application/json \ -d {member_user:{username:admin},role_id:1}5.6 问题harbor-db 容器启动失败日志显示 “FATAL: could not open relation mapping file”现象docker-compose ps显示harbor-db状态为Exit 1docker logs harbor-db输出FATAL: could not open relation mapping file global/pg_filenode.map。Root CausePostgreSQL 数据目录损坏。常见于强制docker kill或宿主机断电后WAL 日志未刷盘。日志定位# 查看详细错误 docker logs harbor-db 21 | head -20修复命令# 备份旧数据重要 cp -r /data/database /data/database.backup # 初始化新数据库 docker run -it --rm -v /data/database:/var/lib/postgresql/data gosu postgres postgres --initdb # 重启 docker-compose restart harbor-db5.7 问题docker login 报错 “Error saving credentials: error storing credentials”现象docker login harbor.example.com
返回列表