Harbor查询镜像接口
背景
当Harbor中的某个镜像下的标签数目过多时,调用Harbor查询tag接口有时候会变得很慢,所以就想知道为什么Harbor官方不通过分页的形式来获取tag而且要一次性全部查询出来
接口调用逻辑
当Harbor页面去查询一个镜像的所有版本详细信息时,会调用一个/api/repositories/{project}/{image}/tags接口,通过查询源码我发现其内部调用逻辑如下
- 发送/api/repositories/{project}/{image}/tags会被core/router.go接收并处理
1
| beego.Router("/api/repositories/*/tags", &api.RepositoryAPI{}, "get:GetTags;post:Retag")
|
- 调用core/api/repository.go的GetTag方法
1 2 3 4 5 6 7 8 9 10 11
| func (ra *RepositoryAPI) GetTags() { ... client, err := coreutils.NewRepositoryClientForUI(ra.SecurityCtx.GetUsername(), repoName) tags, err := client.ListTag() ... # 通过client获取镜像下所有的tags,然后根据tags查询Harbor自身的数据库填充每个版本下镜像的标签、创建时间、大小等信息返回(assembleTagsInParallel方法) ra.Data["json"] = assembleTagsInParallel(client, repoName, tags, ra.SecurityCtx.GetUsername()) ra.ServeJSON() }
|
- 在GetTag内部通过client的ListTag方法获取tags
1 2 3 4 5 6 7 8 9 10
| # common/utils/registry/repository.go func (r *Repository) ListTag() ([]string, error) { ... req, err := http.NewRequest("GET", buildTagListURL(r.Endpoint.String(), r.Name), nil) ... }
func buildTagListURL(endpoint, repoName string) string { return fmt.Sprintf("%s/v2/%s/tags/list", endpoint, repoName) }
|
1 2
| # core/router.go beego.Router("/v2/*", &controllers.RegistryProxy{}, "*:Handle")
|
看这里可以知道Harbor查询镜像版本详细信息的接口实际上就是docker registry的V2/tags/list接口
harbor的架构
Proxy:底层实际上就是跑了一个nginx的容器,向docker client及浏览器暴露端口,将这些客户端发来的请求反向代理到后端Core Service、Registry。
Registry:这个其实是就是官方的docker registry,其配置了webhook到Core Services,这样当镜像的状态发生变化时,可通知到Core Services。
Core Services:这个里面内容就比较多了,主要由多个http服务组成,完成的功能主要有以下几点:
- 监听Registry上镜像的变化,做相应处理,比如记录日志、发起复制等
- 充当Docker Authorization Service的角色,对镜像资源进行基于角色的鉴权
- 连接Database,提供存取projects、users、roles、replication policies和images元数据的API接口
- 提供UI界面:从目前的代码来看主要有这4个部分ui(这个感觉改名为controller好一点)、adminserver、registryctl、portal。
Job Service:定时执行一些任务,提供API供外部提交任务及查询执行结果。
Log collector:说白了就是一个rsyslog日志服务,其它组件可以将日志发送到这里,它负责集中存储。
Database:就是mysql数据库服务,用于存储projects、users、roles、replication policies和images的元数据。
docker registry
查询Harbor makefile的配置文件可以看到
查询对应docker registry2.6版本的源码https://github.com/docker/distribution/blob/c192a281f8ac6f2a351fe729c8a56108f8edb377/registry/handlers/tags.go#L35
GetTags方法逻辑如下,内部调用了tagService.All方法来获取全部tag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| func (th *tagsHandler) GetTags(w http.ResponseWriter, r *http.Request) { defer r.Body.Close()
tagService := th.Repository.Tags(th) tags, err := tagService.All(th) if err != nil { switch err := err.(type) { case distribution.ErrRepositoryUnknown: th.Errors = append(th.Errors, v2.ErrorCodeNameUnknown.WithDetail(map[string]string{"name": th.Repository.Named().Name()})) case errcode.Error: th.Errors = append(th.Errors, err) default: th.Errors = append(th.Errors, errcode.ErrorCodeUnknown.WithDetail(err)) } return }
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w) if err := enc.Encode(tagsAPIResponse{ Name: th.Repository.Named().Name(), Tags: tags, }); err != nil { th.Errors = append(th.Errors, errcode.ErrorCodeUnknown.WithDetail(err)) return } }
|
从blobStore数据库获取镜像的所有tag
https://github.com/docker/distribution/blob/c192a281f8ac6f2a351fe729c8a56108f8edb377/registry/storage/tagstore.go#L25
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| func (ts *tagStore) All(ctx context.Context) ([]string, error) { var tags []string
pathSpec, err := pathFor(manifestTagPathSpec{ name: ts.repository.Named().Name(), }) if err != nil { return tags, err }
entries, err := ts.blobStore.driver.List(ctx, pathSpec) if err != nil { switch err := err.(type) { case storagedriver.PathNotFoundError: return tags, distribution.ErrRepositoryUnknown{Name: ts.repository.Named().Name()} default: return tags, err } }
for _, entry := range entries { _, filename := path.Split(entry) tags = append(tags, filename) }
return tags, nil }
|
参考
http://www.dockerinfo.net/3597.html
https://jeremy-xu.oschina.io/2018/09/harbor%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/
https://github.com/docker/distribution/tree/release/2.6