前文介紹了容器環境下 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%以上。
在上文的基礎上,不難想到 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 後, 鏡像將打上
兩個 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 分鐘,是比較友好的。