作為現代軟體工程的基礎實踐,基礎設施即程式碼(Infrastructure as Code, IaC)是雲原生、容器、微服務以及DevOps背後的底層邏輯。應該說,以上所有這些技術或者實踐都是以基礎設施即程式碼為基本模式的一種或者多種方法的集合。基礎設施即程式碼並不是一種特定的技術,而是一種解決問題的思路。本文將從基礎設施即程式碼的含義,原則和落地方法三個層面來幫助你理解為什麼沒有使用IaC的DevOps系統都是耍流氓。
基礎設施即程式碼的目標是解決一個古老的問題:如何能夠安全、穩定、快捷、高效得完成軟體交付過程。注意這裡的交付並不僅僅指將可部署的軟體部署到最終的執行環境,而是更寬泛的概念上的交付,也就是將軟體從一個看不見摸不到的想法或者創意,轉變成使用者可以操作並使用的一個系統。這個過程涉及軟體創意的捕捉、設計、計劃、開發、測試、部署和釋出的全過程,也包括軟體釋出之後收集使用者反饋重複啟動以上過程的迭代。這個迭代在軟體的整個生命週期裡面會一直重複下去,直到這個軟體不再有人使用,壽終正寢。
軟體生命週期中的這個持續的迭代過程構成DevOps反饋環路的概念,在這個反饋環路的左右兩端分別是Dev和Ops,而程式碼和基礎設施正是Dev和Ops最重要的工件(Artifact),Dev維護程式碼,Ops維護基礎設施。傳統意義上,程式碼和基礎設施是有明確的界限的,一般來說:程式碼特指應用程式碼,而基礎設施則是除了應用以外(或者以下)的所有「基礎」元件。
在雲端計算技術出現以前,硬體被普遍認為是一旦建立就無法改變的,就好像你購買一臺電腦,如果希望更換其中的CPU,內部,磁碟以及網路卡,那麼都必須重新購買相應的元件並重新組裝。雲端計算將計算資源(電腦)解偶成了可以隨意組合的計算、儲存和網路三種資源型別,允許使用者根據需要自助的進行組合。其底層實現機制是將硬體軟體化,比如:物件儲存技術和軟體定義網路(SDN)就是雲端計算的基礎技術。硬體被軟體化之後的結果就是我們可以通過設定來變更硬體的能力。這其實就是基礎設施即程式碼的最基礎的含義。
但是對於使用者而言,其實並不關心所使用的軟體到底執行在什麼硬體,什麼雲上面。比如:對於使用者的社交需求來說,微信就是社交需求的基礎設施;對於需要編寫檔案的使用者來說,WORD就是他的基礎設施。相對於硬體,這其實是基礎設施的另外一個極端含義,也就是:任何支撐使用者完成某種操作的支撐能力,都可以成為使用者這一類別操作的基礎設施。
從這個極端含義的角度來說,以上環境堆疊中的任何一層都可能成為上層的基礎設施。為了能夠為上層提供類似雲端計算的自助化能力,都需要提供可設定性。為了滿足這種可設定性的需要,IT行業裡面就出現了容器化技術、Kubernetes、Terraform、Azure Resource Manager等等基礎設施即程式碼的實現方式。其實這些技術解決的都是一個問題,也就是系統的可設定性問題。
實現可設定效能力的方法很多,傳統運維的做法其實很簡單,就是通過指令碼來自動化這個設定過程,讓原本繁瑣的設定過程自動化起來。
自動化指令碼的方式雖然能夠在一定程度上解決可設定問題,但是當系統變更頻繁程度到達一定量級的時候,維護自動化指令碼的工作量將會抵消自動化指令碼所帶來的效率提升,這個時候運維團隊會發現採用人工處理的方式甚至比編寫和維護自動化指令碼更加高效。因此,在當今軟體迭代速度越來越快的背景下,自動化指令碼逐漸無法滿足團隊應對快速變化的市場的需要。我們需要一種能夠能夠允許團隊輕鬆適應快速變化的環境維護方式,基礎設施即程式碼(Infrastructure as Code, IaC)就是在這樣的背景下誕生的。實際上,說誕生並不準確,IaC其實是工程師們在遇到問題之後持續改進的結果。
當維護自動化指令碼的方式無法適應快速變化的市場需要的時候,如何讓開發和運維團隊解偶就變成了解決這個問題的核心。在上圖左側的工作模式中,問題的核心是開發和運維團隊之間基於「請求-響應」的工作模式,這種工作模式讓開發和運維團隊相互依賴,無法獨立的按照自己的節奏工作。為了解決這個問題,IaC借用了大規模軟體架構設計中的分層原則,讓那些需要被共用的能力變成通用元件,並在開發和運維之間共用這些元件,從而讓本來互相依賴的兩個團隊變成依賴另外一個第三方元件。如下圖:
為了能夠通過第三方元件協同工作,IaC方式需要遵循幾項關鍵原則
宣告式(Declarative):為能夠讓所有編排能力獨立於開發和運維團隊,任何一個團隊都不應該將能力的具體操作保留在自己的團隊中,採用宣告式設定可以確保這一點,因為設定中只有宣告沒有具體邏輯,就意味著原來的依賴雙方都必須將公用能力貢獻出去,否則上圖中的C就無法生效。
冪等性(Idempotence):進一步來說,宣告式設定必須能夠在任何時候確保環境編排的結果,也意味著通用元件C中對於環境的操作必須能夠在任何狀態下執行並且獲得一致的最終結果。換句話說,無論目標環境處於初始狀態,中間狀態,最終狀態還是錯誤的狀態,當載入宣告式設定以後,都會變成我們需要的理想狀態(Desired State)。
從本質上來說,IaC是一種做事情的方法,實現IaC的方法和工具只要遵循以上原則,都可以幫助團隊落地這種方法。在實際工作過程中,我們需要一些基本條件才能夠實現IaC:
落地IaC將改變團隊(特別是開發和運維團隊)的工作模式和共同作業方式,雙方的工作邊界和職責都會發生變化。傳統模式下,開發和運維團隊通過流程進行共同作業,雙方在需要對方配合的時候發起一個流程(發出請求),等待對方按照要求完成操作(給出響應)之後繼續這個流程直到目標達成。而IaC則要求雙方通過共用能力進行共同作業,雙方需要持續發現共同作業中阻礙對方獨立工作的問題點,一起將解決這些問題的能力貢獻給另外一個雙方共用的元件(一般是一個工具),在日常工作中不再依賴流程驅動對方,而是使用這個共用的元件(工具)完成工作。IaC的工作模式要求兩個團隊都接受不確定性思維方式,出現問題的時候要共同解決問題而不是界定和追究責任。如果團隊中的文化不允許這種不確定思維方式的存在,IaC將無法落地。
具備以上文化支撐的團隊,需要共同構建一個雙方都認可的工具,將雙方都需要的環境編排能力全部封裝到這個工具中。這個工具的核心目標有兩個:
解偶:讓雙方在日常工作中不再依賴對方,可以按照自己的節奏和工作模式自由的使用,同時確保雙方關注的標準,規則和策略都可以被正常落實並可以被監督。
可客製化可演進:這個工具存在的目的就是為了能夠適應不停變化市場需要,一個靜態的工具是無法做到這一點的,只有那些具備了高度可客製化性和擴充套件性的工具才有可能具備這樣的能力。因此在設計和實現這個工具過程中做到功能粒度的控制就變得至關重要,如果所有的功能都按照日常業務流程設計,不考慮通用性,最終的結果就是任何的工作流程變更都會造成工具的變更,這樣的工具也就失去了通用元件的存在價值。
實際上,雲原生,微服務,容器化和DevOps都是在從各個不同的層面踐行IaC。雲原生強調利用雲的基本特性賦能團隊,其實就是利用上述雲端計算的底層技術為團隊提供實現IaC的基礎條件。微服務則是通過元件化的思維讓多個團隊可以獨立自主的工作,不再受到其他團隊的影響從而最大化團隊工作效率。容器是在雲端計算技術的基礎上,為作業系統以及其上層的環境堆疊提供IaC能力,包括Docker和Kubernetes為代表的主要容器工具都是基於宣告式設定和冪等性原則設計的。
DevOps在這裡又是什麼呢?DevOps是以上所有這些概念,方法和實踐的綜合,實際上DevOps的範疇比這個還要寬泛。開頭的那張圖上你應該可以看到,從廣度上來說,圍繞Dev和Ops構成的這個無限環其實涵蓋了軟體交付過程的所有環節,從深度上來說,DevOps又可以涵蓋文化理念、管理方法、商業創新、敏捷和精益、專案管理、團隊管理、技術管理和工具實現的全部層次。以至於越來越多的人將越來越多的內容放在DevOps這頂帽子下面,出現了諸如:AIOps, GitOps, TestOps, DevSecOps, BizDevOps等很多擴充套件概念。
其實,我們不必糾結這個概念本身,因為來自於社群自發總結的DevOps本來就沒有一個集中的智慧財產權所有者,也就沒有人能夠給予它一個明確的釋義。這本身其實也是一件好事,因為就如同以上對IaC的分析解釋一樣,DevOps的存在也是在幫助我們持續改進的,如果它本身變成一個靜態的方法集或者工具包,又如何能夠適應當前不斷多變的商業環境和市場需要呢?從這個角度來說,所有那些希望將DevOps進行標準化的所謂認證,體系和方案其實都是一種帶有誤導性的行為。
很多的企業在引入容器技術,微服務,雲原生以及DevOps的過程中仍然在延續原有部門結構和工作流程,並且試圖將這些新技術新方法融入到現有的流程中,而不是探索新技術新方法帶來的全新可能性。我在過去十多年幫助企業實施落地DevOps的過程中遇到了太多類似的組織,其中最明顯的表徵就是你會發現那些推動DevOps實施落地的部門不停的界定責任,造鍋甩鍋,那麼他們一定是在用DevOps耍流氓。以上我所描述的IaC的工作思路對這些人來說從來都不重要,他們不在乎問題出現以後的根源分析和改進措施,相反如果根源分析的結果讓鍋到了自己頭上,那麼寧可不分析;如果改進措施的結果是需要自己將一部分職能貢獻出去,那更加不能做。最後的結果就是那個「共用工具」 裡面埋藏了各種修補程式,寫死流程以及技術債務,可想而知這樣的工具又如何能夠承載不確定性,如何能夠幫助其他部門提高效率,更不要提為組織提升總體效能了。對了,這個埋藏了大量定時炸彈的工具就是你們熱衷的那個「一體化研發平臺/DevOps平臺」。