程序員是很懶的動物,因此想各類辦法解決重複勞動的問題,若是你的工做流中還在重複一些事,那麼可能就得想一想如何優化了前端
持續集成就是能夠幫助咱們解決重複的代碼構建,自動化測試,發佈等重複勞動,經過簡單一個提交代碼的動做,解決接下來要作的不少事。vue
容器技術使這一切變得更完美。java
典型的一個場景:node
咱們寫一個前端的工程,假設是基於vue.js的框架開發的,提交代碼以後但願跑一跑測試用例,而後build壓縮一個到dist目錄裏,再把這個目錄的靜態文件用nginx代理一下。
最後打成docker鏡像放到鏡像倉庫。 甚至還能夠增長一個在線上運行起來的流程。nginx
如今告訴你,只須要一個git push動做,接下來全部的事CI工具會幫你解決!這樣的系統若是你還沒用上的話,那請問還在等什麼。接下來會系統的向你們介紹這一切。git
首先SVN這種渣渣軟件就該儘早淘汰,沒啥好說的,有git真的沒有SVN存在的必要了我以爲。程序員
因此咱們選一個git倉庫,git倉庫比較多,我這裏選用gogs,gitea gitlab都行,根據需求自行選擇github
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不夠全。golang
當你用過drone以後。。。web
裝:
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便可
每一個步驟就是個容器,每一個插件也是個容器,各類組合,簡直就是活字印刷術
怎麼使用這種初級膚淺的內容我就不贅述了,可是有不少坑的地方:
安裝方式2,在k8s上安裝:
helm install stable/drone
首先在你的代碼倉庫主目錄下新建三個文件:
用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的鏡像,等等,發揮想象,靈活使用。
有時咱們但願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集羣,那麼首選: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
不過最佳實踐還有幾個細節:
因此咱們引入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就負責起容器而已,容器幹啥的系統不關心,用戶決定 這句話本文不止強調過一次,很是重要多讀幾遍
鏡像即插件,也就是可能現有不少鏡像都能直接看成插件嵌入到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
要實現高效的自動化,everything as code很重要,不少人喜歡在界面上點點點 填不少參數上線,實際上是一種很容易出錯的方式
不必定能提升效率。 大家項目如何構建,如何發佈,如何部署都應該是代碼,沒有二義性,把人作的事讓程序作,最終人僅是觸發而已。
我的看法,探討可加QQ羣:98488045