作者 | 朱燁
關(guān)于最佳實踐
本系列內(nèi)容是我們在不同項目的維護過程中總結(jié)的關(guān)于DevOps/SRE方面的優(yōu)秀實踐,我們將致力于在項目上盡最大的努力來推行這些優(yōu)秀實踐。我們希望這些最佳實踐能對項目的穩(wěn)定運營提供幫助,也希望剛接觸DevOps/SRE的新人能通過學(xué)習(xí)這些優(yōu)秀實踐來提升自己在這方面的水平。
因為DevOps/SRE涉及到的方方面面比較多,一次性完成的工作量太大,所以我們決定分階段來完成,這一次發(fā)布的是“應(yīng)用開發(fā)和部署”這個部分的內(nèi)容,后續(xù)我們將逐步發(fā)布“云平臺與網(wǎng)絡(luò)”,“操作系統(tǒng)和服務(wù)”,“用戶與權(quán)限”,“監(jiān)控與可視化”,“數(shù)據(jù)與備份”,“敏感數(shù)據(jù)”,“故障與應(yīng)急響應(yīng)”這幾部分的內(nèi)容。
所謂“最佳實踐”應(yīng)該是最適合自己的實踐,而不一定是最先進的,而且每一種實踐本身也存在一定的局限性,所以我們在描述了對應(yīng)實踐的優(yōu)點的同時,也把可能存在的缺點寫了出來,就是希望大家在看到它的好處的時候,也能知道可能存在的風(fēng)險在那里,理性地去評估到底是不是要采用相應(yīng)的實踐,所以這里總結(jié)的最佳實踐請適度取用,不要為了“最佳”而實踐。
我們深知自己在諸多方面存在一定的局限性,相關(guān)的內(nèi)容可能存在一些不足,而且優(yōu)秀實踐本身會隨著技術(shù)更新等因素不停地變化,我們將會把藍皮書內(nèi)容同步發(fā)布在Github上(https://github.com/toc-lib/DevOps-SRE-best-practice) ,希望引發(fā)更廣范圍的傳播和討論。也請使用PR或Issue的方式來提出你的不同的觀點和更好的建議,謝謝。
應(yīng)用開發(fā)和部署
使用牲口模式
在傳統(tǒng)的運維環(huán)境中,由于條件的限制無法快速的提供新的基礎(chǔ)設(shè)施和環(huán)境,所以通常在業(yè)務(wù)的依賴環(huán)境如操作系統(tǒng)內(nèi)核,服務(wù),類庫,運行時版本等需要變化時,我們會根據(jù)需要在現(xiàn)有的環(huán)境上做持續(xù)性變更。而且我們還可能會在機器上運行一些臨時任務(wù),做調(diào)試和排錯等,很多的時候,這些操作對應(yīng)的變化并不具有可追溯性,甚至不可以恢復(fù)到之前的狀態(tài)。這樣,剛開始統(tǒng)一配置的無差別的一批機器隨著時間的推移慢慢的就會變得各自具有一些獨有的特性。另外還有一些類型的服務(wù),比如數(shù)據(jù)庫,存儲等,其業(yè)務(wù)本質(zhì)就導(dǎo)致了集群中的每一臺機器具有獨特的屬性。當(dāng)我們在維護這些服務(wù)的時候,需要根據(jù)每臺機器的特性來做不同的管理和配置,而且一旦機器出現(xiàn)故障的時候,也很難去創(chuàng)建出一樣的機器來替代。因為這種情形和養(yǎng)寵物類似,比如我們會給寵物起一個名字,它也需要悉心照料,生病的時候要帶去看病,所以我們稱這種服務(wù)模式為寵物模式。
而在具有云原生能力的平臺上,我們可以按需定制基礎(chǔ)鏡像,也能快速的從這個基礎(chǔ)鏡像中創(chuàng)建出運行環(huán)境,我們的變更就可以基于基礎(chǔ)鏡像來做更新和版本迭代。這樣當(dāng)某一臺機器發(fā)生了故障,我們可以快速的復(fù)制出一臺一模一樣的機器來替代。如果需要做一些臨行性的操作和變化,在任務(wù)結(jié)束之后,也可以銷毀這臺已經(jīng)發(fā)生了變化的機器,使用一臺新的機器來替代,使整個集群恢復(fù)到一個最初的收斂狀態(tài)。這個場景和我們現(xiàn)實生活中的規(guī)?;陴B(yǎng)殖類似,對應(yīng)的我們稱這種服務(wù)模式為牲口模式。
大家所熟知的無狀態(tài)應(yīng)用,就是牲口模式的最常用的一種實現(xiàn)方式。在業(yè)務(wù)的設(shè)計和實施過程中,我們建議把邏輯和數(shù)據(jù)分離,在邏輯運行環(huán)境不要兼顧數(shù)據(jù)存儲工作,比如請求的session相關(guān)的數(shù)據(jù),不要保存在本地,而是把它放在一個共享數(shù)據(jù)服務(wù)中,從而達到無狀態(tài)的目的,這樣就可以對邏輯運行環(huán)境進行牲口化的管理方式。
優(yōu)點:
- 可隨時被銷毀或替換,結(jié)合自動化基礎(chǔ)設(shè)施和監(jiān)控,自動完成對故障機器或節(jié)點的替換。
- 配合自動化基礎(chǔ)設(shè)施和監(jiān)控,可實現(xiàn)自動水平伸縮,從容應(yīng)對業(yè)務(wù)峰谷,節(jié)約成本。
- 在不影響服務(wù)穩(wěn)定性的前提下可部署所需要版本的應(yīng)用、進行系統(tǒng)升級或者打補丁。
- 監(jiān)控和管理的重心不再是具體的單一資源的使用率,而是整體的承載能力和更深層次的性能關(guān)注點。
缺點:
- 需要基礎(chǔ)設(shè)施平臺具有相應(yīng)的能力支撐,否則很難實現(xiàn)。
- 不是所有的業(yè)務(wù)類型都能做牲口模式設(shè)計,比如數(shù)據(jù)庫。
實施要點:
- 除計算和業(yè)務(wù)處理過程中的臨時產(chǎn)生的數(shù)據(jù),數(shù)據(jù)的來源和最終的持久化應(yīng)由外部服務(wù)來提供,如獨立的內(nèi)存型數(shù)據(jù)庫或者關(guān)系型數(shù)據(jù)庫。
- 可以使用客戶端Cookie、cache取代外部數(shù)據(jù)服務(wù)。如果有敏感數(shù)據(jù),服務(wù)器端可以加密后交由客戶端存儲,在之后的請求時發(fā)回服務(wù)器解密使用。
- 通過鎖或者冪等性設(shè)計,使得應(yīng)用能正確、快速、自動地解決對同一份數(shù)據(jù)的競爭而導(dǎo)致的流程異常、數(shù)據(jù)不一致等問題。例如,多個定時任務(wù)同時處理一批數(shù)據(jù)。
使業(yè)務(wù)升級向前兼容
向前兼容指低版本的系統(tǒng)、程序或技術(shù)能優(yōu)雅處理(例如:忽略其不理解的部分)高版本的系統(tǒng)、程序或技術(shù)。向前兼容技術(shù)的目標(biāo)是讓舊系統(tǒng)能夠識別為新系統(tǒng)生成的數(shù)據(jù),簡單的說就是舊版本的系統(tǒng)可以接受新版本的數(shù)據(jù),是舊版本對新版本的兼容。
我們建議在做業(yè)務(wù)升級時候,設(shè)計你的業(yè)務(wù)具有向前兼容的能力,以應(yīng)對升級失敗時某一功能模塊或者依賴無法隨之回滾的風(fēng)險。比如說在有數(shù)據(jù)庫字段變化的升級中,在正式對數(shù)據(jù)庫做變動之前,基于舊的業(yè)務(wù)流程做代碼層面更新,使其可以兼容數(shù)據(jù)庫將要發(fā)生的改動并加以部署。在數(shù)據(jù)庫升級完成之后,如果新的業(yè)務(wù)流程上線后不幸出現(xiàn)重大的問題等情況需要回滾時,回滾之后的代碼仍然可以兼容數(shù)據(jù)庫的變化,而不用對數(shù)據(jù)庫也進行回滾,畢竟數(shù)據(jù)庫的回滾成本非常高。
優(yōu)點:
- 可以在新版本出現(xiàn)不容易修復(fù)和存在重大的風(fēng)險的時候快速地回滾到舊的版本,業(yè)務(wù)中斷的可能性會大大降低。
- 即使整個系統(tǒng)中存在不可回滾的部分,但我們不用花費很多的精力去考慮和解決完全不可回滾的問題。
缺點:
- 設(shè)計成本:要做到兼容未來的變化。這聽起來就很難。一開始很難獲知所有用例、極端案例和業(yè)務(wù)理解?;仡欉^去并說這是一個錯誤的決定很容易,今天做出明天不會后悔的決定要困難得多。
- 為了同時兼容兩種數(shù)據(jù)格式,需要在代碼中增加額外的處理邏輯,增加復(fù)雜度和投入的成本。
實施要點:
- select語句只獲取需要的字段,避免使用select * from語句,有效防止新增字段對應(yīng)用邏輯的影響,還能減少對性能的影響。
- 對數(shù)據(jù)庫表結(jié)構(gòu)變更通過新增字段實現(xiàn)。
- 盡量新增接口,避免對現(xiàn)有接口做修改,如需要修改現(xiàn)有接口,可嘗試在接口上增加版本標(biāo)識。
使用唯一性標(biāo)識給鏡像打標(biāo)簽
當(dāng)生成容器鏡像時,應(yīng)當(dāng)使用唯一性標(biāo)識來給容器鏡像打標(biāo)簽,唯一標(biāo)識可以更好的標(biāo)記當(dāng)次生成的鏡像,避免出現(xiàn)多個同名標(biāo)簽但不同的版本鏡像被使用的情況。例如多次部署都使用了latest標(biāo)簽的鏡像,可能因為拉取和緩存策略導(dǎo)致不同節(jié)點使用了不同版本的鏡像,從而導(dǎo)致功能上的不一致,在這種情況下,并不能很方便地判斷出某個節(jié)點部署的是哪一個版本。
唯一標(biāo)識最好有一定的含義,不僅可以用來區(qū)分產(chǎn)物,還可以獲取到本次構(gòu)建的關(guān)鍵信息。比如git提交哈希等關(guān)聯(lián)性比較強的標(biāo)識。雖然時間戳也是一個唯一性比較強的標(biāo)識,但是關(guān)聯(lián)性相對較差,如果長度不足,也有一定的幾率產(chǎn)生碰撞??梢钥紤]使用組合型標(biāo)簽,比如使用時間戳,build號,版本號等根據(jù)自己的需求來組合生成唯一標(biāo)識,這樣的標(biāo)簽本身就包含了很豐富的信息。
不建議單純使用pipeline的build序號來作為鏡像的標(biāo)簽,如果需要更換CI工具或者重建pipeline時,這個序號將會被重置而可能產(chǎn)生重復(fù),除非在構(gòu)建腳本中加入偏移量。而且不同的CI工具獲取這個序號的方法也有所不同,對于遷移并不友好。雖然它的可追溯性看起來較好,但是單純的Build序號和代碼之間并沒有直接的關(guān)聯(lián)。
如果不是需要對外公開發(fā)布的鏡像,并不建議對同一鏡像打上多個不同標(biāo)簽。因為絕大部分的情況下,我們只會選用其中一個標(biāo)簽在所有的地方使用,多個標(biāo)簽的實際意義并不會很大。
如果制品庫支持immutable特性,強烈建議開啟這個功能,防止因為意外情況導(dǎo)致對已上傳的鏡像的覆蓋。
優(yōu)點:
- 可以準(zhǔn)確對應(yīng)的到源代碼具體版本,在溯源時可以對應(yīng)到特定的提交而不是可能存在的多個提交。
- 不需要使用SHA256等額外的信息來區(qū)分同一標(biāo)簽的不同版本。
缺點:
- 一些類型的唯一性標(biāo)識可讀性不是很高,比如git提交哈希。
- 一些類型的標(biāo)識受時間影響,不能使用同一命令獲得一致結(jié)果,需要使用其他的方式來傳遞給后續(xù)階段,比如時間戳。
- 制品庫immutable功能開啟之后,重跑已完成構(gòu)建鏡像的pipeline會發(fā)生上傳鏡像失敗的錯誤,有可能會導(dǎo)致后續(xù)任務(wù)不能繼續(xù)。
實施示例:
在所有環(huán)境中使用同一個構(gòu)建產(chǎn)物
應(yīng)該在不同環(huán)境中使用相同的構(gòu)建產(chǎn)物來部署,避免對不同的環(huán)境生成不同的構(gòu)建產(chǎn)物,以確保環(huán)境的一致性,同時也保證部署在不同環(huán)境中的業(yè)務(wù)代碼是測試和驗證通過的。比如某次的構(gòu)建產(chǎn)物,在測試環(huán)境部署后經(jīng)由測試人員和相關(guān)的自動化測試工具完成相關(guān)的測試驗證,如果沒有問題才會繼續(xù)部署到后續(xù)環(huán)境中,應(yīng)繼續(xù)使用該產(chǎn)物部署后面的環(huán)境,不建議重新構(gòu)建新的產(chǎn)物來做后續(xù)環(huán)境的部署,也不建議覆蓋之前的構(gòu)建產(chǎn)物標(biāo)識。因為在現(xiàn)有流行的語言和框架中,普遍存在大量的第三方依賴,即便是同一份源代碼,由于其依賴以及構(gòu)建環(huán)境的不同,會有一定幾率出現(xiàn)由于外部依賴的更新導(dǎo)致構(gòu)建產(chǎn)物存在差異,從而產(chǎn)生非預(yù)期的情況出現(xiàn)。
優(yōu)點:
- 確保所有的環(huán)境部署的構(gòu)建產(chǎn)物是一樣的,盡可能的保證環(huán)境的一致性。
- 確保部署到生產(chǎn)環(huán)境的產(chǎn)物是測試驗證之后并無變化的,避免出現(xiàn)非預(yù)期的差異。
缺點:
- 對于如前端這類純靜態(tài)資源的應(yīng)用,由于不同的環(huán)境需要連接不同的后端服務(wù)地址,因此無法直接使用唯一的構(gòu)建產(chǎn)物。可以考慮在業(yè)務(wù)啟動階段,用一些額外的啟動腳本或命令配合傳入環(huán)境變量或參數(shù)來修改配置文件,從而達到所有環(huán)境使用同一個構(gòu)建產(chǎn)物的目的。
下面例子展示了在使用nginx的容器鏡像里,通過在CMD指令里面先執(zhí)行一段腳本來對配置進行修改,來達到在容器運行時根據(jù)傳入的環(huán)境變量WEB_ENV的值來訪問對應(yīng)環(huán)境的后端服務(wù)的目的。
- 對于移動端app,也存在與前端應(yīng)用類似的問題,需要開發(fā)人員做額外設(shè)計和開發(fā),在app啟動時判斷需要進入什么樣的運行模式。
實施要點:
- 在設(shè)計CICD流水線時,將構(gòu)建產(chǎn)物同步到制品庫時,給該產(chǎn)物打上唯一標(biāo)識。
- 如制品庫支持,開啟制品庫的immutable特性。
- 將該唯一標(biāo)識傳遞到在后面所有的部署流水線任務(wù)中,所有的部署任務(wù)都使用該唯一標(biāo)識所指向的構(gòu)建產(chǎn)物。
- 如果需要在多個制品庫保存同一個構(gòu)建產(chǎn)物,建議在上傳成功之后對構(gòu)建產(chǎn)物做完整性檢查。
減少腳本/工具對環(huán)境的依賴
一般情況下,腳本都會或多或少的使用到一些外部工具。而我們的腳本很有可能會運行在不同的環(huán)境中,不同環(huán)境中提供的工具也會有版本和用法的差異。如果需要在環(huán)境中維護某一工具的多個版本的,工具本身的版本管理,以及多個工具之間的依賴沖突和升級更新也會產(chǎn)生較高的管理和維護成本。
我們建議盡可能的減少所使用的工具對環(huán)境的依賴,尤其是系統(tǒng)不會默認(rèn)安裝的工具。另外在編寫腳本的時候,也盡量避免使用只有某些版本特有的語法特性。這些情況都會導(dǎo)致腳本有可能出現(xiàn)一些不可預(yù)期的結(jié)果。我們建議使用容器化工具或者容器化環(huán)境管理工具如Batect來替代對應(yīng)的需求。
優(yōu)點:
- CI/CD agent中只需要安裝容器運行時即可,可以減小agent的體積。
- 容器化的工具因為對環(huán)境的依賴非常低,所以不論是工具升級還是降級都非常簡單,同時也解耦了對agent特性的依賴,提高agent利用率。
- 最大化的保證環(huán)境一致性,使用容器化的工具消除了環(huán)境差異可能導(dǎo)致的非預(yù)期異常。
- 新人友好,新加入的團隊成員可以快速的配置好可運行的環(huán)境,無需過多的考慮具體工具的安裝,配置等。
解耦對CI/CD工具的依賴,雖然在實際項目中很少會有更換CI/CD工具的情況,但是如果需要遷移,我們也只需在新的工具環(huán)境中構(gòu)建出容器運行環(huán)境即可,大大減少了切換工具工作量,提高遷移的速度。
缺點:
- 因為沒有預(yù)裝構(gòu)建所需要的各種軟件,如果本地沒有鏡像緩存,在運行容器化的工具時都需要去容器倉庫中獲取對應(yīng)的工具鏡像,會有額外的帶寬壓力。
- 因為需要獲取工具鏡像,容器啟動也比二進制的程序要慢,所以整個任務(wù)運行過程需要的時間會更長。
- 理論上來講,容器化技術(shù)性能損耗很小,工具的性能和二進制程序的差別不會很大,但是在實際的使用中,我們發(fā)現(xiàn)因為容器引擎配置不當(dāng)?shù)仍驎?dǎo)致一些工具性能變差甚至無響應(yīng)的情況出現(xiàn)。
實施示例:
在使用 terraform 時,不同版本之間的 terraform 并不兼容,那么如何保證所有人與 CI 都使用相同的 terraform 版本就是一個非常麻煩的事情。那么如果我們無論在 CI 還是本地都基于 docker 去運行 terraform 就可以解決這個問題。
使用auto/ACTION模式來維護管理腳本
auto/ACTION是我們在項目實踐中總結(jié)并希望可以廣泛推廣的一個經(jīng)驗總結(jié),在和客戶合作過程中,尤其是有很多團隊的大型項目上,我們從這個模式中受益匪淺。auto/ACTION模式的核心是使用統(tǒng)一語義能表明腳本目的的ACTION來命名管理腳本,如應(yīng)用的測試(test),驗證(validate),打包(build),發(fā)布(deploy)等相關(guān)任務(wù),統(tǒng)一把這些管理腳本歸放在auto目錄下來維護。
因為類unix系統(tǒng)在運行的時候并不真正使用文件后綴來識別文件的類型,我們建議腳本名字不要加后綴。這個建議是基于管理腳本有可能會在多個地方被使用,而不同的開發(fā)和維護人員對于語言的偏好不同,如果在需要使用另外一種語言重寫腳本的時候,使用這個腳本的地方就不需要做更新,消除了因為文件名變化可能導(dǎo)致的自動化任務(wù)的錯誤和中斷。雖然沒有后綴可能會帶來一些不便,比如編輯器的語言類型識別錯誤等,但是相對于它帶來的優(yōu)點,還是非常值得的。
優(yōu)點:
- 管理代碼和業(yè)務(wù)代碼放在同一代碼庫,使用版本控制,便于進行更新,回退。
- 每個腳本只做一件事,職責(zé)單一,同時便于理解和管理。
- 可以方便的知道所有可用的腳本。如:
- 如果跨團隊合作,或者團隊成員有輪換的時候,可以更快速的掌握業(yè)務(wù)管理的上下文。
- 每個項目都有一套自己的auto腳本,如果有基礎(chǔ)性變化,改動成本較高,可以考慮使用git submodule等模式來管理。
- 沒有后綴的文件名會帶來一些管理上的不便。
實施要點:
- 腳本滿足既可在本地執(zhí)行,又能在CI流水線上執(zhí)行,便于驗證。
- 腳本中的變量內(nèi)容盡可能從環(huán)境變量中讀取,避免向腳本中傳入?yún)?shù),方便運行。
- 專屬于CI/CD平臺的腳本不要放在auto根目錄下,建議創(chuàng)建一個對應(yīng)的子目錄,例如 .buildkite, .github, .travis來做管理。
- 可根據(jù)團隊的需求適當(dāng)?shù)臄U展腳本的名字使之更容易理解,建議使用-而非_ 來分隔單詞, 如auto/upload-image-to-ecr。
管理腳本和業(yè)務(wù)腳本分離
我們的應(yīng)用中一般都會有一些腳本來做一些輔助性的工作。這些腳本通常會和業(yè)務(wù)代碼放在同一個代碼倉庫,使用版本控制來進行管理。這些腳本大致分為兩種:管理腳本和業(yè)務(wù)腳本。
管理腳本是用來做應(yīng)用打包,部署等管理相關(guān)工作工作,這種類型的腳本是無需打包進業(yè)務(wù)運行所需的產(chǎn)出物中的;業(yè)務(wù)腳本是輔助業(yè)務(wù)運行,比如說初始化環(huán)境和配置,結(jié)束時的清理工作等,這些腳本需要打包到業(yè)務(wù)運行的產(chǎn)出物中。
我們建議除了一些有特殊要求的腳本外,不要把腳本放在根目錄。并且把這兩種不同類型的腳本存放在不同的目錄中。
優(yōu)點:
- 在封裝鏡像時,業(yè)務(wù)腳本和業(yè)務(wù)代碼同等重要,需要封裝在鏡像中。將管理腳本和業(yè)務(wù)腳本分離可以減少鏡像中的文件數(shù)量。
- 在軟件開發(fā)過程中,針對業(yè)務(wù)運行和自動化管理關(guān)注信息不一樣,將管理腳本和業(yè)務(wù)腳本分離,讓團隊成員更加清楚腳本的類型和目的。
缺點:
- 將管理腳本和業(yè)務(wù)腳本分離,會增加倉庫的層次結(jié)構(gòu)。
實施要點:
- 推薦將管理腳本放置在auto目錄,將業(yè)務(wù)腳本放置在scripts目錄。
- 腳本中的變量采用從環(huán)境變量中讀取,避免向腳本中傳入?yún)?shù),方便運行。
- 推薦腳本名稱即表明腳本的作用,不建議使用auto/script這樣不表意的腳本命名。
- 不在文件名中使用文件類型后綴。
及時更新容器的基礎(chǔ)鏡像
基礎(chǔ)鏡像是業(yè)務(wù)鏡像的地基,其包含了我們業(yè)務(wù)和應(yīng)用所必需的基礎(chǔ)庫、二進制文件和配置文件等。一個良好維護的基礎(chǔ)鏡像通常會根據(jù)需要做更新,這些更新通常包含安全補丁,新功能或?qū)Σ僮飨到y(tǒng)或框架的改進等,我們建議及時的更新容器的基礎(chǔ)鏡像來保障業(yè)務(wù)的安全性。除非有特定原因需要繼續(xù)使用舊版本鏡像,否則應(yīng)及時跟進使用經(jīng)過充分評估和測試的最新版本鏡像。
在Dockerfile和compose等文件中,可以通過指定鏡像中的標(biāo)識和sha256值組合來指定基礎(chǔ)鏡像的版本。當(dāng)鏡像有了更新之后,及時沿用了如latest或大版本號這類通用性比較高的標(biāo)簽時,其sha256的值也會發(fā)生變化,通過更新這個組合可以更新使用最新版本的基礎(chǔ)鏡像。
優(yōu)點:
- 最新的鏡像通常帶有可以增強應(yīng)用程序安全性的補丁修復(fù),降低安全風(fēng)險。
- 最新的鏡像通常包括可以提高應(yīng)用程序性能的新功能或改進功能。
缺點:
- 新功能可能存在不可預(yù)期的bug。
- 新的功能有非常小的概率存在未知的安全漏洞,如果有特殊的安全需求,請在安全部門的指導(dǎo)下升級。
實施示例:
- 可以使用dfresh或者類似的工具來檢查和更新基礎(chǔ)鏡像。
- 檢查基礎(chǔ)鏡像是否有更新
- 更新基礎(chǔ)鏡像
- 回退方法 在需要回退基礎(chǔ)鏡像版本時,可從代碼庫的提交找到上一個可用版本的相應(yīng)信息。
定期檢查和升級依賴包
隨著 Bug 修復(fù)、新功能的開發(fā)或者其他更新,我們應(yīng)用的依賴包可能會過時。此時應(yīng)用的依賴項越多,就越難跟上這些更新。過時的依賴包可能對安全構(gòu)成威脅,并對性能產(chǎn)生負(fù)面影響。最新的軟件包可防止漏洞,這意味著定期的依賴性檢查和更新很重要。我們建議定期的對應(yīng)用的依賴包做更新和安全檢查,并升級到一個合適的版本。并且我們建議在應(yīng)用的 pipeline 中加入這些檢查任務(wù),并在常規(guī)的開發(fā)過程中及時發(fā)現(xiàn)和升級。如果應(yīng)用已經(jīng)處于維護階段,我們也建議定期執(zhí)行這些檢查并在需要的時候加以升級。
優(yōu)點:
- 定期升級依賴可以讓應(yīng)用的安全性和代碼的可用性都有保障。
- 定期升級依賴會讓解決依賴版本沖突和代碼兼容性變得容易。
- 更新依賴項可以獲得新的依賴項版本提供的所有性能改進。這些改進可以有多種形式,例如修復(fù)以前的性能問題、改進了實現(xiàn)和算法等。
- 升級依賴項不僅可以改進現(xiàn)有功能,還可以使用到以前不存在的新功能。這些新功能最終可能讓我們更好的實現(xiàn)自己應(yīng)用的新功能。
缺點:
- 如果不及時更新依賴,將會使得產(chǎn)品難以維護,并可能導(dǎo)致開發(fā)人員的時間被常規(guī)的、無意義的工作占用。
- 如果長期不更新依賴,會使應(yīng)用面臨無人問津的風(fēng)險,之后在某一天需要進行改動的時候,面臨大量的依賴包過期無法獲取和版本升級造成的接口變化。這時就需要投入非常高的成本來讓代碼重新變得可用,甚至完全無法更新而變成遺留系統(tǒng)。
- 當(dāng)進行大的版本升級時,需要對應(yīng)用程序進行更多的更改才能與較新的庫兼容。這使得付出代價比及時更新依賴大得多。
- 如果忽略升級依賴項,那么會面臨無法在自己喜歡的平臺上運行軟件的可能。例如,如果停止升級軟件中的數(shù)據(jù)庫驅(qū)動程序,那么將無法使用舊版本的數(shù)據(jù)庫系統(tǒng)。這不僅會使應(yīng)用變得過時且易受攻擊,而且甚至可能無法從該數(shù)據(jù)庫系統(tǒng)提供商處獲得任何支持。
- 如果應(yīng)用依賴于過時的依賴項而導(dǎo)致升級困難變得很難維護,會使得項目很難找到對這些舊技術(shù)有經(jīng)驗的人,甚至失去現(xiàn)有的維護者。
實施示例:
(1) 手動檢查
JS 篇:
- npm-outdated & npm-update
- npm outdated:可以使用 npm outdated 獲取當(dāng)前需要升級的包的信息。
- npm update: 會把所有的包升級到我們定義的需要的版本號。如果需要升級到最新的則需要使用@latest eg: npm update cypress@latest。
- npm-check-updates: 是一種更高級的檢查工具
首先需要全局安裝 npm-check-updates: npm install -g npm-check-updates ;
ncu: 檢查需要升級的包信息,這里類似 npm outdated;
ncu --upgrade/ncu -u: 將所有的包升級到最新版本,即便是包含重大更改,也會進行更新。注意:更新完成后不會自動運行 npm install,所以還需要再手動執(zhí)行來更新 package-lock.json。
ncu --interactive/ncu -i : interactive mode 安裝某個包。
小結(jié):npm-outdated 和 npm-check-updates都可以用來做 JS項目的包檢查、升級。
Java 篇:
- 在 build.gradle中配置 owasp.dependency-check
- 執(zhí)行./gradlew dependencyCheckAnalyze
查看報告:項目根目錄>build>reports>dependency-check-report.html
(2) CI Pipeline 集成
- npm-check-updates 與 Buildkite Pipeline 的集成由于 buildkite 沒有官方插件支持 dependency-check。所以對于buildkite 推薦兩種方式:
- 自己開發(fā)對應(yīng)功能的插件,然后集成到 pipeline 的 step 中;
- 通過 docker-compose 的方式去運行對應(yīng)的檢查,將其在 pipeline 的 step 中去運行(如果需要可以添加 block 來強制檢查 npm-check-updates 的結(jié)果)。
- jenkins pipeline 的集成:需要安裝 dependency-check Plugin。步驟如下:
- 在 Jenkins Global Tool Configuration 安裝 dependency-check;
- 在 Jenkins builder 配置已經(jīng)安裝好的 dependency-check;
- 在 Jenkins Publish 里配置讀取 dependency-check 的 report ,通過對相關(guān)指標(biāo)進行讀取,設(shè)置閾值,配置構(gòu)建失敗或者警告等設(shè)置。
如果項目 code 托管在 Github,我們可以使用 Dependabot 和 Renovate 工具和 Github 集成來做依賴檢查。這兩個工具都會做定期掃描,創(chuàng)建依賴版本升級的 PR。
配置 Dependabot 進行版本更新:
- 在 GitHub 的代碼倉庫的主頁,找到代碼倉庫名稱下的 setting;
- 在邊欄的安全性部分中,單擊代碼安全性和分析;
- 在代碼安全和分析下,在Dependabot version updates右側(cè),單擊啟用以打開存儲庫 .github 目錄中的基本 dependabot.yml 配置文件;
- 添加version;
- 添加 updates 部分,并輸入希望 Dependabot 監(jiān)視的每個包管理器的條目;
- 對于每個包管理器,可使用:
- package-ecosystem 指定包管理器。
- directory 指定清單或其他定義文件的位置。
- schedule.interval 指定檢查新版本的頻率。
- 在代碼倉庫的根目錄創(chuàng)建.github目錄;
- 創(chuàng)建 dependabot.yml文件并且存儲到.github目錄下。
示例 dependabot.yml:
配置 Renovate:
- 在 Github 的 App 里面安裝 Renovate app https://github.com/apps/renovate;
- 安裝并配置完成后可以在PR中看到一個自動生成的PR Configure Renovate,這個PR中包含一個 renovate.json 文件,這個文件中包含了 renovate 的一些默認(rèn)設(shè)定;
- 可以根據(jù)文檔 (https://docs.renovatebot.com/configuration-options/) 添加或者修改適合自身項目的具體配置項;
- merge 此 PR;
- Renovate 會根據(jù)你配置的 schedule 時間去自動的掃描并生成包升級 PR 提醒
定期的重新部署維護階段的應(yīng)用
在應(yīng)用處于維護階段,如果業(yè)務(wù)不再會增加新的功能,抑或因為某些原因無法做定期的應(yīng)用依賴升級,我們也建議你定期的重新部署這個應(yīng)用,以應(yīng)對平臺等更底層的變化帶來的部署失敗的風(fēng)險。定期部署可以確保你的應(yīng)用在新的平臺環(huán)境中也可以正常的部署,如果在周期性的部署過程中發(fā)現(xiàn)應(yīng)用無法在新的環(huán)境部署,你也會有一個緩沖期來制訂應(yīng)對策略,而不是在平臺完成升級之后的某一天,應(yīng)用發(fā)生了問題才發(fā)現(xiàn)已經(jīng)無法部署。
優(yōu)點:
- 定期部署應(yīng)用是對部署工具和流程的有效驗證,CI/CD Agent的一些升級有可能會導(dǎo)致我們在部署流程中使用工具發(fā)生兼容性問題,定期部署可以及早的發(fā)現(xiàn)這些問題。
- 定期部署應(yīng)用也能夠有效縮短我們的依賴獲取未驗證的窗口期。雖然我們的應(yīng)用依賴可以鎖定版本,也可以將依賴保存到私有倉庫,但長時間沒有運行相關(guān)部署流程,我們無法保證應(yīng)用的依賴能夠在需要的時候可正常獲取且可用。
- 現(xiàn)在的應(yīng)用的基礎(chǔ)設(shè)施很多都基于各類云平臺,服務(wù)提供商會定期的對自己的基礎(chǔ)設(shè)施做升級和換代,定期部署應(yīng)用可以讓我們及早的獲知基礎(chǔ)設(shè)施變化帶來的兼容性風(fēng)險。
缺點:
- 定期部署會對系統(tǒng)的穩(wěn)定運行造成一些影響,變化本身就會帶來一定的未知風(fēng)險。
- 自動化的發(fā)布一般情況下都需要配有完善的回歸測試流程來確保業(yè)務(wù)的可用性,會帶來成本的增加