为什么还要改造doker registry?
Docker registry 是一个轻量级的镜像仓库,说到这里不得不提一下另外一个企业级的镜像仓库---harbor,
harbor 是在Docker Registry上进行了相应的企业级扩展,从而获得了更加广泛的应用,这些新的企业级特性包括:管理用户界面,基于角色的访问控制 ,AD/LDAP集成以及审计日志等。
harbor什么都好,就是太重了;在资源很有限的情况下,还是用docker registry 合适一些.
有了 registry 镜像仓库的问题解决了,通常,在部署一个离线的微服务环境前,还要做一些前置工作,比如设置rpm 源,下载一些安装包,还有和镜像仓库配套的helm chart 仓库.
这些工作都有对应的工具,但是不能 通用,导致管理起来复杂; 同时这些服务,底层要么是一个文件服务,要么是文件服务+自建索引, 所以这些都是可以在registry 中建立一个文件服务搞定的.
所以这个实现的总体思路是这样的
- 实现一个golang的文件服务,因为registry是golang实现的,需要在它里面集成文件服务
- registry 源码编译.
- 将文件服务添加到registry的http句柄中
- 修改后重新编译打包
1.文件服务的 golang 实现,很简单
核心其实只有一句
http.Handle("/prefix/", http.StripPrefix("/prefix/", http.FileServer(http.Dir("/filesdir"))))
package main
import (
"log"
"net/http"
)
func main() {
http.Handle("/prefix/", http.StripPrefix("/prefix/", http.FileServer(http.Dir("/filesdir"))))
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
registry 源码编译
github地址
gitee地址
代码中提供的编译文档 https://gitee.com/mirrors/distribution/blob/main/BUILDING.md ,需要本地搭建golang 环境。
其实源码中提供了一种基于docker编译环境的构建,就在https://gitee.com/mirrors/distribution/blob/main/Dockerfile 中
FROM golang:1.11-alpine AS build
ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution
ENV BUILDTAGS include_oss include_gcs
ARG GOOS=linux
ARG GOARCH=amd64
ARG GOARM=6
RUN set -ex \
&& apk add --no-cache make git file
WORKDIR $DISTRIBUTION_DIR
COPY . $DISTRIBUTION_DIR
RUN CGO_ENABLED=0 make PREFIX=/go clean binaries && file ./bin/registry | grep "statically linked"
FROM alpine
COPY cmd/registry/config-dev.yml /etc/docker/registry/config.yml
COPY --from=build /go/src/github.com/docker/distribution/bin/registry /bin/registry
VOLUME ["/var/lib/registry"]
EXPOSE 5000
ENTRYPOINT ["registry"]
CMD ["serve", "/etc/docker/registry/config.yml"]
简单说明一下这个Dockerfile
基于golang的alpine 操作系统,安装了make git 等软件
然后在基础的编译镜像中编译
编译后的二进制从编译镜像中拷贝出来到实际的运行镜像中运行
两个镜像中拷贝的过程在下一句中体现:
COPY --from=build /go/src/github.com/docker/distribution/bin/registry /bin/registry
然而在国内直接运行这个会报网络连接错误,因为alpine 默认的源不能访问
所以替换一下源即可
FROM golang:1.11-alpine AS build
ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution
ENV BUILDTAGS include_oss include_gcs
ARG GOOS=linux
ARG GOARCH=amd64
ARG GOARM=6
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
RUN set -ex \
&& apk add --no-cache make git file
#--update-cache --repository http://mirrors.ustc.edu.cn/alpine/v3.4/main/ --allow-untrusted
WORKDIR $DISTRIBUTION_DIR
COPY . $DISTRIBUTION_DIR
RUN CGO_ENABLED=0 make PREFIX=/go clean binaries && file ./bin/registry | grep "statically linked"
FROM alpine
COPY cmd/registry/config-dev.yml /etc/docker/registry/config.yml
COPY --from=build /go/src/github.com/docker/distribution/bin/registry /bin/registry
VOLUME ["/var/lib/registry"]
EXPOSE 5000
ENTRYPOINT ["registry"]
CMD ["serve", "/etc/docker/registry/config.yml"]
解决了编译问题,剩下的就是修改代码了。
添加文件服务
项目的入口在 cmd/main.go 里面,只有一行代码
func main() {
registry.RootCmd.Execute()
}
这里指向 registry模块的 RootCmd ,RootCmd 有对ServeCmd命令的调用,在init函数中 RootCmd.AddCommand(ServeCmd)
,ServerCmd会运行 NewRegistry,在NewRegistry中有httpserver 的启动,所以我们将会在NewRegistry里添加我们写的文件服务。
文件服务还有两个需要考虑的事情是:
一个是 url 访问的路由
一个是 文件存储的路径
url 的访问路径可以自定义,因为会存储很多源文件就叫"/repos/",文件存储路径最好是配置的镜像路径的子路径,避免需要多配置。
所以先要取镜像的配置路径,再创建子目录,xxx/repos ,xxx代表之前配置的目录。
于是乎添加了下面一段代码在NewRegistry 函数中
reposDir, _ := config.Storage["filesystem"]["rootdirectory"].(string)
reposDir = reposDir + "/repos/"
os.MkdirAll(reposDir, os.ModePerm)
加入到http 的处理中
handler = repos("/repos/", reposDir, handler)
其中repos 的实现如下
// repos simply wraps the handler with a repos filesystem
func repos(path string, reposDir string, other http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.RequestURI, "/repos/") {
head := http.FileServer(http.Dir(reposDir))
headServer := http.StripPrefix("/repos/", head)
headServer.ServeHTTP(w, r)
} else {
other.ServeHTTP(w, r)
}
})
}
修改前后的差异如图
再次编译,生成的新的registry 可执行文件,启动后就会在配置的存储路径下多出来一个repos 文件夹,使用 htttp://ip:5000/repos/就可以取出来。