Harbor 镜像仓库运维指令手册
Harbor 镜像仓库运维指令手册
适用版本:Harbor 2.11 / 2.12 / 2.13 / 2.14 / 2.15(当前) 部署方式:Docker Compose(离线安装包)/ Helm(Kubernetes) 约定:
#root 或 sudo,$普通用户,<xxx>为占位符,<project>为 Harbor 项目名。 更新时间:2026-11(版本事实核对至 2026-11-05)
目录
1. 版本与架构
1.1 版本事实(2026-11)
| 版本 | 首发 | 最新补丁 | 状态 |
|---|---|---|---|
| 2.15 | 2026-03-19 | 2.15.4 / 2.15.13(社区分支) | 当前,推荐 |
| 2.14 | 2025-09-09 | 2.14.6 | 支持中 |
| 2.13 | 2025-04-09 | 2.13.6 | 支持中,剩余时间最短 |
| 2.12 | 2024-11-05 | 2.12.4 | 已 EOL(2026-03-20) |
升级规则:只支持 n-2。从 2.12 升 2.15 不行,必须 2.12 → 2.13 → 2.14 → 2.15 逐级升。
1.2 组件构成
┌──────────────────────┐
浏览器/API ────▶│ nginx (proxy) │ :80/:443
└──────────┬───────────┘
│
┌──────────────────────┼───────────────────────┐
▼ ▼ ▼
┌───────────┐ ┌──────────────┐ ┌─────────────┐
│ portal │ │ core │ │ registry │
│ (前端) │ │ (API, 核心) │ │ (镜像存储) │
└───────────┘ └───────┬──────┘ └─────────────┘
│
┌──────────────┬───────┴────────┬──────────────┐
▼ ▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
│ jobservice│ │ postgresql│ │ redis │ │ trivy │
│ (任务) │ │ (元数据) │ │ (缓存/队列)│ │ (扫描) │
└───────────┘ └───────────┘ └───────────┘ └───────────┘
| 容器 | 作用 | 挂了的影响 |
|---|---|---|
nginx |
反向代理、TLS 终结 | 全部不可访问 |
harbor-portal |
Web UI | 界面打不开,API 仍可用 |
harbor-core |
核心 API、认证、策略 | 全部 API 失败 |
registry |
实际存储镜像 blob | push/pull 全部失败 |
registryctl |
registry 配置与 GC 执行器 | 无法执行 GC |
harbor-jobservice |
异步任务(复制、扫描、GC) | 任务排队不执行 |
harbor-db(postgresql) |
元数据(项目、用户、策略) | core 起不来 |
redis |
缓存、任务队列、会话 | jobservice 异常、登录变慢 |
trivy-adapter |
漏洞扫描 | 扫描任务失败 |
1.3 端口
| 端口 | 用途 |
|---|---|
| 80 | HTTP(通常重定向到 443) |
| 443 | HTTPS(生产必须) |
| 4443 | 可选,core 的直连端口(内部) |
| 5432 | PostgreSQL(仅内部) |
| 6379 | Redis(仅内部) |
| 5000 | registry(仅内部,不对外) |
| 8080 | jobservice(仅内部) |
安全要求:只暴露 80/443。PostgreSQL/Redis/registry 端口绝不能映射到宿主机公网。
1.4 版本识别
# Web/API
curl -sk https://harbor.internal/api/v2.0/systeminfo | jq '.harbor_version'
curl -sk https://harbor.internal/api/v2.0/health
# 容器镜像 tag
cd /opt/harbor && docker compose images
# CLI
docker run -it --rm goharbor/harbor-core:v2.15.4 --version 2>/dev/null || true
# 配置文件版本
grep '_version' /opt/harbor/harbor.yml
2. 部署
2.1 部署方式选择
| 方式 | 适合 | 高可用 | 运维复杂度 |
|---|---|---|---|
| Docker Compose(离线包) | 中小规模、单机 | 需外部 PG/Redis/存储 + 多实例 | 低 |
| Helm(K8s) | 已有 K8s | 天然支持多副本 | 中 |
| Harbor Operator | K8s + 自动化 | 支持 | 中高 |
2.2 前置条件
| 项 | 要求 |
|---|---|
| CPU/内存 | 最小 2C4G;生产 4C8G+ |
| 磁盘 | 按镜像量,建议独立数据盘 ≥ 500G |
| Docker | 20.10+ |
| Docker Compose | v2 插件(docker compose) |
| 系统 | 内核 ≥ 4.0,cgroup v2 更佳 |
| 端口 | 80/443 可用 |
| 域名 | 必须有(IP 访问会导致 docker login 证书校验失败) |
docker --version
docker compose version
df -h /data
ss -lntp | grep -E ':80|:443'
关键:不要用 IP 作为 hostname。
docker login 10.0.0.50会因为证书 SAN 不匹配失败(除非配 insecure-registry,但生产不建议)。准备一个域名(内网 DNS 也行)。
2.3 离线安装(推荐,生产常用)
# 1) 下载(从 GitHub Releases 取最新 2.15.x)
# harbor-offline-installer-v2.15.4.tgz
wget https://github.com/goharbor/harbor/releases/download/v2.15.4/harbor-offline-installer-v2.15.4.tgz
# 2) 解压
tar xzf harbor-offline-installer-v2.15.4.tgz -C /opt
cd /opt/harbor
ls # harbor.v2.15.4.tar.gz harbor.yml.tmpl install.sh prepare common.sh
# 3) 准备证书
mkdir -p /opt/harbor/cert
# 自签(内网)
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-keyout /opt/harbor/cert/harbor.key \
-out /opt/harbor/cert/harbor.crt \
-subj "/CN=harbor.internal" \
-addext "subjectAltName=DNS:harbor.internal,IP:10.0.0.50"
# 4) 配置
cp harbor.yml.tmpl harbor.yml
vim harbor.yml
hostname: harbor.internal
http:
port: 80
https:
port: 443
certificate: /opt/harbor/cert/harbor.crt
private_key: /opt/harbor/cert/harbor.key
harbor_admin_password: <强密码>
database:
password: <强密码>
max_idle_conns: 100
max_open_conns: 900
conn_max_lifetime: 5m
conn_max_idle_time: 0
data_volume: /data/harbor
trivy:
ignore_unfixed: false
skip_update: false
skip_java_db_update: false
offline_scan: false
security_check: vuln
insecure: false
timeout: 5m0s
jobservice:
max_job_workers: 10
job_loggers:
- STD_OUTPUT
- FILE
logger_sweeper_duration: 1
notification:
webhook_job_max_retry: 10
webhook_job_http_client_timeout: 3
log:
level: info
local:
rotate_count: 50
rotate_size: 200M
location: /data/harbor/logs
_version: 2.15.0
proxy:
http_proxy:
https_proxy:
no_proxy:
components:
- core
- jobservice
- trivy
upload_purging:
enabled: true
age: 168h
interval: 24h
dryrun: false
cache:
enabled: false
expire_hours: 24
# 5) 安装(--with-trivy 必带,否则没有扫描)
./install.sh --with-trivy
# 6) 验证
docker compose ps
curl -sk https://harbor.internal/api/v2.0/health
curl -sk https://harbor.internal/api/v2.0/systeminfo | jq '.harbor_version'
2.4 在线安装
wget https://github.com/goharbor/harbor/releases/download/v2.15.4/harbor-online-installer-v2.15.4.tgz
tar xzf harbor-online-installer-v2.15.4.tgz -C /opt
cd /opt/harbor
cp harbor.yml.tmpl harbor.yml
vim harbor.yml
./install.sh --with-trivy
区别:在线包不含镜像 tar,install.sh 会从 Docker Hub 拉取。国内网络需先配镜像加速,否则极慢或失败。
2.5 外部数据库与 Redis(生产/HA 必须)
# harbor.yml
database:
# 密码仍需保留(Harbor 用它连外部库)
password: <pwd>
# 指向外部 PostgreSQL
# 注意:Harbor 不读这些键,需要改 env 或直接用 compose override
Harbor 的 harbor.yml 不直接支持外部 DB 配置,需要通过 docker-compose.yml 的环境变量覆盖。标准做法是准备 override 文件:
# /opt/harbor/docker-compose.override.yml
services:
harbor-core:
environment:
POSTGRESQL_HOST: pg.internal
POSTGRESQL_PORT: "5432"
POSTGRESQL_DATABASE: registry
POSTGRESQL_USERNAME: harbor
POSTGRESQL_PASSWORD: <pwd>
POSTGRESQL_SSLMODE: require
_REDIS_URL_CORE: "redis://:pwd@redis.internal:6379/0"
_REDIS_URL_REG: "redis://:pwd@redis.internal:6379/1"
harbor-jobservice:
environment:
_REDIS_URL_CORE: "redis://:pwd@redis.internal:6379/0"
registry:
environment:
REGISTRY_STORAGE: s3
REGISTRY_STORAGE_S3_REGIONENDPOINT: oss-cn-hangzhou.aliyuncs.com
REGISTRY_STORAGE_S3_BUCKET: harbor-registry
REGISTRY_STORAGE_S3_ACCESSKEY: <ak>
REGISTRY_STORAGE_S3_SECRETKEY: <sk>
REGISTRY_STORAGE_S3_REGION: cn-hangzhou
REGISTRY_STORAGE_S3_SECURE: "true"
REGISTRY_STORAGE_S3_V4AUTH: "true"
REGISTRY_STORAGE_DELETE_ENABLED: "true"
# 应用 override(不加 -d 先看效果)
docker compose -f docker-compose.yml -f docker-compose.override.yml up -d
注意:
registry的存储后端改了之后,已有镜像数据不会自动迁移。必须先用registry的 storage driver 迁移工具或skopeo sync搬运,再切换。切换前务必全量备份。
2.6 对象存储(S3/OSS/MinIO)
# docker-compose.override.yml 片段(MinIO 示例)
services:
registry:
environment:
REGISTRY_STORAGE: s3
REGISTRY_STORAGE_S3_REGIONENDPOINT: minio.storage:9000
REGISTRY_STORAGE_S3_BUCKET: harbor
REGISTRY_STORAGE_S3_ACCESSKEY: minioadmin
REGISTRY_STORAGE_S3_SECRETKEY: minioadmin
REGISTRY_STORAGE_S3_REGION: us-east-1
REGISTRY_STORAGE_S3_SECURE: "false"
REGISTRY_STORAGE_S3_V4AUTH: "true"
REGISTRY_STORAGE_S3_FORCEPATHSTYLE: "true"
REGISTRY_STORAGE_DELETE_ENABLED: "true"
harbor-core:
environment:
REGISTRY_STORAGE_PROVIDER_NAME: s3
对象存储的好处:镜像量增长不受单机磁盘限制;GC 直接作用于对象存储;便于多实例共享。
2.7 Kubernetes(Helm)部署
helm repo add harbor https://helm.goharbor.io
helm repo update
helm search repo harbor/harbor --versions | head
cat > values.yaml <<'EOF'
expose:
type: ingress
tls:
enabled: true
certSource: secret
secret:
secretName: harbor-tls
ingress:
hosts:
core: harbor.internal
className: nginx
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
externalURL: https://harbor.internal
persistence:
enabled: true
resourcePolicy: keep
persistentVolumeClaim:
registry:
size: 500Gi
storageClass: fast-ssd
database:
size: 50Gi
storageClass: fast-ssd
redis:
size: 10Gi
jobservice:
jobLog:
size: 10Gi
# 生产建议用外部 PG/Redis
database:
type: external
external:
host: pg.internal
port: "5432"
username: harbor
password: <pwd>
coreDatabase: registry
sslmode: require
redis:
type: external
external:
addr: redis.internal:6379
password: <pwd>
trivy:
enabled: true
offlineScan: false
core:
replicas: 2
portal:
replicas: 2
jobservice:
replicas: 2
registry:
replicas: 2
EOF
helm install harbor harbor/harbor -n harbor --create-namespace -f values.yaml
kubectl -n harbor get pod
kubectl -n harbor get ingress
2.8 部署后验收
# 1) 容器状态(都应为 healthy)
docker compose ps
# 2) 健康检查
curl -sk https://harbor.internal/api/v2.0/health | jq
curl -sk https://harbor.internal/api/v2.0/ping
# 3) 版本
curl -sk https://harbor.internal/api/v2.0/systeminfo | jq '.harbor_version'
# 4) 登录
docker login harbor.internal -u admin
# 5) 推拉测试
docker pull nginx:1.25
docker tag nginx:1.25 harbor.internal/library/nginx:1.25
docker push harbor.internal/library/nginx:1.25
docker rmi harbor.internal/library/nginx:1.25
docker pull harbor.internal/library/nginx:1.25
# 6) 扫描(需 trivy)
# 在 UI 里点扫描,或 API:
curl -sk -u admin:<pwd> -X POST \
"https://harbor.internal/api/v2.0/projects/library/repositories/nginx/artifacts/1.25/scan"
curl -sk -u admin:<pwd> \
"https://harbor.internal/api/v2.0/projects/library/repositories/nginx/artifacts/1.25/additions/vulnerabilities" | jq '.total'
# 7) 磁盘
du -sh /data/harbor
du -sh /data/harbor/registry
3. harbor.yml 配置详解
3.1 核心项
| 配置 | 说明 | 生产建议 |
|---|---|---|
hostname |
对外域名 | 必须域名,不能 IP |
http.port |
HTTP 端口 | 80,配合重定向 |
https.port |
HTTPS 端口 | 443,必须启用 |
https.certificate/private_key |
证书路径 | 用正式 CA 或内部 CA |
harbor_admin_password |
初始 admin 密码 | 首登立即改,或改用 OIDC |
database.password |
内置 PG 密码 | 强密码 |
data_volume |
数据根目录 | 独立大容量盘 |
_version |
配置版本 | 不要手改(升级用) |
3.2 扫描(trivy)
trivy:
ignore_unfixed: false # true 则只显示有修复版本的 CVE
skip_update: false # true 则不自动更新漏洞库(离线环境设 true)
skip_java_db_update: false
offline_scan: false # 离线扫描模式
security_check: vuln # vuln / config / secret(2.11+)
insecure: false # 连外部扫描器时跳过 TLS 校验
timeout: 5m0s
3.3 日志
log:
level: info # debug / info / warning / error / fatal
local:
rotate_count: 50 # 保留文件数
rotate_size: 200M # 单文件大小
location: /data/harbor/logs
# 日志文件
ls -lh /data/harbor/logs/
tail -f /data/harbor/logs/harbor-core.log
tail -f /data/harbor/logs/jobservice/jobservice.log
tail -f /data/harbor/logs/registry/registry.log
3.4 上传清理
upload_purging:
enabled: true
age: 168h # 清理 7 天前的未完成上传
interval: 24h
dryrun: false
重要:中断的 push 会在
/data/harbor/registry/uploads留残片,不清会慢慢吃满磁盘。这个开关必须开。
3.5 缓存与代理
cache:
enabled: true
expire_hours: 24
proxy:
http_proxy: http://proxy.internal:3128
https_proxy: http://proxy.internal:3128
no_proxy: 127.0.0.1,localhost,.internal,pg.internal,redis.internal
components:
- core
- jobservice
- trivy
3.6 配置生效
cd /opt/harbor
# 改了 harbor.yml 后:重新生成配置 + 重启(不重装)
./prepare
docker compose up -d
# 注意:不要用 ./install.sh 改配置(会重建部分状态)
# install.sh 只用于首次安装和版本升级
坑点:
./prepare会根据harbor.yml重新生成common/config/*,直接改common/config下的文件会在下次prepare时被覆盖。
4. 客户端接入
4.1 Docker
# 1) 信任 CA(每台客户端都要做)
mkdir -p /etc/docker/certs.d/harbor.internal
cp harbor.crt /etc/docker/certs.d/harbor.internal/ca.crt
systemctl restart docker
# 或系统级信任
cp harbor.crt /etc/pki/ca-trust/source/anchors/ && update-ca-trust # RHEL
cp harbor.crt /usr/local/share/ca-certificates/ && update-ca-certificates # Debian
# 2) 登录
docker login harbor.internal -u admin
docker login harbor.internal -u 'robot$ci' -p '<token>'
docker login harbor.internal -u admin --password-stdin < ~/.pass
# 3) 推拉
docker tag nginx:1.25 harbor.internal/library/nginx:1.25
docker push harbor.internal/library/nginx:1.25
docker pull harbor.internal/library/nginx:1.25
# 4) 退出
docker logout harbor.internal
# 查看凭据保存位置(base64 明文,注意权限)
cat ~/.docker/config.json
chmod 600 ~/.docker/config.json
4.2 Podman
# Podman 用系统信任或 registries.d
cp harbor.crt /etc/pki/ca-trust/source/anchors/ && update-ca-trust
podman login harbor.internal -u admin
podman tag nginx:1.25 harbor.internal/library/nginx:1.25
podman push harbor.internal/library/nginx:1.25
# HTTP 仓库(不推荐,仅测试)
# /etc/containers/registries.conf
# [[registry]]
# prefix = "harbor.internal"
# location = "harbor.internal:80"
# insecure = true
4.3 containerd(k8s 节点)
mkdir -p /etc/containerd/certs.d/harbor.internal
cp harbor.crt /etc/containerd/certs.d/harbor.internal/ca.crt
cat >/etc/containerd/certs.d/harbor.internal/hosts.toml <<'EOF'
server = "https://harbor.internal"
[host."https://harbor.internal"]
capabilities = ["pull", "resolve", "push"]
ca = "/etc/containerd/certs.d/harbor.internal/ca.crt"
EOF
# 确认 config.toml 开启了 config_path
grep -A2 'registry]' /etc/containerd/config.toml
# config_path = "/etc/containerd/certs.d"
systemctl restart containerd
crictl pull harbor.internal/library/nginx:1.25
4.4 Kubernetes 拉取私有镜像
kubectl create secret docker-registry harbor-cred \
--docker-server=harbor.internal \
--docker-username='robot$ci' \
--docker-password='<token>' \
-n prod
# 方式一:Deployment 显式引用
# spec.template.spec.imagePullSecrets:
# - name: harbor-cred
# 方式二:绑到 ServiceAccount(推荐,全局生效)
kubectl patch sa default -n prod -p '{"imagePullSecrets":[{"name":"harbor-cred"}]}'
# 验证
kubectl run test --image=harbor.internal/library/nginx:1.25 --restart=Never -n prod
kubectl get pod test -n prod
kubectl describe pod test -n prod | grep -A3 Events
kubectl delete pod test -n prod
4.5 node 级免密(不推荐)
# 把凭据放到节点(所有节点都要)
mkdir -p /root/.docker
cp ~/.docker/config.json /root/.docker/
# kubelet 以 root 运行,会读这个文件
# 缺点:凭据散落各节点,轮换麻烦。优先用 imagePullSecrets
5. 项目与 RBAC
5.1 项目(Project)
# API 创建项目
curl -sk -u admin:<pwd> -X POST https://harbor.internal/api/v2.0/projects \
-H 'Content-Type: application/json' \
-d '{
"project_name": "web",
"public": false,
"storage_limit": 107374182400,
"metadata": { "public": "false", "auto_scan": "true", "prevent_vul": "true", "severity": "high" }
}' | jq
# 列出
curl -sk -u admin:<pwd> 'https://harbor.internal/api/v2.0/projects?page=1&page_size=50' | jq '.[].name'
# 删除(★ 会删项目下所有镜像)
curl -sk -u admin:<pwd> -X DELETE https://harbor.internal/api/v2.0/projects/web
| 元数据 | 作用 |
|---|---|
public |
公开项目(匿名可 pull) |
auto_scan |
push 后自动扫描 |
prevent_vul |
有高危漏洞时阻止 pull |
severity |
阻止阈值(critical/high/medium/low) |
reuse_sys_cve_allowlist |
使用系统级 CVE 白名单 |
retention_id |
绑定的保留策略 |
5.2 角色模型
| 角色 | 权限 |
|---|---|
| Project Admin | 项目内全部(成员、策略、删除) |
| Maintainer | 推拉、扫描、复制、删镜像,不能改成员 |
| Developer | 推拉、读 |
| Guest | 只读(pull) |
| Limited Guest | 只读,不能看日志 |
| 系统管理员 | 全局 |
# 加成员
curl -sk -u admin:<pwd> -X POST \
https://harbor.internal/api/v2.0/projects/web/members \
-H 'Content-Type: application/json' \
-d '{
"role_id": 3,
"member_user": { "username": "alice" }
}'
# role_id: 1=ProjectAdmin 2=Maintainer 3=Developer 4=Guest 5=LimitedGuest
# 加用户组(LDAP/OIDC)
curl -sk -u admin:<pwd> -X POST \
https://harbor.internal/api/v2.0/projects/web/members \
-H 'Content-Type: application/json' \
-d '{"role_id": 3, "member_group": {"group_name": "dev-team", "group_type": 1}}'
# 列成员
curl -sk -u admin:<pwd> https://harbor.internal/api/v2.0/projects/web/members | jq
5.3 Robot 账号(CI/CD 首选)
Robot 账号是项目级的机器身份,权限可控、可设过期、可限定 action。
# 创建 robot(项目级)
curl -sk -u admin:<pwd> -X POST \
https://harbor.internal/api/v2.0/projects/web/robots \
-H 'Content-Type: application/json' \
-d '{
"name": "ci-pusher",
"duration": 365,
"level": "project",
"permissions": [
{ "kind": "project", "namespace": "web", "access": [
{ "resource": "repository", "action": "push" },
{ "resource": "repository", "action": "pull" },
{ "resource": "artifact", "action": "read" }
]}
]
}' | jq
# 返回的 "secret" 只显示一次,务必保存
用户名格式:robot$web+ci-pusher(项目级)或 robot$ci-pusher(系统级)。
docker login harbor.internal -u 'robot$web+ci-pusher' -p '<secret>'
# 轮换:删旧建新
curl -sk -u admin:<pwd> https://harbor.internal/api/v2.0/projects/web/robots | jq '.[] | {id,name,expires_at}'
curl -sk -u admin:<pwd> -X DELETE https://harbor.internal/api/v2.0/projects/web/robots/<id>
5.4 配额
# 设置项目配额(字节)
curl -sk -u admin:<pwd> -X PUT \
"https://harbor.internal/api/v2.0/quotas/<quota-id>" \
-H 'Content-Type: application/json' \
-d '{"hard": {"storage": 107374182400}}'
# 查配额
curl -sk -u admin:<pwd> 'https://harbor.internal/api/v2.0/quotas?page_size=100' | jq '.'
5.5 代理缓存(Pull-through Cache)
用于加速公网镜像、隔离外网:
# 1) 建 registry endpoint(类型 docker-registry,指向 docker.io)
curl -sk -u admin:<pwd> -X POST https://harbor.internal/api/v2.0/registries \
-H 'Content-Type: application/json' \
-d '{
"name": "dockerhub-proxy",
"type": "docker-hub",
"url": "https://docker.io",
"access_key": "<user>",
"access_secret": "<pass>"
}'
# 2) 建项目时勾选 proxy cache 并指定该 endpoint
# 之后 docker pull harbor.internal/dockerhub-proxy/nginx:1.25 会按需缓存
代理缓存项目的镜像不占项目配额(由缓存清理策略管理),且只读——不能往代理缓存项目 push。
6. 镜像复制
6.1 使用场景
- 多机房/多集群同步镜像
- 从生产仓库同步到灾备
- 从公网拉取到内网(配合代理)
6.2 创建目标(Registry Endpoint)
curl -sk -u admin:<pwd> -X POST https://harbor.internal/api/v2.0/registries \
-H 'Content-Type: application/json' \
-d '{
"name": "dr-harbor",
"type": "harbor",
"url": "https://harbor-dr.internal",
"access_key": "admin",
"access_secret": "<pwd>",
"insecure": false,
"description": "灾备仓库"
}'
| type | 说明 |
|---|---|
harbor |
另一套 Harbor |
docker-hub |
Docker Hub |
docker-registry |
通用 registry v2 |
aws-ecr / azure-acr / gcr / quay |
云厂商 |
6.3 创建复制规则
curl -sk -u admin:<pwd> -X POST https://harbor.internal/api/v2.0/replication/policies \
-H 'Content-Type: application/json' \
-d '{
"name": "to-dr",
"src_registry": null,
"dest_registry": {"id": 1},
"dest_namespace": "mirror",
"dest_namespace_replace_count": -1,
"trigger": {"type": "event_based", "trigger_settings": {"cron": ""}},
"filters": [
{"type": "name", "value": "web/**"},
{"type": "tag", "value": "v*"},
{"type": "label", "value": "release"}
],
"deletion": false,
"override": true,
"enabled": true,
"speed_kb": 0,
"copy_by_chunk": false
}'
| trigger | 说明 |
|---|---|
event_based |
push 后立即触发(最常用) |
scheduled |
按 cron 定时(trigger_settings.cron) |
manual |
只手工触发 |
| 过滤类型 | 示例 |
|---|---|
name |
library/nginx(支持 ** 通配) |
tag |
v1.* |
label |
release |
resource |
image / artifact |
6.4 触发与查看
# 手工触发整个规则
curl -sk -u admin:<pwd> -X POST \
"https://harbor.internal/api/v2.0/replication/executions" \
-H 'Content-Type: application/json' \
-d '{"policy_id": 1}'
# 查看执行记录
curl -sk -u admin:<pwd> \
"https://harbor.internal/api/v2.0/replication/executions?policy_id=1&page_size=20" | jq '.'
# 状态:InProgress / Succeed / Failed / Stopped
# 查看任务明细(失败原因在这)
curl -sk -u admin:<pwd> \
"https://harbor.internal/api/v2.0/replication/executions/<exec_id>/tasks" | jq '.[] | {id,status,src_resource,dst_resource}'
6.5 复制排障
| 现象 | 原因 | 处理 |
|---|---|---|
Failed 且报 401 |
目标仓库凭据错 | 更新 endpoint 的 access_key/secret |
Failed 且报 x509 |
目标证书不受信 | 上传 CA 到 Harbor(配置 → 系统设置 → 证书)或 insecure: true |
| 只同步部分 tag | 过滤器不匹配 | 检查 filters |
| 大镜像超时 | 网络慢 | 开 copy_by_chunk;限速 speed_kb |
| 目标已存在同名 tag | override: false |
改为 true 或先清理 |
| 复制后目标无法 pull | 目标项目权限 | 检查目标项目是否 public / robot 权限 |
任务一直 InProgress |
jobservice 卡住 | 查 jobservice 日志,必要时重启 |
docker compose logs -f --tail=100 harbor-jobservice
docker compose logs -f --tail=100 harbor-core | grep -i replicat
7. 漏洞扫描
7.1 扫描架构
harbor-core ──创建扫描任务──▶ jobservice ──调用──▶ trivy-adapter
│
拉取镜像层
│
比对漏洞库
│
◀── 结果写回 artifact 的 addition ──────
7.2 扫描方式
| 方式 | 触发 | 说明 |
|---|---|---|
| 手工 | UI 点「扫描」或 API | 按需 |
| 推送时 | 项目元数据 auto_scan: true |
推荐 |
| 定时 | 项目 → 配置 → 扫描计划 | 定期复扫(漏洞库更新后旧结果会过时) |
# 手工扫描
curl -sk -u admin:<pwd> -X POST \
"https://harbor.internal/api/v2.0/projects/web/repositories/app/artifacts/1.0.0/scan"
# 查扫描状态
curl -sk -u admin:<pwd> \
"https://harbor.internal/api/v2.0/projects/web/repositories/app/artifacts/1.0.0" | jq '.scan_overview'
# 查漏洞明细
curl -sk -u admin:<pwd> \
"https://harbor.internal/api/v2.0/projects/web/repositories/app/artifacts/1.0.0/additions/vulnerabilities" \
| jq '.total, (.vulnerabilities[] | {id,severity,package,version,fix_version})'
# 查 SBOM(2.11+)
curl -sk -u admin:<pwd> \
"https://harbor.internal/api/v2.0/projects/web/repositories/app/artifacts/1.0.0/additions/sbom" | jq '.sbom.mediaType'
7.3 阻止不安全镜像
项目元数据:
| 键 | 值 | 效果 |
|---|---|---|
prevent_vul |
true |
有超过阈值漏洞时阻止 pull |
severity |
critical / high / medium / low |
阈值 |
auto_scan |
true |
push 即扫 |
curl -sk -u admin:<pwd> -X PUT \
"https://harbor.internal/api/v2.0/projects/web" \
-H 'Content-Type: application/json' \
-d '{"metadata": {"auto_scan": "true", "prevent_vul": "true", "severity": "high"}}'
注意:
prevent_vul只对新扫描生效,且未扫描的镜像默认放行。所以必须同时开auto_scan,否则策略形同虚设。
7.4 CVE 例外(白名单)
# 系统级白名单(2.10+)
curl -sk -u admin:<pwd> -X POST \
"https://harbor.internal/api/v2.0/system/CVEAllowlist" \
-H 'Content-Type: application/json' \
-d '{
"items": [
{"cve_id": "CVE-2024-12345", "expires_at": 1790000000}
]
}'
# 项目级(继承系统 + 自有)
curl -sk -u admin:<pwd> -X PUT \
"https://harbor.internal/api/v2.0/projects/web" \
-H 'Content-Type: application/json' \
-d '{"metadata": {"reuse_sys_cve_allowlist": "true"}}'
原则:白名单必须带
expires_at,且要有审批记录。永久白名单等于没有安全策略。
7.5 漏洞库更新
# trivy 自动更新(默认)
docker compose logs -f trivy-adapter | grep -i update
# 离线环境:手工导入漏洞库
# 1) 在有网机器上下载
# trivy image --download-db-only --db-repository <内部源>
# 2) 拷贝到 /data/harbor/trivy-adapter/trivy/db/
ls /data/harbor/trivy-adapter/trivy/db/
# 更新后需要重新扫描才能反映新漏洞
# 离线配置
trivy:
skip_update: true
offline_scan: true
7.6 扫描问题
| 现象 | 原因 | 处理 |
|---|---|---|
扫描一直 Pending |
jobservice 队列积压 / 挂了 | 查 jobservice 日志与并发数 |
Failed to analyze |
镜像层损坏 / 不支持格式 | 重新 push |
| 扫描结果为 0 | 漏洞库未更新 | 检查 skip_update、库时间 |
| 扫描很慢 | 镜像大 / 磁盘慢 | 加 max_job_workers、SSD |
| 内存溢出 | trivy 解析大镜像 | 提内存限制,或改用外部 scanner |
prevent_vul 不生效 |
未开 auto_scan / 镜像未扫 | 同时开 auto_scan |
8. 标签保留与垃圾回收
8.1 为什么需要
Harbor 删除 tag(UI 或 API)只是删元数据,底层 blob 还在。不跑 GC,磁盘空间不会释放。CI 每天推 100 个 tag,一个月就能撑爆。
8.2 保留策略(Retention)
# 创建规则:保留最近 10 个 tag,以及所有匹配 release-* 的
curl -sk -u admin:<pwd> -X POST \
"https://harbor.internal/api/v2.0/projects/web/retentions" \
-H 'Content-Type: application/json' \
-d '{
"algorithm": "or",
"rules": [
{
"id": 1,
"priority": 1,
"disabled": false,
"action": "retain",
"template": "latestPushedK",
"params": {"latestPushedK": 10},
"tag_selectors": [{"kind": "doublestar", "decoration": "matches", "pattern": "**"}],
"scope_selectors": {"repository": [{"kind": "doublestar", "decoration": "repoMatches", "pattern": "**"}]}
},
{
"id": 2,
"priority": 2,
"disabled": false,
"action": "retain",
"template": "nDaysSinceLastPush",
"params": {"nDaysSinceLastPush": 90},
"tag_selectors": [{"kind": "doublestar", "decoration": "matches", "pattern": "release-*"}],
"scope_selectors": {"repository": [{"kind": "doublestar", "decoration": "repoMatches", "pattern": "**"}]}
}
],
"trigger": {"kind": "Schedule", "settings": {"cron": "0 0 3 * * *"}},
"scope": {"level": "project", "ref": 1}
}'
| 模板 | 含义 |
|---|---|
latestPushedK |
保留最近 push 的 K 个 |
latestPulledN |
保留最近 pull 的 N 个 |
nDaysSinceLastPush |
保留 N 天内 push 的 |
nDaysSinceLastPull |
保留 N 天内 pull 的 |
always |
永远保留 |
algorithm:or(满足任一即保留)/ and(全部满足才保留)。
危险点:
algorithm: or+ 宽泛规则 = 一个都不删;and+ 严格规则 = 可能删掉正在用的。第一次上线先跑「模拟」(Harbor UI 支持 dry-run),确认清单再启用。
8.3 手工删除镜像
# 删 tag
curl -sk -u admin:<pwd> -X DELETE \
"https://harbor.internal/api/v2.0/projects/web/repositories/app/artifacts/1.0.0"
# 按 digest 删(推荐,tag 可重复指向同一 digest)
DIGEST=$(curl -sk -u admin:<pwd> \
"https://harbor.internal/api/v2.0/projects/web/repositories/app/artifacts/1.0.0" | jq -r '.digest')
curl -sk -u admin:<pwd> -X DELETE \
"https://harbor.internal/api/v2.0/projects/web/repositories/app/artifacts/${DIGEST}"
# 批量删(按 tag 名正则,注意 shell 转义)
curl -sk -u admin:<pwd> \
"https://harbor.internal/api/v2.0/projects/web/repositories/app/artifacts?page_size=100" \
| jq -r '.tags[]?.name' | grep -E '^ci-[0-9]+$' | while read t; do
curl -sk -u admin:<pwd> -X DELETE \
"https://harbor.internal/api/v2.0/projects/web/repositories/app/artifacts/${t}"
done
8.4 垃圾回收(GC)
# 方式一:API 触发(推荐,2.x)
curl -sk -u admin:<pwd> -X POST \
"https://harbor.internal/api/v2.0/system/gc/schedule" \
-H 'Content-Type: application/json' \
-d '{"schedule": {"type": "Manual"}, "parameters": {"delete_untagged": true, "dry_run": false}}'
# 查 GC 历史与状态
curl -sk -u admin:<pwd> "https://harbor.internal/api/v2.0/system/gc" | jq '.[-1]'
# 状态:Pending / Running / Success / Error
# 方式二:手工跑 registry GC(必须停 registry)
cd /opt/harbor
docker compose stop registry
docker run -it --name gc --rm --volumes-from registry \
goharbor/registry-photon:v2.15.4 garbage-collect \
--dry-run /etc/registry/config.yml
# 确认无误后去掉 --dry-run 再跑一次
docker compose start registry
GC 必须互斥:GC 运行时 registry 应只读或停止。在线跑 GC(API 方式)Harbor 会加只读锁,期间 push 会失败。安排在业务低峰。
8.5 空间回收完整流程
# 1) 看当前占用
du -sh /data/harbor/registry
du -sh /data/harbor/database
df -h /data
# 2) 清理策略:先删无用 tag(或让 retention 跑)
# 3) 触发 GC
curl -sk -u admin:<pwd> -X POST \
"https://harbor.internal/api/v2.0/system/gc/schedule" \
-H 'Content-Type: application/json' \
-d '{"schedule":{"type":"Manual"},"parameters":{"delete_untagged":true,"dry_run":false}}'
# 4) 等 GC 完成
watch -n5 'curl -sk -u admin:<pwd> https://harbor.internal/api/v2.0/system/gc | jq ".[-1] | {status, creation_time, update_time}"'
# 5) 复查占用
du -sh /data/harbor/registry
# 定时 GC(crontab,每周日凌晨 3 点)
# 0 3 * * 0 curl -sk -u admin:<pwd> -X POST https://harbor.internal/api/v2.0/system/gc/schedule \
# -H 'Content-Type: application/json' \
# -d '{"schedule":{"type":"Manual"},"parameters":{"delete_untagged":true}}' >/dev/null
8.6 上传残片清理
# 未完成的上传
du -sh /data/harbor/registry/uploads 2>/dev/null
find /data/harbor/registry/uploads -type f -mtime +7 | head
# 靠 harbor.yml 的 upload_purging 自动清(见 3.4)
# 手工清理(确认 registry 无活跃上传)
find /data/harbor/registry/uploads -type f -mtime +7 -delete
9. 备份与恢复
9.1 备份什么
| 对象 | 位置 | 丢失后果 |
|---|---|---|
| 元数据库(PostgreSQL) | 容器内 /var/lib/postgresql/data |
项目、用户、策略全丢 |
| 镜像数据(registry) | /data/harbor/registry |
镜像全丢 |
| 配置文件 | /opt/harbor/harbor.yml |
需重配 |
| 证书 | /opt/harbor/cert |
HTTPS 失效 |
| 密钥 | /data/harbor/secretkey |
加密数据无法解密 |
secretkey必须备份。丢失后数据库里的加密字段(如 registry 密码、robot secret)无法解密,等于重建。
9.2 全量备份(停服,最可靠)
#!/usr/bin/env bash
# /opt/scripts/harbor-backup.sh
set -euo pipefail
BK=/backup/harbor/$(date +%F)
mkdir -p "$BK"
cd /opt/harbor
# 1) 停服务(保证一致性)
docker compose stop
# 2) 数据(数据库 + registry + 密钥)
tar czf "$BK/harbor-data.tar.gz" -C /data harbor
tar czf "$BK/harbor-conf.tar.gz" -C /opt harbor/harbor.yml harbor/cert
# 3) 记录版本
docker compose images > "$BK/images.txt" 2>/dev/null || true
grep '_version' harbor.yml > "$BK/version.txt"
# 4) 起服务
docker compose start
echo "backup done: $BK"
ls -lh "$BK"
9.3 热备份(不停服,DB 单独 dump)
BK=/backup/harbor/$(date +%F-%H%M)
mkdir -p "$BK"
# 1) PostgreSQL dump(不锁表,一致性略弱但可用)
docker compose exec -T postgresql pg_dump -U postgres -d registry -Fc \
> "$BK/registry.dump"
# 2) registry 数据(用 rsync 硬链接快照,减少空间)
rsync -a --link-dest=/backup/harbor/latest/registry \
/data/harbor/registry/ "$BK/registry/"
# 3) 配置
cp /opt/harbor/harbor.yml /opt/harbor/cert/* "$BK/"
# 4) 密钥
cp /data/harbor/secretkey "$BK/"
# 5) 更新 latest 链接
ln -sfn "$BK" /backup/harbor/latest
热备份期间若有 push,registry 目录与 DB 可能不一致(镜像存在但 DB 无记录)。恢复后表现为"孤儿 blob",跑一次 GC 即可清理。
9.4 恢复
# 1) 装同版本 Harbor(版本必须一致)
cd /opt/harbor
docker compose down
# 2) 恢复配置
tar xzf /backup/harbor/2026-11-05/harbor-conf.tar.gz -C /
# 确认 harbor.yml 与备份时一致
# 3) 恢复数据
rm -rf /data/harbor
tar xzf /backup/harbor/2026-11-05/harbor-data.tar.gz -C /data
# 4) 起服务(用 prepare 重新生成配置,不重装)
./prepare
docker compose up -d
# 5) 验证
docker compose ps
curl -sk https://harbor.internal/api/v2.0/health
curl -sk -u admin:<pwd> https://harbor.internal/api/v2.0/projects | jq '.[].name'
docker pull harbor.internal/library/nginx:1.25
9.5 部分恢复(只恢复某个项目)
# 难点:Harbor 不支持项目级导入导出
# 做法:在新实例重建项目 + 用 skopeo 搬运镜像 + 重建策略
# 1) 旧实例导出镜像清单
curl -sk -u admin:<pwd> \
"https://harbor.internal/api/v2.0/projects/web/repositories?page_size=100" \
| jq -r '.[].name' > /tmp/repos.txt
# 2) 逐仓库搬运
while read repo; do
curl -sk -u admin:<pwd> \
"https://harbor.internal/api/v2.0/projects/web/repositories/${repo#web/}/artifacts?page_size=100" \
| jq -r '.tags[]?.name' | while read tag; do
skopeo copy \
--src-creds admin:<pwd> --dest-creds admin:<pwd2> \
--src-tls-verify=false --dest-tls-verify=false \
"docker://harbor.internal/${repo}:${tag}" \
"docker://harbor-new.internal/${repo}:${tag}"
done
done < /tmp/repos.txt
9.6 迁移到新机器
# 旧机
/opt/scripts/harbor-backup.sh
scp -r /backup/harbor/2026-11-05 root@new-host:/backup/harbor/
# 新机
# 1) 装 docker + compose
# 2) 装同版本 harbor(只需解压,不 install)
tar xzf harbor-offline-installer-v2.15.4.tgz -C /opt
cd /opt/harbor
# 3) 恢复配置与数据(同 9.4)
# 4) 改 hostname(若域名变了)
vim harbor.yml
./prepare
docker compose up -d
# 5) DNS 切换
# 6) 客户端更新 CA(若证书变了)
10. 升级
10.1 升级前检查
# 1) 当前版本
curl -sk https://harbor.internal/api/v2.0/systeminfo | jq '.harbor_version'
# 2) 确认升级路径(只能 n-2)
# 2.12 → 2.13 → 2.14 → 2.15
# 2.13 → 2.15(可直接,因为 2.15 是 2.13 的 n+2)
# 3) 读 release notes 的 breaking changes
# https://github.com/goharbor/harbor/releases
# 4) 备份(必须)
/opt/scripts/harbor-backup.sh
# 5) 确认磁盘余量(升级需临时空间)
df -h /data /opt
10.2 升级步骤
cd /opt/harbor
# 1) 停服务
docker compose down
# 2) 备份旧目录(回滚用)
mv /opt/harbor /opt/harbor.bak-$(date +%F)
# 3) 解压新版
tar xzf harbor-offline-installer-v2.15.4.tgz -C /opt
cd /opt/harbor
# 4) 迁移配置(关键:不能直接用旧 yml,字段会变)
cp /opt/harbor.bak-$(date +%F)/harbor.yml ./harbor.yml
# 对比新版模板,补齐新增字段
diff harbor.yml.tmpl harbor.yml
# 5) 执行升级安装
./install.sh --with-trivy
# 6) 验证
docker compose ps
curl -sk https://harbor.internal/api/v2.0/systeminfo | jq '.harbor_version'
curl -sk https://harbor.internal/api/v2.0/health
docker pull harbor.internal/library/nginx:1.25
重要:
install.sh在已有数据目录上执行 = 升级(会跑数据库 migration)。不要删/data/harbor。
10.3 升级后检查
# 1) 数据库 migration 是否成功
docker compose logs harbor-core | grep -iE 'migrat|error' | tail -20
# 2) 服务健康
docker compose ps
for c in $(docker compose ps -q); do docker inspect --format '{{.Name}} {{.State.Health.Status}}' $c; done
# 3) 登录与推拉
docker login harbor.internal -u admin
docker pull harbor.internal/library/nginx:1.25
# 4) 复制、扫描、GC 是否正常
curl -sk -u admin:<pwd> https://harbor.internal/api/v2.0/replication/policies | jq '.[].name'
curl -sk -u admin:<pwd> "https://harbor.internal/api/v2.0/projects?page_size=10" | jq '.[].name'
# 5) 审计日志
curl -sk -u admin:<pwd> "https://harbor.internal/api/v2.0/audit-logs?page_size=10" | jq '.[] | {op_time,username,operation}'
10.4 回滚
cd /opt
docker compose -f harbor/docker-compose.yml down 2>/dev/null || true
# 恢复旧目录
mv harbor harbor.failed-$(date +%F)
mv harbor.bak-2026-11-05 harbor
cd harbor
# 恢复数据(升级已改过数据库结构,必须回到备份的数据)
rm -rf /data/harbor
tar xzf /backup/harbor/2026-11-05/harbor-data.tar.gz -C /data
./prepare
docker compose up -d
curl -sk https://harbor.internal/api/v2.0/systeminfo | jq '.harbor_version'
回滚的代价:升级后到回滚之间的数据(新推的镜像、新加的用户)会全部丢失。所以升级要选在窗口期,且升级后立即验证。
10.5 Helm 方式升级
helm repo update
helm search repo harbor/harbor --versions | head -3
# 先 dry-run 看变更
helm upgrade harbor harbor/harbor -n harbor -f values.yaml --dry-run
# 执行
helm upgrade harbor harbor/harbor -n harbor -f values.yaml
# 回滚
helm history harbor -n harbor
helm rollback harbor <revision> -n harbor
11. API 运维
11.1 认证
# Basic(简单脚本用)
curl -sk -u admin:<pwd> https://harbor.internal/api/v2.0/projects
# Session(浏览器行为)
curl -sk -i -X POST https://harbor.internal/c/login \
-d 'principal=admin&password=<pwd>' -c /tmp/harbor-cookie
curl -sk -b /tmp/harbor-cookie https://harbor.internal/api/v2.0/projects
# Robot 账号(推荐自动化用)
curl -sk -u 'robot$ci-pusher:<secret>' https://harbor.internal/api/v2.0/projects
11.2 常用端点
| 端点 | 方法 | 作用 |
|---|---|---|
/api/v2.0/health |
GET | 健康检查 |
/api/v2.0/systeminfo |
GET | 版本、存储、认证方式 |
/api/v2.0/ping |
GET | 存活 |
/api/v2.0/projects |
GET/POST | 项目列表/创建 |
/api/v2.0/projects/{name} |
GET/PUT/DELETE | 项目详情/更新/删除 |
/api/v2.0/projects/{name}/repositories |
GET | 仓库列表 |
/api/v2.0/projects/{p}/repositories/{r}/artifacts |
GET | artifact 列表 |
.../artifacts/{ref} |
GET/DELETE | 查/删 artifact |
.../artifacts/{ref}/scan |
POST | 触发扫描 |
.../artifacts/{ref}/additions/vulnerabilities |
GET | 漏洞 |
.../artifacts/{ref}/additions/sbom |
GET | SBOM |
/api/v2.0/registries |
GET/POST | 复制目标 |
/api/v2.0/replication/policies |
GET/POST | 复制规则 |
/api/v2.0/replication/executions |
GET/POST | 复制执行 |
/api/v2.0/system/gc |
GET | GC 历史 |
/api/v2.0/system/gc/schedule |
POST | 触发 GC |
/api/v2.0/quotas |
GET/PUT | 配额 |
/api/v2.0/users |
GET/POST | 用户 |
/api/v2.0/audit-logs |
GET | 审计日志 |
/api/v2.0/statistics |
GET | 统计 |
11.3 实战脚本
# 统计各项目镜像数量与大小
for p in $(curl -sk -u admin:<pwd> 'https://harbor.internal/api/v2.0/projects?page_size=100' | jq -r '.[].name'); do
cnt=$(curl -sk -u admin:<pwd> \
"https://harbor.internal/api/v2.0/projects/$p/repositories?page_size=1000" | jq 'length')
echo "$p: $cnt repos"
done
# 找最大的 20 个 artifact
curl -sk -u admin:<pwd> \
"https://harbor.internal/api/v2.0/projects/web/repositories/app/artifacts?page_size=100&with_tag=true" \
| jq -r '.[] | "\(.size) \(.tags[0].name // .digest)"' | sort -rn | head -20
# 批量给所有项目开自动扫描
for id in $(curl -sk -u admin:<pwd> 'https://harbor.internal/api/v2.0/projects?page_size=100' | jq -r '.[].project_id'); do
curl -sk -u admin:<pwd> -X PUT "https://harbor.internal/api/v2.0/projects/$id" \
-H 'Content-Type: application/json' \
-d '{"metadata":{"auto_scan":"true"}}' >/dev/null && echo "project $id ok"
done
# 导出所有项目的镜像清单(盘点/审计)
for p in $(curl -sk -u admin:<pwd> 'https://harbor.internal/api/v2.0/projects?page_size=100' | jq -r '.[].name'); do
curl -sk -u admin:<pwd> "https://harbor.internal/api/v2.0/projects/$p/repositories?page_size=1000" \
| jq -r --arg p "$p" '.[] | "\($p)/\(.name)"'
done > /backup/harbor-repos.txt
11.4 Registry v2 API(原生,绕过 Harbor 元数据)
# 列仓库
curl -sk -u admin:<pwd> https://harbor.internal/v2/_catalog
# 列 tag
curl -sk -u admin:<pwd> https://harbor.internal/v2/library/nginx/tags/list
# 取 manifest 与 digest
curl -sk -u admin:<pwd> -I \
-H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \
https://harbor.internal/v2/library/nginx/manifests/1.25
# 删 manifest(Harbor 侧建议用 API,原生删除不会更新元数据)
curl -sk -u admin:<pwd> -X DELETE \
https://harbor.internal/v2/library/nginx/manifests/sha256:xxx
不要混用:用 registry 原生 API 删的 tag,Harbor 数据库里仍认为存在(UI 显示但拉不到)。删除统一走 Harbor API。
12. 日志与监控
12.1 日志位置
ls -lh /data/harbor/logs/
tail -f /data/harbor/logs/harbor-core.log
tail -f /data/harbor/logs/harbor-portal.log
tail -f /data/harbor/logs/jobservice/jobservice.log
tail -f /data/harbor/logs/registry/registry.log
tail -f /data/harbor/logs/postgresql/postgresql.log
tail -f /data/harbor/logs/redis/redis.log
# 容器 stdout
docker compose logs -f --tail=200 harbor-core
docker compose logs --since 10m harbor-jobservice | grep -i error
调整日志级别:
log:
level: debug # 排障用,日志量很大
./prepare && docker compose up -d
12.2 指标
# Harbor 自带 exporter(端口 9090,内部)
curl -sk https://harbor.internal/metrics 2>/dev/null | head
# 或内部直连
docker compose exec harbor-core curl -s localhost:9090/metrics | head -30
关键指标:
| 指标 | 含义 |
|---|---|
harbor_project_total |
项目数 |
harbor_repo_total |
仓库数 |
harbor_artifact_total |
artifact 数 |
harbor_project_quota_usage_byte |
项目配额使用 |
harbor_artifact_pulled |
拉取次数 |
harbor_jobservice_task_total |
任务数 |
harbor_jobservice_task_fail_total |
任务失败数 |
# Prometheus 抓取配置
scrape_configs:
- job_name: harbor
scheme: https
tls_config:
insecure_skip_verify: true
static_configs:
- targets: ['harbor.internal']
metrics_path: /metrics
basic_auth:
username: admin
password: <pwd>
12.3 审计日志
# API
curl -sk -u admin:<pwd> \
"https://harbor.internal/api/v2.0/audit-logs?page_size=50&q=operation=delete" | jq
# 查某人操作
curl -sk -u admin:<pwd> \
"https://harbor.internal/api/v2.0/audit-logs?q=username%3Dalice&page_size=100" | jq
# 时间范围(q 用 URL 编码)
curl -sk -u admin:<pwd> \
"https://harbor.internal/api/v2.0/audit-logs?q=op_time%3D%5B2026-11-01~2026-11-05%5D" | jq
12.4 巡检脚本
#!/usr/bin/env bash
set -uo pipefail
H=https://harbor.internal
U=admin
P='<pwd>'
echo "===== harbor check $(date '+%F %T') ====="
echo "--- 版本"; curl -sk -u $U:$P $H/api/v2.0/systeminfo | jq -r '.harbor_version'
echo "--- 健康"; curl -sk $H/api/v2.0/health | jq -c '{status, components:[.components[]|{name,status}]}'
echo "--- 容器"; cd /opt/harbor && docker compose ps --format 'table {{.Name}}\t{{.Status}}'
echo "--- 磁盘"; df -h /data | tail -1
echo "--- 数据占用"; du -sh /data/harbor/registry /data/harbor/database
echo "--- 项目数"; curl -sk -u $U:$P "$H/api/v2.0/projects?page_size=1" -I | grep -i 'X-Total-Count'
echo "--- 失败任务"
curl -sk -u $U:$P "$H/api/v2.0/replication/executions?page_size=20" \
| jq -r '.[] | select(.status=="Failed") | "\(.id) \(.policy_id) \(.status_text)"' | head
echo "--- 最近 GC"
curl -sk -u $U:$P "$H/api/v2.0/system/gc" | jq -r '.[-1] | "\(.status) \(.creation_time)"'
echo "--- 大日志"
find /data/harbor/logs -name '*.log' -size +200M -printf '%s\t%p\n' 2>/dev/null
13. 故障速查
13.1 部署与启动
| 现象 | 排查 | 处理 |
|---|---|---|
install.sh 报端口占用 |
ss -lntp | grep -E ':80|:443' |
停占位服务或改端口 |
| 容器反复重启 | docker compose logs <svc> |
多为 DB/Redis 连不上 |
harbor-core 起不来 |
docker compose logs harbor-core |
数据库密码错、migration 失败 |
| 页面 502 | docker compose ps |
后端容器挂了 |
| 页面白屏 | 浏览器控制台、portal 日志 | portal 镜像版本不匹配 |
permission denied 写 /data/harbor |
目录属主/SELinux | chown -R 10000:10000 /data/harbor;chcon -Rt container_file_t |
13.2 登录与证书
| 现象 | 原因 | 处理 |
|---|---|---|
x509: certificate signed by unknown authority |
客户端不信任 CA | 配 certs.d 或系统信任 |
x509: certificate is valid for X, not Y |
证书 SAN 不含访问域名 | 重签证书,加 SAN |
dial tcp ... connect: connection refused |
端口不通 | 查防火墙/端口映射 |
400 Bad Request 且是 HTTP 仓库 |
客户端按 HTTPS 访问 | 配 insecure-registry 或用 HTTPS |
| 登录成功但 push 401 | robot 权限不足 | 检查 robot 的 action |
denied: requested access to the resource is denied |
项目不存在/无权限 | 建项目、加成员 |
| 浏览器登录正常、CLI 失败 | 开了 OIDC 但 CLI 未用 robot | CI 用 robot 账号 |
13.3 推送与拉取
| 现象 | 原因 | 处理 |
|---|---|---|
push 报 413 Request Entity Too Large |
nginx client_max_body_size 太小 |
改 proxy 配置 |
| push 中断后重试失败 | uploads 残片 | 清 /data/harbor/registry/uploads |
| push 成功但 UI 看不到 | core 与 registry 不一致 | 重启 core;查 DB |
pull 报 manifest unknown |
tag 不存在或已删 | 查 artifact 列表 |
| pull 很慢 | 网络/磁盘/无缓存 | 就近部署、加缓存层 |
no space left on device |
磁盘满 | 清 tag + GC(见 8.5) |
| 大镜像 push 超时 | nginx/registry 超时 | 调 proxy_read_timeout、registry timeout |
# docker-compose.override.yml 调 nginx(若用自带 proxy)
services:
proxy:
volumes:
- ./common/config/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
# nginx.conf 关键项
client_max_body_size 0; # 0 = 不限制
proxy_read_timeout 900s;
proxy_send_timeout 900s;
proxy_request_buffering off;
13.4 数据库与 Redis
| 现象 | 排查 | 处理 |
|---|---|---|
harbor-core 报 pq: password authentication failed |
DB 密码不一致 | 改 harbor.yml 的 database.password,./prepare 重启 |
too many connections |
连接池/并发过大 | 调 max_open_conns,查慢查询 |
| DB 磁盘满 | du -sh /data/harbor/database |
清审计日志(保留策略)、扩盘 |
| Redis 连不上 | docker compose logs redis |
检查密码、内存上限 |
| 登录变慢/会话丢失 | Redis 内存满(eviction) | 提内存或设 maxmemory-policy |
jobservice 任务不执行 |
Redis 队列异常 | 重启 jobservice + redis |
13.5 复制与扫描
| 现象 | 排查 | 处理 |
|---|---|---|
复制一直 InProgress |
jobservice 卡住 | 重启 jobservice |
复制 Failed 401/x509 |
目标凭据/证书 | 更新 endpoint |
扫描一直 Pending |
队列积压 | 加 max_job_workers |
扫描 Failed |
镜像层问题/漏洞库损坏 | 重推镜像;重建 trivy db |
| 扫描 0 结果 | 漏洞库未更新 | 检查 skip_update |
prevent_vul 不拦 |
未扫描 | 开 auto_scan |
13.6 GC 与空间
| 现象 | 原因 | 处理 |
|---|---|---|
| 删了 tag 空间没降 | 未跑 GC | 触发 GC(见 8.4) |
| GC 卡在 Running | registry 有活跃连接 | 低峰执行;必要时重启 |
| GC 后镜像拉不到 | GC 误删(并发 push) | 从备份恢复;GC 期间禁止 push |
| 磁盘持续增长 | uploads 残片 / 日志 | 开 upload_purging;日志轮转 |
du 与 UI 统计不符 |
UI 按 DB 算,含引用但未 GC 的 blob | 正常,GC 后一致 |
13.7 快速定位命令
cd /opt/harbor
# 全量状态
docker compose ps
docker compose logs --tail=50 harbor-core
docker compose logs --tail=50 harbor-jobservice
docker compose logs --tail=50 registry
docker compose logs --tail=50 postgresql
# 健康
curl -sk https://harbor.internal/api/v2.0/health | jq
curl -sk https://harbor.internal/api/v2.0/ping
# 端口
ss -lntp | grep -E ':80|:443'
# 磁盘
df -h /data
du -sh /data/harbor/*
# DB 连通(容器内)
docker compose exec postgresql pg_isready -U postgres
docker compose exec postgresql psql -U postgres -d registry -c '\dt' | head
# Redis
docker compose exec redis redis-cli ping
14. 卸载
卸载前:① 所有镜像已迁移或备份;② 客户端已切到新仓库;③ 数据已全量备份(见 9.2)。
14.1 标准卸载
cd /opt/harbor
# 1) 停并删容器(保留数据)
docker compose down
# 2) 备份后再删数据(★ 不可逆)
# /opt/scripts/harbor-backup.sh ← 先执行
rm -rf /data/harbor
# 3) 删程序目录
rm -rf /opt/harbor
# 4) 删镜像
docker images | grep -E 'goharbor|harbor' | awk '{print $1":"$2}' | xargs -r docker rmi
# 5) 删客户端信任与登录态
rm -rf /etc/docker/certs.d/harbor.internal
rm -rf /etc/containerd/certs.d/harbor.internal
docker logout harbor.internal
# 6) 删 k8s 里的 imagePullSecret(若有)
kubectl delete secret harbor-cred -n prod --ignore-not-found
14.2 Helm 卸载
helm uninstall harbor -n harbor
kubectl delete ns harbor
# PVC 通常不会自动删(resourcePolicy: keep)
kubectl get pvc -n harbor
kubectl delete pvc --all -n harbor # ★ 数据会丢
# 删 CRD(若有)
kubectl get crd | grep harbor
14.3 只清数据(重置)
cd /opt/harbor
docker compose down
rm -rf /data/harbor
mkdir -p /data/harbor
./prepare
docker compose up -d
# 相当于全新安装,admin 密码回到 harbor.yml 中的值
14.4 卸载后验证
| 检查 | 命令 | 期望 |
|---|---|---|
| 容器 | docker ps -a | grep -E 'harbor|goharbor' |
无 |
| 端口 | ss -lntp | grep -E ':80|:443' |
无(或非 Harbor) |
| 数据 | ls /data/harbor |
不存在 |
| 程序 | ls /opt/harbor |
不存在 |
| 镜像 | docker images | grep goharbor |
无 |
| 网络 | docker network ls | grep harbor |
无 |
# compose 创建的网络可能残留
docker network rm harbor_harbor 2>/dev/null
15. 附录
15.1 关键路径
| 路径 | 内容 |
|---|---|
/opt/harbor/harbor.yml |
主配置 |
/opt/harbor/docker-compose.yml |
编排文件(prepare 生成) |
/opt/harbor/common/config/ |
各组件配置(prepare 生成,勿直改) |
/opt/harbor/cert/ |
证书 |
/data/harbor/ |
数据根(data_volume) |
/data/harbor/registry/ |
镜像 blob |
/data/harbor/registry/uploads/ |
未完成上传残片 |
/data/harbor/database/ |
PostgreSQL 数据 |
/data/harbor/redis/ |
Redis 数据 |
/data/harbor/secretkey |
加密密钥(必须备份) |
/data/harbor/logs/ |
各组件日志 |
/data/harbor/trivy-adapter/trivy/db/ |
漏洞库 |
/data/harbor/jobservice/jobs/ |
任务日志 |
15.2 端口
| 端口 | 组件 | 是否对外 |
|---|---|---|
| 80 | proxy(HTTP) | 是(建议重定向 443) |
| 443 | proxy(HTTPS) | 是 |
| 4443 | core(HTTPS 直连) | 否 |
| 5432 | PostgreSQL | 否 |
| 6379 | Redis | 否 |
| 5000 | registry | 否 |
| 8080 | jobservice | 否 |
| 9090 | exporter/metrics | 否(给 Prometheus) |
15.3 默认账号
| 项 | 值 |
|---|---|
| 用户名 | admin |
| 初始密码 | Harbor12345(或 harbor.yml 中的 harbor_admin_password) |
| Robot 用户名格式 | robot$<project>+<name> 或 robot$<name> |
首次登录必须改密码。生产建议接 OIDC/LDAP,admin 只保留应急用。
15.4 健康检查与常用 curl
# 健康
curl -sk https://harbor.internal/api/v2.0/health | jq
# {
# "status": "healthy",
# "components": [
# {"name": "core", "status": "healthy"},
# {"name": "database", "status": "healthy"},
# {"name": "jobservice", "status": "healthy"},
# {"name": "portal", "status": "healthy"},
# {"name": "redis", "status": "healthy"},
# {"name": "registry", "status": "healthy"},
# {"name": "registryctl", "status": "healthy"}
# ]
# }
# 存活
curl -sk https://harbor.internal/api/v2.0/ping
# 版本与配置
curl -sk https://harbor.internal/api/v2.0/systeminfo | jq '{
harbor_version, auth_mode, registry_url, project_creation_restriction,
has_ca_root, with_trivy, with_chartmuseum, read_only
}'
# 统计
curl -sk -u admin:<pwd> https://harbor.internal/api/v2.0/statistics | jq
15.5 容量规划参考
| 场景 | 镜像数 | 日 push 数 | 建议存储 | GC 频率 |
|---|---|---|---|---|
| 小团队(<20 人) | < 500 | < 50 | 500G | 每月 |
| 中型(20~100 人) | 500~5000 | 50~500 | 2T | 每两周 |
| 大型(>100 人) | > 5000 | > 500 | 10T+ / 对象存储 | 每周 |
经验值:单个 Java 镜像 300600MB,Node 镜像 200400MB,Go 静态镜像 20~80MB。按 tag 数量 × 平均大小 × 1.3(未 GC 的冗余)估算。
15.6 与其他手册的配合
- Docker 客户端配置与证书:
Docker-常用运维指令手册.md - containerd 节点信任配置:
containerd-常用运维指令手册.md - k8s 拉取私有镜像(imagePullSecrets):
Kubernetes-常用运维指令手册.md - Podman 登录与镜像搬运:
Podman-常用运维指令手册.md(skopeo用法) - 宿主磁盘与防火墙:
CentOS-7-10-常规运维指令手册.md
一句话总结:Harbor 运维的核心是空间治理——保留策略(retention)决定删什么,GC 决定真正释放。两者必须配套,且 GC 期间不能 push。其次是证书链——客户端、containerd、k8s 三处都要信任 CA,且 hostname 必须是域名不是 IP。把这两件事做对,Harbor 基本不需要日常干预。