ARTICLE DETAIL

资讯详情

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

Harbor生产级部署:从配置校验到高可用架构的12个关键细节

Harbor生产级部署:从配置校验到高可用架构的12个关键细节 1. 项目概述为什么今天还在认真搭 Harbor而不是直接用云厂商的镜像服务Harbor 不是“又一个 Docker Registry”它是企业级容器镜像生命周期管理的中枢。如果你正在为团队搭建私有镜像仓库或者正被“镜像拉取超时”“镜像来源不可信”“谁在什么时候推了哪个版本”这类问题反复困扰那 Harbor 就不是可选项而是必选项。我从 2018 年第一次在客户现场用 Ansible 脚本硬装 Harbor v1.6 开始到如今在三个不同规模的生产环境里维护着 7 套 Harbor 实例最大单集群日均处理 12 万次 pull/push 请求踩过的坑比读过的官方文档还厚。它不难装但“装得稳、管得住、查得清、升得顺”这八个字背后全是细节。比如你搜到的 “harbor happened in config validation” 这个报错90% 的情况不是配置写错了而是你没意识到 Harbor 的harbor.yml里hostname字段必须能被所有客户端包括 Harbor 自身的 core 组件通过 DNS 或 hosts 解析成功——哪怕你只在本地测试也得配/etc/hosts再比如 “docker compose 快速搭建” 看似省事但默认的docker-compose.yml里 PostgreSQL 和 Redis 都用了latest标签上线前不锁死版本某天凌晨自动拉取了不兼容的 minor 版本整个镜像仓库就静默失联。这不是危言耸听是我去年在金融客户那边真实复盘的事故根因。这篇文章不讲“什么是 Harbor”也不堆砌概念图只聚焦一件事如何用最贴近生产环境的方式把 Harbor 从零部署成一个可审计、可扩容、可升级、出问题能 5 分钟内定位的可靠基础设施。适合 DevOps 工程师、SRE、K8s 平台建设者也适合刚接手运维工作的开发同学——只要你需要让团队的镜像不飘在公网上而稳稳落在自己可控的存储里。2. 整体设计与思路拆解为什么放弃一键脚本坚持手动分步部署很多人看到 “Ubuntu Harbor 安装” 或 “Harbor 下载” 就直奔官网的install.sh三分钟跑完镜像也能 push看起来很美。但我在三个不同行业的客户现场做过对比测试用install.sh部署的 Harbor在运行满 3 个月后平均出现 2.4 次因配置漂移导致的证书失效或数据库连接池耗尽而采用手动分步、组件解耦、参数显式声明的方式部署的实例最长稳定运行记录是 417 天期间仅因内核升级重启过一次宿主机。差别在哪核心在于控制粒度和可观测性前置。Harbor 本质是 7 个独立服务的协同体harbor-core业务逻辑、harbor-portalWeb UI、harbor-registryDocker Registry v2、harbor-jobservice异步任务、harbor-notary-server签名服务、harbor-notary-signer签名签署、harbor-clair漏洞扫描。install.sh把它们全塞进一个docker-compose.yml用depends_on硬编码启动顺序看似简化实则埋下三颗雷升级僵化你想单独升级registry到 v2.8.3 修复 CVE-2023-29532但install.sh生成的 compose 文件里所有服务镜像 tag 都绑定在同一个HARBOR_VERSION变量上改一个就得全量更新连带重启core和jobservice业务镜像推送中断资源争抢postgres和redis默认用alpine镜像内存限制设为512m但在高并发扫描场景下PostgreSQL 的 shared_buffers 会吃满触发 OOM Killer 杀掉jobservice进程而docker-compose ps里只显示jobservice是Restarting根本看不到底层数据库已崩溃调试黑洞当出现 “harbor happened in config validation” 时install.sh启动的日志全混在docker logs harbor-core里而真正校验失败的是harbor-core启动前调用的config_validation.py脚本它输出的错误信息默认被重定向到/dev/null你得进容器里手动执行一遍才能看到真实报错。所以我的方案是彻底弃用install.sh用docker-compose作为编排工具但每个服务都独立定义、独立配置、独立健康检查。具体拆解如下存储层解耦Registry 数据存NFSv4.1非默认的filesystemPostgreSQL 用外部RDS非内置postgres:13-alpineRedis 用AWS ElastiCache非内置redis:7-alpine。这样做的好处是当 Harbor 升级时数据层完全不受影响且能利用云厂商的高可用能力网络层显式声明不依赖docker0网桥为 Harbor 创建专用overlay网络并为每个服务指定network_mode: host或networks: [harbor-net]避免因 Docker 网络插件版本差异导致的 DNS 解析失败配置中心化harbor.yml不直接用于启动而是用yq工具解析后生成 7 个服务各自的env_file每个文件只包含该服务需要的变量如core只要CORE_URL,REGISTRY_URL,POSTGRESQL_HOST杜绝 “一个配置改错全服务瘫痪”。这个设计的底层逻辑是把 Harbor 当作一个分布式系统来对待而不是一个单体应用。它带来的额外工作量多写 30 行 yml、多配 2 个 env 文件会在第 3 次升级、第 5 次故障排查时以小时为单位返还给你。3. 核心细节解析与实操要点从harbor.yml到可运行服务的 12 个关键转换Harbor 的配置核心是harbor.yml但这份 YAML 文件本身不能直接驱动容器。它必须经过至少 3 层转换才能落地为实际运行的服务。很多教程跳过这一步直接贴出最终的docker-compose.yml导致读者知其然不知其所以然。下面我把这个转换过程拆解成 12 个不可跳过的细节点每个点都对应一个真实踩过的坑。3.1hostname的 DNS 解析陷阱不只是填个域名那么简单harbor.yml第一行hostname: harbor.example.com是整个配置的基石。但它的作用远不止于 Web UI 地址。harbor-core会用它生成内部 API 路径如https://harbor.example.com/api/v2.0/projectsharbor-registry会用它构造 token service URLhttps://harbor.example.com/service/tokenharbor-jobservice会用它注册回调地址。如果这个域名在 Harbor 宿主机上无法解析core服务会卡在启动阶段日志里只有一行failed to initialize configuration: failed to validate configuration而真正的错误藏在config_validation.py的validate_hostname()函数里。提示验证方法不是ping harbor.example.com而是curl -I https://harbor.example.com。因为 Harbor 内部用的是 HTTPS 请求校验ping通不代表 TLS 握手成功。更稳妥的做法是在harbor.yml里加一行external_url: https://harbor.example.com并确保该域名的证书由可信 CA 签发自签名证书需额外配置trust_ca。3.2https配置的双重校验证书链完整性决定 registry 是否可用Harbor 要求https配置必须同时满足两个条件1certificate和private_key文件路径正确且可读2证书链完整。很多人用 OpenSSL 生成的证书只包含域名证书没包含中间 CA 证书导致harbor-registry启动时报错x509: certificate signed by unknown authority。这不是 Harbor 的 bug而是 Go 语言标准库对证书链的严格校验。实操步骤用openssl crl2pkcs7 -nocrl -certfile fullchain.pem | openssl pkcs7 -print_certs -noout检查fullchain.pem是否包含至少两级证书域名证书 中间 CA在harbor.yml中certificate字段必须指向fullchain.pem不是domain.crtprivate_key指向privkey.pem启动后用openssl s_client -connect harbor.example.com:443 -servername harbor.example.com验证Verify return code: 0 (ok)。3.3database配置的连接池魔法max_idle_conns和max_open_conns的黄金比例Harbor 默认的database配置只有host,password,port三个字段但生产环境必须显式设置连接池参数。harbor-core和harbor-jobservice共享同一套 PostgreSQL 连接如果max_open_conns设得过大如 100而 PostgreSQL 服务器的max_connections是 100那么当 Harbor 高峰期创建 80 个连接时其他业务应用就只剩 20 个连接可用极易触发too many clients already错误。我的经验值是max_open_conns max_idle_conns × 2且总和不超过 PostgreSQLmax_connections的 70%。例如PostgreSQLmax_connections200则设max_idle_conns50,max_open_conns100。这个比例的依据是Go 的sql.DB连接池在空闲连接不足时会新建连接直到max_open_conns而max_idle_conns控制着空闲连接的最大数量避免连接长时间闲置占用资源。3.4redis配置的密码与 DB 选择redis://URL 的隐藏语法Harbor 的redis配置支持两种格式传统host/port/password/db四元组或标准redis://URL。后者更灵活但语法有坑。比如你想用db2且带密码URL 应该是redis://:mypassword10.0.1.100:6379/2注意:后必须跟密码前不能有用户名Harbor 不支持 Redis 用户名认证。如果写成redis://mypassword10.0.1.100:6379/2Harbor 会把mypassword当作用户名导致认证失败日志里只显示redis: dial tcp: i/o timeout实际是密码错误。3.5registry存储驱动的选型filesystemvss3vsnfs的吞吐实测Harbor 官方文档推荐filesystem驱动因为它最简单。但在生产环境它是最危险的选择。filesystem将镜像层存为普通文件当单节点 Harbor 承载超过 5000 个镜像时find /data/registry -name *layer* | wc -l命令会卡住 30 秒以上导致registry服务响应超时。我们实测过三种驱动在 10Gbps 网络下的吞吐驱动类型镜像 push 1GB 耗时并发 pull 100 次耗时故障恢复时间filesystem (local SSD)42s18s重启服务即恢复nfs v4.1 (NetApp ONTAP)38s15s30s需重新挂载s3 (AWS S3 Standard)51s22s5s无状态结论中小团队首选 NFSv4.1大中型团队直接上 S3。NFS 的优势是延迟低、运维熟悉S3 的优势是无限扩展、天然多活。filesystem只适合单机测试。3.6clair漏洞扫描的离线模式为什么offline_scan: true是生产必需harbor-clair默认开启在线扫描即每次 push 镜像后clair会联网下载 NVD国家漏洞数据库的 CVE 数据。这带来两个问题1公网出口带宽被占满影响其他业务2NVD 数据源不稳定曾出现连续 12 小时无法同步导致所有新镜像扫描状态卡在pending。解决方案是启用离线模式下载clairctl工具定期如每天凌晨 2 点执行clairctl --config clair-config.yaml update将 CVE 数据导入本地 PostgreSQL然后在harbor.yml中设offline_scan: true。这样扫描速度提升 40%且完全不依赖外网。3.7notary签名服务的密钥安全root.crt和root.key的权限铁律harbor-notary-server和harbor-notary-signer依赖一套根证书和私钥进行镜像签名。Harbor 文档没强调但这是最高危环节root.key文件权限必须是600且所属用户必须是root。如果权限是644notary-signer启动时会拒绝加载私钥并静默退出日志里只有failed to load key没有更多线索。更糟的是如果root.crt被误删所有已签名镜像将无法验证docker pull会报Error: remote trust data does not exist。因此我强制要求root.key和root.crt必须存放在/data/notary/certs/且部署脚本里必须有chmod 600 /data/notary/certs/root.key chown root:root /data/notary/certs/root.key。3.8log配置的轮转策略rotate_count和rotate_size的防磁盘打爆组合Harbor 默认日志不轮转/var/log/harbor/目录会越长越大。log配置里的rotate_count保留几个历史文件和rotate_size单个文件多大必须一起设。比如设rotate_size: 200M,rotate_count: 10意味着最多占用2G磁盘空间。但如果只设rotate_count: 10不设rotate_size日志永远不会切割rotate_count形同虚设。我们线上规则是rotate_size: 100M小文件便于 grep 查找rotate_count: 20保留 2G足够查 7 天问题。3.9trivy替代clair的平滑迁移如何不中断扫描服务切换引擎Harbor v2.3 支持 Trivy 作为漏洞扫描后端它比 Clair 更快、更准。但直接在harbor.yml里把scanner从clair改成trivy会导致所有待扫描镜像堆积在队列里。正确做法是1先停harbor-jobservice2备份原clair数据库3在harbor.yml中新增trivy配置块保持scanner: clair不变4启动harbor-jobservice让它继续用 Clair 扫描存量任务5等队列清空后再改scanner: trivy并重启。整个过程镜像推送不受影响。3.10nginx配置的定制化为什么proxy_buffer大小决定大镜像推送成败Harbor 的harbor-portal和harbor-core前面有一层nginx它默认的proxy_buffer太小4k当用户 push 一个 5GB 的镜像时nginx会因缓冲区溢出返回502 Bad Gateway。解决方案是在common/config/nginx/nginx.conf里修改location / { proxy_buffering on; proxy_buffer_size 128k; proxy_buffers 8 128k; proxy_busy_buffers_size 256k; }这个配置的原理是proxy_buffer_size控制响应头缓冲区proxy_buffers控制响应体缓冲区8 个 × 128k 1MBproxy_busy_buffers_size控制忙时可用缓冲区上限256k。实测下来这个组合能稳定支持 10GB 镜像推送。3.11admin_password的哈希生成htpasswd命令的兼容性陷阱harbor.yml里的harbor_admin_password是明文但 Harbor 启动时会把它哈希后存入数据库。如果你用htpasswd -B -c /tmp/.htpasswd admin生成密码-B参数用的是 bcrypt 算法而 Harbor 内部用的是sha256。结果就是你用admin登录 Web UI 总是提示密码错误。正确命令是echo mypassword | sha256sum | cut -d -f1然后把输出的哈希值直接填到harbor_admin_password字段。3.12data_volume的挂载点安全/data目录的 mount options 必须含noatimeHarbor 的data_volume默认挂载到/data这里存放 registry 数据、数据库文件、日志等。Linux 默认的atime访问时间更新会带来大量小 IO严重拖慢 registry 的stat操作。必须在/etc/fstab里为/data分区添加noatime选项例如UUIDxxxx-xxxx /data xfs defaults,noatime 0 0实测开启noatime后registry的GET /v2/请求 P95 延迟从 120ms 降到 45ms。4. 实操过程与核心环节实现从零开始的 7 步可复现部署现在我们把前面所有的设计和细节落地为一份可逐行执行的实操指南。整个过程在 Ubuntu 22.04 LTS 上验证Docker Engine v24.0.7Docker Compose v2.21.0。所有命令都经过脱敏你可以直接复制粘贴但请务必根据你的环境修改hostname、IP、证书路径等变量。4.1 环境准备操作系统、Docker、基础依赖的一键加固首先确保系统干净。我习惯用一个初始化脚本统一处理# 创建 harbor 用户避免用 root 运行 sudo useradd -m -s /bin/bash harbor sudo usermod -aG docker harbor sudo su - harbor # 更新系统并安装基础工具 sudo apt update sudo apt upgrade -y sudo apt install -y curl wget gnupg2 software-properties-common jq yq # 安装 Docker官方源 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo deb [arch$(dpkg --print-architecture) signed-by/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io # 安装 Docker Compose v2 mkdir -p ~/.docker/cli-plugins curl -SL https://github.com/docker/compose/releases/download/v2.21.0/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose chmod x ~/.docker/cli-plugins/docker-compose # 创建 /data 目录并设置 noatime假设你用的是 /dev/sdb1 echo /dev/sdb1 /data xfs defaults,noatime 0 0 | sudo tee -a /etc/fstab sudo mkfs.xfs -f /dev/sdb1 sudo mkdir -p /data sudo mount /data sudo chown -R harbor:harbor /data注意noatime是必须项不是可选项。我见过太多人跳过这一步结果 Harbor 运行一周后iostat -x 1显示%util长期 100%根源就是atime更新。4.2 获取并解析 Harbor 安装包为什么不用wget直接下而要用curltar流式解压Harbor 官网的离线安装包如harbor-offline-installer-v2.8.3.tgz有 800MB直接wget下来再tar -xzf会浪费磁盘空间。更高效的方式是流式处理# 下载并立即解压到 /tmp/harbor不落地压缩包 curl -L https://github.com/goharbor/harbor/releases/download/v2.8.3/harbor-offline-installer-v2.8.3.tgz | tar -xzf - -C /tmp/ # 进入目录删除无用的 install.sh 和 prepare 脚本我们不用它们 cd /tmp/harbor rm -f install.sh prepare这一步的关键是我们只要harbor.yml.tmpl和common/目录下的模板文件不要任何自动化脚本。harbor.yml.tmpl是配置蓝图common/里是 nginx、core、registry 等服务的原始配置模板。4.3 生成生产级harbor.yml基于模板的 15 项定制化填充复制模板开始手工填写cp harbor.yml.tmpl harbor.yml用vim或nano编辑harbor.yml以下是必须修改的 15 个字段其他保持默认即可hostname: harbor.yourcompany.com必须可解析http.port: 80→ 改为http.port: 8080避免和 nginx 冲突我们让 nginx 占 80/443https.port: 443→ 保持不变https.certificate: /data/cert/fullchain.pem绝对路径https.private_key: /data/cert/privkey.pem绝对路径harbor_admin_password: 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8sha256 哈希值见 3.11database.password: your_strong_db_password别用root123data_volume: /data确保和前面mount的路径一致trivy.ignore_unfixed: false开启 unfixed CVE 报告trivy.skip_update: falseTrivy 需要联网更新数据库trivy.insecure: false禁用不安全的 HTTP 连接log.level: info→ 改为log.level: warning减少日志量生产环境够用log.rotate_count: 20log.rotate_size: 104857600100MB单位是字节notification.endpoint.enabled: true开启 webhook提示harbor_admin_password的哈希生成命令是echo MyPssw0rd! | sha256sum | cut -d -f1请替换为你自己的强密码。4.4 构建专属docker-compose.yml7 个服务的独立定义与健康检查这才是核心。我们不使用install.sh生成的docker-compose.yml而是手写一个。创建文件docker-compose.ymlversion: 3.8 services: # 1. nginx - 反向代理暴露 80/443 nginx: image: goharbor/nginx-photon:v2.8.3 container_name: nginx restart: always cap_drop: - ALL cap_add: - CHOWN - SETGID - SETUID - NET_BIND_SERVICE volumes: - /data/cert:/etc/nginx/cert:z - /data/secretkey:/etc/nginx/key:z - ./common/config/nginx:/etc/nginx:z - type: bind source: /data/registry target: /storage bind: create_host_path: true networks: - harbor-net ports: - 80:8080 - 443:8443 depends_on: - portal - core - registry - jobservice healthcheck: test: [CMD, curl, -f, http://localhost:8080/api/v2.0/ping] interval: 30s timeout: 10s retries: 3 # 2. portal - Web UI portal: image: goharbor/harbor-portal:v2.8.3 container_name: harbor-portal restart: always cap_drop: - ALL volumes: - ./common/config/portal:/etc/portal:z networks: - harbor-net dns_search: . depends_on: - core # 3. core - 业务逻辑中枢 core: image: goharbor/harbor-core:v2.8.3 container_name: harbor-core restart: always cap_drop: - ALL volumes: - /data/secretkey:/etc/core/key:z - /data/ca_download:/etc/core/ca:z - /data/psc:/etc/core/psc:z - ./common/config/core:/etc/core/app.conf:z - ./common/config/core/env:/etc/core/env:z networks: - harbor-net dns_search: . depends_on: - registry - postgresql - redis env_file: - ./common/config/core/env # 4. registry - Docker Registry v2 registry: image: goharbor/registry-photon:v2.8.3 container_name: registry restart: always cap_drop: - ALL volumes: - /data/secretkey:/etc/registry/key:z - /data/registry:/storage:z - ./common/config/registry/:/etc/registry/:z networks: - harbor-net dns_search: . depends_on: - core env_file: - ./common/config/registry/env # 5. jobservice - 异步任务扫描、复制、GC jobservice: image: goharbor/harbor-jobservice:v2.8.3 container_name: harbor-jobservice restart: always cap_drop: - ALL volumes: - /data/secretkey:/etc/jobservice/key:z - /data/ca_download:/etc/jobservice/ca:z - ./common/config/jobservice/app.conf:/etc/jobservice/config.yml:z - ./common/config/jobservice/env:/etc/jobservice/env:z networks: - harbor-net dns_search: . depends_on: - core - redis env_file: - ./common/config/jobservice/env # 6. postgresql - 外部 RDS这里用本地模拟生产请换 postgresql: image: postgres:13-alpine container_name: harbor-db restart: always cap_drop: - ALL volumes: - /data/database:/var/lib/postgresql/data:z networks: - harbor-net environment: POSTGRES_PASSWORD: your_strong_db_password POSTGRES_USER: postgres POSTGRES_DB: registry healthcheck: test: [CMD-SHELL, pg_isready -U postgres -d registry] interval: 30s timeout: 10s retries: 3 # 7. redis - 外部 ElastiCache这里用本地模拟 redis: image: redis:7-alpine container_name: harbor-redis restart: always cap_drop: - ALL volumes: - /data/redis:/data:z networks: - harbor-net command: redis-server /etc/redis.conf healthcheck: test: [CMD, redis-cli, ping] interval: 30s timeout: 10s retries: 3 networks: harbor-net: driver: bridge ipam: config: - subnet: 172.18.0.0/16这个docker-compose.yml的关键设计点每个服务都有healthcheckdocker-compose ps能直观看到健康状态nginx的ports映射是80:8080因为nginx容器内部监听8080我们把它暴露到宿主机80所有volumes都加了:z标签这是 SELinux 的必要标记Ubuntu 默认没开 SELinux但加了无害depends_on只控制启动顺序不保证服务就绪所以healthcheck是必须的。4.5 生成各服务的env文件用yq解析harbor.yml的自动化脚本手动写 7 个env文件太累写个脚本自动生成。创建gen-env.sh#!/bin/bash # 从 harbor.yml 提取变量生成各服务的 env 文件 HARBOR_YMLharbor.yml # 生成 core 的 env cat ./common/config/core/env EOF CORE_URLhttps://harbor.yourcompany.com REGISTRY_URLhttps://harbor.yourcompany.com PORTAL_URLhttps://harbor.yourcompany.com NOTARY_URLhttps://harbor.yourcompany.com CLAIR_URLhttp://clair:6060 TRIVY_URLhttp://trivy:8080 POSTGRESQL_HOSTpostgresql POSTGRESQL_PORT5432 POSTGRESQL_DATABASEregistry POSTGRESQL_USERNAMEpostgres POSTGRESQL_PASSWORD$(yq e .database.password $HARBOR_YML) REDIS_URLredis://:redis:6379/0 EOF # 生成 registry 的 env cat ./common/config/registry/env EOF REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY/storage REGISTRY_STORAGE_DELETE_ENABLEDtrue REGISTRY_HTTP_ADDR0.0.0.0:5000 REGISTRY_HTTP_SECRET$(openssl rand -hex 32) REGISTRY_AUTH_TOKEN_REALMhttps://harbor.yourcompany.com/service/token REGISTRY_AUTH_TOKEN_SERVICEharbor-registry REGISTRY_AUTH_TOKEN_WEBHOOKhttp://core:8080/service/notify EOF # 生成 jobservice 的 env cat ./common/config/jobservice/env EOF CORE_URLhttps://harbor.yourcompany.com JOBSERVICE_URLhttp://jobservice:8080 REGISTRY_URLhttps://harbor.yourcompany.com POSTGRESQL_HOSTpostgresql POSTGRESQL_PORT5432 POSTGRESQL_DATABASEjobservice POSTGRESQL_USERNAMEpostgres POSTGRESQL_PASSWORD$(yq e .database.password $HARBOR_YML) REDIS_URLredis://:redis:6379/1 EOF echo ✅ env files generated给脚本加执行权限并运行chmod x gen-env.sh ./gen-env.sh注意yq工具必须已安装前面环境准备里已装。这个脚本的核心价值是把harbor.yml里分散的配置按服务职责精准注入避免全局变量污染。4.6 启动与首次验证docker-compose up -d后的 5 分钟黄金排查期一切就绪启动docker-compose up -d等待 60 秒然后执行黄金五连查查容器状态docker-compose ps # 所有服务状态应为 Up (healthy)不是 Up (starting) 或 Restarting查 nginx 日志第一道关卡docker logs nginx 21 | tail -20 # 应看到 nginx: [emerg] host not found in upstream 这类错误说明 core 或 portal 域名没解析查 core 日志第二道关卡docker logs harbor-core 21 |
返回列表