容器環境持續集成優化,Drone CI 提速 500%

前文介紹了容器環境下 Drone + semantic release 實現的語義化持續集成 Workflow,爲了方便演示,流程僅給出了工做流中最重要的幾個環節,實際用起來可能會發現很多值得優化的地方。html

所以本次在這個工做流的基礎上,介紹一些容器環境下 CI 的優化及提速方法,方法自己不限定必定要使用 Drone,使用一樣的思路徹底能夠套用到其餘的 CI 工具中。前端

優化前項目概況

以一個生產環境的實際項目爲例,項目的主要結構以下node

├── Dockerfile
├── dist/
├── node_modules/
├── package.json
└── src/
複製代碼

這是一個比較常見的基於 React 的前端項目,用 npm list | wc -l 能夠看到有 3952 個依賴,項目會經過 webpack 打包到 dist目錄下,打包命令被封裝成 npm run build。最終 dist 目錄經過 Dockerfile 被打包到 Nginx 的 Docker 鏡像內, 生產環境直接運行打包後的鏡像便可。webpack

Dockerfile 是這樣編寫的nginx

FROM node:10 as build  
WORKDIR /app COPY . /app RUN npm install RUN npm run build 
FROM nginx:1.15-alpine  
COPY --from=build /app/dist /usr/share/nginx/html 複製代碼

使用了 Docker 的多階段構建功能,即 npm 的安裝,編譯做爲第 1 個階段,編譯完成後僅將編譯的結果 dist 文件夾複製出來,其他未複製的文件丟棄,這樣打包後的鏡像僅爲 23.2MB,更利於部署。git

流程瓶頸分析

發佈流程直接套用前文介紹的 Gitflow + semantic release 工做流。能夠看到此時的一次發佈是比較慢的,push 到 master 構建 staging 鏡像用時 9:18,semantic release 打上 Tag 構建 production 鏡像用時 6:24。github

這個過程當中到底慢在哪裏呢, 在 Drone 的構建過程當中看到,容器構建的耗時佔了 90%以上,一方面 npm 須要下載安裝 3000 多個依賴,另外一方面 webpack 的編譯也須要 30s 左右,若是網絡再有不穩定,等待時間無疑會更長。web

另外一個耗時的元兇也很明顯,因爲引入了 semantic release, push master 和 release 兩個動做會觸發 2 次 CI,每次 CI 都進行了 Docker 鏡像的構建,但其實若是沒有異常發生,兩個 Docker 鏡像對應的實際上是同一份代碼,應當是徹底一致的,即 release 時的鏡像構建所花費的時間是浪費的。docker

其餘固然還有應用層面的優化,好比能夠用 yarn 替代 npm,使用更快的源,去除沒必要要的依賴等等,但這些並不在本文的討論範圍內,就略過不提。npm

引入緩存減小重複下載

每次構建都要下載 3000 多個依賴,那麼最容易想到的固然是將這些依賴緩存起來,可是在這個項目中,npm 下載/編譯都發生在容器構建環節,這是比較難引入緩存的。所以首先要作的,是將下載/編譯過程從容器轉移到 CI,經過 CI 完成下載/編譯,再將結果複製到容器鏡像內。

在下載/編譯轉移到 CI 的基礎上,能夠直接使用 Drone 提供的緩存插件,目前根據不一樣文件系統,Drone 可選的緩存插件有

這裏以 Volume Cache 爲例,.drone.yml以下。這裏的語法對應 Drone-v1.0 以上版本,可能與官方部分舊文檔有出入。

steps:
- name: restore-cache  
 image: drillster/drone-volume-cache  
 settings:  
 restore: true  
 mount:  
 - ./.npm-cache  
 - ./node_modules  
 volumes:  
 - name: cache  
 path: /cache   

- name: npm-install  
 image: node:10  
 commands: 
 - npm config set cache ./.npm-cache --global  
 - npm install  
 
- name: build-dist  
 image: node:10  
 commands:  
 - npm run build

- name: rebuild-cache  
 image: drillster/drone-volume-cache  
 settings:  
 rebuild: true  
 mount:  
 - ./.npm-cache  
 - ./node_modules  
 volumes:  
 - name: cache  
 path: /cache

volumes:  
 - name: cache  
 host:  
 path: /tmp/cache
複製代碼

Volume Cache 插件使用很簡單,首先須要聲明一個 Volume,對應主機的一個文件夾,這裏使用的是/tmp/cache。Volume Cache 插件的參數中,mount 列出須要緩存的文件夾,restore: true會將文件從主機複製到容器,所以放在 pipeline 的開頭,rebuild: true則反之,放在 pipeline 最後。

另外注意使用 Volume 須要在 Drone 中將 Repo 設置爲 Trusted。

而此時的 Dockerfile 就只剩下文件複製的部分了

FROM nginx:1.15-alpine  
COPY ./dist /usr/share/nginx/html 複製代碼

在增長了緩存後,構建的時長大幅降低到 2:38,總體耗時降低了 50%以上。

經過 Docker Tag 省略重複的構建

在上文的基礎上,不難想到 push master 和 release 形成的重複構建,是否也能夠一樣經過緩存去除。這固然在理論上也是可行的,可是因爲緩存並不穩定,所以須要更爲通用的方法。

在 semantic release 的流程中,push master 和 release 的惟一區別就是 release 增長了一個 git tag。而 git tag 本質上只是對一個特定 commit 的引用,並不會改變 commit 記錄,所以 push master 和 release 兩次觸發的 CI 中,最後一次 commit 是相同的,即 DRONE_COMMIT_SHA 不會改變。

基於這一點,咱們能夠在 push master 的構建中,將DRONE_COMMIT_SHA做爲 Docker 鏡像額外的 Tag,在 release 環節,只要給有 DRONE_COMMIT_SHA Tag 的鏡像再打上最終的版本號 Tag 便可,並不須要在 release 環節從頭構建鏡像。

這個過程對應 .drone.yml 以下

 - name: push-docker-staging  
 image: plugins/docker  
 settings:  
 repo: allovince/xxx
 username: allovince  
 password: 
 from_secret: DOCKER_PASSWORD  
 tag:  
 - staging  
 - sha_${DRONE_COMMIT_SHA} 
 when:  
 branch: master  
 event: push  
  
 - name: semantic-release  
 image: gtramontina/semantic-release:15.13.3  
 environment:  
 GITHUB_TOKEN:  
 from_secret: GITHUB_TOKEN  
 entrypoint:  
 - semantic-release  
 when:  
 branch: master  
 event: push  
  
 - name: push-docker-production  
 image: plugins/docker  
 environment:  
 DOCKER_PASSWORD:  
 from_secret: DOCKER_PASSWORD  
 commands:   
 - docker -v  
 - nohup dockerd &  
 - docker login -u allovince -p $${DOCKER_PASSWORD}  
 - docker pull allovince/xxx:sha_$${DRONE_COMMIT_SHA}  
 - docker tag allovince/xxx:sha_$${DRONE_COMMIT_SHA} allovince/xxx:$${DRONE_TAG}  
 - docker push allovince/xxx:$${DRONE_TAG}  
 when:  
 event: tag  
 privileged: true
複製代碼

假設最後一次 commit 的 hash 是 c0558777, release 版本是 v1.0.9, push master 後, 鏡像將打上

  • staging
  • sha_c0558777

兩個 Tag,在 release 後,鏡像將再增長一個 v1.0.9的 Tag。

須要注意的是爲鏡像打 Tag 使用到了 Docker-in-Docker,須要 privileged flag,即privileged: true

同時 docker tag 等命令依賴 docker daemon 的啓動,不然會報錯

Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

一種方式是掛載主機的 daemon /var/run/docker.sock,另外一種方式是在容器內啓動 docker daemon,我這裏使用的是後者,對應 nohup dockerd &,而在 release 階段,因爲任務僅僅是爲 docker 鏡像額外增長一個 tag,以及通知生產環境發佈,所以上文中的 cache 等環節均可以經過條件省略,結果以下。

如此優化後 release 環節的耗時縮短到 1 分鐘之內,看看最終成果,從代碼提交到發佈完成,總耗時不到 5 分鐘,是比較友好的。

相關文章
相關標籤/搜索