一、寫在前面
1、開發模式的演進
(1) 傳統的開發模式
在傳統的開發模式下,開發、運維、實體機三者之間的關系是非常緊密的。當開發完成項目後,運維會負責把項目部署到一台實體機上,由這台實體機向外提供服務。
由于服務和實體機關系緊密,導緻服務非常依賴于實體機的環境。一旦需要調換實體機器,運維同僚又需要在另一台實體機上安裝服務依賴的環境,經過一頓折騰後,才能完成服務的部署。
(2) 容器的颠覆革命
為了解決這個問題,出現了一種名為虛拟機的作業系統虛拟化産品。不過還發展沒有太久,就已經被一種更輕量級的作業系統容器化産品替代了,它就是Docker。
運用Docker容器技術,運維可以把服務依賴的環境資源都編入 Image 中,然後把服務運作在 Container 上,實作服務與實體機的解耦。開發人員也可以将運維的工作以可程式設計的方式編入 Dockerfile 檔案中,從此打破了開發和運維之間的壁壘,大大降低了部署成本。
(3) 靈活開發的創新
靈活開發把一個産品傳遞過程拆分為了多個小周期,創新性的通過不斷重複疊代的方式,實作産品的逐漸改進和提前傳遞。從此改變了以往瀑布流模式下的大周期開發問題,可以提前了解市場需求,規避風險。
(4) Devops文化運動
正是在基于容器、靈活開發、CI/CD 等各種前瞻技術思想的發展下,催生了一場名為 Devops 的文化運動。在這場文化運動中,更加強調加強開發、測試、運維之間的溝通。現在已經越來越多的公司或團隊開始重視和投入到 Devops 的建設中,基于 CI/CD 建構服務端的自動化,讓整個開發模式高效高品質的同時,也更加流程化、規範化。
2、演進過程中的特點
從上可以發現,随着技術發展,RD 與測試、運維之間的不再是泾渭分明的工作線,反而他們之間的關系愈加緊密,RD 的整個開發模式也開始向自動化方向演進。
3、為什麼要寫這篇文章
在目前的實際工作中,遇到了一系列影響效率和品質的問題。雖然目前有現成的 CI/CD 機制可以使用,但它的接入成本較高且不易擴充。
是以開始嘗試運用 Devops 思想并結合 CI/CD 技術,建立一個基于CI的服務端自動化,把項目的開發、單測、集測、部署、檢測、監控等整個完整的項目流程,都閉環內建至這個自動化中,實作更加快速高效高品質地傳遞産品。
經過一段時間的實踐,效果很好,驗證了想法的正确性,故總結和分享此文章,為大家提供思路與經驗。
4、這篇文章主要寫了什麼
本文會以 CI/CD 技術為核心,重點介紹它能為我們帶來什麼,我們為什麼要設計自己的CI服務端自動化,以及如何設計。
5、閱讀文章前的注意内容
- CI/CD 這項技術,下文中會簡稱為 CI
- 文章内容如果出現錯誤了解、病句、錯别字等,請見諒并歡迎大家的指出
二、問題現狀
1、項目疊代速度快
在瀑布流開發模式下,一個項目的周期是足夠長的,一般有充足的時間來完成整個項目流程。但在靈活開發模式下,開發内容的縮減,伴随而來的也是需求、測試等環節時間的縮減,導緻疊代速度也越來越快。
舉例 :今天需要開發一個功能,但可能不經過測試,産品驗收後就可以上線。
2、第三方工具太重
市面上流行的各種第三方工具大都非常重,功能繁多,依賴複雜,甚至還都會附帶一個背景管理系統。導緻很多時候我們隻需要某個工具10%的功能,但不得不接受它90%附加的功能,這類重型工具隻适合穩定發展的項目。
對于快速疊代的項目,需要的是輕量、輕量、還是輕量!
舉例 :接口文檔自動生成工具中,大部分都需要私有伺服器部署而且會附有各種管理系統。
3、測試的時間緊張
在傳統項目流程中,測試一直是時間配置設定最少的環節,再加上多端測試、Bug驗證以及整體回歸,導緻測試時間不足的問題很常見也很難避免。
舉例 :QA在驗某個功能時發現一直有問題,由于RD沒有寫單元測試,較久後才排查出原因,導緻QA測試時間更加緊張
4、需求的滿足較慢
在上面已經說道,靈活開發下的節奏整體是非常快的。對于RD而言,無論是需要 QA、OP、還是 SRE 等任何一方的需求,滿足速度雖然可能不慢,但還是不能支援靈活開發下疊代的速度。
舉例1 :某個業務接口需要自動化測試支援,但 QA 可能需要排期才能完成
舉例2 :後端服務上線時可能由于配置缺少等問題發生 panic,需要 SRE 在 CI 中新增配置檢查,但需要排期才能完成
三、如何解決
在面對以上種種問題下,如果沒有一個完整健全的機制,是難以輕松應對靈活開發這種快疊代速度的,傳遞品質也會大打折扣。
是以為了解決這個問題,我們開始運用 Devops 思想,基于 CI 來建立一套完整的、覆寫廣的服務端自動化,打破不同部門之間的壁壘,适應快速疊代,滿足品質要求。
四、我們的實踐
1、設計方案
(1) 輕量化設計
在設計之初,最主要的目标就是輕量化 。輕量絕不代表着不完整或不成熟,反而是省去了所有的細枝末節後,用一種更少的成本,更快速的滿足實作了我們的需求。 是以在整個設計中,都會貫穿輕量化的思想。
- 僅依賴 Gitlab 現有的 Runner 機器作為伺服器,沒有再使用額外的機器資源
- 沒有使用額外的背景管理系統,直接選擇了 Gitlab Repository 作為托管服務,接口文檔分别放置在 Gitlab倉庫的 /apidoc.md 和 Wiki 中
- 雖然使用了第三方緩存倉庫,但為了速度足夠快,我們希望可以不使用50MB以上的工具。其實到目前為止,大部分使用的工具都在10MB以下。
(2) 多項目共用
在微服務架構下,一套代碼被劃分到多個代碼庫中,多個代碼庫下都有自己的 CI 代碼,一旦 CI 中任意一個流程有變動,那麼所有項目都需要配合修改,造成的整體關聯調整過大。
為了解決這個問題,采用了如下多項目共用的設計。在這個設計中,不需要再把 CI 運作邏輯寫在 gitlab-ci.yml 檔案中了,而是寫在 CommonCI 這樣的倉庫中,并由 start.sh 腳本啟動。
(3) 插拔式任務
基于插拔式擴充的思想,所有的任務也都是可插拔、易擴充且完全可控的,還可以實作多任務的編排。
(4) 第三方緩存
在運作過程中,雖然 gitlab CI 提供了如 cache 和 artifacts 這樣的中間産物功能,但它們會有很多限制,有時候可能無法良好滿足。是以會設計自己的第三方 Cache 庫,用來存放已經編譯好的二進制檔案,加快 CI 的執行時間。
2、技術和思想
(1) 使用到的技術
- Gitlab 技術棧 :基于 gitlab CI 和 gitlab API 實作流水線的自動工作和相關托管功能
- Shell 程式設計 :為了實作流水線中不同任務的插拔和編排,需要使用大量的 Shell 程式設計
- Go 技術棧 :對于配置檢查、依賴檢查、接口文檔生成、自動化測試等一系列需要對業務代碼處理的工作,都依賴 Go 技術棧
- 容器技術棧 :雖然目前僅第三方緩存庫基于 Docker 對各種源碼包編譯實作,不過為了友善支援日後的服務容器化管理,容器技術棧會在計劃之中
(2) 使用到的思想
- Devops :「為什麼要做服務端自動化」「這樣做的意義是什麼」「如何去做」等等,它們的背後都是 Devops 思想
3、整體的架構
基于 CI 建立的服務端自動化架構如下所示,它一共分為三層 :
- 代碼倉庫層 :是代碼倉庫的總稱,包括業務和通用倉庫
- Runner 層 :是 Gitlab 配置的 Runner ,是實際運作 CI 的伺服器
- CI 自動化層 :是對服務端自動化的抽象,包括了一系列的插拔式任務,它們共同構成了整個自動化的流水線。
4、代碼倉庫層
從上圖架構中的代碼倉庫層可知,除了業務代碼倉庫外,還有通用代碼倉庫,其中最重要的就是我們設計的 CommonCI 倉庫。CommonCI倉庫是解決多項目 CI 共用的核心實作,它的目錄結構如下所示。
- /.gitlab :包括通用方法和不同任務的 CI 目錄
- /start.sh :啟動 CI 的腳本。當執行這個腳本時,會自動執行 /.gitlab 目錄下的 *.sh 腳本檔案 ,它們就是服務端自動化中的所有任務
5、CI 自動化層
(1) 預安裝
詳情請見源代碼 .gitlab/apidoc_gen.sh
在執行 CI 前,可能需要大量的安裝操作,我們都放在了 .gitlab/pre_install.sh 預安裝腳本中。
(2) 代碼檢查
詳情請見源代碼 .gitlab/check_code.sh
一般情況下,對于代碼檢查工具的使用,不僅僅是為了規範代碼,更多更強烈的需求是希望它能盡可能的幫助我們檢查出大部分的代碼錯誤。
通過代碼掃描、詞法/文法分析、控制流分析等技術實作程式的靜态分析,甚至還可以針對我們的業務做定制化的 Bug 分析。
是以我們在 CI 中添加了 golangci-lint 代碼檢查工具,讓RD可以更加放心的送出代碼。
① 定制化檢查
如以下代碼是在業務中比較常見且易犯的錯誤,基于定制化 Bug 分析的這種場景需求,還可以開發更加輕量的内部代碼檢查工具,它可以簡單的分析識别出以下文法錯誤。
// 正确
var logger = logging.For(ctx, "arg1", "value1", "arg2", value2)
// 錯誤
var logger = logging.For(ctx, "arg1", "value1", "arg2")
(3) 配置檢查
在開發階段,在測試環境添加了某個(Kafka、Redis、Server)的配置後,可能由于疏忽線上上忘記了添加對應的配置,導緻服務一上線就發生 panic 或觸發某個邏輯後産生 panic ,嚴重可能會影響到業務。
為了避免這種情況,基于這種場景,我們基于 Go 開發了一個輕量的配置檢查工具,并通過 Shell 內建至 CI 中,它可以基于測試和線上兩種環境的配置内容,做出相應的 diff ,幫助我們檢查是否缺少相應的線上配置。
(4) 依賴檢查
在微服務架構下,一般每個業務至少都會有幾十個代碼庫,對于相似的邏輯,有時候避免不了需要跨項目甚至跨業務的複制粘貼,如果稍加不注意,就很容易出現把A項目的 package 錯誤添加到了B項目的依賴中。
如果你夠幸運的話,代碼可能會跑不起來,這時候就會發現原來是引入了錯誤的依賴,修改後即可避免一次錯誤。如果不幸運,代碼可能會運作起來,導緻上線後可能才會在某個條件下觸發這個錯誤,進而影響業務。
為了避免這種情況,基于這種場景,我們基于 Go 開發了一個輕量的依賴檢查工具,并通過 Shell 內建至 CI 中,它會解析項目的 Godeps.json 檔案,從中找出錯誤的依賴。
(5) 單元測試
詳情請見源代碼 .gitlab/unit_test.sh
相信大部分的 RD 都有這樣的經曆,開發了一期大需求,雖然QA也正常測試完成了,但看着幾十個檔案、上千行代碼的 diff ,總感覺心裡沒底。出現這個問題的大部分原因,是因為 RD 自己沒有做好單元測試。
由于 RD 的代碼對 QA 來說是透明的,他們根本不知道代碼的邏輯是什麼,隻能從使用者的使用角度去測試,但使用者的動作是無法枚舉完的,總會有想象不到的地方。
是以為了避免這個問題,也為了降低代碼改動對以前業務邏輯的影響,我們在項目代碼中編寫了較大量的單元測試并內建至 CI 中。
(6) 本地建構
詳情請見源代碼 .gitlab/local_build.sh
現代DevOps涉及軟體應用程式在整個開發生命周期内的持續內建和持續部署。是以我們在 CI 中內建了本地建構的任務,它會完成整個項目部署建構的過程,包括打包上傳等後續操作。
(7) 啟動自檢
詳情請見源代碼 .gitlab/health_check.sh
根據以往經驗,項目線上上或者測試環境部署時直接出現 panic 的情況是時有發生的。為了解決這個問題,上面的配置檢查是其中一個方案,但它還遠遠不夠,因為我們的服務并沒有真正的啟動起來,如果不啟動就無法确定服務是否真的可以正常運作。
是以為了避免這種情況,我們在 CI 中內建了服務啟動和自檢查的功能。
(8) 接口文檔生成
詳情請見源代碼 .gitlab/apidoc_gen.sh
衆所周知,接口文檔的治理一直是一個開發流程中非常難真正解決掉的問題,是以也由此催生很多和 Swagger 類似的接口文檔自動生成工具。
不過我們并沒有使用 Swagger ,雖然它足夠應對遇到的所有場景,但是它太過龐大,完全不适合我們的業務使用。
是以我們用 Go 開發了一個基于 Model 的 API Mock 工具,它是一個絕對輕量且能滿足業務需求的接口文檔生成工具,不但不需要伺服器托管,還節省了大量成本。
(9) 接口自動化測試
詳情請見源代碼 .gitlab/api_test.sh
雖然我們已經有了單元測試,但單元測試隻是對某個邏輯中細小的一個單元進行測試,無法確定整個接口層面的正常工作。
是以為了更高一層的服務穩定考慮,我們決定加入自動化測試,它不但可以替代一部分 QA 的工作,而且還可以提高服務的穩定性。
實作原理是我們建立了一個基于 Go 語言的接口自動化測試倉庫,RD 負責編寫相關的接口測試用例,最後通過 Shell 接入至 CI 中。
(10) 任務的添加删除
當需要添加新的任務或删除任務時,隻需要在 start.sh 腳本中添加或删除即可。
比如現在需要新增一個自動部署的任務,先添加 auto_deploy.sh 腳本 ,然後添加到 start.sh 腳本中即可 ,如下所示。
6、項目如何接入
在以往的項目中,當接入 CI 時,需要在 .gitlab-ci.yml 檔案中寫大量複雜的代碼邏輯,可維護性非常差。為了支援多業務的快速接入,必須盡可能減小接入成本。
(1) 隻需要建立一個配置檔案
不同項目接入時,隻需要建立一個非常簡單的 .gitlab-ci.yml 檔案,然後按照如下模闆化的方式配置業務方所需要的變量即可。
# this example yml file: https://jihulab.com/WGrape/apimock-example/-/blob/main/.gitlab-ci.yml
image: golang:1.17
variables:
# variable configuration for [your private gitlab host]
GITLAB_HOST: ""
GITLAB_API_TOKEN: ""
# variable configuration for [your project]
PROJECT_NAME: "apimock-example"
PROJECT_ID: 48845
# variable configuration for [DingDing WebHook]
DING_KEYWORD: "apimock-example"
DING_ACCESS_TOKEN: ""
DING_NOTICE_SWITCH: "off"
# variable configuration for [check code]
CHECK_CODE_SWITCH: "on"
# variable configuration for [unit test]
UNIT_TEST_TRIGGER_CMD: "cd mock && go test -v . && cd .. && \
cd service && go test -v . && cd ..
"
UNIT_TEST_SWITCH: "on"
# variable configuration for [apidoc generator]
APIDOC_TRIGGER_CMD: "cd mock && go test -v . && cd .."
APIDOC_FILE: "apidoc.md"
APIDOC_SWITCH: "off"
# variable configuration for [local build]
LOCAL_BUILD_TRIGGER_CMD: "go mod download && go build -o project && nohup ./project &"
LOCAL_BUILD_SWITCH: "on"
# variable configuration for [health check]
HEALTH_CHECK_TRIGGER_CMD: "curl -X GET 127.0.0.1:8000/ping"
HEALTH_CHECK_SUCCESS: "ok"
HEALTH_CHECK_SWITCH: "on"
before_script:
- echo '====== CIManager Start Running ========='
after_script:
- echo '====== CIManager Stopped Successfully ========='
stages:
- CIManager
CIManager:
stage: CIManager
script:
- git clone -b testing https://github.com/wgrape/CIManager.git ; cp -an ./CIManager/. ./ ; rm -rf ./CIManager ; bash start.sh
(2) 自動建立一個配置檔案
建立一個配置檔案的方式十分簡單友善,但這種方式還是需要相應的人工成本。
為了更加低成本的接入,可以使用我們的CLI工具,它基于 read -p 指令和模闆替換的思想,通過人機互動的輸入,可以完成配置檔案的自動建立。
(3) 配置檔案的構成
從上面的配置檔案中可以發現,它主要由 image 、variables 、before_script 、after_script 、stages 這5個部分構成。
- image :指定一個鏡像
- stages :定義了 uniteci 這唯一的一個 Stage
- before_script :在 UniteCI Stage 執行前需要執行的指令
- after_script :在 UniteCI Stage 執行後需要執行的指令
- variables :整個配置的核心,它定義了在 UniteCI Stage 中所有需要的變量
(4) 配置檔案的特點
它不同于傳統配置内容主要展現在以下幾個方面。
- 隻有一個Stage
- 主要是基于變量驅動的方式
- 極簡的配置設計,降低了編寫和接入的門檻,提高可維護性
7、緩存帶來的性能提升
一切的設計都是有原因的!之是以設計中使用第三方緩存,是因為在早期整個 CI 運作過程中,大量的耗時都在 golangci-lint 工具的下載下傳和編譯上面,正常耗時都在5分鐘~10分鐘,有時甚至直接挂起到幾十分鐘以上,嚴重影響正常使用。
在嘗試了artifacts和cache方案無法解決時,決定開始使用第三方緩存,我們把下載下傳編譯好的 golangci-lint 工具放在了第三方緩存庫中,這樣每次直接下載下傳這個編譯後的二進制檔案即可。
後期使用了緩存服務後,幾乎每次運作整個 CI 時,都可以在1分鐘内即可完成,速度提升了足足5倍以上!
五、我們的目标
在經過我們的實踐後,最終确立了最終要實作的目标 :保證服務穩定且高效的疊代
1、穩定
在整個項目過程中,特别是上線流程中,不出現各種低級錯誤導緻的部署失敗問題,比如以下情況
- 缺少某個服務配置,導緻panic
- 資源未初始化導緻的panic,如map未初始化
- 代碼在修改時影響到了其他的邏輯,導緻其他地方出現bug
- 代碼有低級錯誤,但是沒有自測,導緻服務部署時根本無法部署成功
2、高效
在整個項目過程中,特别是開發聯調和測試跟進流程中,盡可能少出現低效率或工作進度阻塞的問題,比如以下情況
- 提測的項目根本無法達到提測标準,測試工作嚴重阻塞
- 開發過程中由于接口文檔等問題,導緻前後端工作效率都受到較大影響
六、回顧和總結
閱讀至此,本文已經臨近結束了。下面我們再來回顧下主要内容 :
1、在靈活開發的快速疊代下,我們必須選擇一種合适的服務端自動化方案,來提高整個開發周期的速度、品質、和流程規範化。
2、正是基于這個背景,我們才運用 Devops 思想,基于 CI 建立了一套完整的、覆寫廣的服務端自動化。
3、在設計的過程中,我們的目标是更加的輕量、可擴充和低接入成本,友善随時随地快速疊代。
4、在實踐的過程中,我們遇到了如多項目共用、執行速度慢等諸多問題并逐一解決。
言而總之,本文從原因到方案,為大家分享了一個比較全面的《基于CI的服務端自動化》解決方案,希望對大家有所幫助。不過還有一點必須要清楚的是,我們做這個東西不是為了做而做的,而是有切實的背景需求。這樣即使在快節奏的疊代下,它也可以為整個開發流程提高效率和品質。