当你的 Docker 镜像从 10 个涨到 1000 个,当你需要在多个集群之间同步镜像,当你需要对镜像做安全扫描和版本管理——你需要一个靠谱的容器镜像仓库。本文对比三主流方案:开源的 Harbor、云厂商托管 ACR、GitHub Container Registry,帮你根据团队规模和场景做出最优选择。
1. 三大方案核心对比
| 对比维度 | Harbor | 腾讯云 TCR / 阿里云 ACR | GitHub Container Registry (GHCR) |
|---|---|---|---|
| 部署方式 | 自建(Docker/K8s) | 云托管(开箱即用) | 云托管(与 GitHub 深度集成) |
| 费用 | 免费(需自备服务器) | 按存储+请求量付费 | 公开仓库免费,私有仓库按流量 |
| 安全扫描 | Trivy 集成 | 云原生漏洞扫描 | Dependabot 集成 |
| 多区域同步 | 支持(Replication) | 全球加速自动同步 | 不支持 |
| CI/CD 集成 | 需自行配置 | 与云厂商 CI/CD 深度集成 | GitHub Actions 原生支持 |
| 镜像签名 | Notary 支持 | KMS 密钥管理 | Cosign 支持 |
| 适合团队规模 | 50+ 人的中大型团队 | 任何规模 | 开源项目/小型团队 |
2. 方案一:Harbor 自建(适合中大型团队)
Harbor 是 CNCF 毕业项目,功能最全面,但需要自己维护。
Docker Compose 快速部署
# docker-compose.yml — Harbor 2.12 部署
version: "3.8"
services:
harbor-db:
image: goharbor/harbor-db:v2.12.0
container_name: harbor-db
restart: always
environment:
POSTGRES_PASSWORD: harbor123
POSTGRES_USER: postgres
POSTGRES_DB: registry
volumes:
- harbor_db:/var/lib/postgresql/data
redis:
image: redis:7-alpine
container_name: harbor-redis
restart: always
harbor-core:
image: goharbor/harbor-core:v2.12.0
container_name: harbor-core
restart: always
environment:
CORE_SECRET: notsecure
JOBS_SERVICE_SECRET: notsecure
EXT_ENDPOINT: https://harbor.yourdomain.com
DATABASE_TYPE: postgresql
POSTGRESQL_HOST: harbor-db
POSTGRESQL_PORT: 5432
POSTGRESQL_DATABASE: registry
POSTGRESQL_USERNAME: postgres
POSTGRESQL_PASSWORD: harbor123
REGISTRY_CONTROLLER_URL: http://harbor-registry:8080
PORTAL_URL: http://harbor-portal:8080
TOKEN_SERVICE_URL: http://harbor-core:8080/service/token
REGISTRY_URL: http://harbor-registry:5000
depends_on:
- harbor-db
- redis
- harbor-registry
registry:
image: goharbor/registry-photon:v2.12.0
container_name: harbor-registry
restart: always
volumes:
- registry_data:/storage
portal:
image: goharbor/harbor-portal:v2.12.0
container_name: harbor-portal
restart: always
trivy:
image: aquasec/trivy:0.50.0
container_name: harbor-trivy
restart: always
volumes:
harbor_db:
registry_data:
Harbor 自动化镜像管理脚本
# harbor_manager.py — Harbor 镜像仓库管理脚本
import requests
import json
from datetime import datetime
from typing import Optional
class HarborManager:
"""Harbor 镜像仓库管理"""
def __init__(self, url: str, username: str, password: str):
self.base_url = url.rstrip("/")
self.session = requests.Session()
self.session.auth = (username, password)
self.session.headers.update({"Content-Type": "application/json"})
# 获取认证 token
self._auth()
def _auth(self):
"""获取 Harbor JWT token"""
resp = self.session.get(f"{self.base_url}/api/v2.0/users/current")
resp.raise_for_status()
self.token = resp.json().get("username", "")
def list_projects(self) -> list:
"""列出所有项目"""
resp = self.session.get(f"{self.base_url}/api/v2.0/projects")
resp.raise_for_status()
return [{"name": p["name"], "repo_count": p.get("repo_count", 0)}
for p in resp.json()]
def list_repositories(self, project: str) -> list:
"""列出项目下的所有镜像仓库"""
resp = self.session.get(
f"{self.base_url}/api/v2.0/projects/{project}/repositories"
)
resp.raise_for_status()
return [r["name"] for r in resp.json()]
def list_artifacts(self, project: str, repo: str) -> list:
"""列出镜像的所有 tag"""
resp = self.session.get(
f"{self.base_url}/api/v2.0/projects/{project}/repositories"
f"/{repo}/artifacts"
)
resp.raise_for_status()
artifacts = []
for a in resp.json():
tags = [t["name"] for t in a.get("tags", [])]
size_mb = round(a.get("size", 0) / 1024 / 1024, 1)
push_time = a.get("push_time", "")
artifacts.append({
"digest": a["digest"][:16],
"tags": tags or ["<none>"],
"size_mb": size_mb,
"push_time": push_time[:10],
})
return artifacts
def cleanup_old_images(self, project: str, repo: str,
keep_tags: int = 5) -> list:
"""清理旧镜像,保留最新的 N 个 tag"""
artifacts = self.list_artifacts(project, repo)
if len(artifacts) <= keep_tags:
return []
# 按推送时间排序
artifacts.sort(key=lambda x: x.get("push_time", ""), reverse=True)
to_delete = artifacts[keep_tags:]
deleted = []
for art in to_delete:
for tag in art["tags"]:
if tag == "<none>":
# 通过 digest 删除无 tag 的镜像
try:
self.session.delete(
f"{self.base_url}/api/v2.0/projects/{project}"
f"/repositories/{repo}/artifacts/{art['digest']}"
)
deleted.append(f"删除 digest: {art['digest'][:16]}")
except Exception as e:
deleted.append(f"失败: {art['digest'][:16]} ({e})")
else:
try:
self.session.delete(
f"{self.base_url}/api/v2.0/projects/{project}"
f"/repositories/{repo}/artifacts/{tag}"
)
deleted.append(f"删除 tag: {tag}")
except Exception as e:
deleted.append(f"失败: {tag} ({e})")
return deleted
def get_quota_usage(self, project: str) -> dict:
"""获取项目存储配额使用情况"""
resp = self.session.get(
f"{self.base_url}/api/v2.0/projects/{project}/quotas"
)
quotas = resp.json()
usage = {"hard": 0, "used": 0, "usage_percent": 0}
for q in quotas:
if q.get("hard"):
usage["hard"] = q["hard"]["storage"]
usage["used"] = q.get("used", {}).get("storage", 0)
usage["usage_percent"] = round(
usage["used"] / usage["hard"] * 100, 1
) if usage["hard"] > 0 else 0
return usage
def scan_image(self, project: str, repo: str, reference: str) -> dict:
"""触发镜像安全扫描"""
resp = self.session.post(
f"{self.base_url}/api/v2.0/projects/{project}"
f"/repositories/{repo}/artifacts/{reference}/scan"
)
if resp.status_code in (200, 201):
return {"status": "scan_started", "reference": reference}
return {"status": "error", "detail": resp.text}
# 使用示例
if __name__ == "__main__":
harbor = HarborManager(
url="https://harbor.yourdomain.com",
username="admin",
password="Harbor12345"
)
# 列出所有项目
print("=== 项目列表 ===")
projects = harbor.list_projects()
for p in projects:
print(f" 📁 {p['name']} ({p['repo_count']} 个仓库)")
# 清理旧镜像
print("\n=== 清理旧镜像 ===")
for project in ["myapp"]:
repos = harbor.list_repositories(project)
for repo in repos:
results = harbor.cleanup_old_images(project, repo, keep_tags=3)
for r in results:
print(f" {r}")
# 存储配额
print("\n=== 存储配额 ===")
for project in ["myapp"]:
usage = harbor.get_quota_usage(project)
print(f" {project}: {usage['used'] / 1024**3:.1f}GB / "
f"{usage['hard'] / 1024**3:.1f}GB "
f"({usage['usage_percent']}%)")
3. 方案二:云厂商托管 ACR(适合快速上手)
以腾讯云 TCR(容器镜像服务)为例,展示云托管方案的使用方式:
# tcr_manager.py — 腾讯云 TCR 镜像仓库管理
import hashlib
import hmac
import time
import base64
import json
import requests
from datetime import datetime
class TCRManager:
"""腾讯云容器镜像服务管理"""
def __init__(self, secret_id: str, secret_key: str,
region: str = "ap-guangzhou"):
self.secret_id = secret_id
self.secret_key = secret_key
self.region = region
self.base_url = f"https://tcr.tencentcloudapi.com"
def _sign(self, payload: str) -> str:
"""TC3-HMAC-SHA256 签名"""
timestamp = int(time.time())
date = datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d")
# 拼接签名字符串
credential_scope = f"{date}/tcr/tc3_request"
canonical_headers = (
f"content-type:application/json\n"
f"host:tcr.tencentcloudapi.com\n"
f"x-tc-action:DescribeInstances"
)
signed_headers = "content-type;host;x-tc-action"
hash_payload = hashlib.sha256(payload.encode()).hexdigest()
canonical_request = (
f"POST\n/\n\n{canonical_headers}\n"
f"{signed_headers}\n{hash_payload}"
)
hash_request = hashlib.sha256(canonical_request.encode()).hexdigest()
string_to_sign = (
f"TC3-HMAC-SHA256\n{timestamp}\n"
f"{credential_scope}\n{hash_request}"
)
# 签名
key_date = hmac.new(
("TC3" + self.secret_key).encode(),
date.encode(), hashlib.sha256
).digest()
key_service = hmac.new(
key_date, "tcr".encode(), hashlib.sha256
).digest()
key_signing = hmac.new(
key_service, "tc3_request".encode(), hashlib.sha256
).digest()
signature = hmac.new(
key_signing, string_to_sign.encode(), hashlib.sha256
).hexdigest()
return {
"Authorization": (
f"TC3-HMAC-SHA256 Credential={self.secret_id}/"
f"{credential_scope}, SignedHeaders={signed_headers}, "
f"Signature={signature}"
),
"Content-Type": "application/json",
"X-TC-Action": "DescribeInstances",
"X-TC-Version": "2022-05-12",
"X-TC-Region": self.region,
"X-TC-Timestamp": str(timestamp),
}
def list_instances(self) -> list:
"""列出镜像仓库实例"""
payload = json.dumps({})
headers = self._sign(payload)
resp = requests.post(self.base_url, headers=headers, data=payload)
if resp.status_code == 200:
data = resp.json()
return data.get("Response", {}).get("Instances", [])
print(f"请求失败: {resp.status_code} {resp.text}")
return []
def docker_login_tcr():
"""Docker 登录腾讯云 TCR"""
# 格式:docker login ccr.ccs.tencentyun.com
import subprocess
cmd = (
"echo 'your_password' | "
"docker login ccr.ccs.tencentyun.com "
"-u your_appid"
)
print(f"登录命令: docker login ccr.ccs.tencentyun.com")
print("⚠️ 实际使用时请替换为真实密码,不要硬编码")
def push_image_to_tcr():
"""推送镜像到 TCR 完整流程"""
steps = """
# 1. 标记镜像
docker tag myapp:v1.0 ccr.ccs.tencentyun.com/your-ns/myapp:v1.0
# 2. 推送镜像
docker push ccr.ccs.tencentyun.com/your-ns/myapp:v1.0
# 3. 验证
docker pull ccr.ccs.tencentyun.com/your-ns/myapp:v1.0
"""
print(steps.strip())
if __name__ == "__main__":
# 云方案的使用非常简单——主要是 docker login + push
print("=== 云厂商 ACR 方案 ===")
print("优势:零运维、全球加速、自动漏洞扫描")
print("步骤:")
print(" 1. 控制台开通 TCR 服务")
print(" 2. docker login <镜像仓库地址>")
print(" 3. docker push <镜像仓库地址>/namespace/image:tag")
print(" 4. 在控制台配置自动扫描 + 生命周期规则")
print()
push_image_to_tcr()
4. 方案三:GHCR(适合开源项目和 GitHub 生态)
GitHub Container Registry 与 GitHub Actions 深度集成,对开源项目最友好:
# .github/workflows/build-and-push.yml — GHCR 自动构建推送
name: Build and Push Docker Image
on:
push:
branches: [main]
tags: ["v*"]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# ghcr_analyzer.py — GHCR 使用分析脚本
import requests
from typing import Optional
class GHCRAnalyzer:
"""GitHub Container Registry 分析工具"""
def __init__(self, token: str = ""):
self.base_url = "https://api.github.com"
self.headers = {
"Accept": "application/vnd.github.v3+json",
}
if token:
self.headers["Authorization"] = f"Bearer {token}"
def list_packages(self, org: str) -> list:
"""列出组织的所有容器包"""
resp = requests.get(
f"{self.base_url}/orgs/{org}/packages",
params={"package_type": "container", "per_page": 100},
headers=self.headers
)
resp.raise_for_status()
return [
{
"name": p["name"],
"visibility": p["visibility"],
"created_at": p["created_at"][:10],
"updated_at": p["updated_at"][:10],
}
for p in resp.json()
]
def get_package_versions(self, org: str, package: str) -> list:
"""获取包的所有版本"""
resp = requests.get(
f"{self.base_url}/orgs/{org}/packages/container/{package}/versions",
params={"per_page": 10},
headers=self.headers
)
resp.raise_for_status()
versions = []
for v in resp.json():
tags = [m["name"] for m in v.get("metadata", {}).get("container", {}).get("tags", [])]
versions.append({
"id": v["id"],
"tags": tags,
"created": v["created_at"][:10],
"size_mb": round(v.get("size", 0) / 1024 / 1024, 1),
})
return versions
def compare_storage(self, org: str):
"""对比存储使用情况"""
packages = self.list_packages(org)
total_size = 0
print(f"=== {org} 镜像仓库概况 ===")
print(f"{'名称':<30} {'可见性':<10} {'最后更新':<12}")
print("-" * 55)
for pkg in packages:
print(f"{pkg['name']:<30} {pkg['visibility']:<10} {pkg['updated_at']:<12}")
print(f"\n总计: {len(packages)} 个包")
print("⚠️ GHCR 不提供精确存储总量,建议在 GitHub Settings > Billing 查看")
if __name__ == "__main__":
# 使用方式
analyzer = GHCRAnalyzer()
# analyzer.list_packages("your-org")
print("GHCR 优势:与 GitHub Actions 无缝集成")
print("GHCR 限制:无多区域同步,私有仓库有流量费用")
5. 选型决策矩阵
根据你的实际场景选择:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 10 人以下小团队,用 GitHub | GHCR | 零配置,CI/CD 原生支持 |
| 中型企业,需要合规审计 | Harbor | 完整 RBAC + 扫描 + 签名 |
| 已在用阿里云/腾讯云 | 云 ACR | 与现有云服务深度集成 |
| 需要跨云/跨区域同步 | Harbor 或云 ACR | 都支持 replication |
| 开源项目维护 | GHCR | 公开仓库免费 + 依赖扫描 |
| 金融/政务合规场景 | Harbor(私有化部署) | 数据不出内网 |
成本对比(存储 100GB / 月传输 500GB)
| 方案 | 存储 | 传输 | 合计 | 隐藏成本 |
|---|---|---|---|---|
| Harbor 自建 | 服务器 ¥300/月 | 集群内网免费 | ~¥300 | 运维人力 0.5 人 |
| 腾讯云 TCR | ¥15 | ¥50 | ~¥65 | 无 |
| 阿里云 ACR | ¥12 | ¥60 | ~¥72 | 无 |
| GHCR | $0(公开) | $0 | $0 | 私有仓库 $0.02/GB/天 |
6. 实用技巧:统一管理多仓库
当你同时使用多个镜像仓库时,可以用 regctl 统一管理:
# 安装 regctl
go install github.com/regclient/regclient/cmd/regctl@latest
# 配置多仓库
export REGCTL_CONFIG=~/.regctl/config.yaml
# 从 Harbor 同步到 TCR
regctl image copy \
harbor.example.com/myapp:v1.0 \
ccr.ccs.tencentyun.com/ns/myapp:v1.0
# 从 GHCR 拉取到本地
regctl image pull ghcr.io/username/myapp:latest
# 查看镜像详情(layer、size、digest)
regctl manifest get ghcr.io/username/myapp:latest
# 批量打标签(为同一镜像打不同 tag)
for tag in v1.0 v1.0.0 latest; do
regctl image tag harbor.example.com/myapp:v1.0 \
harbor.example.com/myapp:$tag
done
一句话总结:如果你已经在用某家云厂商的 K8s 服务,优先用它的 ACR;如果你需要数据不出内网或者有复杂的合规要求,选 Harbor;如果你是开源项目维护者,GHCR 最省心。
👤 作者简介
一枚在大中原腹地(河南)卖公有云的女/男士,主营腾讯云/阿里云/华为云,曾踩坑无数,现专注AI大模型应用落地。关注公众号「AI热点日报」,围观AI前沿动态~
博客:y1cloud.cn
