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-gitpipeline: 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.compipeline: 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.compipeline: 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.compipeline: 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解碼為mapfunc 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編碼為stringfunc 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 } } } }
至此,改造完畢。
參考:
http://readme.drone.io/0.5/