簡介
如今,軟件通常會作為一種服務(wù)來交付主穗,它們被稱為網(wǎng)絡(luò)應(yīng)用程序,或軟件即服務(wù)(SaaS)。12-Factor 為構(gòu)建如下的 SaaS 應(yīng)用提供了方法論:
使用標(biāo)準(zhǔn)化流程自動配置胆绊,從而使新的開發(fā)者花費最少的學(xué)習(xí)成本加入這個項目。
和操作系統(tǒng)之間盡可能的劃清界限欧募,在各個系統(tǒng)中提供最大的可移植性压状。
適合部署在現(xiàn)代的云計算平臺,從而在服務(wù)器和系統(tǒng)管理方面節(jié)省資源跟继。
將開發(fā)環(huán)境和生產(chǎn)環(huán)境的差異降至最低种冬,并使用持續(xù)交付實施敏捷開發(fā)。
可以在工具舔糖、架構(gòu)和開發(fā)流程不發(fā)生明顯變化的前提下實現(xiàn)擴展娱两。
這套理論適用于任意語言和后端服務(wù)(數(shù)據(jù)庫、消息隊列金吗、緩存等)開發(fā)的應(yīng)用程序十兢。
背景
本文的貢獻者參與過數(shù)以百計的應(yīng)用程序的開發(fā)和部署,并通過 Heroku 平臺間接見證了數(shù)十萬應(yīng)用程序的開發(fā)摇庙,運作以及擴展的過程旱物。
本文綜合了我們關(guān)于 SaaS 應(yīng)用幾乎所有的經(jīng)驗和智慧,是開發(fā)此類應(yīng)用的理想實踐標(biāo)準(zhǔn)卫袒,并特別關(guān)注于應(yīng)用程序如何保持良性成長宵呛,開發(fā)者之間如何進行有效的代碼協(xié)作,以及如何 避免軟件污染 夕凝。
我們的初衷是分享在現(xiàn)代軟件開發(fā)過程中發(fā)現(xiàn)的一些系統(tǒng)性問題宝穗,并加深對這些問題的認(rèn)識户秤。我們提供了討論這些問題時所需的共享詞匯,同時使用相關(guān)術(shù)語給出一套針對這些問題的廣義解決方案讽营。本文格式的靈感來自于 Martin Fowler 的書籍: 《企業(yè)應(yīng)用架構(gòu)模式》虎忌、《重構(gòu):改善既有代碼的設(shè)計(第2版)》 。
讀者應(yīng)該是哪些人橱鹏?
任何 SaaS 應(yīng)用的開發(fā)人員膜蠢。部署和管理此類應(yīng)用的運維工程師。
12 要素
-
I. 基準(zhǔn)代碼
一份基準(zhǔn)代碼莉兰,多份部署 -
II. 依賴
顯式聲明依賴關(guān)系 -
III. 配置
在環(huán)境中存儲配置 -
IV. 后端服務(wù)
把后端服務(wù)當(dāng)作附加資源 -
V. 構(gòu)建挑围,發(fā)布,運行
嚴(yán)格分離構(gòu)建和運行 -
VI. 進程
以一個或多個無狀態(tài)進程運行應(yīng)用 -
VII. 端口綁定
通過端口綁定提供服務(wù) -
VIII. 并發(fā)
通過進程模型進行擴展 -
IX. 易處理
快速啟動和優(yōu)雅終止可最大化健壯性 -
X. 開發(fā)環(huán)境與線上環(huán)境等價
盡可能的保持開發(fā)糖荒,預(yù)發(fā)布杉辙,線上環(huán)境相同 -
XI. 日志
把日志當(dāng)作事件流 -
XII. 管理進程
后臺管理任務(wù)當(dāng)作一次性進程運行
I. 基準(zhǔn)代碼
一份基準(zhǔn)代碼(Codebase),多份部署(deploy)
12-Factor應(yīng)用(譯者注:應(yīng)該是說一個使用本文概念來設(shè)計的應(yīng)用捶朵,下同)通常會使用版本控制系統(tǒng)加以管理蜘矢,如Git, Mercurial, Subversion。一份用來跟蹤代碼所有修訂版本的數(shù)據(jù)庫被稱作 代碼庫(code repository, code repo, repo)综看。
在類似 SVN 這樣的集中式版本控制系統(tǒng)中品腹,基準(zhǔn)代碼 就是指控制系統(tǒng)中的這一份代碼庫;而在 Git 那樣的分布式版本控制系統(tǒng)中红碑,基準(zhǔn)代碼 則是指最上游的那份代碼庫舞吭。
基準(zhǔn)代碼和應(yīng)用之間總是保持一一對應(yīng)的關(guān)系:
一旦有多個基準(zhǔn)代碼,就不能稱為一個應(yīng)用析珊,而是一個分布式系統(tǒng)羡鸥。分布式系統(tǒng)中的每一個組件都是一個應(yīng)用,每一個應(yīng)用可以分別使用 12-Factor 進行開發(fā)忠寻。
多個應(yīng)用共享一份基準(zhǔn)代碼是有悖于 12-Factor 原則的惧浴。解決方案是將共享的代碼拆分為獨立的類庫,然后使用 依賴管理 策略去加載它們奕剃。
盡管每個應(yīng)用只對應(yīng)一份基準(zhǔn)代碼赶舆,但可以同時存在多份部署。每份 部署 相當(dāng)于運行了一個應(yīng)用的實例祭饭。通常會有一個生產(chǎn)環(huán)境芜茵,一個或多個預(yù)發(fā)布環(huán)境。此外倡蝙,每個開發(fā)人員都會在自己本地環(huán)境運行一個應(yīng)用實例九串,這些都相當(dāng)于一份部署。
所有部署的基準(zhǔn)代碼相同,但每份部署可以使用其不同的版本猪钮。比如品山,開發(fā)人員可能有一些提交還沒有同步至預(yù)發(fā)布環(huán)境;預(yù)發(fā)布環(huán)境也有一些提交沒有同步至生產(chǎn)環(huán)境烤低。但它們都共享一份基準(zhǔn)代碼肘交,我們就認(rèn)為它們只是相同應(yīng)用的不同部署而已。
II. 依賴
顯式聲明依賴關(guān)系( dependency )
大多數(shù)編程語言都會提供一個打包系統(tǒng)扑馁,用來為各個類庫提供打包服務(wù)涯呻,就像 Perl 的 CPAN 或是 Ruby 的 Rubygems 。通過打包系統(tǒng)安裝的類庫可以是系統(tǒng)級的(稱之為 “site packages”)腻要,或僅供某個應(yīng)用程序使用复罐,部署在相應(yīng)的目錄中(稱之為 “vendoring” 或 “bunding”)。
12-Factor規(guī)則下的應(yīng)用程序不會隱式依賴系統(tǒng)級的類庫雄家。 它一定通過 依賴清單 效诅,確切地聲明所有依賴項。此外趟济,在運行過程中通過 依賴隔離 工具來確保程序不會調(diào)用系統(tǒng)中存在但清單中未聲明的依賴項乱投。這一做法會統(tǒng)一應(yīng)用到生產(chǎn)和開發(fā)環(huán)境。
例如顷编, Ruby 的 Bundler 使用 Gemfile 作為依賴項聲明清單篡腌,使用 bundle exec
來進行依賴隔離。Python 中則可分別使用兩種工具 – Pip 用作依賴聲明勾效, Virtualenv 用作依賴隔離。甚至 C 語言也有類似工具叛甫, Autoconf 用作依賴聲明层宫,靜態(tài)鏈接庫用作依賴隔離。無論用什么工具其监,依賴聲明和依賴隔離必須一起使用萌腿,否則無法滿足 12-Factor 規(guī)范。
顯式聲明依賴的優(yōu)點之一是為新進開發(fā)者簡化了環(huán)境配置流程抖苦。新進開發(fā)者可以檢出應(yīng)用程序的基準(zhǔn)代碼毁菱,安裝編程語言環(huán)境和它對應(yīng)的依賴管理工具,只需通過一個 構(gòu)建命令 來安裝所有的依賴項锌历,即可開始工作贮庞。例如,Ruby/Bundler 下使用 bundle install
究西,而 Clojure/Leiningen 則是 lein deps
窗慎。
12-Factor 應(yīng)用同樣不會隱式依賴某些系統(tǒng)工具,如 ImageMagick 或是curl。即使這些工具存在于幾乎所有系統(tǒng)遮斥,但終究無法保證所有未來的系統(tǒng)都能支持應(yīng)用順利運行峦失,或是能夠和應(yīng)用兼容。如果應(yīng)用必須使用到某些系統(tǒng)工具术吗,那么這些工具應(yīng)該被包含在應(yīng)用之中尉辑。
III. 配置
在環(huán)境中存儲配置
通常,應(yīng)用的 配置 在不同 部署 (預(yù)發(fā)布较屿、生產(chǎn)環(huán)境隧魄、開發(fā)環(huán)境等等)間會有很大差異。這其中包括:
數(shù)據(jù)庫吝镣,Memcached堤器,以及其他 后端服務(wù) 的配置
第三方服務(wù)的證書,如 Amazon S3末贾、Twitter 等
每份部署特有的配置闸溃,如域名等
有些應(yīng)用在代碼中使用常量保存配置,這與 12-Factor 所要求的代碼和配置嚴(yán)格分離顯然大相徑庭拱撵。配置文件在各部署間存在大幅差異辉川,代碼卻完全一致。
判斷一個應(yīng)用是否正確地將配置排除在代碼之外拴测,一個簡單的方法是看該應(yīng)用的基準(zhǔn)代碼是否可以立刻開源乓旗,而不用擔(dān)心會暴露任何敏感的信息。
需要指出的是集索,這里定義的“配置”并不包括應(yīng)用的內(nèi)部配置屿愚,比如 Rails 的 config/routes.rb,或是使用 Spring 時 代碼模塊間的依賴注入關(guān)系 务荆。這類配置在不同部署間不存在差異妆距,所以應(yīng)該寫入代碼。
另外一個解決方法是使用配置文件函匕,但不把它們納入版本控制系統(tǒng)娱据,就像 Rails 的 config/database.yml 。這相對于在代碼中使用常量已經(jīng)是長足進步盅惜,但仍然有缺點:總是會不小心將配置文件簽入了代碼庫中剩;配置文件的可能會分散在不同的目錄,并有著不同的格式抒寂,這讓找出一個地方來統(tǒng)一管理所有配置變的不太現(xiàn)實结啼。更糟的是,這些格式通常是語言或框架特定的屈芜。
12-Factor推薦將應(yīng)用的配置存儲于 環(huán)境變量 中( env vars, env )妆棒。環(huán)境變量可以非常方便地在不同的部署間做修改,卻不動一行代碼;與配置文件不同糕珊,不小心把它們簽入代碼庫的概率微乎其微动分;與一些傳統(tǒng)的解決配置問題的機制(比如 Java 的屬性配置文件)相比,環(huán)境變量與語言和系統(tǒng)無關(guān)红选。
配置管理的另一個方面是分組澜公。有時應(yīng)用會將配置按照特定部署進行分組(或叫做“環(huán)境”),例如 Rails 中的 development, test 和 production 環(huán)境喇肋。這種方法無法輕易擴展:更多部署意味著更多新的環(huán)境坟乾,例如 staging 或 qa 。 隨著項目的不斷深入蝶防,開發(fā)人員可能還會添加他們自己的環(huán)境甚侣,比如 joes-staging ,這將導(dǎo)致各種配置組合的激增间学,從而給管理部署增加了很多不確定因素殷费。
12-Factor 應(yīng)用中,環(huán)境變量的粒度要足夠小低葫,且相對獨立详羡。它們永遠(yuǎn)也不會組合成一個所謂的“環(huán)境”,而是獨立存在于每個部署之中嘿悬。當(dāng)應(yīng)用程序不斷擴展实柠,需要更多種類的部署時,這種配置管理方式能夠做到平滑過渡善涨。
IV. 后端服務(wù)
把后端服務(wù)(backing services)當(dāng)作附加資源
后端服務(wù)是指程序運行所需要的通過網(wǎng)絡(luò)調(diào)用的各種服務(wù)窒盐,如數(shù)據(jù)庫(MySQL,CouchDB)钢拧,消息/隊列系統(tǒng)(RabbitMQ蟹漓,Beanstalkd),SMTP 郵件發(fā)送服務(wù)(Postfix)娶靡,以及緩存系統(tǒng)(Memcached)。
類似數(shù)據(jù)庫的后端服務(wù)看锉,通常由部署應(yīng)用程序的系統(tǒng)管理員一起管理姿锭。除了本地服務(wù)之外,應(yīng)用程序有可能使用了第三方發(fā)布和管理的服務(wù)伯铣。示例包括 SMTP(例如 Postmark)呻此,數(shù)據(jù)收集服務(wù)(例如 New Relic 或 Loggly),數(shù)據(jù)存儲服務(wù)(如 Amazon S3)腔寡,以及使用 API 訪問的服務(wù)(例如 Twitter, Google Maps, Last.fm)焚鲜。
12-Factor 應(yīng)用不會區(qū)別對待本地或第三方服務(wù)。 對應(yīng)用程序而言,兩種都是附加資源忿磅,通過一個 url 或是其他存儲在 配置 中的服務(wù)定位/服務(wù)證書來獲取數(shù)據(jù)糯彬。12-Factor 應(yīng)用的任意 部署 ,都應(yīng)該可以在不進行任何代碼改動的情況下葱她,將本地 MySQL 數(shù)據(jù)庫換成第三方服務(wù)(例如 Amazon RDS)撩扒。類似的,本地 SMTP 服務(wù)應(yīng)該也可以和第三方 SMTP 服務(wù)(例如 Postmark )互換吨些。上述 2 個例子中搓谆,僅需修改配置中的資源地址。
每個不同的后端服務(wù)是一份 資源 豪墅。例如泉手,一個 MySQL 數(shù)據(jù)庫是一個資源,兩個 MySQL 數(shù)據(jù)庫(用來數(shù)據(jù)分區(qū))就被當(dāng)作是 2 個不同的資源偶器。12-Factor 應(yīng)用將這些數(shù)據(jù)庫都視作 附加資源 斩萌,這些資源和它們附屬的部署保持松耦合。
部署可以按需加載或卸載資源状囱。例如术裸,如果應(yīng)用的數(shù)據(jù)庫服務(wù)由于硬件問題出現(xiàn)異常,管理員可以從最近的備份中恢復(fù)一個數(shù)據(jù)庫亭枷,卸載當(dāng)前的數(shù)據(jù)庫袭艺,然后加載新的數(shù)據(jù)庫 – 整個過程都不需要修改代碼。
V. 構(gòu)建叨粘,發(fā)布猾编,運行
嚴(yán)格分離構(gòu)建和運行
基準(zhǔn)代碼 轉(zhuǎn)化為一份部署(非開發(fā)環(huán)境)需要以下三個階段:
- 構(gòu)建階段 是指將代碼倉庫轉(zhuǎn)化為可執(zhí)行包的過程。構(gòu)建時會使用指定版本的代碼升敲,獲取和打包 依賴項答倡,編譯成二進制文件和資源文件。
- 發(fā)布階段 會將構(gòu)建的結(jié)果和當(dāng)前部署所需 配置 相結(jié)合驴党,并能夠立刻在運行環(huán)境中投入使用洽议。
- 運行階段 (或者說“運行時”)是指針對選定的發(fā)布版本,在執(zhí)行環(huán)境中啟動一系列應(yīng)用程序 進程识脆。
12-factor 應(yīng)用嚴(yán)格區(qū)分構(gòu)建,發(fā)布鹏氧,運行這三個步驟渤涌。 舉例來說,直接修改處于運行狀態(tài)的代碼是非常不可取的做法把还,因為這些修改很難再同步回構(gòu)建步驟实蓬。
部署工具通常都提供了發(fā)布管理工具茸俭,最引人注目的功能是退回至較舊的發(fā)布版本。比如安皱, Capistrano 將所有發(fā)布版本都存儲在一個叫 releases 的子目錄中调鬓,當(dāng)前的在線版本只需映射至對應(yīng)的目錄即可。該工具的 rollback 命令可以很容易地實現(xiàn)回退版本的功能练俐。
每一個發(fā)布版本必須對應(yīng)一個唯一的發(fā)布 ID袖迎,例如可以使用發(fā)布時的時間戳(2011-04-06-20:32:17),亦或是一個增長的數(shù)字(v100)腺晾。發(fā)布的版本就像一本只能追加的賬本燕锥,一旦發(fā)布就不可修改,任何的變動都應(yīng)該產(chǎn)生一個新的發(fā)布版本悯蝉。
新的代碼在部署之前归形,需要開發(fā)人員觸發(fā)構(gòu)建操作。但是鼻由,運行階段不一定需要人為觸發(fā)暇榴,而是可以自動進行。如服務(wù)器重啟蕉世,或是進程管理器重啟了一個崩潰的進程蔼紧。因此,運行階段應(yīng)該保持盡可能少的模塊狠轻,這樣假設(shè)半夜發(fā)生系統(tǒng)故障而開發(fā)人員又捉襟見肘也不會引起太大問題奸例。構(gòu)建階段是可以相對復(fù)雜一些的,因為錯誤信息能夠立刻展示在開發(fā)人員面前向楼,從而得到妥善處理查吊。
VI. 進程
以一個或多個無狀態(tài)進程運行應(yīng)用
運行環(huán)境中,應(yīng)用程序通常是以一個和多個 進程 運行的湖蜕。
最簡單的場景中逻卖,代碼是一個獨立的腳本,運行環(huán)境是開發(fā)人員自己的筆記本電腦昭抒,進程由一條命令行(例如python my_script.py)评也。另外一個極端情況是,復(fù)雜的應(yīng)用可能會使用很多 進程類型 灭返,也就是零個或多個進程實例盗迟。
12-Factor 應(yīng)用的進程必須無狀態(tài)且 無共享 。 任何需要持久化的數(shù)據(jù)都要存儲在 后端服務(wù) 內(nèi)婆殿,比如數(shù)據(jù)庫诈乒。
內(nèi)存區(qū)域或磁盤空間可以作為進程在做某種事務(wù)型操作時的緩存罩扇,例如下載一個很大的文件婆芦,對其操作并將結(jié)果寫入數(shù)據(jù)庫的過程怕磨。12-Factor應(yīng)用根本不用考慮這些緩存的內(nèi)容是不是可以保留給之后的請求來使用,這是因為應(yīng)用啟動了多種類型的進程消约,將來的請求多半會由其他進程來服務(wù)肠鲫。即使在只有一個進程的情形下,先前保存的數(shù)據(jù)(內(nèi)存或文件系統(tǒng)中)也會因為重啟(如代碼部署或粮、配置更改导饲、或運行環(huán)境將進程調(diào)度至另一個物理區(qū)域執(zhí)行)而丟失。
源文件打包工具(Jammit, django-compressor) 使用文件系統(tǒng)來緩存編譯過的源文件氯材。12-Factor 應(yīng)用更傾向于在 構(gòu)建步驟 做此動作——正如 Rails資源管道 渣锦,而不是在運行階段。
一些互聯(lián)網(wǎng)系統(tǒng)依賴于 “粘性 session”氢哮, 這是指將用戶 session 中的數(shù)據(jù)緩存至某進程的內(nèi)存中袋毙,并將同一用戶的后續(xù)請求路由到同一個進程。粘性 session 是 12-Factor 極力反對的冗尤。Session 中的數(shù)據(jù)應(yīng)該保存在諸如 Memcached 或 Redis 這樣的帶有過期時間的緩存中听盖。
VII. 端口綁定
通過端口綁定(Port binding)來提供服務(wù)
互聯(lián)網(wǎng)應(yīng)用有時會運行于服務(wù)器的容器之中。例如 PHP 經(jīng)常作為 Apache HTTPD 的一個模塊來運行裂七,正如 Java 運行于 Tomcat 皆看。
12-Factor 應(yīng)用完全自我加載 而不依賴于任何網(wǎng)絡(luò)服務(wù)器就可以創(chuàng)建一個面向網(wǎng)絡(luò)的服務(wù)”沉悖互聯(lián)網(wǎng)應(yīng)用 通過端口綁定來提供服務(wù) 腰吟,并監(jiān)聽發(fā)送至該端口的請求。
本地環(huán)境中捉兴,開發(fā)人員通過類似 http://localhost:5000/
的地址來訪問服務(wù)蝎困。在線上環(huán)境中,請求統(tǒng)一發(fā)送至公共域名而后路由至綁定了端口的網(wǎng)絡(luò)進程倍啥。
通常的實現(xiàn)思路是禾乘,將網(wǎng)絡(luò)服務(wù)器類庫通過 依賴聲明 載入應(yīng)用。例如虽缕,Python 的 Tornado, Ruby 的Thin , Java 以及其他基于 JVM 語言的 Jetty始藕。完全由 用戶端 ,確切的說應(yīng)該是應(yīng)用的代碼氮趋,發(fā)起請求伍派。和運行環(huán)境約定好綁定的端口即可處理這些請求。
HTTP 并不是唯一一個可以由端口綁定提供的服務(wù)剩胁。其實幾乎所有服務(wù)器軟件都可以通過進程綁定端口來等待請求诉植。例如,使用 XMPP 的 ejabberd 昵观, 以及使用 Redis 協(xié)議 的 Redis 晾腔。
還要指出的是舌稀,端口綁定這種方式也意味著一個應(yīng)用可以成為另外一個應(yīng)用的 后端服務(wù) ,調(diào)用方將服務(wù)方提供的相應(yīng) URL 當(dāng)作資源存入 配置 以備將來調(diào)用灼擂。
VIII. 并發(fā)
通過進程模型進行擴展
任何計算機程序壁查,一旦啟動,就會生成一個或多個進程剔应∷龋互聯(lián)網(wǎng)應(yīng)用采用多種進程運行方式。例如峻贮,PHP 進程作為 Apache 的子進程存在席怪,隨請求按需啟動。Java 進程則采取了相反的方式纤控,在程序啟動之初 JVM 就提供了一個超級進程儲備了大量的系統(tǒng)資源(CPU 和內(nèi)存)何恶,并通過多線程實現(xiàn)內(nèi)部的并發(fā)管理。上述 2 個例子中嚼黔,進程是開發(fā)人員可以操作的最小單位细层。
在 12-factor 應(yīng)用中唬涧,進程是一等公民疫赎。12-Factor 應(yīng)用的進程主要借鑒于 unix 守護進程模型 。開發(fā)人員可以運用這個模型去設(shè)計應(yīng)用架構(gòu)碎节,將不同的工作分配給不同的 進程類型 捧搞。例如,HTTP 請求可以交給 web 進程來處理狮荔,而常駐的后臺工作則交由 worker 進程負(fù)責(zé)胎撇。
這并不包括個別較為特殊的進程,例如通過虛擬機的線程處理并發(fā)的內(nèi)部運算殖氏,或是使用諸如 EventMachine, Twisted, Node.js 的異步/事件觸發(fā)模型晚树。但一臺獨立的虛擬機的擴展有瓶頸(垂直擴展),所以應(yīng)用程序必須可以在多臺物理機器間跨進程工作雅采。
上述進程模型會在系統(tǒng)急需擴展時大放異彩爵憎。 12-Factor 應(yīng)用的進程所具備的無共享,水平分區(qū)的特性 意味著添加并發(fā)會變得簡單而穩(wěn)妥婚瓜。這些進程的類型以及每個類型中進程的數(shù)量就被稱作 進程構(gòu)成 宝鼓。
12-Factor 應(yīng)用的進程 不需要守護進程 或是寫入 PID 文件。相反的巴刻,應(yīng)該借助操作系統(tǒng)的進程管理器(例如 systemd 愚铡,分布式的進程管理云平臺,或是類似 Foreman 的工具)胡陪,來管理 輸出流 沥寥,響應(yīng)崩潰的進程正蛙,以及處理用戶觸發(fā)的重啟和關(guān)閉超級進程的請求。
IX. 易處理
快速啟動和優(yōu)雅終止可最大化健壯性
12-Factor 應(yīng)用的 進程 是 易處理(disposable)的营曼,意思是說它們可以瞬間開啟或停止。 這有利于快速愚隧、彈性的伸縮應(yīng)用蒂阱,迅速部署變化的 代碼 或 配置 ,穩(wěn)健的部署應(yīng)用狂塘。
進程應(yīng)當(dāng)追求 最小啟動時間 录煤。 理想狀態(tài)下,進程從敲下命令到真正啟動并等待請求的時間應(yīng)該只需很短的時間荞胡。更少的啟動時間提供了更敏捷的 發(fā)布 以及擴展過程妈踊,此外還增加了健壯性,因為進程管理器可以在授權(quán)情形下容易的將進程搬到新的物理機器上泪漂。
進程 一旦接收 終止信號(SIGTERM) 就會優(yōu)雅的終止 露筒。就網(wǎng)絡(luò)進程而言趟径,優(yōu)雅終止是指停止監(jiān)聽服務(wù)的端口,即拒絕所有新的請求,并繼續(xù)執(zhí)行當(dāng)前已接收的請求,然后退出。此類型的進程所隱含的要求是HTTP請求大多都很短(不會超過幾秒鐘)锌雀,而在長時間輪詢中婿牍,客戶端在丟失連接后應(yīng)該馬上嘗試重連。
對于 worker 進程來說,優(yōu)雅終止是指將當(dāng)前任務(wù)退回隊列亮垫。例如,RabbitMQ 中壹瘟,worker 可以發(fā)送一個NACK信號稻轨。 Beanstalkd 中,任務(wù)終止并退回隊列會在worker斷開時自動觸發(fā)政冻。有鎖機制的系統(tǒng)諸如 Delayed Job 則需要確定釋放了系統(tǒng)資源线欲。此類型的進程所隱含的要求是明场,任務(wù)都應(yīng)該 可重復(fù)執(zhí)行 苦锨, 這主要由將結(jié)果包裝進事務(wù)或是使重復(fù)操作 冪等 來實現(xiàn)秃励。
進程還應(yīng)當(dāng)在面對突然死亡時保持健壯皆尔,例如底層硬件故障。雖然這種情況比起優(yōu)雅終止來說少之又少谣旁,但終究有可能發(fā)生。一種推薦的方式是使用一個健壯的后端隊列滋早,例如 Beanstalkd 榄审,它可以在客戶端斷開或超時后自動退回任務(wù)。無論如何杆麸,12-Factor 應(yīng)用都應(yīng)該可以設(shè)計能夠應(yīng)對意外的搁进、不優(yōu)雅的終結(jié)。Crash-only design 將這種概念轉(zhuǎn)化為 合乎邏輯的理論昔头。
X. 開發(fā)環(huán)境與線上環(huán)境等價
盡可能的保持開發(fā)饼问,預(yù)發(fā)布,線上環(huán)境相同
從以往經(jīng)驗來看揭斧,開發(fā)環(huán)境(即開發(fā)人員的本地 部署)和線上環(huán)境(外部用戶訪問的真實部署)之間存在著很多差異莱革。這些差異表現(xiàn)在以下三個方面:
- 時間差異: 開發(fā)人員正在編寫的代碼可能需要幾天,幾周讹开,甚至幾個月才會上線盅视。
- 人員差異: 開發(fā)人員編寫代碼,運維人員部署代碼旦万。
- 工具差異: 開發(fā)人員或許使用 Nginx闹击,SQLite,OS X成艘,而線上環(huán)境使用 Apache赏半,MySQL 以及 Linux。
12-Factor 應(yīng)用想要做到 持續(xù)部署 就必須縮小本地與線上差異淆两。 再回頭看上面所描述的三個差異:
- 縮小時間差異:開發(fā)人員可以幾小時断箫,甚至幾分鐘就部署代碼。
- 縮小人員差異:開發(fā)人員不只要編寫代碼秋冰,更應(yīng)該密切參與部署過程以及代碼在線上的表現(xiàn)瑰枫。
- 縮小工具差異:盡量保證開發(fā)環(huán)境以及線上環(huán)境的一致性。
將上述總結(jié)變?yōu)橐粋€表格如下:
傳統(tǒng)應(yīng)用 | 12-Factor 應(yīng)用 | |
---|---|---|
每次部署間隔 | 數(shù)周 | 幾小時 |
開發(fā)人員 vs 運維人員 | 不同的人 | 相同的人 |
開發(fā)環(huán)境 vs 線上環(huán)境 | 不同 | 盡量接近 |
后端服務(wù) 是保持開發(fā)與線上等價的重要部分,例如數(shù)據(jù)庫光坝,隊列系統(tǒng)尸诽,以及緩存。許多語言都提供了簡化獲取后端服務(wù)的類庫盯另,例如不同類型服務(wù)的 適配器 性含。下列表格提供了一些例子。
類型 | 語言 | 類庫 | 適配器 |
---|---|---|---|
數(shù)據(jù)庫 | Ruby/Rails | ActiveRecord | MySQL, PostgreSQL, SQLite |
隊列 | Python/Django | Celery | RabbitMQ, Beanstalkd, Redis |
緩存 | Ruby/Rails | ActiveSupport::Cache | Memory, filesystem, Memcached |
開發(fā)人員有時會覺得在本地環(huán)境中使用輕量的后端服務(wù)具有很強的吸引力鸳惯,而那些更重量級的健壯的后端服務(wù)應(yīng)該使用在生產(chǎn)環(huán)境商蕴。例如,本地使用 SQLite 線上使用 PostgreSQL芝发;又如本地緩存在進程內(nèi)存中而線上存入 Memcached绪商。
12-Factor 應(yīng)用的開發(fā)人員應(yīng)該反對在不同環(huán)境間使用不同的后端服務(wù) ,即使適配器已經(jīng)可以幾乎消除使用上的差異辅鲸。這是因為格郁,不同的后端服務(wù)意味著會突然出現(xiàn)的不兼容,從而導(dǎo)致測試独悴、預(yù)發(fā)布都正常的代碼在線上出現(xiàn)問題例书。這些錯誤會給持續(xù)部署帶來阻力。從應(yīng)用程序的生命周期來看刻炒,消除這種阻力需要花費很大的代價决采。
與此同時,輕量的本地服務(wù)也不像以前那樣引人注目坟奥。借助于Homebrew树瞭,apt-get等現(xiàn)代的打包系統(tǒng),諸如Memcached爱谁、PostgreSQL移迫、RabbitMQ 等后端服務(wù)的安裝與運行也并不復(fù)雜。此外管行,使用類似 Chef 和 Puppet 的聲明式配置工具厨埋,結(jié)合像 Vagrant 這樣輕量的虛擬環(huán)境就可以使得開發(fā)人員的本地環(huán)境與線上環(huán)境無限接近。與同步環(huán)境和持續(xù)部署所帶來的益處相比捐顷,安裝這些系統(tǒng)顯然是值得的荡陷。
不同后端服務(wù)的適配器仍然是有用的,因為它們可以使移植后端服務(wù)變得簡單迅涮。但應(yīng)用的所有部署废赞,這其中包括開發(fā)、預(yù)發(fā)布以及線上環(huán)境叮姑,都應(yīng)該使用同一個后端服務(wù)的相同版本唉地。
XI. 日志
把日志當(dāng)作事件流
日志 使得應(yīng)用程序運行的動作變得透明据悔。在基于服務(wù)器的環(huán)境中,日志通常被寫在硬盤的一個文件里耘沼,但這只是一種輸出格式极颓。
日志應(yīng)該是 事件流 的匯總,將所有運行中進程和后端服務(wù)的輸出流按照時間順序收集起來群嗤。盡管在回溯問題時可能需要看很多行菠隆,日志最原始的格式確實是一個事件一行。日志沒有確定開始和結(jié)束狂秘,但隨著應(yīng)用在運行會持續(xù)的增加骇径。
12-factor 應(yīng)用本身從不考慮存儲自己的輸出流。 不應(yīng)該試圖去寫或者管理日志文件者春。相反破衔,每一個運行的進程都會直接的標(biāo)準(zhǔn)輸出(stdout)事件流。開發(fā)環(huán)境中钱烟,開發(fā)人員可以通過這些數(shù)據(jù)流晰筛,實時在終端看到應(yīng)用的活動。
在預(yù)發(fā)布或線上部署中忠售,每個進程的輸出流由運行環(huán)境截獲传惠,并將其他輸出流整理在一起迄沫,然后一并發(fā)送給一個或多個最終的處理程序稻扬,用于查看或是長期存檔。這些存檔路徑對于應(yīng)用來說不可見也不可配置羊瘩,而是完全交給程序的運行環(huán)境管理泰佳。類似 Logplex 和 Fluentd 的開源工具可以達到這個目的。
這些事件流可以輸出至文件尘吗,或者在終端實時觀察逝她。最重要的,輸出流可以發(fā)送到 Splunk 這樣的日志索引及分析系統(tǒng)睬捶,或 Hadoop/Hive 這樣的通用數(shù)據(jù)存儲系統(tǒng)黔宛。這些系統(tǒng)為查看應(yīng)用的歷史活動提供了強大而靈活的功能,包括:
找出過去一段時間特殊的事件擒贸。
圖形化一個大規(guī)模的趨勢臀晃,比如每分鐘的請求量。
根據(jù)用戶定義的條件實時觸發(fā)警報介劫,比如每分鐘的報錯超過某個警戒線徽惋。
XII. 管理進程
后臺管理任務(wù)當(dāng)作一次性進程運行
進程構(gòu)成(process formation)是指用來處理應(yīng)用的常規(guī)業(yè)務(wù)(比如處理 web 請求)的一組進程。與此不同座韵,開發(fā)人員經(jīng)常希望執(zhí)行一些管理或維護應(yīng)用的一次性任務(wù)险绘,例如:
運行數(shù)據(jù)移植(Django 中的 manage.py migrate
, Rails 中的 rake db:migrate
)。
運行一個控制臺(也被稱為 REPL shell),來執(zhí)行一些代碼或是針對線上數(shù)據(jù)庫做一些檢查宦棺。大多數(shù)語言都通過解釋器提供了一個 REPL 工具(python 或 perl) 瓣距,或是其他命令(Ruby 使用 irb, Rails 使用 rails console)。
運行一些提交到代碼倉庫的一次性腳本渺氧。
一次性管理進程應(yīng)該和正常的 常駐進程 使用同樣的環(huán)境旨涝。這些管理進程和任何其他的進程一樣使用相同的 代碼 和 配置 ,基于某個 發(fā)布版本 運行侣背。后臺管理代碼應(yīng)該隨其他應(yīng)用程序代碼一起發(fā)布白华,從而避免同步問題。
所有進程類型應(yīng)該使用同樣的 依賴隔離 技術(shù)贩耐。例如弧腥,如果Ruby的web進程使用了命令 bundle exec thin start
,那么數(shù)據(jù)庫移植應(yīng)使用 bundle exec rake db:migrate
潮太。同樣的管搪,如果一個 Python 程序使用了 Virtualenv,則需要在運行 Tornado Web 服務(wù)器和任何 manage.py
管理進程時引入 bin/python
铡买。
12-factor 尤其青睞那些提供了 REPL shell 的語言更鲁,因為那會讓運行一次性腳本變得簡單。在本地部署中奇钞,開發(fā)人員直接在命令行使用 shell 命令調(diào)用一次性管理進程澡为。在線上部署中,開發(fā)人員依舊可以使用ssh或是運行環(huán)境提供的其他機制來運行這樣的進程景埃。
本文章根據(jù) The Twelve-Factor App 官方網(wǎng)站整理媒至,版權(quán)歸原作者所有。
來源:The Twelve-Factor App