天天看點

基于drone的CI/CD,對接kubernetes實踐教程

CI 概述

用一個可描述的配置定義整個工作流

程式員是很懶的動物,是以想各種辦法解決重複勞動的問題,如果你的工作流中還在重複一些事,那麼可能就得想想如何優化了

持續內建就是可以幫助我們解決重複的代碼建構,自動化測試,釋出等重複勞動,通過簡單一個送出代碼的動作,解決接下來要做的很多事。

容器技術使這一切變得更完美。

典型的一個場景:

我們寫一個前端的工程,假設是基于vue.js的架構開發的,送出代碼之後希望跑一跑測試用例,然後build壓縮一個到dist目錄裡,再把這個目錄的靜态檔案用nginx代理一下。

最後打成docker鏡像放到鏡像倉庫。 甚至還可以增加一個線上上運作起來的流程。

現在告訴你,隻需要一個git push動作,接下來所有的事CI工具會幫你解決!這樣的系統如果你還沒用上的話,那請問還在等什麼。接下來會系統的向大家介紹這一切。

代碼倉庫管理

首先SVN這種渣渣軟體就該盡早淘汰,沒啥好說的,有git真的沒有SVN存在的必要了我覺得。

是以我們選一個git倉庫,git倉庫比較多,我這裡選用gogs,gitea gitlab都行,根據需求自行選擇

docker run -d --name gogs-time -v /etc/localtime:/etc/localtime -e TZ=Asia/Shanghai --publish 8022:22 \
 --publish 3000:3000 --volume /data/gogs:/data gogs:latest      

通路3000端口,然後就沒有然後了

gogs功能沒有那麼強大,不過占用資源少,速度快,我們穩定運作了幾年了。缺點就是API不夠全。

CI 工具

當你用過drone之後。。。

裝:

version: '2'

services:
 drone-server:
 image: drone/drone:0.7
 ports: - 80:8000
 volumes: - /var/lib/drone:/var/lib/drone/
 restart: always
 environment: - DRONE_OPEN=true - DOCKER_API_VERSION=1.24 - DRONE_HOST=10.1.86.206 - DRONE_GOGS=true - DRONE_GOGS_URL=http://10.1.86.207:3000/ # 代碼倉庫位址 - DRONE_SECRET=ok

 drone-agent:
 image: drone/drone:0.7
 command: agent
 restart: always
 depends_on: - drone-server
 volumes: - /var/run/docker.sock:/var/run/docker.sock
 environment: - DOCKER_API_VERSION=1.24 - DRONE_SERVER=ws://drone-server:8000/ws/broker - DRONE_SECRET=ok      

docker-compose up -d 然後你懂的,也沒有然後了

用gogs賬戶登入drone即可

每個步驟就是個容器,每個插件也是個容器,各種組合,簡直就是活字印刷術

怎麼使用這種初級膚淺的内容我就不贅述了,但是有很多坑的地方:

  • 裝drone的機器能用aufs盡量用,device mapper有些插件是跑不了的,如一些docker in docker的插件,這不算是drone的毛病,隻能說docker對 docker in docker支援不夠好
  • centos對aufs支援不夠好,如果想用centos支援aufs,那你可得折騰折騰了,社群方案在此: https://github.com/sealyun/kernel-ml-aufs
  • 最推薦的是drone的機器核心更新到4.9以上,然後docker使用overlay2存儲驅動,高版本核心跑容器筆者也實踐過比較長的時間了,比低核心穩定很多

安裝方式2,在

k8s上安裝

helm install stable/drone      

使用篇

首先在你的代碼倉庫主目錄下建立三個檔案:

  • .drone.yml : 描述建構與部署的流程(狹義),流程配置檔案(廣義)CI/CD無本質差別
  • Dockerfile : 告訴你的應用如何打包成鏡像,當然如果不是容器化傳遞可以不需要
  • k8s yaml配置檔案 or docker-compose檔案 or chart包 :告訴你的應用如何部署
  • 其他 :如kube-config等

用gogs賬戶密碼登入到drone頁面上之後同步下項目就可以看到項目清單,打開開關就可以關聯到git倉庫,比較簡單,自行探索

官方事例

pipeline:
 backend: # 一個步驟的名稱,可以随便全名
 image: golang # 每個步驟的本質都是基于這個鏡像去啟動一個容器
 commands: # 在這個容器中執行一些指令 - go get - go build
 - go test

 frontend:
 image: node:6
 commands: - npm install
 - npm test

 publish:
 image: plugins/docker
 repo: octocat/hello-world
 tags: [ 1, 1.1, latest ]
 registry: index.docker.io

 notify:
 image: plugins/slack
 channel: developers
 username: drone      

各步驟啟動的容器共享workdir這個卷, 這樣build步驟的結果産物就可以在publish這個容器中使用

結合Dockerfile看:

# docker build --rm -t drone/drone .

FROM drone/ca-certs
EXPOSE 8000 9000 80 443

ENV DATABASE_DRIVER=sqlite3
ENV DATABASE_CONFIG=/var/lib/drone/drone.sqlite
ENV GODEBUG=netdns=go
ENV XDG_CACHE_HOME /var/lib/drone

ADD release/drone-server /bin/ # 因為工作目錄共享,是以就可以在publish時使用到 build時的産物,這樣建構和釋出就可以分離

ENTRYPOINT ["/bin/drone-server"]      

上面說到建構與釋出分離,很有用,如建構golang代碼時我們需要go環境,但是線上或者運作時其實隻需要一個可執行檔案即可,

是以Dockerfile裡就可以不用FROM一個golang的基礎鏡像,讓你的鏡像更小。又比如java建構時需要maven,而線上運作時不需要,

是以也是可以分離。

用drone時要發揮想象,千萬不要用死了,上面每句話都需要仔細讀一遍,細細了解。再總結一下關鍵點:

drone自身是不管每個步驟是什麼功能的,隻傻瓜式幫你起容器,跑完正常就執行下個步驟,失敗就終止。

編譯,送出到鏡像倉庫,部署,通知等功能都是由鏡像的功能,容器的功能決定的 drone裡叫插件,插件本質就是鏡像,有一丢丢小差別後面說

這意味着你想幹啥就弄啥鏡像,如編譯時需要maven,那去做個maven鏡像,部署時需要對接k8s,那麼搞個有kubectl用戶端的鏡像;要實體機部署那麼搞個

ansible的鏡像,等等,發揮想象,靈活使用。

drone環境變量

有時我們希望CI出來的docker鏡像tag與git的tag一緻,這樣的好處就是知道運作的是哪個版本的代碼,更新等等都很友善,不過每次都去修改pipeline

檔案顯然很煩,那麼drone就可以有很多環境變量來幫助我們解決這個問題:

pipeline:
 build:
 image: golang:1.9.2 
 commands: - go build -o test --ldflags '-linkmode external -extldflags "-static"' when: event: [push, tag, deployment]
 publish:
 image: plugins/docker
 repo: fanux/test
 tags: ${DRONE_TAG=latest}
 dockerfile: Dockerfile
 insecure: true when: event: [push, tag, deployment]      

這個例子${DRONE_TAG=latest} 如果git tag事件觸發了pipeline那就把git tag當鏡像tag,否則就用latest,這樣我們自己研發過程中就

可以一直用latest疊代,覺得版本差不多了,打個tag,生成一個可以給測試人員測試的鏡像,非常優雅,不需要改什麼東西,不容易出錯

同理還有很多其它的環境變量可以用,如git的commitID 分支資訊等等,

這裡可以查

對接k8s實踐

首先得有個k8s叢集,那麼首選:

kubernetes叢集三步安裝

廣告,無視就好。。。

有了上面的鋪墊,對接k8s就相當簡單了:搞個kubectl的鏡像嵌入流程中即可:

把項目的k8s yaml檔案放到代碼中,然後pipelie裡直接apply

publish:
 image: plugins/docker # 鏡像倉庫,執行Dockerfile插件
 tags: - ${DRONE_TAG=latest}
 insecure: true # 照抄

 deploy:
 image: kubectl:test # 這個鏡像自己去打即可
 commands: - cat test.yaml
 - ls 
 - rm -rf /root/.kube && cp -r .kube /root # k8s 的kubeconfig檔案,可以有多個,部署到哪個叢集就拷貝哪個kubeconfig檔案 - kubectl delete -f test.yaml || true - kubectl apply -f test.yaml      

不過最佳實踐還有幾個細節:

  • k8s 的kubeconfig檔案同樣放在了代碼目錄(這個不×××全,不過倉庫私有還好,還可以利用drone的secret,不細展開)
  • k8s 部署的yaml檔案裡的鏡像怎麼配置? 每次都修改test.yaml多不爽
  • 如果多個叢集yaml配置有差別怎麼辦?寫多個yaml?造成混亂,非常不優雅

是以我們引入chart, 用helm進行部署:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
 name: test
 namespace: {{ .Values.namespace }}
spec:
 replicas: {{ .Values.replicaCount }} template:
 metadata:
 labels:
 name: test
 spec:
 serviceAccountName: test
 containers: - name: test
 image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" # deployment的yaml檔案是模闆,建立時再傳參進來渲染
 imagePullPolicy: {{ .Values.image.pullPolicy }} ....      

注意,有了模闆之後,我們部署v1版本和v2版本時就不需要改動yaml檔案,這樣降低出錯風險,pipeline執行時把環境變量傳進來,完美解決

這樣git tag 鏡像tag與yaml裡鏡像配置實作了完全的統一:

deploy_dev: # 部署到開發環境
 image: helm:v2.8.1
 commands: - mkdir -p /root/.kube && cp -r .kube/config-test101.194 /root/.kube 
 - helm delete test --purge || true - helm install --name test --set image.tag=${DRONE_TAG=latest} Chart when: event: deployment
 environment: deploy_dev

 deploy_test: # 部署到測試環境
 image: helm:v2.8.1
 commands: - mkdir -p /root/.kube && cp -r .kube/config-test101.84 /root/.kube # 兩個環境使用不同的kubeconfig - helm delete test --purge || true - helm install --name test --set image.tag=${DRONE_TAG=latest} Chart # 把git tag傳給helm,這樣運作的鏡像就是publish時建構的鏡像,tag一緻 when: event: deployment
 environment: deploy_test      

以上,優雅的解決了上面問題

細節:event可以是git的事件也可以是手動處罰的事件,類型是deployment時就是手動觸發的,drone支援指令行觸發

我們進行了二次開發,讓drone可以在頁面上觸發對應的事件

原理篇

drone上開通一個倉庫時,會給倉庫設定一個webhook,在項目設定裡可以看到,這樣git的事件就可以通知到drone,drone根據事件去拉取代碼走流程

pipeline基本原理

了解原理對使用這個系統非常重要,否則就會把一個東西用死。

pipeline就負責起容器而已,容器幹啥的系統不關心,使用者決定 這句話本文不止強調過一次,非常重要多讀幾遍

插件原理

鏡像即插件,也就是可能現有很多鏡像都能直接當作插件嵌入到drone流程中。

有個小差別是,你會發現drone有些插件還帶一些參數,這就是比普通的鏡像多做了一丢丢事,如publish時打docker的鏡像:

publish:
 image: plugins/docker
 repo: octocat/hello-world
 tags: [ 1, 1.1, latest ]
 registry: index.docker.io      

你會發現它有 repo tags什麼的參數,其實drone處理時非常簡單,就是把這些參數轉化成環境變量傳給容器了,

然後容器去處理這些參數。

本質就是做了這個事情:

docker run --rm \
 -e PLUGIN_TAG=latest \
 -e PLUGIN_REPO=octocat/hello-world \
 -e DRONE_COMMIT_SHA=d8dbe4d94f15fe89232e0402c6e8a0ddf21af3ab \
 -v $(pwd):$(pwd) \
 -w $(pwd) \
 --privileged \
 plugins/docker --dry-run      

那我們自定義一個插件就簡單了,隻要寫個腳本能處理特定環境變量即可,如一個curl的插件:

pipeline:
 webhook:
 image: foo/webhook
 url: http://foo.com
 method: post
 body: |
 hello world      

寫個腳本

#!/bin/sh

curl \
 -X ${PLUGIN_METHOD} \ # 處理一個幾個環境變量 -d ${PLUGIN_BODY} \
 ${PLUGIN_URL}      
FROM alpine
ADD script.sh /bin/
RUN chmod +x /bin/script.sh
RUN apk -Uuv add curl ca-certificates
ENTRYPOINT /bin/script.sh      
docker build -t foo/webhook .
docker push foo/webhook      

打成docker鏡像,大功告成

是以大部分情況我們會很懶的什麼也不寫,直接在容器裡執行指令就是了,同樣是一個curl的需求,不寫插件的話

pipeline:
 webhook:
 image: busybox # 直接用busybox
 command: - curl -X POST -d 123 http://foo.com 完事,插件都懶得開發了      

值得注意的是一些複雜功能還是需要開發插件的,如publish鏡像時用的插件。關于該插件我想補充一句

它是docker裡面起了一個docker engine,用docker内的docker engine進行打鏡像的

是以devicemapper存儲驅動是支援不了的。請更新核心用overlay2,或者ubuntu用aufs

其它推薦

  • 鏡像倉庫: harbor
  • 制品庫: nexus 做maven倉庫,yum倉庫放二進制檔案等非常合适,強烈推薦

總結

要實作高效的自動化,everything as code很重要,很多人喜歡在界面上點點點 填很多參數上線,其實是一種很容易出錯的方式

不一定能提高效率。 你們項目如何建構,如何釋出,如何部署都應該是代碼,沒有二義性,把人做的事讓程式做,最終人僅是觸發而已。

本文轉自中文社群-

基于drone的CI/CD,對接kubernetes實踐教程