私有镜像仓库搭建
私有镜像仓库搭建
写在前面
前面所有文章默认你从 Docker Hub 拉镜像,但生产环境绝不应该用 Docker Hub 做内部分发——网速慢、敏感镜像外泄风险、没有审计。本篇会从最简单的 Docker Registry 入手,逐步升级到 Harbor,最后讲我们迁移 Harbor 的真实经历。
一、为什么需要私有仓库
1.1 直接用 Docker Hub 不行吗?
| 维度 | Docker Hub | 自建私有仓库 |
|---|---|---|
| 速度 | 受公网带宽影响,国内更慢 | 内网拉取,几秒就到 |
| 安全 | 内网业务镜像推到公网 | 完全在内网,不外泄 |
| 审计 | 没有 | 完整的拉取/推送日志 |
| 配额 | 限速(匿名 100次/6h,免费账户 200次/6h) | 无限 |
| 高可用 | 依赖 Docker | 自己保证 |
我们公司迁移的第一动力是速度——从 Docker Hub 拉一个 1GB 镜像要 20 分钟,从 Harbor 拉只要 30 秒。
1.2 选型对比
| 方案 | 优点 | 缺点 | 适合 |
|---|---|---|---|
| Docker Registry | 简单、官方 | 无 UI、无鉴权 | 个人 / 测试 |
| Harbor | 功能完整、企业级 | 资源占用大 | 公司生产 |
| Quay | 安全扫描强 | 收费 | 大企业 |
| Nexus/Artifactory | 通用制品管理 | 重 | 已有这些工具 |
Harbor 是绝大多数公司的最优解,本篇重点讲它。
二、最简方案:Docker Registry
先用 5 分钟起一个 Registry 感受一下:
# compose.yaml
services:
registry:
image: registry:2
restart: unless-stopped
ports:
- "5000:5000"
volumes:
- registry-data:/var/lib/registry
volumes:
registry-data:
docker compose up -d
测试:
# 打个 tag
docker tag nginx:alpine localhost:5000/my-nginx:v1
# 推送
docker push localhost:5000/my-nginx:v1
# 拉取
docker pull localhost:5000/my-nginx:v1
# 浏览 API
curl http://localhost:5000/v2/_catalog
# {"repositories":["my-nginx"]}
没有鉴权,任何人都能 push/pull——只能在内网用,或者加上一层网络隔离。
2.1 加 TLS
services:
registry:
image: registry:2
ports:
- "5000:5000"
volumes:
- registry-data:/var/lib/registry
- ./certs:/certs:ro
environment:
REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt
REGISTRY_HTTP_TLS_KEY: /certs/domain.key
或者放在 Nginx 后面(生产推荐)。
2.2 加基础认证
# 创建密码文件
mkdir -p auth
docker run --rm --entrypoint htpasswd \
httpd:2 -Bbn admin yourpassword > auth/htpasswd
services:
registry:
image: registry:2
ports:
- "5000:5000"
volumes:
- registry-data:/var/lib/registry
- ./auth:/auth:ro
environment:
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
登录:
docker login localhost:5000
# Username: admin
# Password: ******
三、生产方案:Harbor
3.1 核心特性
Harbor 在 Registry 基础上加了:
- Web UI:可视化镜像管理
- 基于角色的访问控制(RBAC):项目 / 用户 / 角色
- 镜像复制:多 Harbor 之间同步
- 漏洞扫描:Trivy 集成
- 镜像签名:Cosign / Notary
- 审计日志:谁什么时候 push/pull 了什么
- 垃圾回收(GC):清理无 tag 的层,节省磁盘
- Helm Chart 仓库:顺便管 K8s 的 chart
3.2 部署方式
方式 1:Harbor 官方 compose(单机)
# 下载离线安装包(自带所有依赖镜像)
wget https://github.com/goharbor/harbor/releases/download/v2.10.0/harbor-offline-installer-v2.10.0.tgz
tar xzf harbor-offline-installer-v2.10.0.tgz
cd harbor
# 编辑配置
cp harbor.yml.tmpl harbor.yml
vi harbor.yml
harbor.yml 关键配置:
hostname: harbor.company.com
http:
port: 80
https:
port: 443
certificate: /etc/harbor/certs/harbor.crt
private_key: /etc/harbor/certs/harbor.key
harbor_admin_password: Harbor12345 # 首次登录后改掉
database:
password: root123
data_volume: /data/harbor
trivy:
ignore_unfixed: true
skip_update: true
offline_scan: false
安装:
sudo ./install.sh
方式 2:K8s Helm(集群)
helm repo add harbor https://helm.goharbor.io
helm install harbor harbor/harbor \
--namespace harbor \
--create-namespace \
--set expose.ingress.hosts.core=harbor.company.com
3.3 首次使用
# 配置 Docker 信任私有仓库(自签名证书)
mkdir -p /etc/docker/certs.d/harbor.company.com
cp harbor.crt /etc/docker/certs.d/harbor.company.com/ca.crt
# 重启 Docker
systemctl restart docker
# 登录
docker login harbor.company.com
# Username: admin
# Password: Harbor12345
# 创建项目(在 Web UI 里更直观)
# Project -> New Project -> "myproject" (私有)
推送镜像:
docker tag myapp:v1.0 harbor.company.com/myproject/myapp:v1.0
docker push harbor.company.com/myproject/myapp:v1.0
3.4 RBAC 配置
Harbor 的权限模型:
- 系统角色:超级管理员、安全管理员
- 项目角色:
- 项目管理员:完整权限
- 维护人员:push,不能改成员
- 开发者:push/pull
- 访客:只能 pull
- 受限访客:只能 pull 自己被授权的镜像
实战中我们的分配:
- 业务开发 → 项目开发者
- 运维 → 项目管理员
- QA → 受限访客(只 pull 测试用镜像)
- 外部合作方 → 受限访客(只能 pull 自己用的镜像)
3.5 镜像复制(跨 Harbor 同步)
比如我们公司在多个数据中心,需要同步镜像:
# 在目标 Harbor 上:
# Registries -> New Endpoint -> 源 Harbor 信息
# Replication -> New Replication Rule:
# - Source: source-harbor
# - Destination: target-harbor
# - Trigger: 手动 / 定时 / 事件触发
实战坑:触发器用 “Event Based” 经常漏触发,推荐用 Scheduled(比如每小时同步一次)。
3.6 镜像签名
镜像签名能防止中间人篡改镜像。Harbor 用 Cosign(Sigstore 项目):
# 在 CI 里
cosign sign --key cosign.key harbor.company.com/myproject/myapp:v1.0
# 验证
cosign verify --key cosign.pub harbor.company.com/myproject/myapp:v1.0
生产中我们后来用 keyless 模式(绑定 OIDC 身份,不需要私钥):
cosign sign --keyless harbor.company.com/myproject/myapp:v1.0
Kubernetes 配合 kyverno / conftest 在部署时验证签名。
四、垃圾回收(GC)实战
4.1 镜像为什么会膨胀?
每次 CI 构建都会生成新的镜像并 push,但旧的镜像不会自动删除。时间一长,Harbor 磁盘爆炸。
我们曾经遇到:30 天没清理的 Harbor 占了 4TB 磁盘,大多是已经被覆盖的中间层。
4.2 手动 GC
Harbor 的 GC 不会自动跑,需要手动触发:
# 进入 Harbor 容器
docker exec -it harbor-core bash
# 触发 GC
cd /etc/core
python -c "
import os
os.environ['HARBOR_ADMIN_PASSWORD'] = 'xxx'
import core
core.gc_api.gc()
"
# 或者通过 API
curl -X POST "https://harbor.company.com/api/v2.0/system/gc/schedule" \
-H "Authorization: Bearer $TOKEN" \
-d '{"schedule":{"type":"Hourly","cron":"0 0 * * * *"}}'
4.3 实战:Harbor 迁移到 2.10 时 GC 的坑
我们从 Harbor 2.6 升到 2.10 时,GC 行为变了——2.10 之前的 GC 不会真正删除 blob,只是标记。需要手工做一次 full GC:
# 1. 触发 GC
curl -X POST "https://harbor.company.com/api/v2.0/system/gc" -H "..."
# 2. 等 GC 完成(看 Web UI / Harbor.log)
# 3. 进入 registry 容器,手工清理标记的 blob
docker exec -it registry bash
cd /etc/registry
# 触发 blob 清理
registry garbage-collect /etc/registry/config.yml
# 4. 真的删除磁盘文件
registry garbage-collect --delete-untagged /etc/registry/config.yml
清理了 3.2TB 的孤儿 blob,磁盘从 92% 降到 41%。
4.4 自动清理策略
我们后来在每个项目里配置 保留策略:
# Tag Retention Rule
rules:
- name: "保留最近 10 个"
tag_selectors:
- kind: latest # 或者正则
action: retain
params:
most_recent: 10
- name: "保留带 prod 标签的"
tag_selectors:
- decoration: matches
pattern: "prod-.*"
action: retain
定时清理的效果:Harbor 长期稳定在 1.2TB 左右。
五、备份与恢复
5.1 备份什么?
Harbor 的数据分两部分:
- 数据库(PostgreSQL):存元数据(项目、用户、tag 信息)
- 镜像数据(Registry):存在
/data/harbor/registry/目录
5.2 备份脚本
#!/bin/bash
# backup-harbor.sh
set -euo pipefail
BACKUP_DIR=/backup/harbor/$(date +%F)
HARBOR_DIR=/data/harbor
PG_CONTAINER=harbor-db
mkdir -p $BACKUP_DIR
# 1. 备份数据库
docker exec $PG_CONTAINER pg_dump -U postgres harbor > $BACKUP_DIR/harbor.sql
# 2. 备份镜像数据
tar czf $BACKUP_DIR/registry.tar.gz -C $HARBOR_DIR registry/
# 3. 备份配置(可选,但建议)
cp $HARBOR_DIR/harbor.yml $BACKUP_DIR/
# 4. 上传到远程
aws s3 cp $BACKUP_DIR s3://my-backups/harbor/$(date +%F)/ --recursive
echo "Backup done: $BACKUP_DIR"
定时任务:
# 每天凌晨 3 点备份
0 3 * * * /opt/scripts/backup-harbor.sh
5.3 恢复
# 1. 部署一个空的 Harbor
./install.sh
# 2. 恢复数据库
docker exec -i harbor-db psql -U postgres harbor < backup/harbor.sql
# 3. 恢复镜像数据
tar xzf registry.tar.gz -C /data/harbor/
# 4. 重启 Harbor
docker compose down && docker compose up -d
六、性能优化
6.1 启用 P2P 分发
Harbor 支持 P2P 镜像分发(基于 Kraken),拉取大镜像时速度提升明显:
# harbor.yml
distribution:
policy:
- name: my-p2p-policy
params:
enabled: true
但这个我们没启用——绝大多数镜像小于 500MB,瓶颈不在分发,在网络。
6.2 镜像分层缓存(客户端)
Docker 24+ 自带 Registry Pull Through Cache,可以缓存上游镜像:
# docker daemon 配置
{
"registry-mirrors": ["https://harbor.company.com"]
}
这样 docker pull nginx:alpine 第一次从 Harbor 代理 Docker Hub,第二次直接从 Harbor 缓存拉。
我们后来把所有员工机器都配置了 Harbor mirror,Docker Hub 流量降了 80%。
6.3 资源调优
Harbor 默认配置偏保守。生产中我们调大了:
# harbor.yml
proxy:
http:
- component: core
timeout: 60s
https:
- component: core
timeout: 60s
# 数据库连接池
database:
max_idle_conns: 50
max_open_conns: 200
七、本篇小结
| 主题 | 关键点 |
|---|---|
| Registry 基础 | 简单、无 UI、适合测试 |
| Harbor 部署 | 官方 compose / K8s Helm |
| RBAC | 项目级权限,符合最小权限原则 |
| 镜像复制 | 多数据中心同步 |
| 镜像签名 | Cosign,防止中间人篡改 |
| GC | 必须定期跑,避免磁盘爆炸 |
| 备份 | 数据库 + 镜像数据都要备份 |
下一篇讲 CI/CD 集成实战,会用 GitHub Actions 完整跑一遍构建→测试→推送→部署的流水线。