大家好,我叫王泊,負責云效應用交付AppStack的開發,把應用部署到各個環境、一步步進行集成測驗,最終發布到生產環境,是程式員作業中必不可少的組成部分;而云原生技術引入的容器化、IaC(基礎設施即代碼,Infrastructure as Code)等等技術與理念,為持續交付的程序提供了規范化的可能,但也引入了讓人不時埋首于組態檔的小山里的麻煩,我們不妨從一次略有波折、稍顯隱患的集成部署案例開始,看看如何著手設計一條更為穩定的應用交付流程,

一次波折的部署
許多個迭代后,面對陪風扇一起嘎吱嘎吱轉著的流水線,程式員阿偉會回憶起把系統部署到預發環境、提交最后一輪驗收,然后被打回來的那個并不遙遠的下午,當時他有一個酷炫的Java SpringBoot應用要上線,實作了酷炫的“在不同部署環境下、發送帶環境路由標簽的業務訊息”的介面:

日常環境的鏡像構建、部署和驗收測驗一路OK,但是在再次構建部署到預發環境后,阿偉發現訊息丟了:預發環境的消費方并沒有消費到訊息,經過一系列不管黑屏白屏康到bug就是好屏的排查,發現問題起源于在預發環境使用的SpringBoot組態檔application-staging.yaml中漏配了routing.env屬性,應用啟動時使用了預設配置application.yaml中的兜底值,導致訊息tag打錯,
具體的問題倒是解決了,不過多少會留下點顧慮:以后寫配置項的時候,免不了翻來覆去diff一下,是不是漏了什么,會不會導致各個環境里的產物有微妙的結構差異引發bug……
舊交付方式的潛在問題
仍然以SpringBoot應用為例,一部分開發者將應用從傳統的虛機部署遷移到Kubernetes上的容器化部署時,會使用類似下面的思路:

框架提供了為不同環境撰寫不同application.yaml組態檔的機制,用以達到環境差異化部署的效果,我們不難構陷出小故事的主人公阿偉也使用了類似的思路:
● 使用application.yaml提供所有環境的共性(和一部分兜底)配置;
● 各環境的差異化配置由單獨的application-xxx.yaml給出,覆寫兜底配置;各差異化配置不作特別的規范要求,允許屬性取值不同,也允許引入某個環境特有的屬性值;
● 為不同環境的鏡像撰寫不同的Dockerfiles, 環境配置方面的差異主要在于啟動應用時指定的引數不同,
一個典型的工程目錄看起來像是這樣:

看起來很規整,但其實也引入了一些問題:
● 環境差異化配置需要靠人工核對來減少錯漏,撰寫application.yaml這類基準配置的時候也需要慎重考慮提供什么樣的兜底值,一旦有差錯則排查成本相對高;
● Dockerfile往往沒有很大的差異,但構建出來的產物是和具體環境強系結的,沒辦法復用;多次編譯可能因為某些隱患(最典型的比如依賴版本不嚴格)導致不同環境下的交付內容并不一致,有引入bug、招致線上問題的風險,
○ 比如在日常環境下完成構建后,某個(可能是間接)依賴的快照包被更新了(可能是不規范的快照包更新,也可能是安全包之類選擇傾向于讓接入方無感升級而使用快照版本當作release);此后部署到預發環境時,構建參考了新版本的依賴包,導致日常環境下的測驗驗收結論可信度下降,
單應用逐環境晉級方案的考量
吃一塹長一智,我們不妨幫阿偉的應用發布方案列出下面的考量:
● 產物對環境中立:環境差異化配置在部署時注入,一份鏡像可以用于所有環境的部署,
● 環境配置統一:所有環境使用同樣格式的配置模板和差異化的值注入,避免“兜底+覆寫”引入的配置模板差別,
具體來說,在“日常-預發-生產”的整條集成發布流程中,使用的鏡像和編排只有一份;鏡像中的SpringBoot應用里,也只使用application.yaml,不再引入其他差異化配置,
這樣做看起來限制了一些靈活性,但核心考慮在于:通常情況下很難規范組態檔和編排的具體格式;一旦存在“一份配置兜底+多份差異化調整”的情況,理解應用代碼邏輯和部署細節的成本會變高,維護、驗證應用邏輯所需理解的內容也隨組態檔的增加而線性增長,即使是應用的設計者或是owner,也難免隨著時過境遷而忘記一些細節(“我當時為什么要加這個環境變數來著”),更不用提中途加入進行功能迭代的其他開發人員了,

實際部署到Kubernetes集群中時,環境變數通過編排中容器的環境變數注入,接下來需要統一Deployment編排——如果為不同的環境使用多份編排檔案,仍然會引入無意義的重復,這里我們可以使用Helm chart的形式,諸如鏡像、環境變數等等在構建部署時才能決定的差異化配置,都可以通過values配置進行注入:

需要定制化的部分,則是CICD系統中動態生成Values.yaml配置的腳本,這部分的復雜性相對容易控制,具體的實作則根據使用的CICD工具不同而略有差異,我們將會在后文中看到一個概略的示例,
方案改造例
現在可以回到阿偉的服務上進行改造了,
Step 1: 統一application.yaml和Dockerfile
首先我們要壓縮服務中的SpringBoot application yaml配置,只留下一份:

這里使用了占位符${DEPLOY_ENV},要求環境變數提供routing.env的值,
Dockerfile則可以去掉所有環境差異化的環境變數定義、統一為一份配置,并假定環境變數都已經正確注入,
Step 2: 撰寫Helm chart
從創建一份空的helm chart開始:

接下來,可以把原先的編排檔案按照helm模板的格式簡單改寫,放置到cool-service-chart/templates/目錄下,以Deployment為例:

我們使用.Values.image這一helm占位符將鏡像注入容器,環境變數注入的方式則有多種——變數較少的情況下可以在pod template中直接定義name和value;不過如果考慮到更長遠的擴展性,也可以采用關注點分離的方式,單獨定義一份ConfigMap用于定義環境變數;這樣做的好處,則是添加環境變數的開發者無需理解Deployment的具體結構,甚至只需要理解“往ConfigMap的資料定義里寫一個鍵值對就能實作環境變數注入”就可以了,
基于這些考慮,我們定義容器使用下面的ConfigMap提供鍵值對、注入環境變數:

Chart里的模板撰寫完成后,記得推送到一個git庫里,方便后面使用,
Step 3: 撰寫Values.yaml生成腳本
在準備好Helm chart的靜態模板部分之后,需要為CICD工具撰寫部署時生成Values.yaml的腳本,我們不妨假設阿偉的團隊選擇使用Jenkins建設CICD流水線:

這里我們主要關注chart-complete.sh,它需要完成如下的任務:
● 從git倉庫克隆chart庫的主干;
● 從環境變數中,生成values.yaml.

如果已經搭建了helm倉庫,這里也可以選擇把生成好的chart推到倉庫,
總結
在單一應用逐環境晉級部署的程序中,往往會涉及到針對環境定制的差異化配置;為了避免Dockerfile、組態檔等冗余帶來的治理成本及bug隱患,我們可以利用云原生IaC的優勢,基于統一的制品和編排定義,將環境的差異化配置項延遲到部署時注入,這樣,在各個環境中所部署的代碼是完全一致的,提高了集成的可信程度及測驗效率,
當然,從頭搭建CICD體系往往也需要一定的試錯;云效應用交付AppStack提供了符合前述實踐的編排管理、環境治理和差異化配置能力,可以通過集成云效流水線Flow,快速搭建出整條晉級流程,歡迎大家嘗試,若有識訓,就點個贊吧!
點擊下方鏈接,免費體驗應用交付平臺 AppStack,
https://www.aliyun.com/product/yunxiao/appstack?channel=yy_practice

轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/458569.html
標籤:其他
上一篇:代碼規范淺談
