譯者注:本文重點講述Reddit部署工作從初的小規(guī)模到現(xiàn)在的演變過程,在這過程中的每次調(diào)整都做了哪些,以及起到了什么樣的效果,使讀者能夠更加清晰的了解Reddit的演變史。以下為譯文。
我們經(jīng)常在Reddit上面部署代碼。每位工程師都要寫代碼,進行檢查,檢查,并定期將其推廣到生產(chǎn)。 通常情況下每周發(fā)生200次,部署通常需要不到10分鐘。
這一體系已經(jīng)演變了多年。讓我們來看看它發(fā)生了什么改變(以及沒發(fā)生的)。
目前這套系統(tǒng)是從push這個Perl腳本發(fā)展而來的。這個腳本誕生的時間與Reddit現(xiàn)在的歷史時間有很大不同。當時整個開發(fā)團隊小的可憐,用一間很小的會議室就可以裝滿了。甚至Reddit還沒有在AWS上。該站點運行在固定數(shù)量的服務器上,需要手動添加額外的容量,并且由一個稱為r2的大型單片Python應用程序組成。
在這么多年的發(fā)展過程中,還沒有變過的一件事就是請求在負載均衡器上進行分類,然后分配給不同的應用服務器的特定“池”。比如,列表頁面和評價頁面是由單獨的服務器池提供的。盡管給定任意的r2進程都可以處理任何類型的請求,但是單獨的池從流量的峰值再到其他的池,并且當它們有不同的依賴關(guān)系時,有可能就會處理失敗。
push工具有一套硬編碼的服務器列表,并且是圍繞著整體的部署進程進行搭建的。它將遍歷所有應用程序服務器,SSH到機器,運行預設的命令序列,以通過git更新服務器上的代碼副本,然后重新啟動所有應用程序進程。本質(zhì)上(也就是說經(jīng)過大量的凈化,而不是真正的代碼):
部署是按照順序逐步進行的。它通過逐個服務器進行工作。聽起來好像很簡單,但這其實是一種很好的思路:它允許使用canary式的部署形式。如果您僅部署到少量的服務器,并注意到出現(xiàn)了一個新的異常,你應該就會知道代碼里面可能有bug,你會終止部署(快捷鍵是Ctrl + C),并且在出現(xiàn)影響之前盡快回滾。因為這種部署很簡單,因此在生產(chǎn)環(huán)境中能很輕松的發(fā)現(xiàn)問題,并且在沒有產(chǎn)生影響之前也可以輕而易舉的對代碼進行回滾。這也意味著有必要一次就部署一下,以確保新的錯誤來自本次部署,而不是另一個,因此更容易知道什么時候恢復以及恢復什么。
這樣部署可以很好地確保部署一致性和可重復。它部署的很快,并且情況也很容易了解。
之后我們又招聘了一批新的員工,總工程師變成6位了,看起來似乎需要一間稍微大一點的會議室了。在這之后我們開始覺得需要有更好的合作開發(fā)的機制了,尤其是每個人在家里工作的時候。于是我們便修改了一下push工具,當開始部署或者結(jié)束部署時,它會通過IRC的聊天機器人軟件通知到每個人。機器人軟件是在IRC里面,也是由它去發(fā)送通知的。事實上,部署的形式看上去還是沒有改變,但是現(xiàn)在是系統(tǒng)代替了員工進行工作,并且也是由它去通知其他人你正在做什么。
也就是從這時候開始,我們使用了通過聊天去通知部署這種工作流程。其實在這段期間我們使用了很多交流類的軟件去管理部署,但是由于我們使用的是第三方軟件IRC服務,我們沒辦法完全信任由產(chǎn)品控制的聊天室,因此它仍然是單向信息流。
隨著網(wǎng)站流量的增長,基礎(chǔ)設施的支持也顯得愈發(fā)重要。我們有時不得不推出一批新的應用服務器并投入使用。這項工作還是非常的人工化,包括還需要去手工更新push里面的主機列表。
當我們增加規(guī)模時,我們通常是通過一次性增加若干臺服務器,從而增加池的數(shù)量。其結(jié)果是通過順序的迭代服務器列表,可以在同一池子中快速連續(xù)地訪問多個服務器,而不是多樣化的組合進行訪問。
我們使用uWSGI來管理員工的工作進程,因此如果重啟它的話,會導致現(xiàn)有的進程被殺掉,并且啟動一批新的進程。新的進程需要花費部分時間來準備服務請求,這將會影響到服務請求池的數(shù)量。因此我們把速度限制到了只要安全部署到服務器上面就可以了。
我們對部署工具進行了一次徹頭徹尾的調(diào)整,現(xiàn)在用的是Python開發(fā)出來的,但是沒人知道為啥還是叫push。新版本在某些功能上做了一些改善。
首先,現(xiàn)在是從DNS去獲取所有的主機列表了,而不是在通過硬編碼的形式。這個調(diào)整就避免再出現(xiàn)那種更新主機列表的時候,忘記去更新部署工具了,這才是一個基本的服務發(fā)現(xiàn)系統(tǒng)。
為了解決需要按照順序進行重啟的問題,我們在重啟之前會打亂主機列表。因為這么做會使得服務器池變得混亂,這樣就可以以更快的速度回滾,因此部署也就變得更快。
開始的時候,每次都是通過打亂順序去解決,但這么做就增加了快速恢復代碼的難度,因為你不會每次都部署,臺服務器不可能每次都相同,因此我們又調(diào)整了這種機制,使用了seed技術(shù),在恢復代碼時,它可以在第二次部署的時候重新使用。
另一個雖然小但是也很重要的調(diào)整點就是始終部署固定版本的代碼。這個工具的前一個版本會去更新指定主機上面的master,但是如果有人不小心提交了代碼而使得master改變了中期部署的方式,會怎么樣呢?通過部署特定的git修訂而不是分支名稱,我們確保了每次部署的時候,無論在生產(chǎn)環(huán)境的哪個環(huán)節(jié),都能獲取到相同的版本。
后,新版本的工具區(qū)分了代碼(主要針對主機列表和SSHing)和正在運行的命令。它仍然與r2的需求有很大的偏差,但它有了排序的原始的API。這允許r2控制自己的部署步驟,對建立和發(fā)布流的更改更加容易。例如,以下可能是在單個服務器上運行的功能。確切的命令是隱藏的,但是序列仍然是特定于r2的工作流程的。
fetch-names這一步是r2重點關(guān)注的。
于是我們決定使用云端和autoscale(單獨博客文章的主題)。這可以幫我們節(jié)省一大筆錢,當網(wǎng)站處于沒有大量的訪問量時,自動增長可以滿足意料之外的需求。。
先前改為從DNS獲取主機列表的調(diào)整使得這是一個自然的過渡。主機列表的更改頻率比以前更塊,但與該工具沒有什么不同。本來是生活的一部分,只不過現(xiàn)在變成了自動啟動autoscaler。
然而,autoscaling的確是引發(fā)了一些有趣的邊緣案例。就像沒有免費的午餐一樣。如果一臺服務器運行的同時,也發(fā)生了部署,那么會產(chǎn)生什么情況呢?我們必須確保每個新啟動的服務器都登記過了,以獲取新的代碼,如果存在的話。服務器如何中途部署呢?工具必須做得更智能,以檢測服務器何時可以合理地移除,而不是在部署過程本身中出現(xiàn)問題的時候才進行提醒。
自然而然,由于各種各樣的原因,這次我們也把uWSG換成了Gunicorn。就部署而言,這并沒有帶來什么不同。
所以革命還得繼續(xù)。
隨著時間的流逝,高峰的流量也需要越來越多的服務器進行支撐。也就意味著每次部署所需要的時間也越來越長。糟糕的情況下,正常的部署需要將近一個小時。這簡直糟透了。
我們重寫了部署工具來并行處理主機。新版本的名稱叫rollingpin。舊工具所花的大部分時間是消耗在了初始化連接ssh以及等待命令被執(zhí)行完的時候,所以在一定的安全數(shù)量上進行并行的話是可以縮短時間的。部署的時間也立即被降到了5分鐘。
為了降低一次重啟多臺服務器帶來的影響,工具在打亂順序時也變得更明智了。它將以大程度地將服務器從每個池中分離出來,而不僅僅是盲目的打亂主機的順序。這樣做也是有意地減少對網(wǎng)站的影響。
新工具重要的調(diào)整使部署工具和在每個服務器上的工具之間API定義的更加清晰,并與r2的需求分離了。這初是為了考慮開源而關(guān)注的,但終在不久之后的確是非常有用。以下是部署示例,突出顯示的命令是遠程執(zhí)行的API。
忽然就感覺到好像有很多人一下就使用r2了。這真是太偉大了,當然也意味著需要更多的部署。在同一個時間規(guī)則下,保持一個部署速度會變得越來越困難,因為每個工程師都需要在口頭上協(xié)調(diào)他們會在什么順序上部署代碼。為了解決這個問題,我們又新加了一個功能,就是可以自動協(xié)調(diào)部署隊列。工程師將要求部署鎖定,并獲取或放入隊列。這有助于維持部署秩序,讓人們在等待的時候放松一下。
隨著團隊的發(fā)展,另一個重要的補充是在核心位置跟蹤部署。我們修改了部署工具以將指標發(fā)送給Graphite,因此很容易將部署與指標更改相關(guān)聯(lián)。
就像突然間,我們有第二個服務上線了。網(wǎng)站的新移動版本正在上線。這是一個完全不同的堆棧,并有自己的服務器和構(gòu)建過程。這是部署工具解耦API的個真正考驗。隨著對每個項目不同位置構(gòu)建步驟的增加,它保持不變,并且我們能夠在相同的系統(tǒng)下管理這兩個服務。
在接下來的一年中,我們看到了Reddit團隊的爆炸性增長。我們從這兩個服務發(fā)展到了二十幾個,從兩個團隊到十五個,我們的大部分服務都建立在Baseplate,后端服務框架,或類似于移動網(wǎng)絡的節(jié)點應用也都是。基礎(chǔ)部分是所有部署的基礎(chǔ),而且會上線越來越多,因為rollpin不在乎它的部署內(nèi)容。這使得人們可以使用熟悉的工具輕松地開發(fā)新的服務。
隨著服務器數(shù)量的增加,這一塊也就變得越來越龐大,部署所需要的時間也越來越多。我們想要用高并行計數(shù)來進行部署,但是這樣做會導致應用服務器帶出現(xiàn)大量的的重啟。于是,我們降低了規(guī)模,對于傳進來的請求也不能及時處理,其它的應用服務器也出現(xiàn)了超載的情況。
Gunicorn的主進程使用了跟uWSGI相同的模型,也會一次重啟所有員工的工作進程。當新的工作進程啟動時,您無法滿足任何請求。我們整體的啟動時間為10-30秒,這意味著在此期間,我們將無法提供任何請求。為了解決這個問題,我們把Gunicorn的主進程換成了Stripe的工作經(jīng)理Einhorn,同時保留了Gunicorn的HTTP stack和WSGI container。Einhorn是通過創(chuàng)建新的進程從而完成重啟的,等待它自己準備好,然后收獲老的進程,并重復,直到所有的升級。這種機制創(chuàng)建了一個安全網(wǎng),并允許我們在部署期間可以響應請求。
新模式當然也帶來了新問題。就像前面提到的那樣,它需要30秒的時間完成進程的替代和啟動。這也就是說如果的你代碼里面有bug,它不會立即顯示,您可以回滾多臺服務器。為了防止這種情況產(chǎn)生,我們引入了一種方法,可以阻止部署到另一臺服務器上,直到所有的進程都重新啟動才可以。很簡單,只需要調(diào)整einhorn的狀態(tài),并且等到所有的機器都準備好就可以了。為了保證速度,我們增加了并發(fā),這現(xiàn)在已經(jīng)很安全了。
新的機制可以讓我們同時對多臺服務器進行部署,部署800臺機器的時間降低到了7分鐘,盡管需要額外的等待。
部署的基礎(chǔ)部分是多年的逐步改進積累而來的,絕不是任何一次大的調(diào)整導致的。在當前系統(tǒng)和過去的任何時候,都可以看到歷史的陰影和每一次選擇的權(quán)衡。這樣一種演變的方法是有利弊的:在任何一個期間,都不需要花費太多的代價,但是我們終可能會陷入絕境。重要的是要關(guān)注你的發(fā)展方向,這樣你就可以繼續(xù)朝著有用的方向前進。
Reddit的基礎(chǔ)部分需要能夠跟得上團隊以及新事物的發(fā)展。公司的成長速度達到了Reddit歷史上的峰值,與以前相比,我們所從事的項目也越來越有趣。現(xiàn)在我們面臨的問題有兩部分:提高工程師自主權(quán),同時維護生產(chǎn)基礎(chǔ)設施中的系統(tǒng)安全,以及為他們搭建更好的網(wǎng)絡環(huán)境,讓他們更有信心進行部署。
本站文章版權(quán)歸原作者及原出處所有 。內(nèi)容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構(gòu)成任何投資及應用建議。本站是一個個人學習交流的平臺,網(wǎng)站上部分文章為轉(zhuǎn)載,并不用于任何商業(yè)目的,我們已經(jīng)盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯(lián)系我們,我們將根據(jù)著作權(quán)人的要求,立即更正或者刪除有關(guān)內(nèi)容。本站擁有對此聲明的最終解釋權(quán)。