DevOps實施的驅動力和難點
對於軟體研發人員,一定要形成一個意識,即對於重複的內容一定要自動化,對於共性內容則透過抽象可變引數後也自動化。軟體系統開發應用本身就是為了實現業務工作的自動化,但是很多開發人員反而沒有這種自動化和可配置化的意識。
對於技術團隊來說,DevOps的實施或者說狹義的持續整合和交付過程的實施,很大的驅動來源於問題驅動,而不是風險驅動。比如在實施微服務架構化後,原來一個單體已經拆分為20個微服務,整個編譯構建,打包,部署的工作如果還需要靠人工來完成已經巨大的工作量,這個時候不得不尋求變化,將人工的工作自動化掉提升效率。
對於高層管理來說,推進DevOps實施和文化建設,很大的一個驅動力來源於一個意識,即任何IT系統的建設最終都需要形成對企業發展有用的資產,這個資產不是伺服器,而是可管理可運維的軟體,可分析可決策的資料。而DevOps剛好是積累這個資產最好的一個過程支撐,簡單來說就是良好過程支撐最終形成的資產才是可見可控的。
其次,對於整個IT團隊來說,最大的價值是IT系統能夠高效的支撐業務,這個支撐即包括了IT敏捷的響應和快速上線交付能力,也包括產品的高質量交付能力。這些雖然在DevOps出現前,已經有類似敏捷開發,自動化測算,程式碼檢查,灰度釋出等大量的過程實踐。但是可以看到透過DevOps過程實施可以將這些實踐更好的進行融合,形成一個完整的整體。
那麼DevOps過程實施真正的難點在哪裡?
在我前面文章也談到了,即使你自己採用類似Jekins工具來打通一個完整的持續整合和部署的過程鏈也不是太難得事情,因此整個DevOps實施難點不在基礎的支撐工具層面。
真正的難點實際體現在三個方面。
團隊技能儲備:面對太多新技術,團隊技術本身的儲備問題遺留系統遷移:不涉及微服務化還好,如果涉及微服務化往往改造遷移難度巨大研發敏捷文化:不要期望工具能夠驅動文化改進,而是文化改進推動工具應用
當面臨以上問題點的時候,可以看到DevOps推進相對困難,這個還沒說高層領導本身是否支援,即使高層支援研發團隊仍然面臨上面幾個關鍵問題需要解決。
比如一個研發團隊,原來就沒有實踐過敏捷開發,或連Jekins持續整合也沒有實施過程,更沒有接觸過Docker容器方面的內容。那麼要實踐這個過程實際仍然有很大的技術難度。當然DevOps實施比較好的點在於整個底層技術鏈支撐平臺構建不需要每個人都參與,只需要1到2個核心人員來構建即可,但是仍然需要規範所有研發人員的研發流程,溝通協同機制等。
二進位制交付和分支管理從最早談每日構建和持續整合的時候,就在談一個關鍵的概念,即整個持續整合的過程應該是基於打包完成後的二進位制部署包檔案的,而不是在每個環境再重新打包構建。
這個點為何如此重要?
簡單來說就是測試人員在測試環境測試驗證透過的版本是基於二進位制部署包的,因此只應該對二進位制部署包質量負責,如果在生產釋出的時候重新在構建,那麼對於程式碼一致性,構建過程等引入的其它質量隱患將無法預知。這也是經常出現測試和開發人員扯皮的一個原因,即本來測試的版本沒有問題,但是一發布到生產環境去功能就出現問題。
但是每個環境總會有一些和環境相關的全域性引數或配置檔案資訊,因此這些資訊不能打包到部署包裡面,而是應該單獨拿出來進行管理。
如果是簡單的全域性引數,可以直接啟用類似環境變數進行設定。但是如果涉及到比較複雜的配置檔案,那麼就需要考慮單獨進行配置檔案的管理和分發,類似當前微服務架構下,可以採用全域性的配置中心來管理和分發配置檔案。而這個功能在進行DevOps持續整合和交付的時候仍然需要。
在基於二進位制包進行持續整合後,可以看到實際不會再存在類似測試環境進行獨立的編譯構建的說法,因此對於傳統的分支管理策略也需要調整。
在最早的軟體開發和分支管理裡面可以看到,如果有測試環境會單獨存在一個測試分支,也有獨立的生產分支,其次才是開發分支和Bug分支。
先分析下傳統開發下為何需要獨立的測試分支?
看下測試分支的使用場景,啟用測試分支的一個重要目的就是要將測試環境和測試的原始碼版本和開發環境版本完全區分開。保證測試環境版本的穩定性。我們可以試想一個最簡單的環境,同樣的一個軟體模組或原始碼檔案,涉及到兩次需求變更的處理,分別對應到兩次的版本規劃和釋出。而第一次的版本變更修改完後就需要提交測試,如果沒有測試分支,直接導致的後果就是開發人員對第二次需要變更的內容和測試人員對第一次變更的測試兩個工作無法並行。而傳統的做法往往是開發人員將二次變更暫時不 check in 到開發分支上,這直接導致的問題仍然是最新的原始碼無法在配置伺服器環境上準確受控。同時開發分支,往往每天都涉及到程式設計師程式碼的檢入和檢出,直接導致開發環境分支本身並不是一個穩定的,隨時都可以提交給測試的版本。這也是啟用測試分支的一個重要意義。
而在持續整合模式下,開發需要確保每天checkin的程式碼都是能夠編譯透過,構建成功的。同時在敏捷短週期迭代模式下,也儘量減少了同時需要應對上面談到的多個版本需求變更的情況。
因此在這種場景下測試分支沒有存在的意義。在DevOps和持續整合下,實際基於Master主分支拆分開發分支和Bug分支兩個分支即可以滿足日常配置管理需求。
對於日常作為版本規劃後的功能開發在開發分支進行,在版本開發完成並測試通過後進行版本基線,同時將內容Deliver到Master主分支。同時基於Master主幹可以拉出一個Bug修改分支,專門負責Bug的修改,當Bug修改完成測試通過後Deliver到主幹分支。同時開發在拉取程式碼或交付下個版本到主幹的時候,如果存在程式碼衝突再啟動自動或人工的Merge操作。
敏捷研發過程和持續整合的協同在實施DevOps的時候往往重點都在思考研發和運維的協同和一體化。但是實際上可以看到研發過程管理和持續整合的協同才是一個需要關鍵解決的問題點。
在我前面就提到一個觀點,即如果在實施完成DevOps後,你發現研發和測試,研發和運維之間還存在大量需要線下去人工溝通的協同點。那麼即使DevOps工具鏈讓持續整合過程自動化了,但是對於整個研發過程仍然沒有實現理想的自動化和流水線作業。
首先基於上圖再對整個協同過程做下說明。
1.開發人員開發完成程式碼,在本機進行單元測試或自測透過。2.開發人員將對應的需求或缺陷狀態修改為待部署。2.開發人員檢入程式碼到Git程式碼庫,這裡可以實時觸發或定時觸發流水線。3.流水線觸發,開始執行拉取程式碼,編譯構建,打包,映象製作和部署流水線作業過程4.流水線執行成功,自動將對應的需求或缺陷狀態變更到待測試5.測試人員進入研發平臺在待辦中可以看到待測試的內容,同時當前測試環境已經是最新版本6.測試人員進行測試,如果有缺陷則提交缺陷7.在有缺陷情況下,測試人員將流水線處理未不透過退回8.開發基於測試提交的缺陷進一步修改,同時整個流水線持續迭代。9.在測試驗證透過所有缺陷後,測試透過,流水線執行環境遷移動作。
以上過程希望達到的一個最簡單目標就是開發和測試之間並不需要複雜的溝通,類似哪個功能開發完了,你可以到測試環境測試了。這些都是不必要溝通,這些資訊完全可以透過研發任務管理平臺和持續整合的協同進行推送,各方只需要關注待辦即可。同時對於環境的遷移也應該是自動化的,只要當前版本測試問題全部關閉,則可以進行環境遷移動作。
流水線設計不是一個開發環境的編譯構建自動化,還涉及到整個環境遷移。比如當前有開發,SIT測試,UAT測試三個環境,實際的流水線應該為:
程式碼拉取->編譯構建->打包->開發部署->測試驗證->UAT遷移部署
特別是當你面對外部的使用者進行UAT測試或需要提供給外部人員一個演示環境的時候,整個流水線過程的價值就進一步體現。比如內部測試人員最終測試透過的版本,可以完全自動化快速的在5到10分鐘就交付一個UAT環境給外部使用者進行測試。
關於程式碼檢入是否實時觸發構建的問題?
實際上可以看到,沒有必要一有程式碼檢入就實時觸發構建,每天定時2到3次左右的構建頻度基本完全可以滿足日常敏捷性的需求。
在定時進行流水線執行的時候,在我前面也提到一個關鍵點,即如果定時觸發執行的時候,發現程式碼分支沒有任何修改或檢入,這個時候應該直接跳過執行,不再進行構建操作。
產品到專案兩級管理體系對於一個軟體產品在進行微服務架構改造後,就形成了產品-專案的兩級管理體系,專案這級對應到具體的微服務模組。比如我們自己的DevOps支撐平臺產品,已經拆分為門戶,研發過程管理,持續整合,容器管理和排程,資源管理,製品庫,測試管理,度量管理等多個微服務模組。
但是最終向生產環境交付的是一個完整的產品,產品中包含了多個微服務,每個微服務本身又是以容器化部署的方式進行獨立交付。那麼在進行持續整合和持續交付的時候,就一定存在產品-專案的兩級流水線,兩級流水線如何協同就是必須要考慮的一個關鍵問題。
基於兩級流水線設計,我們希望達到的效果就是:
我們不用去關心產品變更的時候究竟變更了哪些微服務模組,即一次變更我們直接啟動產品流水線。產品流水線啟動後自動檢查哪些微服務發生變更,如果發生變更則出現持續整合操作,如果沒有變更直接調整到End完成節點。
在所有微服務單個流水線執行完成後,我們聚合到產品流水線進行人工測試和驗證,沒有問題後我們可以進一步進行打標籤操作,或觸發環境遷移操作。
注意環境遷移我們可以根據本次變更版本號或根據我們手工打的標籤號進行,同時環境遷移操作本身不再涉及到編譯構建和打包操作,因此環境遷移應該配置在產品級流水線上進行。
如果從SIT環境遷移到UAT環境後測試不透過,測試出了相應的Bug,這個時候產品流水線退回到開始節點,同時開發人員在修改完成Bug後,同樣系統自動判斷哪些微服務模組程式碼出現變更並自動觸發構建打包操作,直至所有的Bug修改完成並驗證透過。
當然,更好的方式是在規劃了新的迭代版本的時候,首先分析會影響到哪些微服務模組,然後對這些微服務模組進行開發版本升級,並啟動迭代開發任務,檢入檢出程式碼。但是執行整個產品流水線的時候,只對本次版本升級到的微服務進行編譯構建和打包操作,而對未影響的微服務不啟動相應的重複編譯構建操作。
同時在最終的產品向生產環境的交付環境,同樣需要做到只對變更過的微服務進行版本部署和交付,而對沒有變更的模組不再進行重複的部署和交付。
透過產品和專案微服務兩級的流水線管理方式,實際的產品迭代版本是管理到產品這個粒度,但是最終的持續整合和交付又是管控到微服務這個粒度。產品層面可以人工或自動的分析究竟哪些微服務需要變更和受影響,僅交付和部署變更的內容,而不是整個產品重新部署。
基礎依賴Jar包版本變化
假設上面的研發管理,持續整合,製品庫三個微服務,都同時依賴一個common.jar包。裡面為公共可複用的方法函式。
這時候研發管理微服務對common包提出需求,新增加了一個共性介面,這個common包需要重新構建並進行了版本升級。那麼這個時候實際上持續整合和製品庫兩個微服務是沒有必要進行重新編譯的,即使釋出了新的jar包版本,仍然不需要重新編譯這兩個微服務。
不能因為是共性依賴包,只要版本變化就對所有上層的微服務模組全部重新編譯構建,這個和我們前面談到的透過微服務進行解耦思路是相違背的。
同時更好的方法應該是將common包本身提升為一個獨立的微服務,同時提供API介面給上層的微服務模組使用,透過API介面來實現徹底的解耦。
內部微服務協同和對外介面暴露先說下整體微服務架構下的內部協同。
對應內部協同仍然可以使用Eureka註冊中心,即Eureka本身也做為一個獨立的微服務元件透過容器化的方式託管部署到整個容器資源池中。同時在微服務模組自動部署或自動進行彈性伸縮擴充套件後,註冊中心仍然能夠自動發現對應的微服務,仍然能對擴充套件的微服務模組進行心跳監控。也就是說傳統微服務下的內部基於註冊中心的服務註冊發現,服務呼叫模式本身並不需要大變化。
其次是對外進行的API介面暴露,這塊在前面文章給出了一個說明如上圖。
當微服務和容器雲集成後,在微服務部署完成或擴充套件完成後,會形成一個ClusterIP虛擬的叢集節點供內部模組訪問。如果需要對外也可以走Ingress或Loadbancer模式進行。
如果僅僅是BS端內部應用模式,走K8s叢集IP完全可以滿足需要。
但是如果涉及到外部APP或其它第三方應用訪問,仍然需要將相關的API介面註冊到API閘道器再統一對外暴露。而API閘道器本身又存在兩種方法。
其一是直接對接到Kurbernetes叢集的叢集IP上
其二是對接底層的NodePort各個節點,然後進行負載均衡
不論是哪種方式,實際期望的是在微服務自動部署或彈性擴充套件完成後,能夠自動的完成相關的API介面服務在API閘道器上的自動註冊操作。
比如可以在微服務開發中新增加一個配置檔案,確定哪些微服務介面需要進行自動化註冊,然後再部署完成後,系統自動讀取這個配置檔案,呼叫API閘道器的介面完成自動化註冊操作。
對單元測試進行最簡化對於單元測試和自動化測試,實際問題不在於技術和方法,而在於工作量。在10多年前團隊進行的單元測試實踐就看到,要做全部覆蓋所有業務場景的單元測試,那麼寫Junit單元測試用例的時間往往比程式碼開發的時間還長。這也是導致全面的單元測試很難推行的一個原因。
而到了當前敏捷研發過程中,要花費如此長的時間去寫單元測試用例並執行,顯然也是很難落地執行,這個事情雖然是長期有效,但是短期來看往往是降低了整體交付效率。
那麼單元測試究竟做不做?
如上圖,可以看到微服務B存在後端和前端兩個微服務模組,而微服務B提供的80%以上介面都是直接給前端模組使用。如圖僅僅只有03,04兩個介面需要提供給微服務C使用,而05介面需要提供給微服務D使用。當然微服務B本身也依賴微服務A提供的兩個API介面服務。
在這個分析清楚後,為了減少微服務模組之間本身的相互依賴影響。對於微服務B暴露給C和D使用的介面我們建議是必須進行單元測試,並確保驗證透過。如果這個單元測試沒有透過那麼微服務B部署失敗,需要進行回退。
其核心原因就是一個微服務的部署不能影響到其它微服務模組的使用,這個是必須進行嚴格的邊界控制的地方。而這種對外介面本身量不大,完全可以啟用單元測試並進行自動化執行。
這部分內容應該由開發人員編寫類似Junit程式碼來完成單元測試和驗證,而不是由測試人員進行自動化測試,這樣才能夠確保交付到測試時候的基本功能完整性。
基礎框架和技術服務能力提供上圖是一個典型的微服務部署邏輯架構,實際上可以看到在後期的持續整合和開發中,只有紅色部分會不斷的進行版本變更和迭代升級。對於藍色和綠色部分屬於基礎框架和技術服務內容,往往只需一次部署即可。
在當前雲原生架構趨勢下可以看到,PaaS雲平臺逐漸這部分能力,當前比較主流的就是類似圖中涉及到的資料庫,訊息中介軟體,快取,監控,日誌等能力全部由PaaS雲平臺提供共性技術服務支撐。而對於綠色部分註冊中心,內部閘道器,限流熔斷等往往可能暫時還由應用私有,需要自己進行部署和管理。
在這種情況下就需要提供對這些基礎框架和技術服務的容器化部署和託管支撐。
這塊的能力實際上並不存在微服務模組一樣的基於程式碼的編譯構建操作,而是直接基於已有的安裝包進行安裝部署。因此對於這塊的能力提供主要有兩種方式:
其一是自己基於Dockerfile製作容器,並託管部署到雲平臺
其二是提供標準的技術服務申請能力,直接申請你需要的技術服務,由平臺完成部署
對於類似微服務閘道器,限流熔斷本身是否可以自動的完成高可用性架構託管部署,實際需要進一步驗證,包括後期如果出現效能問題,能否類似微服務模組一樣進行自動化擴充套件等。
面向API介面的設計開發在微服務開發過程中,整個微服務劃分和微服務間的介面設計仍然需要保持高度的架構完整性和概念一致性。即首先透過架構人員進行微服務拆分,關鍵介面設計,其次才是進行各個微服務模組的開發,在開發完成後進行整合工作。
如上圖,大家遵循同樣的介面契約,那麼後端開發,前端開發和測試人員可以並行開始各自的工作。對於前端優先進行介面開發和實現,前端則透過介面契約產生Mock模擬,透過介面模擬實現來進行前端功能的開發。在前後端開發過程中,測試人員也可以根據介面定義進行測試設計工作,同時進行相關的測試指令碼設計或錄製工作。
介面開發完成後,前端和後端首先各自進行單元測試,在單元測試完成後進行前後端的整合測試和驗證。同時測試人員可以啟動相應的介面自動化測試工作。
介面暴露和開放範圍
在確定了介面驅動開發的思路後,還是要重新回顧介面開放的範圍問題。比如上面測試部分談到的介面整合圖。微服務B開放了很多Rest API介面,但是大介面都是給自己的前端模組使用,只有03-05三個介面是給其它微服務使用的。
在這種場景下我們並不希望微服務C和D能夠隨意訪問微服務B提供的所有介面。
因此就需要進一步對介面訪問許可權進行控制,約束到外部微服務僅僅能夠訪問的介面,至少首先要做到對於需要對外跨模組暴露的介面單獨編寫獨立的介面檔案併發布。