构建制品不一致,后续工作都是白费 | 研发效能提升36计
簡介:本篇文章,我們從軟件交付的終態出發,提出了不可變構建的概念。在軟件開發的過程中,我們怎樣才能享受產業生態的紅利,實現軟件交付過程的標準化呢?軟件交付當中的集裝箱應該是什么樣的?
專欄策劃|雅純
志愿編輯|馮朝凱、橙蜂
之前我們舉了《集裝箱改變世界》(作者:馬克.萊文森)中的一個例子,書中提到上世紀五六十年代,集裝箱的使用,使得整體貨運成本降低了95%,大部分的碼頭工人都面臨著失業。
這件事情看起來很簡單,但卻給經濟全球化帶來了非常大的影響。后面美國企業的訂單可以下到中國、以及中國成為“世界工廠”,都與之有很大的關系。集裝箱的背后是標準化和基于統一標準的產業鏈,這里有兩點比較重要的,一個是標準化,另外一個是不可變。
那么,在軟件開發的過程中,我們怎樣才能享受產業生態的紅利,實現軟件交付過程的標準化呢?軟件交付當中的集裝箱應該是什么樣的?
如何保證軟件交付過程的標準化
近十幾年,軟件交付形態發生了很大的變化,從最開始買物理機、建機房到虛擬機再到現在的容器。這中間為什么會產生這樣的變化呢?
容器本身的底層技術是namespace和cgroup,然而這兩個東西在十幾二十年前就出現了。最早應用這些技術的是對資源利用率和隔離有明確訴求的云廠商,比如說阿里云不希望跑在機器上不同用戶的東西互相串,最好的辦法就是能限制每個用戶的資源,如CPU、內存等。有了這個訴求,就會用LXC等方式去隔離,去限制資源。但是這還是沒有產生容器。為什么呢?問題是各個云廠商只能在自己內部做,但是不能對外分發。所以Docker的偉大之處并不是在底層做了多大地創新,而是提供了一個可以對外分發的容器鏡像。
容器鏡像是一個分發的形式,我們可以把容器鏡像分發給別人,或者是讓別人繼承我們的鏡像。同時Docker又提供了Dockerfile。Dockerfile允許我們通過一個文件的形式去描述鏡像。一旦能夠定義鏡像就可以協作了。有了這樣的能力以后,容器就很快被大家所接受了。所以容器的接受看起來好像是技術發展的過程,其實是隨著云原生、云市場的發展必然帶來的結果。
我們很多人認為的“集裝箱”就是容器,這個容器很多時候我們都認為是docker容器。在K8s里面支持很多個容器運行,大部分的情況都是用docker容器。docker容器的優勢就是剛才說的兩點:鏡像和Dockerfile。這兩點使得docker鏡像可以像集裝箱一樣做分發。
此外,容器還提供了很好的資源隔離,可以在比較小的粒度上進行隔離。虛擬機雖然也做了隔離,但是它的粒度比較大。不僅如此,容器還提供了非常彈性的資源管理方式,這點比虛擬機和物理機都有非常大的改善。本質上它就是物理機上的一個進程,這是它和虛擬機的本質的差別。
了解了軟件集裝箱是什么后,然后我們再來了解下容器鏡像的組成。
如上圖所示,這張圖非常形象地展示了容器鏡像的內部結構。當我們自己執行dockerbuild構建鏡像的時候,你會發現它出來的日志有很多hash值,一層一層的。實際上它是由很多層組成的,我們通過LXC或者其他的技術,把容器的進程創建出來,這個進程通過namespace和cqroup做了資源隔離和限制。容器鏡像都有一個BaseImage,我們知道運行一個程序,對操作系統的環境是要求的,比如依賴的library等。這個程序如果隨便在一臺物理機和虛擬機部署,會隨著機器的環境不同而不同,有可能導致風險。所以容器鏡像給了一個基本鏡像,把這個東西放里面了。再往上是Addemacs和Addapache,這兩層我們會在Dockerfile中去寫。然后最上面的是Writable,就是我們在容器Container運行的時候真正可以去寫的東西。
那么容器鏡像的特點是什么呢?它是分層的,每一層都是可以復用的,我們在某個機器上有很多個容器,如果Base鏡像一樣,只要下一次就行了。可以看到鏡像的大小是所有的層堆起來的,堆的東西越少,這個鏡像就會越小。容器鏡像有一個最小的鏡像叫scratch,就是一個最原始的基礎鏡像。這里面幾乎什么都沒有,基于它構建一個非常非常小的容器的話,可能就是幾兆的大小。但是如果你基于CentOS基礎鏡像可能就是上G的大小。
容器鏡像有一個非常重要的概念叫“One process per container”(容器生命周期=進程生命周期)。我們可以認為容器就是K8s上的一個進程,如果把K8s比作操作系統,那么容器就是它上面運行的一個進程。進程的生命周期是可以被管理的。雖然容器有這么多的優點,但實際在用的時候也會遇到很多的問題。
下面我們聊一聊容器鏡像的一些常見的問題和建議。
容器鏡像常見問題及實踐建議
容器鏡像常見的問題:
- 把所有的東西都裝到一個容器里面,把容器當虛擬機來用。
- 把ENTRYPOINT設置為systemd:systemd管理的進程運行的結果和狀態和的容器狀態是不一致的,有可能里面的進程已經僵死了,或者Crash了,但是systemd還活著,從外部看起來這個容器沒問題。
- 私有化部署的時候帶一堆導出的鏡像tar包。tar包是不分層的,它不知道里面是有很多層。
- 每次把基礎鏡像下發到整個集群,導致網絡變得特別擁堵
我們的實踐建議是:
- 盡量采用輕量的基礎鏡像和確定的鏡像版本。
- 通過分層來復用鏡像內容,避免重復拉取。
- 避免采用systemd,包括supervisord和類似這樣的daemon管理服務來做ENTRYPOINT。
- 采用本地的dockerregistry等以層為粒度來離線拷貝鏡像。
- 避免同時要做大量的pull,可采用P2P的方式(如使用dragonfly)提升鏡像分發效率。
容器鏡像可以實現軟件交付過程的標準化。標準化是手段不是目的,標準化是幫助我們更高效的復用的技術。
回到軟件交付的終態,我們的目的是希望提供一個穩定可預期的系統。
而達成這個目標的前提是,要有確定的運行環境和軟件制品。確定的環境是指代碼(及其依賴)、構建環境、構建腳本與預期一致的產出軟件制品,這一點如何做到我們后面再作分享。我們先看如何保證軟件制品的一致性。
如何保證軟件制品的一致性
要保證軟件制品的一致性,軟件制品應該有確定的格式、唯一的版本、能夠追溯到源碼、能夠追溯到生產和消費過程,這樣才能使持續交付更好地服務于企業的制品管理與開發。
在制品構建過程中,經常會遇到一些問題。例如應用的代碼庫里沒有Makefile,package.json,go.mod而沒法確定依賴,或者制品能構建成功但缺失幾個依賴,又或是在自己的開發環境運行正常而在生產環境出現了開發環境沒有的bug。導致這些問題出現的原因是因為構建本身是可變的,當你構建可變時,就會帶來一系列的問題。為此,我們需要通過不可變構建來使制品與預期一致。
要實現不可變構建,我們需要保證有:
- 相同的代碼
- 相同的構建環境
- 相同的構建腳本
相同的代碼
例如程序員開發時,不在依賴描述文件(如go.mod,package-lock.json,pom.xml,requirements.txt等)中指定依賴的版本,則會默認使用最新的版本作為依賴,這樣產出的制品會隨著依賴的更新而不能保持一致,這將帶來完全不在預期內的風險。
相同的構建環境
對于構建環境來說,Dockerfile可以用來在容器平臺下描述環境,通過Dockerfile我們能為制品使用一致的環境。很多時候我們并不需要在運行中使用構建環境的很多依賴,而構建鏡像的體積往往比較驚人,這個時候我們就需要將構建環境與運行環境分開,以得到盡可能輕量的鏡像制品。
相同的構建腳本
對應的,使用相同的,與代碼實現無關的構建腳本也是非常重要的,在Dockerfile的環境中必須指定確定的環境依賴版本。
只有在同一份代碼(及同一個依賴)、同樣構建環境的描述、和同樣構建腳本的環境下,所產生的軟件制品才是相同的。這里強調的是說所有的東西都要保證一致性,如果說三者是一樣的話,那產生出來的制品也是一樣的,即使構建時間不同,產出的制品也是相同的。
做好不可變基礎設施,首先要標準化最終交付制品的形態,并且明確此交付形態的運維管理方式。而要保證不可變,那首先要做好不可變的構建,然后才能有一致的軟件制品。
NOTE:構建準確性,永遠比構建更快重要。制品的構建信息不準確,導致構建制品不一致、版本不可控,所有后續的工作都是浪費。
如何提升構建效率
在構建這塊,一個需要關注的點的是如何提升構建效率。我們先看一個簡單的計算問題:
這是一個非常大的數據,也是非常大的損耗。很多時候一個項目的工程效率太低的原因就是因為構建太慢。構建耗時過長使得制品迭代非常慢,功能更新和bug修復也會受到影響。
那我們如何提升構建的效率呢?下面是我們的一些實踐建議:
1個基本原則:保證構建的準確性,構建的準確性永遠優于構建的效率。只有在保證準確性的前提下提升效率才有意義。
5點建議:
- 應用瘦身:檢查應用的依賴情況,應用包體積是否過大,依賴項是否過多,能否去除不必要的依賴,能否構建更小的鏡像。
- 分層構建:底層的東西先構建出來以后被上層所復用,然后就可以做增量式的了。
- 構建緩存:構建過程中拉取依賴是很耗時的,要避免重復拉取。
- 網絡優化:主要是保證代碼、構建機器和制品庫之間的低網絡延時。代碼和構建機器是否是在同一個低時延鏈路中。例如代碼在Github上而使用云效構建,此時的延時相對于內網會高出許多。
- 倉庫鏡像:倉庫鏡像可以極大地減少拉取依賴項的時間。在國內的網絡環境下,如果從源倉庫獲取依賴,可能延時會非常長,這時可以使用鏡像網絡降低延時。例如nodejs開發者常使用淘寶的npm鏡像源,而Python開發者使用清華的鏡像源。對于企業來說也可以構建自己的鏡像倉庫以提升可靠性與降低延時。云效也使用了鏡像倉庫,來減少拉取的時間。
總結
本篇文章,我們從軟件交付的終態出發,提出了不可變構建的概念。希望通過:相同的源碼+相同的環境+相同的構建腳本=>帶來一致的軟件制品。而這些東西都是保存在源代碼里的,所以源代碼的管理非常重要。
下篇文章,我們將分享如何對源代碼進行有效管理。
原文鏈接
本文為阿里云原創內容,未經允許不得轉載。?
總結
以上是生活随笔為你收集整理的构建制品不一致,后续工作都是白费 | 研发效能提升36计的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pip install scikit-i
- 下一篇: 全网首发|阿里资深技术专家数仓调优经验分