# 私有镜像仓库

私有镜像仓库用于把 Harbor、Docker Hub 私有仓库、ECR、ACR、Artifact Registry 等上游接入 SparkCR。接入后，客户端仍使用 Docker Registry V2 协议，SparkCR 负责认证上游、转发请求，并按缓存策略保存可缓存内容。

私有镜像仓库不支持匿名访问。拉取需要包含 `pull:private` 的 Access Token；推送需要额外包含 `push`。

## 创建私有上游

1. [登录 SparkCR 控制台](/login)。
2. 打开 [`Settings` -> `Private Registries`](/settings/private-registries)。
3. 点击添加私有镜像仓库。
4. 填写 `slug`、`host`、显示名称、上游账号和上游密码或 Token。
5. 选择镜像仓库类型、访问模式和缓存策略。
6. 保存后复制页面生成的运行时命令。

字段含义：

| 字段 | 说明 |
| --- | --- |
| `slug` | 当前账号内的私有上游标识，只允许小写字母和数字，例如 `harbor`。 |
| `host` | 私有镜像仓库 host，例如 `harbor.example.com` 或 `123456789012.dkr.ecr.ap-southeast-1.amazonaws.com`。 |
| 镜像仓库类型 | 通用 Docker Registry V2、Docker Hub、AWS ECR、阿里云 ACR、Google Artifact Registry、Azure ACR。 |
| 模式 | `拉取`、`推送` 或 `拉取 + 推送`。 |
| 缓存策略 | 隔离缓存、只代理不缓存、共享镜像仓库缓存。 |

共享镜像仓库缓存只适合单租户上游。Docker Hub、GHCR 等公共多租户镜像仓库不应使用共享缓存。

## 创建 Access Token

在 [`Access Token`](/tokens) 页面创建运行时使用的令牌：

- 只拉取公共镜像：选择 `pull:public`。
- 拉取私有上游：选择 `pull:private`。
- 推送到私有上游：选择 `push`。

同一个令牌可以同时包含 `pull:private` 和 `push`。CI 建议为每个项目或 runner 单独创建令牌。

## 拉取私有镜像

页面会展示当前私有上游的 endpoint。假设 endpoint 为：

```text
https://sparkcr.cn/acme-harbor/
```

登录 SparkCR 镜像仓库：

```bash
docker login sparkcr.cn
```

用户名填写 SparkCR 账号邮箱，密码填写包含 `pull:private` 的 Access Token。

拉取镜像：

```bash
docker pull sparkcr.cn/acme-harbor/myapp:latest
```

如果使用别名入口，登录和镜像 host 使用页面展示的私有上游入口。

## 推送私有镜像

私有上游模式需要允许推送，Access Token 需要包含 `push`。

```bash
docker login sparkcr.cn
docker tag myapp:latest sparkcr.cn/acme-harbor/myapp:latest
docker push sparkcr.cn/acme-harbor/myapp:latest
```

SparkCR 会把 blob 和 manifest 转发到上游私有镜像仓库。缓存策略不是“只代理不缓存”时，推送成功的内容也会尝试写入缓存，后续拉取可复用。

## containerd

containerd 使用 `certs.d` 配置。私有上游需要把 host 指向 SparkCR endpoint，并按模式设置 capabilities。

```toml
server = "https://harbor.example.com"

[host."https://sparkcr.cn/acme-harbor"]
  capabilities = ["pull", "resolve", "push"]
```

只拉取时可以省略 `"push"`：

```toml
capabilities = ["pull", "resolve"]
```

修改后重启 containerd：

```bash
sudo systemctl restart containerd
```

## CI/CD

在 CI 中使用私有镜像仓库时，推荐准备三类 Secret：

- `SPARKCR_REGISTRY`：SparkCR 镜像仓库 host，例如 `sparkcr.cn`。
- `SPARKCR_USERNAME`：SparkCR 账号邮箱。
- `SPARKCR_TOKEN`：包含 `pull:private` 或 `push` 的 Access Token。

登录和推送示例：

```bash
echo "$SPARKCR_TOKEN" | docker login "$SPARKCR_REGISTRY" \
  --username "$SPARKCR_USERNAME" \
  --password-stdin

docker pull "$SPARKCR_REGISTRY/acme-harbor/myapp:base"
docker build -t "$SPARKCR_REGISTRY/acme-harbor/myapp:$CI_COMMIT_SHA" .
docker push "$SPARKCR_REGISTRY/acme-harbor/myapp:$CI_COMMIT_SHA"
```

## 故障排查

| 现象 | 优先检查 |
| --- | --- |
| `401 Unauthorized` | SparkCR Access Token 是否包含 `pull:private` 或 `push`。 |
| `403 DENIED` | 私有上游模式是否允许当前操作，上游账号是否有对应仓库权限。 |
| `manifest unknown` | 镜像路径是否使用页面生成的 endpoint 和 slug。 |
| `blob upload unknown` | 推送会话可能已过期，重新执行 `docker push`。 |
| 推送成功但后续拉取仍慢 | 当前缓存策略可能是只代理不缓存，或 blob 尚未命中缓存。 |
