Drone对私有镜像仓库的支持方式,以及源码改造优化
Drone介绍
Drone是新一代的CI/CD工具,基于pipeline+docker模式,可以非常灵活的支撑很多业务场景,目前,Done最新为0.8.6版本,在github上,已经斩获15K高星star。
Drone和gitlab结合,可以在项目中设置 .drone.yml 文件来定制你需要执行的各种各样的流程,比如,代码拉取、镜像构建推送、PHP composer 包管理、Golang构建、消息通知、自动部署、自动化测试等等。插件化的支持,以及插件的开发和使用模式,使得Drone的扩展性非常灵活。
目前来说,Drone官方插件仓库已经提供了很多插件来扩展Drone的功能,而实现一套插件也非常简单。基本上,靠着 编写pipeline配置文件(.drone.yml) 的灵活编写+插件模式,足够应付无限的场景。个人觉得,相比Jenkins(其实Jenkins也出了一个基于docker、k8s的新一代工具:Jenkins X),Drone灵活简单多了。
Drone对私有镜像仓库的支持
在我们的实际使用Drone过程中,有可能需要私有镜像仓库的支持的话,以下面的pipeline为例:
clone:
git:
image: xxx.com/plugins/drone-plugin-git
pipeline:
build:
image: xxx.com/octocat/hello-image
push_image:
image: xxx.com/plugins/docker
repo: xxx.com/xxx/test
从这个例子中,可以看到,这个pipeline分为3个步骤
①:git步骤通过image对应的镜像拉取代码。
②:build步骤,通过对应的image镜像,执行代码构建操作。
③:push_image操作,通过image对应的镜像,完成镜像的构建,以及镜像推送到 repo对应的镜像仓库上。
这个过程中,假设我们的私有镜像仓库地址是 xxx.com,且这个镜像仓库有权限校验,那么这个Pipeline中,有2个地方涉及到私有镜像仓库的权限处理:
①:pipeline里,image对应的3个镜像,需要从私有镜像仓库拉取。
②:最后一个步骤是镜像构建和镜像推送操作,镜像构建其实就是基于具体项目里的Dockerfile,而这个Dockerfile的基础镜像,有可能也是一个私有镜像。另外,docker push也可能是推送到私有的镜像仓库。
官方方案
从上,要解决这2个方面对私有镜像仓库的需求。Drone本身提供了一套解决方案了。
第一,“pipeline里,image对应的镜像,需要从私有镜像仓库拉取”,这个解决方案,Drone提供了2种:
①:为每个image,通过 drone 客户端工具,设置镜像仓库token
drone secrets add \
--image=octocat/hello-image \
octocat/hello-world REGISTRY_USERNAME octocat
这种方案,比较麻烦,好处是,.drone.yml 不需要做任何改动
②:直接把pipeline里需要私有镜像仓库的image,在.drone.yml中,写好认证信息,比如写为这样:
clone:
git:
image: xxx.com/plugins/drone-plugin-git
auth_config:
username: octocat
password: password
email: octocat@github.com
pipeline:
build:
image: xxx.com/octocat/hello-image
auth_config:
username: octocat
password: password
email: octocat@github.com
push_image:
image: xxx.com/plugins/docker
repo: xxx.com/xxx/test
auth_config:
username: octocat
password: password
email: octocat@github.com
这种方案,不需要操作客户端工具,但需要每个项目的 .drone.yml 的pipeline配置里,都明文写好。
第二、如何解决镜像FROM私有仓库,以及镜像推送到私有仓库的问题
Drone其实提供了 plugins/docker 插件,来做镜像的构建和推送。解决此问题的单子,其实是靠此插件来做的,此插件,解决此问题,同样需要你配置pipeline的时候,明确写明认证信息(最后三行)
clone:
git:
image: xxx.com/plugins/drone-plugin-git
auth_config:
username: octocat
password: password
email: octocat@github.com
pipeline:
build:
image: xxx.com/octocat/hello-image
auth_config:
username: octocat
password: password
email: octocat@github.com
push_image:
image: xxx.com/plugins/docker
repo: xxx.com/xxx/test
auth_config:
username: octocat
password: password
email: octocat@github.com
username: octocat
password: password
email: octocat@github.com
改造的期望效果
我们可以看到,Drone,灵活就灵活在 pipeline 的配置上,但也因此,很多东西,都会写在 pipeline的配置文件 .drone.yml 里,明文暴露,这样不够优雅,我们可以尝试对源码进行一定的改造,用来更好的支持对私有镜像仓库的支持,但又不暴露认证信息。
首先,我期望的pipeline配置效果是这样的:
clone:
git:
image: xxx.com/plugins/drone-plugin-git
auth_config:
innerid: xxx.com
pipeline:
build:
image: xxx.com/octocat/hello-image
auth_config:
innerid: xxx.com
push_image:
image: xxx.com/plugins/docker
repo: xxx.com/xxx/test
auth_config:
innerid: xxx.com
auth_config_innerid: xxx.com
我们对比之前的.drone.yml,发现,里边少了明文暴露的私有镜像仓库认证信息,多了一个 innerid,这个innerid,其实就是私有镜像仓库的标识,这个标识,我们就定为私有镜像仓库的域名。
我的目的是,让 Drone 程序,可以通过识别 innerid,来自动从其他地方(比如drone-server的环境变量)获取对应的私有镜像仓库的认证信息,这样一来,.drone.yml,就不用在写明文认证信息了。
我们要改造2个项目,第一个是 drone 项目,第二个是 plugins/docker 这个插件。
源码改造
改造 drone 项目(用来支持pipeline中image使用私有镜像仓库)
1、增加一个自定义的环境变量,让 drone server 知道,有这个环境变量且有值的话,这个值,就是我们配置的所有私有镜像仓库的认证信息集合了
①:更改 cmd/drone-server/server.go 文件,增加 配置的环境变量名:REGISTRY_AUTH_INNER_CONFIG
//pipeline的authconfig默认配置
cli.StringFlag{
EnvVar: "REGISTRY_AUTH_INNER_CONFIG",
Name: "registry-auth-inner-config",
Usage: "private docker registry authentication username",
Value: "",
},
②:还是更改 cmd/drone-server/server.go 文件,更改 server 函数,增加刚刚环境变量的使用,目的是解析这个字符串,把它解析成golang的map类型
//初始化全局registry认证数据
registryInnerAuthConfig := c.String("registry-auth-inner-config")
if registryInnerAuthConfig != "" {
authinfo := droneserver.DecodeToMap(registryInnerAuthConfig)
for k, v := range authinfo {
registry := compiler.Registry{}
if e := json.Unmarshal([]byte(v), ®istry); e == nil {
droneserver.GlobalRegistryAuthConfig[k] = registry
}
}
}
这里边,有一个将字符串,转换为map的过程,使用的是自定义函数:DecodeToMap,这个函数的定义是这样的
//将string解码为map
func DecodeToMap(s string) map[string]string {
b := new(bytes.Buffer)
var decodedMap map[string]string
if data, err := base64.StdEncoding.DecodeString(s); err == nil {
b.Write(data)
d := gob.NewDecoder(b)
// Decoding the serialized data
err = d.Decode(&decodedMap)
}
return decodedMap
}
//将map编码为string
func EncodeMapToString(m map[string]string) string {
b := new(bytes.Buffer)
e := gob.NewEncoder(b)
// Encoding the map
err := e.Encode(m)
if err == nil {
return base64.StdEncoding.EncodeToString(b.Bytes())
} else {
return ""
}
}
③:更改 server/hook.go 文件,这个文件的开始部分,需要一个包引用,以及一个变量
//import下面的包
"github.com/cncd/pipeline/pipeline/frontend/yaml/compiler"
//全局的私有仓库认证信息,这个数据,从环境变量里取(从环境变量,解析为map,key为私有镜像仓库标识,比如 hub.mfwdev.com,value为认证信息json)
var GlobalRegistryAuthConfig = make(map[string]compiler.Registry)
④:我们的核心,就是要把环境变量里配置的编码后的字符串,解析成上面的变量,也就是一个 string 转 map 的过程。还是更改 上面的文件:
//找到下面的这行代码
parsed, err := yaml.ParseString(y)
//在上面行代码下面,补充下面代码
//处理私有镜像仓库认证信息问题
if parsed != nil && err == nil {
if parsed.Pipeline.Containers != nil && len(parsed.Pipeline.Containers) > 0 {
for _, c := range parsed.Pipeline.Containers {
if c.AuthConfig.Innerid != "" && GlobalRegistryAuthConfig != nil {
if registrytemp, ok := GlobalRegistryAuthConfig[c.AuthConfig.Innerid]; ok {
c.AuthConfig.Username = registrytemp.Username
c.AuthConfig.Password = registrytemp.Password
c.AuthConfig.Email = registrytemp.Email
}
}
}
}
}
⑤:好了,改定义我们的innerid数据类型了,更改文件:
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/container.go
type (
// AuthConfig defines registry authentication credentials.
AuthConfig struct {
//私有镜像仓库标识,比如:hub.xxx.com,有了这个标识,就可以结合环境变量,取出来这个标识对应的认证信息,取代下面的三个字段的内容
Innerid string
Username string
Password string
Email string
改造 plugins/docker 插件(用来改造 docker build、docker push使用私有镜像仓库)
我们需要新建一个项目,克隆 plugins/docker 作为我们自己的项目,来进行改造,通 drone 项目一样
我们为什么,既要改动 drone,又要改动 plugins/docker 项目呢?
这是因为,drone的改造,drone其实是server端,而 plugins/docker,仅仅是pipeline的一个步骤,我们可以让drone,把这个认证信息,传递给 plugins/docker。所以,回过头,我们还需要再次改造一下 drone 项目,将 drone的环境变量的认证信息,传递给 plugins/docker,这样一来,认证信息,传递给 plugins/docker,就不要 plugins/docker再去做什么从外部获取认证信息的事儿了
①:改一下 drone项目,文件:server/hook.go,在 hook函数中,设置环境变量的部分,增加下面一行
//全局的registry认证信息
envs["REGISTRY_AUTH_INNER_CONFIG"] = os.Getenv("REGISTRY_AUTH_INNER_CONFIG")
然后,回到 plugins/docker 项目来。
②:更改 cmd/drone-docker/main.go 文件,定义新类型结构体
type Registry struct {
// mfwupdate 是否内部验证(pipeline的docker认证,其实是需要在pipeline的yml中配置的,不好的地方是,会导出暴露认证信息
// 这里提供一个 Innerid 表示,优先走内部验证,也就是,通过drone-server的环境变量中取认证username、password、email信息)
Innerid string
Hostname string
Username string
Password string
Email string
Token string
}
再同样的文件中,增加取环境变量的部分
//use PLUGIN_AUTH_CONFIG_INNERID first,compared to PLUGIN_USERNAME、PLUGIN_PASSWORD、PLUGIN_EMAIL,做docker login
cli.StringFlag{
Name: "auth_config_innerid",
Usage: "use auth_config_innerid first,compared to username、password、email for docker login",
EnvVar: "PLUGIN_AUTH_CONFIG_INNERID",
},
}
还是这个文件,增加对 这个环境变量的处理
//mfwupdate
// login to the Docker registry
authConfigInnerId := c.String("auth_config_innerid")
registryAuthInnerInfo := os.Getenv("REGISTRY_AUTH_INNER_CONFIG")
if registryAuthInnerInfo != "" && authConfigInnerId != "" {
authinfo := DecodeStringToMap(registryAuthInnerInfo)
for k, v := range authinfo {
registry := Registry{}
if e := json.Unmarshal([]byte(v), ®istry); e == nil {
if k == authConfigInnerId {
plugin.Login.Email = registry.Email
plugin.Login.Username = registry.Username
plugin.Login.Password = registry.Password
plugin.Login.Registry = authConfigInnerId
}
}
}
}
至此,改造完毕。
参考: