卞中杰
(上海甚解信息技術(shù)有限公司,上海 200235)
隨著交通運(yùn)輸行業(yè)的飛速發(fā)展,道路貨物運(yùn)輸量逐年上升,為了幫助運(yùn)輸企業(yè)降低運(yùn)輸過程中的事故率,出現(xiàn)了由第三方企業(yè)開展的針對道路貨物運(yùn)輸?shù)娘L(fēng)險控制業(yè)務(wù);然而由于風(fēng)控軟件的特殊性,軟件發(fā)布需要做全套的單元測試、集成測試、系統(tǒng)測試、回歸測試,傳統(tǒng)的人工測試無法在有限時間和資源內(nèi)確保測試的全面性和準(zhǔn)確性,同時伴隨長鏈路的業(yè)務(wù)流程越來越多,人工測試在數(shù)據(jù)準(zhǔn)備和業(yè)務(wù)驗證方面效率低下[1]。因此,自動化測試是一種能夠保證測試正確執(zhí)行的可行方法,只有保證自動化測試的正確性,才能實現(xiàn)系統(tǒng)后續(xù)的自動部署和快速迭代。
持續(xù)集成和持續(xù)部署(CI/CD)作為關(guān)鍵內(nèi)容,包含多種實現(xiàn)方式,比如通過Jenkins 和Ansible 實現(xiàn)軟件的自動化編譯和部署[1];通過GitLab 的Pipeline 功能和Terraform 結(jié)合實現(xiàn)基礎(chǔ)設(shè)施的自動化創(chuàng)建和部署[3];基于容器的DevOps 實施方案和實踐[4]。
容器(指Docker、Podman 等容器技術(shù))的出現(xiàn)使得DevOps 更容易被實現(xiàn),DevOps 往往需要多種軟件協(xié)同實現(xiàn),由于容器天生具備隔離性,可使得不同版本的軟件互不影響地在同一宿主機(jī)上運(yùn)行,相比虛擬機(jī)而言,啟動速度更快、資源占用更小。此外,容器的打包方式可實現(xiàn)開發(fā)環(huán)境和生產(chǎn)環(huán)境的軟件版本、依賴項等高度統(tǒng)一,打包完成的鏡像易于分發(fā)。
Kubernetes 的出現(xiàn)改變了容器編排方式,能從更高維度管理容器的生命周期,將服務(wù)器作為一整個集群統(tǒng)一管理,使得容器可靈活地在集群中被啟動和調(diào)度。Kubernetes 可以幫助基于容器分發(fā)的軟件獲得高可用、可擴(kuò)展、可漂移的能力,并具備細(xì)粒度的容器管理能力。本文探討的CI/CD 以及自動化測試的實現(xiàn)均基于Kubernetes,充分利用容器的能力和特性,實現(xiàn)從開發(fā)到部署的自動化。
對于CI/CD 流程而言,觸發(fā)整個流程開始的事件通常是開發(fā)人員提交代碼或者代碼被合并到測試或生產(chǎn)分支。一旦代碼提交,如果GitLab 發(fā)現(xiàn)代碼倉庫根目錄中存在.gitlabci.yml 文件,GitLab 會通知獨(dú)立部署的GitLab Runner 組件,依次運(yùn)行該文件內(nèi)定義的流程任務(wù),CI/CD 的整體流程見圖1。
圖1 CI/CD 整體流程設(shè)計
CI/CD 流程腳本都寫在.gitlab-ci.yml 文件中,在文件的開頭往往會定義一些變量為后續(xù)步驟服務(wù),同時該文件也定義了運(yùn)行每個步驟默認(rèn)使用的容器(文本使用docker:stable 鏡像)以及整個流程的3 個流程節(jié)點(diǎn)(又稱Stage):test、build 和deploy。
在流程的第一步中,編譯測試會被首先執(zhí)行,可確保新的代碼能夠被編譯通過。在這之后,會根據(jù)不同項目類型執(zhí)行單元測試、集成測試、系統(tǒng)測試等,在所有測試通過以后,到下一步鏡像打包階段,如果測試失敗,則流程終止。
鏡像打包階段軟件需要被打包成Docker 鏡像,由于每一步流程都是在一個容器中啟動,此方式被成為DinD(Docker in Docker)。運(yùn)行DinD 需要priveleged 權(quán)限,出于安全考慮,選用kaniko 鏡像來實現(xiàn)鏡像打包且無需額外權(quán)限即可使用。
鏡像打包并上傳到鏡像倉庫以后,在測試環(huán)境或預(yù)發(fā)布環(huán)境中進(jìn)行部署,而Kubernetes 中,部署方式通常有兩種:使用原生部署配置文件或Helm 工具。Helm 通過向模板注入不同的配置和參數(shù),來生成不同的部署文件,其靈活性優(yōu)于Kubernetes 原生部署文件;并且Helm 具備部署的版本管理功能,便于版本切換,故使用Helm 進(jìn)行部署,配置如下:
Helm 從app/test 下載模板文件,并且結(jié)合代碼倉庫下. /deploy / test / values . yaml 的屬性更改,會在Kubernetes的test 命名空間(namespace)下,部署一個名為test-app 的應(yīng)用。至此,測試、預(yù)發(fā)布程序已經(jīng)完成部署。對于測試環(huán)境則流程結(jié)束;而對于正式環(huán)境,可在預(yù)發(fā)布環(huán)境做最后的核對和測試,當(dāng)測試完成后,繼續(xù)后續(xù)的發(fā)布到生產(chǎn)環(huán)境的流程。
在整個自動化部署流程中,測試是最為關(guān)鍵的一環(huán)。測試方法和用例的設(shè)計以及用例是否能被正確執(zhí)行,決定了能否自動執(zhí)行后續(xù)的發(fā)布流程。在測試執(zhí)行過程中,面臨著兩類問題:(1)多人提交代碼導(dǎo)致測試并發(fā)執(zhí)行,引起數(shù)據(jù)沖突使得測試失??;(2)由于前一次的測試用例修改了測試數(shù)據(jù),再次執(zhí)行測試導(dǎo)致測試失敗。下文將介紹通過容器技術(shù)和測試管線設(shè)計來解決以上兩個問題。
代碼在提交、合并或進(jìn)入發(fā)布通道時均可設(shè)置不同的測試環(huán)節(jié)。當(dāng)軟件通過了所有的測試,流程自動進(jìn)入到后續(xù)的發(fā)布階段,而測試中有某一項未通過,則發(fā)布流程終止。如果流程涉及合并請求,可以駁回并自動關(guān)閉本次合并請求。自動化的測試流程分為4 個階段:創(chuàng)建和啟動測試環(huán)境;導(dǎo)入和初始化測試數(shù)據(jù);執(zhí)行測試;銷毀測試環(huán)境。當(dāng)環(huán)境和數(shù)據(jù)準(zhǔn)備完成之后,執(zhí)行所有測試并判斷結(jié)果;當(dāng)測試完成以后,銷毀測試環(huán)境,執(zhí)行后續(xù)流程。
高質(zhì)量、可復(fù)用的測試數(shù)據(jù)除了能夠支撐后臺軟件測試,同樣要能夠為前端測試提供服務(wù)。單一存儲的測試數(shù)據(jù)會被測試樣例在運(yùn)行期間刪除、修改,導(dǎo)致測試無法重現(xiàn),并且在多人并發(fā)測試場景下,如果某一方修改了測試數(shù)據(jù),會導(dǎo)致其他依賴該測試數(shù)據(jù)的測試無法進(jìn)行。因此,測試數(shù)據(jù)必須被獨(dú)立抽取出來,在測試流程開始時完整地恢復(fù)到測試環(huán)境中,形成可復(fù)用的測試數(shù)據(jù)。在該項目中,測試數(shù)據(jù)按照生產(chǎn)環(huán)境要求和格式預(yù)先被設(shè)計好并存放在MySql 中,使用MySql 的dump 命令將測試數(shù)據(jù)全部抽取形成文件,并存放到源代碼管理系統(tǒng)中。例如對于MySql5.7 版本,把App 數(shù)據(jù)庫中的users 表數(shù)據(jù)全部導(dǎo)出為users.sql 文件,可使用如下命令:mysql _user USER _password PASSWORD _host HOST dump _database App _tables users > users.sql,其中USER、PASSWORD、HOST 分別需要替換為真實的數(shù)據(jù)庫用戶名、密碼和數(shù)據(jù)庫服務(wù)器地址。要把該導(dǎo)出文件恢復(fù)到測試環(huán)境數(shù)據(jù)庫中,則對應(yīng)的恢復(fù)命令是:mysql _user USER_password PASSWORD _host HOST < users.sql
在后續(xù)的測試指令執(zhí)行完成后,本次Stage 內(nèi)啟動的所有容器及其中數(shù)據(jù)都會被刪除,不會造成數(shù)據(jù)和環(huán)境殘留。
由于整個CI/CD 流程均在基于容器的環(huán)境中進(jìn)行,因此可以使用容器在測試環(huán)節(jié)中啟動測試該業(yè)務(wù)所需的所有配套設(shè)施。
3.3.1 后臺測試環(huán)境配置和啟動流程
以測試一個微服務(wù)為例,需要啟動的依賴設(shè)施有MySQL 數(shù)據(jù)庫和Flask 服務(wù)器。在Pipeline 中對應(yīng)的Stage配置如下:
這段配置文件首先修改了默認(rèn)運(yùn)行環(huán)境為:${TEST_ENV_IMAGE},這是一個變量,指向開頭定義的對應(yīng)的鏡像名,該鏡像是一個安裝了flask 和pytest 的帶有python3.7 運(yùn)行時環(huán)境的容器。services 語句定義了除運(yùn)行主容器以外,在這個Stage 中還需要運(yùn)行一個MySql5.7 的容器,該容器和主容器之間可進(jìn)行網(wǎng)絡(luò)通信。接下來before_script 屬性的值描述了容器啟動后先運(yùn)行的腳本,這里是向MySql 導(dǎo)入測試數(shù)據(jù)的合適時機(jī)。再接下來的script屬性定義了環(huán)境準(zhǔn)備完畢后運(yùn)行測試的指令,在這里運(yùn)行的是pytest 測試指令,可以根據(jù)實際需要定義若干條測試指令。因此,一個完整的測試環(huán)境在容器中得以運(yùn)行,該微服務(wù)所依賴的所有外部服務(wù)同時也以容器方式啟動,在所有測試語句運(yùn)行完畢以后,這些容器都會被銷毀,不會留下數(shù)據(jù)和配置殘留。
3.3.2 前端測試環(huán)境配置和啟動流程
前端軟件的測試會和后臺服務(wù)軟件測試存在差異。除了小游戲、工具型軟件等無需和后臺交互的程序外,與業(yè)務(wù)結(jié)合的前端軟件絕大部分依賴于后臺服務(wù)才能運(yùn)行,因此在測試前端的過程中,后臺服務(wù)環(huán)境也要一并啟動。單元測試由于不依賴于后臺,只需啟動一個帶有headless 瀏覽器的容器(本文使用cypress/browsers:node16.13.0-chrome95-ff94 鏡像),在該容器內(nèi)可以運(yùn)行前端的單元測試代碼;但是e2e 測試由于需要最大程度地模擬用戶真實使用場景,且為了確保測試可靠性,整個流程需要真實后臺數(shù)據(jù)參與,在e2e-test該環(huán)節(jié)中的services 中定義了需要額外啟動2 個容器,分別是MySql 和后臺微服務(wù)。通過啟動后臺微服務(wù)確保前端測試匹配對應(yīng)版本后臺邏輯,測試過程中可與真實測試數(shù)據(jù)進(jìn)行交互,而MySql 則是運(yùn)行后臺微服務(wù)所需要的依賴項。
在代碼通過測試后,自動化流程來到集成和部署階段。CI/CD 的實現(xiàn)因不同軟件打包方式、系統(tǒng)運(yùn)行環(huán)境和部署方式而不同,由于本系統(tǒng)全部運(yùn)行在基于容器的Kubernetes之上,因此軟件的分發(fā)需要把軟件打包成Docker 鏡像,并在Kubernetes 上進(jìn)行部署。在.gitlab-ci.yml 中,可以分成2 個Stage 來進(jìn)行:(1)在build 這個stage 中,使用Docker-in-Docker 的方式編譯打包微服務(wù)的Docker 鏡像,并上傳到一個私有的鏡像倉庫。而在build-test-mysql 中,基于MySql 鏡像打包了一個帶有測試數(shù)據(jù)的鏡像,可作為一個帶有初始數(shù)據(jù)的數(shù)據(jù)庫鏡像靈活地運(yùn)用在所有需要測試數(shù)據(jù)的環(huán)節(jié)中。(2)在deploy 這個stage 中,使用kubernetes 原生工具kubectl 通過配置文件部署應(yīng)用,除了前文提到的Helm,如果不需要每次提交都對部署文件進(jìn)行改動,而只是進(jìn)行簡單的版本更新操作,則此方式更為便捷。
從圖2 可以看出,從代碼提交到測試,再從軟件打包到部署成功,整個流程并無人工參與,只耗時3 分32 秒(當(dāng)中還包含每個Stage 的啟動耗時)。如果項目使用敏捷開發(fā)模式,每天均可發(fā)布新的版本,這種方式能極大地提高軟件測試和部署效率且避免人工操作錯誤,從而提高流程可靠性。
圖2 GitLab 中后臺服務(wù)CI/CD 效果圖
本文通過實現(xiàn)DevOps 以及使用Kubernetes 作為生產(chǎn)環(huán)境,軟件的測試和發(fā)布效率都相比傳統(tǒng)方式有了大幅度的提升。在本文中基于容器和自動化腳本的測試環(huán)境可以方便地啟動和生產(chǎn)環(huán)境相同的組件,但這種測試環(huán)境追求的是最小化的類生產(chǎn)環(huán)境,因此該測試方法適用于測試業(yè)務(wù)的正確性。由于受限于測試環(huán)境所啟動的單機(jī)資源限制,在自動化Pipeline 中啟動的測試環(huán)境一般達(dá)不到和生產(chǎn)環(huán)境等同的資源和性能,如果生產(chǎn)環(huán)境是分布式、大數(shù)據(jù)系統(tǒng),則啟動相同形式的最小測試環(huán)境也會占用相當(dāng)大的資源,會限制可同時進(jìn)行的測試數(shù)量。除此以外,測試環(huán)境的特性決定了并不適合進(jìn)行一些極端的壓力測試,因為單機(jī)資源存在瓶頸且本地回環(huán)網(wǎng)絡(luò)和真實服務(wù)器之間的物理網(wǎng)絡(luò)吞吐能力都存在很大不同,這就造成性能測試結(jié)果無法代表軟件在真實的生產(chǎn)環(huán)境中的性能表現(xiàn),對于如何解決以上這些問題,仍需進(jìn)行更進(jìn)一步的研究與實踐。