有贊零售移動CI/CD實踐

一、背景

隨着有贊零售業務的蓬勃發展,爲了儘早交付有價值的應用滿足客戶需求,我們採用了敏捷開發的模式,快速擁抱變化的同時保持競爭優勢。從 2019 年起,零售客戶端的發版週期更改爲每週一次,這對移動端的持續集成與交付提出更高的要求。如何根據現有的團隊規模,在有限的資源下,快速搭建穩定可靠的持續集成與交付系統,我們有了自己的實踐與思考。

二、問題與挑戰

對於一個業務需求的開發或者迭代,移動開發同學除了編寫代碼,還需要經歷打包、測試,如果 QA 同學反饋問題,則需要進行修復,然後再次經歷打包、測試,直至測試通過,才能交付產品。整個過程中,開發同學需要手動提交 MR,手動觸發打包構建以及實時監控打包流程的狀態。如此下去,開發同學手頭上的工作會被頻繁打斷,去跟蹤處理這些流程的銜接,必然會嚴重影響開發專注度,降低開發生產力。

那麼,隨着業務的複雜,項目的壯大,團隊人員的增多,這類問題會愈發嚴重。而且,在編碼過程中,一些通過文檔規定或是意識上達成的共識,實際執行起來會變得越來越困難,這些都是不可持續的。因此,從代碼提交到最終打包交付便成了保證代碼與產品質量,十分重要的一個環節。

三、自動化流程體系

持續集成與交付系統是一塊大又雜的領域,我們這次主要介紹一下從代碼提交到打包交付的一套自動化流程體系,解決上述提到的問題。由於有贊零售的代碼是託管在 GitLab 平臺上的,因此我們採用基於 GitLab CI 搭建持續集成平臺,當然我們也使用 Jenkins 配合做一些輔助性的工作。

3.1 流程與架構

3.1.1 流程

我們先從一個開發工程師的角度,來整體感受一下現在的開發及交付的流程。開發工程師首先從 dev 分支 checkout 出自己的 feature 分支進行迭代,在迭代過程中不斷地向自己的 feature 分支提交代碼,提交代碼過程中會有本地檢查這一道保障,當需求開發完成,在 feature 分支上構建出對應的階段提測包並提交 QA 進行測試,測試通過後,提交 MR 準備合併入 dev 分支,通過編譯檢查和 Code Review 後,才能允許合併至 dev 分支。在 dev 分支上的代碼是可靠的,會有靜態檢查再次進行保障,而且也會有 QA 進行迴歸測試,直至所有 bug 修復完畢,交付最終的產品進行驗收。

3.1.2 架構

這套體系主要分爲 5 個部分,下文會具體介紹一些細節:

  1. CI(持續集成):GitLab、Jenkins
  2. CD(持續交付):MBD、APUB、移動助手 App
  3. 檢查:編譯檢查、本地檢查、靜態檢查
  4. Code Review:Git Hooks、GitLab CI
  5. 消息與閉包:企業微信消息通知、企業微信羣機器人、企業郵件、檢查報告、JIRA…

3.2 打包與分發

持續集成與交付的目的是快速迭代,並且交付穩定可靠的產品。從移動端的角度來看,我們可以理解爲快速構建出不同分支、版本,且穩定可靠的應用包。並且,能夠將應用包快速分發到 QA、PM、UED 等各個業務方同學手上。

3.2.1 打包

有贊零售的打包接入了有讚的移動構建集成平臺 MBD (Mobile Build),MBD 提供了便利的打包操作,支持 iOS、Android、Weex 等多個構建集成平臺,可以選擇任意分支、版本進行構建,並且提供了可遠程調用的 API 方便持續集成。當然,如果技術團隊還沒有類似的構建集成平臺,或者現階段去開發一個平臺的效益並沒有那麼高,也可以選擇通過 Jenkins 搭建 iOS 或 Android 的自動化打包構建任務,實現成本較低。

簡單介紹一下 MBD 的使用流程,首先需要添加組件,填入工程的一些重要信息(如 Bundle ID、SSH 地址…),再編寫構建腳本(如運行 pod、fastlane…),根據組件創建出對應的集成單。我們後續的打包流程,主要就是通過已經創建的好的集成單,輸入對應的版本號、分支名等信息,觸發集成單的構建,最後處理構建反饋的結果,構建產物就是可供下載的應用包。

3.2.2 分發

爲了減少 QA 和開發之間的低效溝通以及優化 App 包的分發流程,我們急需一個平臺來統一管理分發公司內部的 App 包,於是有贊移動應用發佈平臺 APUB (App Publish) 應運而生。與 MBD 無縫銜接,可以實現從構建到發佈、熱修復、交付一系列流程的打通,並且提供了有贊移動助手 App 這個入口,方便下載各個業務線的 App 包。

有贊移動助手 App 提供了許多好用的功能:

  • 正式版、測試版應用的下載
  • 內網網關、開發環境的切換
  • 應用包的基礎信息(打包人、包版本、包體積、構建分支…)

想要了解有贊移動基礎設施建設相關內容,可以詳見文章:有贊移動基礎設施建設的實踐和思考

3.3 檢查體系

不過,談到持續集成與交付,我們不能忽略了一個關鍵點:出包的可靠性。如何保證出包的可靠性呢?答案:檢查。

3.3.1 編譯檢查

編譯檢查可以認爲是最重要的守門員,編譯檢查能否通過,直接決定了打包能否成功。我們的第一反應是可以通過修改編譯腳本,在每次打包前加入編譯檢查,來確保出包的可靠性。然而,這其中潛藏着一個很嚴重的流程性問題。原本出包是一個箭在弦上的事情,但是,因爲在出包前加入了編譯檢查,使整個流程受這個前置條件的影響。想象這樣一個場景,一旦編譯檢查出問題,我們就得通過日誌定位問題,然後找相關工程師進行修復,修復完畢,再觸發打包,可能又檢查出新的問題,反反覆覆,使得出包變得非常的低效。

爲了解決上述的問題,我們調整了檢查策略。在一些可靠的分支,如 dev、release 進行 MR 的時候,通過 GitLab Runner 觸發編譯檢查的 Pipeline,只有檢查通過,相關的代碼才能夠被允許合入對應的分支。

爲了能讓大家有個簡單的概念,介紹一下幾個名詞:

  • GitLab Runner:GitLab CI 提供註冊 CI 服務器的接口,執行構建任務的一個服務,即 Pipeline 運行的具體環境,能夠運行 Pipeline 並將結果發送回 GitLab,通常是和倉庫託管的服務區分開來,部署在不同的機器上
  • Pipeline:一次 Pipeline 其實相當於一次構建任務,裏面可以包含 CI 不同階段的不同任務,我們的編譯檢查就是運行在這個流程中,觸發的條件也很多,我們選擇 MR 的時候觸發編譯檢查的 Pipeline

至於 GitLab Runner 如何搭建就不再贅述了,可以查看官方文檔。我們可以來感受一下 GitLab CI 的架構設計:

我們對於編譯檢查進行了一些提速優化,使得平均時間穩定在 5 分鐘左右:

  • 全量編譯和差量編譯的區分
  • 可忽略編譯的文件,進行白名單配置
  • 緩存外部依賴文件…

3.3.2 本地檢查

由於資源有限,目前的 GitLab Runner 是單機狀態,並沒有進行多機併發處理。所以,如果一股腦全部依賴編譯檢查,一旦同一時間段內 Pipeline 數量變多,很可能就會處於排隊等待中,MR 的環節也會因此變得十分的冗長。除了優化編譯檢查,我們可以把部分檢查的時機再提前。

本地檢查,具體一點可以叫做本地代碼提交檢查。本地代碼提交檢查可以有效的保證代碼提交質量。除了 Lint 這類的代碼風格統一的檢查,在業務上,最重要的是對跨模塊的代碼修改做了一定的限制,還可以檢查一些關鍵的配置文件是否被不小心修改。

舉個小例子,比如 A 同學沒有修改 RetailStock 模塊的權限,但是 A 同學在 RetailStock 下的 YZStockBundle.h 文件增加了一行註釋,準備提交代碼。會在 git commit 這個時機,進行本地代碼提交檢查,發現了 A 同學修改了 RetailStock 模塊的代碼對其進行提醒,並且提供了一個 Code Review 的 URL 鏈接通過 git diff 展現代碼修改的內容,如果真的由於業務需要,可以由 RetailStock 模塊的相關負責人 Review 後同意修改。

搭建本地代碼提交檢查,需要使用 Git Hooks,我們這邊 hook 了 commit-msg ,當然你也可以 hook prepare-commit-msgpre-commit 等其他 Git 鉤子將腳本進行更精細的拆分。

簡單介紹一下,如何配置本地代碼提交檢查。

  • deploy_git_hooks.sh:部署腳本,方便小夥伴一鍵配置
  • commit_msg_analyzer.sh:本地代碼提交檢查的入口腳本

工程目錄結構:

.
├── .git
│   ├── hooks
│   │   ├── commit-msg
│   │   ├── commit-msg.sample
└── scripts
    └── git-hooks
        ├── commit_msg_analyzer.sh
        └── deploy_git_hooks.sh

deploy_git_hooks.sh

#!/bin/bash -l

current_dir=$(cd $(dirname $0) pwd)
scripts_dir=$(dirname $current_dir)
project_dir=$(dirname $scripts_dir)
git_dir="$project_dir/.git"
git_hooks_dir="$git_dir/hooks"

commit_msg="$git_hooks_dir/commit-msg"
analyzer="$current_dir/commit_msg_analyzer.sh"

if [ ! -d $git_dir ]; then
    echo ".git not exist"
    exit 1
fi

if [ -f $commit_msg ]; then
    echo "commit-msg already exist"
    exit 1
else
    if [ -f $analyzer ]; then
        chmod +x $analyzer
        ln -sf $analyzer $commit_msg
        echo "deploy success"
    else
        echo "commit_msg_analyzer.sh not exist"
        exit 1
    fi
fi

commit_msg_analyzer.sh

#!/bin/bash -l

# 校驗提交說明是否標準
# 檢查代碼風格
# 檢查是否存在關鍵配置文件的修改
# 檢查是否存在跨模塊的修改
# ...

3.3.3 靜態檢查

靜態檢查可以在編碼規範,代碼缺陷,性能等問題上提前預知,從而保證項目的交付質量。現階段自助研發一套靜態檢查工具,投入產出比並不高,但是我們可以藉助三方框架快速搭建起來,並且定義了一些錯誤的過濾規則,讓我們更加聚焦迫切需要解決的問題。iOS 側我們選擇了 Clang 支持度最好的 scan-build 作爲首選,以及精度最高的 Infer 作爲配合使用。Android 側首選 Android Lint,其內容涵蓋了大部分 Android 的檢測內容,並且使用 FindBugs 作爲 Android Lint 在 Java 語言層上的補充。

  • iOS:scan-build + Infer
  • Android:Android Lint + FindBugs

對於可靠的分支,比如 dev 分支,我們選擇定時觸發,如每天晚上觸發。當然,開發者也可以選擇主動觸發,選擇對應的分支執行靜態檢查。靜態檢查報告會通過企業微信消息通知的方式,發送給執行者。定時觸發的靜態檢查,檢查出錯誤後,除了生成報告,而且會根據錯誤找到相應的模塊負責人,創建 JIRA Issue。

3.4 Code Review

Code Review 的重要性是毋庸置疑的,這裏提到的 Code Review 並不是指項目開發完成後組織的 Group Code Review,主要是針對持續集成與交付過程中,需要或者說必須要進行的一環。我們前面講到的本地代碼提交檢查後,發現有跨模塊代碼的修改,就是需要相關模塊的負責人進行 Code Review 的。

對於那些可靠的分支進行 MR 的時候,則必須要經過兩個同學 Review 後確認沒有問題,才能允許進行合入操作。Review 時,可以對需要改進的代碼進行評論。從進行 Review 的同學角度來說,不僅能夠看到新需求的邏輯與問題,還可以碰撞不同的架構思想。從被 Review 的同學角度來說,則可以發現一些由於思維定式或者粗心造成的問題,也可以採納一些好的建議,讓代碼更加健壯且優雅。

通過 GitLab CI 的 Merge Request 機制,可以很方便快捷的搭建這套體系。每個 MR 只有通過了 Pipeline 並且所有 Reviewer 的評論都得到解決,最終由擁有 MR 權限的同學進行 Merge 操作。

簡單介紹一下,GitLab Merge Request 幾個好用的功能:

  • 不僅支持對整個 MR 進行評論,而且支持對每行代碼進行評論,並且評論後會自動將其標註爲 待解決 的狀態
  • 在提交 MR 的時候支持配置目標 Approvers 以及 Reviewers,在對項目進行配置的時候也可以配置至少需要哪幾個同學同意才能進行 Merge
  • 可以設置只有 Pipeline 執行通過才允許進行 Merge,這裏的 Pipeline 就是指前面提到的編譯檢查,只有檢查通過了,有合併權限的 Reviewer 才被允許點擊 Merge 按鈕進行合入操作

日常實踐中,我們發現了直接通過 MR 進行 Code Review 的一個痛點。比如在一個稍微大一點的項目開發中,動輒就是幾百個 Changes,幾千個 Additions 或 Deletions。這樣不僅 Reviewer 需要耗費大量的時間去理解邏輯和審查代碼,而且往往也很難發現一些隱藏很深的問題,簡而言之,就是使得這次的 Code Review 非常的耗時且低效。

我們也在積極探索和實踐更好的 Code Review 的形式,有個簡單思路就是將最後 MR 進行前置分解,通過調整 GitLab Flow 把 Code Review 放到每個分解的 MR 中。

3.5 消息與閉環

持續集成與交付過程中,消息與閉環也是非常重要的一環。能夠減少溝通成本,能夠在自動化的流程中,讓使用者更加無感知,不用時不時的去跟蹤處理流程的銜接。監控的成本大大降低,正常情況下感知流程進行的節點即可,重點只需要關注異常流程,因爲這個時候需要人工介入處理。

目前,主要依賴的消息通知方式:

  • 企業微信的消息通知
  • 企業微信的羣機器人
  • 企業郵件

閉環方式中,最值得一提的是蒐集出包的變更集。應用包在提測期間,也經常會有一些 bug 產生,修復後需要重新打包,如何比較 2 個提測包之間的差異及變更?這裏,給出 2 個解決方案:

3.5.1 方案一

根據上述的流程體系可知,目前可靠分支的代碼合併都是需要通過 MR 的方式。我們可以蒐集 2 個提測包之間所有的 MR 提交,並規定好 MR 的提交模板, 變更內容 就是其中的必填項之一,然後過濾出提交信息中的 變更內容 ,由羣機器人進行通知。

這是我們目前採取的方案,對現有的流程以及小夥伴們的操作習慣改變最小。小夥伴們提交 MR 的時候,只需要根據提供的 MR 模板填入對應信息即可。還有一個好處, 變更內容 更像是 MR 申請者對本次改動點的總結,能夠更好的概括此次 MR 涉及的改動點。而且,我們規定在提測期間的每個 MR 都是需要有對應的 JIRA Issue 鏈接,方便 QA 追蹤及迴歸。

3.5.2 方案二

首先,需要規範 git commit,這一步可以通過一些開源的工具解決,比如:commitizen。最終通過 git log 的方式,過濾出 2 個包之間新增的所有 git commit。

這樣做的好處是,能夠通過 git log 獲取到所有代碼提交的改動信息。壞處是,需要規範 git commit 流程的學習成本,其次是龐大的 git commit 信息是否真的有必要?大量的 git commit 信息不僅冗雜,而且不能很好的區分到底哪些是爲了修復某個問題而產生的提交。

當然,也可以解決這個問題,就是前面提到的需要嚴格規範每一次的 git commit。簡而言之,嚴格執行規範本身就是不可持續的,可能對小夥伴們的操作習慣改變也較大,因此快速落地的成本也會比較大。但是,規範 git commit 仍然是一個很好舉措,它的價值遠遠不只是爲了蒐集變更集,也是開發過程中排查問題的一個好習慣,是後期需要真正落地的一個規範。

流程中,還有一些比較重要的閉環方式:

  • 核心用例自動化報告
  • 靜態檢查報告
  • JIRA Issue

四、思考與展望

在持續集成與交付系統實踐過程中,並沒有一套所謂最好的或者是標準的解決方案。每個公司或者團隊所處的階段不同,提供的資源不同,採用的方案也會不一樣。但是,如果你目前正在或者將要做相關實踐時,一定要衡量好投入產出比,確保整個流程儘可能的簡化,儘可能的減少使用者的學習成本,將其系統化、高效化以及自動化。

當前,有贊零售業務仍然處於快速增長階段,移動端在持續集成與交付這塊纔有了一點雛形。未來我們的建設主要會集中在 2 個方向,更加健壯的代碼管控體系和更加完備的自動化測試流程。如上文所述,隨着業務越來越複雜,涉及的角色越來越多,代碼集成的管控需要更加嚴格,而嚴格的代碼集成管控將增加團隊成員每次提代碼的痛苦。如何做到差異化的檢查和准入,將是未來持續集成的建設方向。

目前,除了移動端發版核心用例 UI 自動化,還有大量的測試 Case 都需要 QA 人工保障。我們希望未來能夠儘可能地採用自動化爲主的方式進行測試,覆蓋大部分的場景,而讓 QA 可以集中精力測試新功能或非常邊界異常的場景驗證。

本文轉載自公衆號有贊coder(ID:youzan_code)。

原文鏈接

有贊零售移動CI/CD實踐

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章