本文實際上是在群內第二次分享的內容。這次其實想來聊聊,關於 CI/CD 的一些破事和演進過程中我們所需要遇到的一些問題,當然本文中是一個偏新手向的文章和一點點爆論,隨便看看就好。
開宗明義,定義先行#
在我們談論一個事物之前,我們需要對這個事物給出一個定義,那我們先來看一下我們今天要聊的 CI 與 CD 的定義。
首先,CI 指 Continuous Integration ,在中文語境中的表述是持續集成。而 CD 在常見語境下可能是兩種意思:Continuous Delivery 或 Continuous Deployment,與之對應的表述是持續交付 / 持續部署。這裡借用一下 Brent Laster 在 What is CI/CD?1 中給出的定義
Continuous integration (CI) is the process of automatically detecting, pulling, building, and (in most cases) doing unit testing as source code is changed for a product. CI is the activity that starts the pipeline (although certain pre-validations—often called "pre-flight checks"—are sometimes incorporated ahead of CI).
The goal of CI is to quickly make sure a new change from a developer is "good" and suitable for further use in the code base.
Continuous deployment (CD) refers to the idea of being able to automatically take a release of code that has come out of the CD pipeline and make it available for end users. Depending on the way the code is "installed" by users, that may mean automatically deploying something in a cloud, making an update available (such as for an app on a phone), updating a website, or simply updating the list of available releases.
光看定義,可能大家還是會很懵逼,那麼下面我們用一些實際的例子來給大家從頭捋一遍 CI/CD 那些事
Re:從 0 開始構建流程#
這個標題好像起的有點草,不過不管了。首先我們假定這樣一個最簡單的需求
我們基於 Hexo 構建了一個個人的博客系統。其中包含我們所需要發布的文章,我們配置的主題。我們需要將其發布到具體的 Repo 上。
好了,基於這個需求,我們來從 0 到 0 玩一圈吧(笑(
構建原生之初#
可能這裡有很多人會問,為啥會選擇 Hexo 來作為我們的切入點。原因很簡單啊!因為它夠簡單啊!
言歸正傳,首先 Hexo 有兩個命令 hexo g
&& hexo d
,分別是根據當前目錄下的 Markdown 文件來生成靜態的網頁。然後將生成的產物根據配置推送到對應的 repo 上
OK,那麼我們在最原始的階段一個構建的流程就是這樣
- 用一個編輯器,開開心心的寫文章
- 然後在本地終端執行
hexo g && hexo d
問題來了,現在有些時候提交了博客,但是忘了執行生成命令怎麼辦?或者是我每次都需要敲重複的命令很麻煩怎麼辦?那就讓我們把整個過程自動化一下吧。Let's rock!
更進一步的構建#
OK,我們先來假設一下,我們如果完成了自動化,我們現在發布一個博客的工作流應該編程什麼樣的
- 我們編寫一個 Markdown 文件,推送到 GitHub 倉庫裡的 Master 分支上
- 我們的自動任務開始構建我們博客,生成一系列靜態文件和樣式
- 將我們的靜態文件和樣式推送到我們的站點 Repo/CDN 等目標位置
好了,那麼這裡有兩個核心的問題
- 在我們推送代碼的時候,自動開始構建
- 在構建完成後,推送產物
那我們現在基於 GitHub Action 來配置一套我們的自動化構建任務
name: Build And Publish Blog
on:
push:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '12.x'
- name: Install Package
run: npm install -g hexo-cli && npm install
- name: Generate Html File
run: hexo g
- name: Deploy 🚀
uses: JamesIves/[email protected]
with:
GITHUB_TOKEN: ${{ secrets.PUBLSH_TOKEN }}
BRANCH: gh-pages # The branch the action should deploy to.
FOLDER: public # The folder the action should deploy.
我們能看到這段配置實際上完成了這樣一些事情
- 在我們往 master 分支提交代碼的時候觸發構建
- 拉取代碼
- 安裝構建所需依賴
- 構建生成靜態文件
- 推送靜態文件
如果上面任何一個步驟失敗了,都將取消後面步驟的執行。實際上這樣一個簡單的任務已經包含了一個 CI && CD 所包含的基礎要素(這裡 CD 我並未嚴格區分 Continuous Delivery/Continuous Deployment)
- 與已有的代碼持續的構建與集成
- 集成中區分多個 phase。每個 phase 將依賴上個 phase 結果。
- 將構建產物交付 / 部署出去。交付 / 部署的成功依賴於集成的成功
那麼在這裡,我們將博客系統換成一個我們工程中的例子。將 Hexo 換成我們的 Python 服務。將新增博文換成我們新增的代碼。將構建命令換成 mypy/pylint 等檢查工具。你看,CI/CD 實際上和你想像的複雜的系統,是不是有很大差別?
這裡可能有很多人會提出這樣一個問題,如果說這裡我們將這些命令,不用線上的形式觸發。而在本地用 Git Hook 等形式進行實現。那麼這算不算一種 CI 與 CD 呢?我覺得毫無疑問算的,從我的視角來看,CI/CD 核心的要素在於通過可以重複,自動化的任務,來盡早暴露缺陷,減輕人為因素所帶來的不必要的事故發生。
這個開發過份傻逼卻不謹慎#
首先拋出一個最基礎的爆論,然後我們接著往下談
所有人都有傻逼的時候,而且這個傻逼的時候可能還會很多。
在這樣一個爆論的情況下,我們來回顧一下上面舉基於 Hexo 去構建一個個人博客系統的例子中,如果我們不選擇通過一種收斂的,自動化的系統去解決我們的構建,發布需求。那麼我們哪些環節會出現風險
- 最基礎的,寫完博客,忘了構建,忘了發布
- 比如我們升級一下依賴中的 Hexo 版本或者主題版本,我們沒有測試,導致構建出來的樣式失效
- 我們的 Markdown 有問題,導致構建失敗
- 比如多個人維護一個博客的情況下,我們每個人都需要保存目標倉庫 / CDN 的密鑰等信息。導致信息洩漏等
將基於 Hexo 去構建一個個人博客系統的例子切換成我們日常開發的場景,那麼我們可能遇到的問題會更多。簡單舉幾個
- 沒法很快速的回滾
- 沒法溯源具體的構建 / 發布記錄
- 沒有自動化的任務,研發懶得跑測試或者 lint 導致代碼腐化
- 高峰期上線導致事故
嗯,這些問題大家是不是都很熟悉?大概就是,我起了,構建了,出事故了,有啥好說的 23333
講到這裡的大家實際上有沒有發現一個問題?我在這篇文章中,沒有對 CI 與 CD 進行區分?從我的視角來看,CI/CD 本質上是踐行的同一事。即 對於研發流程與交付流程的收斂
從我的視角來看,去構建一個 CI/CD 系統核心的目標在於
- 通過收斂入口以及自動化的任務觸發,盡可能減輕人為因素所帶來的系統不穩定性
- 通過快速,多次,可重複,無感知的任務,盡可能的在較早階段暴露系統中的問題
在這樣兩個大目標的前提下,我們便會根據不同的業務場景,採用不同的手段與形式豐富我們 CI/CD 中的內容,包括不限於
- 在 CI 階段自動化的單元測試,E2E 測試等
- 在 CI 階段周期性的 Nighty Build 等
- 在 CD 階段進行發布管控等
不過無論我們怎麼樣去構建一個 CI/CD 系統,或者選擇什麼樣的粒度去進行 CI/CD。我覺得一個合格的 CI/CD 系統與機制 都需要遵照這樣幾個原則(個人向總結)
- 入口的收斂,SOP 的建立。如果不達成這點共識,研發能夠通過技術手段繞過 CI/CD 系統那麼便又回到了我們本章的標題(這個研發過份傻逼卻不謹慎)
- 對於業務代碼無侵入
- 集成任務 / 發布任務一定要是自動化,可重複的
- 可回溯的歷史記錄與結果
- 可回溯的構建集成產物
- 從上到下的支持
那麼遵照我總結的這幾個原則,我們來迭代一下我們之前的博客的發布過程
name: Build And Publish Blog
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '12.x'
- name: Install Package
run: npm install -g hexo-cli && npm install
- name: Generate Html File
run: hexo g
- name: Deploy To Repo🚀
if: ${{ github.ref == 'refs/heads/master'}}
uses: JamesIves/[email protected]
with:
GITHUB_TOKEN: ${{ secrets.PUBLSH_TOKEN }}
BRANCH: gh-pages # The branch the action should deploy to.
FOLDER: public # The folder the action should deploy.
- name: Upload to Collect Repo
uses: JamesIves/[email protected]
with:
GITHUB_TOKEN: ${{ secrets.PUBLSH_TOKEN }}
BRANCH: build-${{ github.run_id }} # The branch the action should deploy to.
FOLDER: public # The folder the action should deploy.
在這段更改後的構建流程中,我選擇以 PR 為粒度去觸發 CI 流程,並將歷史產物進行存儲,而在合併主分支後,新增發布流程。在這樣一來,我在博客構建發布的時候,便能夠通過回溯的歷史產物來驗證我框架升級,新增博文等操作的正確性。同時依托 GitHub Action,我能很好地完成歷史構建的回溯
嗯,這樣便可以盡可能避免我傻逼的操作所帶來的各種副作用(逃
進擊的構建:終章#
好了,啥都沒有,傻眼了吧
。
。
。
。
只是開個玩笑。實際上本文到這差不多就可以告一段落了。實際上大家通過這篇文章可以發現一個問題。就是實際上構建一個 CI/CD 系統可能並不會涉及很多,很高深的技術問題 (極少數的場景除外)無論是傳統的 Jenkins,還是新生的 GitHub Action,GitLab-CI,亦或者是雲廠商提供的服務都能很好地幫助我們去構建一套貼合業務的 CI/CD 系統。但我之前在推特上發表了的一個爆論 “CI/CD 的建立往往不是一個技術問題,而是一個制度問題,更可以稱為是一個想法問題”。
所以,我希望我們每個人都能認識到我們都會犯錯這樣一個事實。然後盡可能的將自己所負責的系統的開發流程與交付流程盡可能的收斂與自動化。讓一個 CI/CD 真正稱為我們日常工作中的一部分。
差不多這樣,溜了,溜了。