公司的應用架構一開始就選定了微服務+Kubernetes,整個開發環境都在內網,使用 Jenkins 做半自動化的 CI/CD.
整個前后端都拆分得很細,分了很多層次:
- 腳手架層:封裝了開源的各種庫(mysql/redis/es 的 CRUD 庫,log/tracing/config 庫等)。
- 基礎層(BaseXxx):基於腳手架層,實現了如下幾類應用的基礎層:測試器、數據庫升級器、微服務、網關、通用工具、一次性任務等。
- 中台:在基礎服務上,按功能實現了一些比較通用的中台服務。比如權限認證服務、用戶服務、訂單服務等。
- 應用層:每個倉庫對應一個具體的微服務,也可能包含測試器、數據庫升級器等。可能會在 CI 中被打包成多個鏡像。
最終只對外暴露出幾個對外網關。
這種結構的目的,就是提升代碼的復用能力,把應用層能復用的東西,都抽到下面兩層去了。
但這要求我們的基礎層API一開始就設計得足夠好,因為越到后期,API 的影響面就越大,幾乎無法修改。
一、CI/CD
1. Continuous Integration 方案
目前我們是使用 Jenkins 作為 CI/CD 工具,層次結構也完全對應前面講到的代碼層次結構。
通過一套 BatchJob (批量構建任務,串行或並行地調用相關的子任務)來按依賴順序,自下向上地層層更新 csharp/python/golang 依賴,構建 nuget 包,最后打包成 docker 鏡像。
更新過程中會通過如下幾個檢測項來判斷是否需要構建 nuget 包/docker 鏡像:
- 更新私有依賴,返回值:是否更新了依賴
- 更新第三方依賴的小版本,返回值:是否更新了依賴、是否有主版本變更(主版本需要手動升級)
- 與上次構建相比,代碼倉庫是否存在變更(目的是加速構建)
每一個倉庫的依賴更新與版本自增都對應一個 jenkins 任務,由每一層的 BatchJob 按預先定義好的順序啟動這些小任務。(相互獨立的任務會被並行調用,以加速構建)
而在應用層,是通過 batchjob 並行構建所有的 docker 鏡像。
有一個專門的 job_config 倉庫(Python 模塊),存儲着:
- Git 倉庫、Jenkinsfile 與 Jenkins 任務的對應關系,以 yaml 格式保存
- 通過 python 代碼提供 api,動態地從上述配置中查詢出各層的 Git 倉庫、Jenkins 任務的各種信息。
- 提供命令從上述 yaml 配置中生成出所有的 jenkins jobs 配置(xml文檔),這樣就不需要通過 UI 一個個添加 Jenkins 任務。
- 上述的任務分層、任務之間的優先級(相同優先級的任務是相互獨立的,可並行構建)
2. Continuous Deployment 方案
構建完成后,需要通過一個“鏡像快照”的功能,將所有鏡像的版本號、掃描出來,然后生成它們的 k8s yaml 配置文件,保存到 git 倉庫中,並打上 tag(時間戳)。方便隨時回退。
k8s 配置生成方面,我們目前是使用的自定義模板,通過字符串替換的方式進行填充。以后可能會考慮使用 kustomize。
最后通過一個部署的任務將指定的版本的 yaml 應用到集群中。
另外現在正在考察 jenkins-x,以后可能會將應用層的鏡像構建到 k8s 部署,從 jenkins 移出來。
3. CI/CD 目前存在的問題
3.1 Kubernetes 配置生成
先說說 k8s 配置生成,試用了 kustomize,發現它功能還是比較弱,匹配不上我們現在的 yaml 配置生成的需求。也可能是我們目前的使用姿勢不對吧。
3.2 GitOps
另外也查了很多 Jenkins-X 的資料,它遵從 GitOps 開發流程,能檢測 Git 倉庫變更/Pull Request,直接生成 Docker 鏡像並部署到 Preview - Stageing - Production 環境。
但是我 GitOps 和公司目前的這套構建體系不太契合:這種以 Git 倉庫為中心的方式,好像只面向能生成最終的 Docker 鏡像/K8s Pod 的 Git 代碼,而不適合用於構建底層依賴包。
比如說我更新了一個基礎層的依賴包 A,現在需要讓所有的應用層項目都引用這個新版本依賴。應用層可能有幾十上百個倉庫,手動更新幾乎不可能。
通過 GitOps 做不到讓應用層的這上百個倉庫自己更新一下底層依賴。只能借助一個 Jenkins 的 BatchJob,調用一下所有應用層依賴更新的子任務。
P.S. dotnet-nuget/java-maven 目前沒有很多現代現代語言都有的 動態依賴(如
pyproject.toml
/package.json
/pubspec.yml
/go.mod
/Cargo.toml
,可以通過指定范圍之類的方法靈活配置依賴) + 依賴快照(如pyproject.lock
/yarn.lock
/pubspec.lock
/go.sum
/Cargo.lock
,所有依賴的完整快照,保證當前依賴環境可復現) 這類的依賴管理方法,以及poetry update
/yarn upgrade
/flutter packages upgrade
/go get -u
/cargo update
之類的依賴升級命令。
只有一個 xxx.csproj/xxx.xml 記錄所有直接依賴的精確版本。要做自動化依賴管理,只能自己寫腳本去訪問 nuget api,讀取並更新 csproj 文件。
查詢資料 nuget versions 發現 nuget/maven 確實也支持基於范圍的版本依管理,但問題是它們沒有 xxx.lock 文件作為環境快照!這意味着使用靈活的依賴管理,可能導致歷史環境無法復現。
另外 GitOps 自動化部署的流程也和公司目前的部署方法不切合。我們每個開發/測試人員都有一套自己的開發/測試環境,有的測試會需要使用特定版本的一套后端微服務。也就是說測試人員需要能夠控制自己測試環境整套微服務的版本,比如回退到某個時間點、將版本固定在某個時間點。而且不能在工作時間自動更新測試環境的后端微服務。(否則測試到一半,后端滾動更新了微服務,那大半天的測試就作廢了。)
GitOps 只適合一些扁平的應用,而對公司這種分層結構的代碼就有點無所適從。
解決方法
- 已有的 Jenkins 分層更新構建流程不變,只在應用層進行 GitOps 方式的 CI/CD。因為應用層倉庫相互之間是獨立的。
- 給 GitOps 的構建部署提供專用環境:Preview - Stageing - Production,個人服務器的部署仍然使用現有流程。
舊的分層更新任務會修改應用層的 csproj 文件,這樣就會觸發 GitOps。
以這種方式進行結合是比較好的,GitOps 和現有的分層結構不會沖突。
3.3 網絡問題/緩存問題
每次構建 dotnet 程序時,都需要從公網拉取依賴,使用靜態 Jenkins Slave 時,拉了第一遍后本地就有緩存了,不需要再拉第二第三遍。
可用 Serverless Jenkins (Jenkins-X)的話,每次都是啟動新容器,豈不是每次都要拉依賴?這個挺費時間的。
暫時想到的解決方案是使用 Baget 的緩存功能,讓私有 nuget 倉庫來緩存這些依賴。
雲上生產環境部署
開發人員需要一個直觀的 UI 界面,進行生產環境的灰度部署、監控分析。我們調研了很多管理平台:Rancher/Rainbond/KubeSphere,以及這些工具與 OAM/Istio 的契合度。
目前想到的比較好的一個方案,就是 flux+flagger+istio(istio 可換成 linkerd2),
此方案使用 github.com/gitee.com/coding.net 私有倉庫保存生產環境的 k8s 配置,在內網通過 jenkins 生成 k8s 配置,在生產環境中通過 flux 監控該倉庫的更新,然后 flux 使用 flagger 對其中的 k8s deployment 變更進行自動的灰度發布。
另外 Jenkins-X 有 Preview-Staging-Production 的一套 GitOps 部署流程,也可以一試。只是 jx 目前只提供了 CLI,沒有 UI。上手可能有點難。