引言
如今,軟件通常會(huì)作為一種服務(wù)來交付,它們被稱為網(wǎng)絡(luò)應(yīng)用程序,或軟件即服務(wù)(SaaS)。12-Factor 為構(gòu)建如下的 SaaS 應(yīng)用提供了方法論:
●使用標(biāo)準(zhǔn)化流程自動(dòng)配置,從而使新的開發(fā)者花費(fèi)最少的學(xué)習(xí)成本加入這個(gè)項(xiàng)目。
●和操作系統(tǒng)之間盡可能的劃清界限,在各個(gè)系統(tǒng)中提供最大的可移植性。
●適合部署在現(xiàn)代的云計(jì)算平臺(tái),從而在服務(wù)器和系統(tǒng)管理方面節(jié)省資源。
●將開發(fā)環(huán)境和生產(chǎn)環(huán)境的差異降至最低,并使用持續(xù)交付實(shí)施敏捷開發(fā)。
●可以在工具、架構(gòu)和開發(fā)流程不發(fā)生明顯變化的前提下實(shí)現(xiàn)擴(kuò)展。
這套理論適用于任意語言和后端服務(wù)(數(shù)據(jù)庫、消息隊(duì)列、緩存等)開發(fā)的應(yīng)用程序。
特別聲明
本文轉(zhuǎn)自國(guó)外一篇文章,由Adam Wiggins所著,原文地址:https://12factor.net/
在此文基礎(chǔ)上增加個(gè)人的理解以及部分圖解。
統(tǒng)一源代碼管理系統(tǒng)
一份基準(zhǔn)代碼(Codebase),多份部署(deploy)
在類似 SVN 這樣的集中式版本控制系統(tǒng)中,基準(zhǔn)代碼就是指控制系統(tǒng)中的這一份代碼庫;而在 Git 那樣的分布式版本控制系統(tǒng)中,基準(zhǔn)代碼則是指最上游的那份代碼庫。
基準(zhǔn)代碼和應(yīng)用之間總是保持一一對(duì)應(yīng)的關(guān)系:
●一旦有多個(gè)基準(zhǔn)代碼,就不能稱為一個(gè)應(yīng)用,而是一個(gè)分布式系統(tǒng)。分布式系統(tǒng)中的每一個(gè)組件都是一個(gè)應(yīng)用,每一個(gè)應(yīng)用可以分別使用 12-Factor 進(jìn)行開發(fā)。
●多個(gè)應(yīng)用共享一份基準(zhǔn)代碼是有悖于 12-Factor 原則的。解決方案是將共享的代碼拆分為獨(dú)立的類庫,然后使用依賴管理策略去加載它們。
依賴管理
顯式聲明依賴
大多數(shù)編程語言都會(huì)提供一個(gè)打包系統(tǒng),用來為各個(gè)類庫提供打包服務(wù),就像 Perl 的 CPAN 或是 Ruby 的 Rubygems 。通過打包系統(tǒng)安裝的類庫可以是系統(tǒng)級(jí)的(稱之為 “site packages”),或僅供某個(gè)應(yīng)用程序使用,部署在相應(yīng)的目錄中(稱之為 “vendoring” 或 “bunding”)
12-Factor規(guī)則下的應(yīng)用程序不會(huì)隱式依賴系統(tǒng)級(jí)的類庫。 它一定通過 依賴清單 ,確切地聲明所有依賴項(xiàng)。此外,在運(yùn)行過程中通過依賴隔離工具來確保程序不會(huì)調(diào)用系統(tǒng)中存在但清單中未聲明的依賴項(xiàng)。這一做法會(huì)統(tǒng)一應(yīng)用到生產(chǎn)和開發(fā)環(huán)境。
顯式聲明依賴的優(yōu)點(diǎn)之一是為新進(jìn)開發(fā)者簡(jiǎn)化了環(huán)境配置流程。新的開發(fā)者可以檢出應(yīng)用程序的基準(zhǔn)代碼,安裝編程語言環(huán)境和它對(duì)應(yīng)的依賴管理工具,只需通過一個(gè)構(gòu)建命令來安裝所有的依賴項(xiàng),即可開始工作,如Maven,Pip,Npm等
12-Factor 應(yīng)用同樣不會(huì)隱式依賴某些系統(tǒng)工具,如 ImageMagick 或是curl。即使這些工具存在于幾乎所有系統(tǒng),但終究無法保證所有未來的系統(tǒng)都能支持應(yīng)用順利運(yùn)行,或是能夠和應(yīng)用兼容。如果應(yīng)用必須使用到某些系統(tǒng)工具,那么這些工具應(yīng)該被包含在應(yīng)用之中。
配置管理
在環(huán)境中存儲(chǔ)配置
通常,應(yīng)用的配置在不同部署(預(yù)發(fā)布、生產(chǎn)環(huán)境、開發(fā)環(huán)境等等)間會(huì)有很大差異。這其中包括:
●數(shù)據(jù)庫,Memcached,以及其他后端服務(wù)的配置。
●第三方服務(wù)的證書,憑證,如 Amazon S3、Twitter 等。
●每份部署特有的配置,如域名等
應(yīng)用程序不允許將配置存儲(chǔ)為代碼中的常量,這需要嚴(yán)格地將配置與代碼分離。配置在部署之間差異很大,代碼則沒有。另外,"config" 的這個(gè)定義不包括內(nèi)部應(yīng)用程序配置,這種類型的配置在部署之間不會(huì)有所不同,因此最好在代碼中保存。
提示:對(duì)應(yīng)用程序是否在代碼中正確分配了所有配置的試金石是,代碼庫是否可以隨時(shí)變?yōu)殚_源,而不用擔(dān)心泄漏任何敏感憑據(jù)。
應(yīng)用程序應(yīng)將配置存儲(chǔ)在環(huán)境變量中(通??s寫為env vars 或 env)。在不更改任何代碼的情況下,可以在部署之間輕松更改 Env 變量; 與配置文件不同,它們幾乎沒有機(jī)會(huì)被意外地檢入代碼倉庫; 與自定義配置文件或其他配置機(jī)制(如 Java 系統(tǒng)屬性)不同,它們是與語言和操作系統(tǒng)無關(guān)的標(biāo)準(zhǔn)。
后端服務(wù)
把后端服務(wù)(backing services)當(dāng)作附加資源
后端服務(wù)是指程序運(yùn)行所需要的通過網(wǎng)絡(luò)調(diào)用的各種服務(wù),如數(shù)據(jù)庫(MySQL,CouchDB),消息/隊(duì)列系統(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ù)存儲(chǔ)服務(wù)(如 Amazon S3),以及使用 API 訪問的服務(wù)(例如 Twitter, Google Maps, Last.fm)。
12-Factor 應(yīng)用不會(huì)區(qū)別對(duì)待本地或第三方服務(wù)。 對(duì)應(yīng)用程序而言,兩種都是附加資源,通過一個(gè) url 或是其他存儲(chǔ)在配置中的服務(wù)定位/服務(wù)證書來獲取數(shù)據(jù)。12-Factor 應(yīng)用的任意 部署 ,都應(yīng)該可以在不進(jìn)行任何代碼改動(dòng)的情況下,將本地 MySQL 數(shù)據(jù)庫換成第三方服務(wù)(例如 Amazon RDS)。類似的,本地 SMTP 服務(wù)應(yīng)該也可以和第三方 SMTP 服務(wù)(例如 Postmark )互換。上述 2 個(gè)例子中,僅需修改配置中的資源地址。
12-Factor 應(yīng)用將這些都視作附加資源 ,這些資源和它們附屬的部署保持松耦合。
構(gòu)建,發(fā)布,運(yùn)行
嚴(yán)格分離構(gòu)建和運(yùn)行
基準(zhǔn)代碼轉(zhuǎn)化為一份部署(非開發(fā)環(huán)境)需要以下三個(gè)階段:
構(gòu)建階段是指將代碼倉庫轉(zhuǎn)化為可執(zhí)行包的過程。構(gòu)建時(shí)會(huì)使用指定版本的代碼,獲取和打包依賴項(xiàng),編譯成二進(jìn)制文件和資源文件。
發(fā)布階段會(huì)將構(gòu)建的結(jié)果和當(dāng)前部署所需配置相結(jié)合,并能夠立刻在運(yùn)行環(huán)境中投入使用。
運(yùn)行階段(或者說“運(yùn)行時(shí)”)是指針對(duì)選定的發(fā)布版本,在執(zhí)行環(huán)境中啟動(dòng)一系列應(yīng)用程序進(jìn)程。
12-factor 應(yīng)用嚴(yán)格區(qū)分構(gòu)建,發(fā)布,運(yùn)行這三個(gè)步驟。 舉例來說,直接修改處于運(yùn)行狀態(tài)的代碼是非常不可取的做法,因?yàn)檫@些修改很難再同步回構(gòu)建步驟。
每一個(gè)發(fā)布版本必須對(duì)應(yīng)一個(gè)唯一的發(fā)布 ID,例如可以使用發(fā)布時(shí)的時(shí)間戳(2011-04-06-20:32:17),亦或是一個(gè)增長(zhǎng)的數(shù)字(v100)。發(fā)布的版本就像一本只能追加的賬本,一旦發(fā)布就不可修改,任何的變動(dòng)都應(yīng)該產(chǎn)生一個(gè)新的發(fā)布版本。這樣也便于回退到任意歷史版本,而不需要冒風(fēng)險(xiǎn)重新構(gòu)建。
新的代碼在部署之前,需要開發(fā)人員觸發(fā)構(gòu)建操作。但是,運(yùn)行階段不一定需要人為觸發(fā),而是可以自動(dòng)進(jìn)行。如服務(wù)器重啟,或是進(jìn)程管理器重啟了一個(gè)崩潰的進(jìn)程。因此,運(yùn)行階段應(yīng)該保持盡可能少的模塊,這樣假設(shè)半夜發(fā)生系統(tǒng)故障而開發(fā)人員又捉襟見肘也不會(huì)引起太大問題。構(gòu)建階段是可以相對(duì)復(fù)雜一些的,因?yàn)殄e(cuò)誤信息能夠立刻展示在開發(fā)人員面前,從而得到妥善處理。
進(jìn)程
以一個(gè)或多個(gè)無狀態(tài)進(jìn)程運(yùn)行應(yīng)用
運(yùn)行環(huán)境中,應(yīng)用程序通常是以一個(gè)和多個(gè)進(jìn)程運(yùn)行的。
12-Factor 應(yīng)用的進(jìn)程必須無狀態(tài)且無共享。 任何需要持久化的數(shù)據(jù)都要存儲(chǔ)在后端服務(wù)內(nèi),比如數(shù)據(jù)庫。
內(nèi)存區(qū)域或磁盤空間可以作為進(jìn)程在做某種事務(wù)型操作時(shí)的緩存,例如下載一個(gè)很大的文件,對(duì)其操作并將結(jié)果寫入數(shù)據(jù)庫的過程。12-Factor應(yīng)用根本不用考慮這些緩存的內(nèi)容是不是可以保留給之后的請(qǐng)求來使用,這是因?yàn)閼?yīng)用啟動(dòng)了多種類型的進(jìn)程,將來的請(qǐng)求多半會(huì)由其他進(jìn)程來服務(wù)。即使在只有一個(gè)進(jìn)程的情形下,先前保存的數(shù)據(jù)(內(nèi)存或文件系統(tǒng)中)也會(huì)因?yàn)橹貑ⅲㄈ绱a部署、配置更改、或運(yùn)行環(huán)境將進(jìn)程調(diào)度至另一個(gè)物理區(qū)域執(zhí)行)而丟失。
一些系統(tǒng)依賴于 “粘性 session”, 這是指將用戶 session 中的數(shù)據(jù)緩存至某進(jìn)程的內(nèi)存中,并將同一用戶的后續(xù)請(qǐng)求路由到同一個(gè)進(jìn)程。粘性 session 是 12-Factor 極力反對(duì)的。Session 中的數(shù)據(jù)應(yīng)該保存在諸如 Memcached 或 Redis 這樣的帶有過期時(shí)間的緩存中。
端口綁定
通過端口綁定(Port binding)來提供服務(wù)
應(yīng)用有時(shí)會(huì)運(yùn)行于服務(wù)器的容器之中。例如 PHP 經(jīng)常作為 Apache HTTPD 的一個(gè)模塊來運(yùn)行,正如 Java 運(yùn)行于 Tomcat 。
12-Factor 應(yīng)用完全自我加載而不依賴于任何網(wǎng)絡(luò)服務(wù)器就可以創(chuàng)建一個(gè)面向網(wǎng)絡(luò)的服務(wù)?;ヂ?lián)網(wǎng)應(yīng)用通過端口綁定來提供服務(wù) ,并監(jiān)聽發(fā)送至該端口的請(qǐng)求。
還要指出的是,端口綁定這種方式也意味著一個(gè)應(yīng)用可以成為另外一個(gè)應(yīng)用的后端服務(wù) ,調(diào)用方將服務(wù)方提供的相應(yīng) URL 當(dāng)作資源存入配置以備將來調(diào)用。
并發(fā)
通過進(jìn)程模型進(jìn)行擴(kuò)展
在 12-factor 應(yīng)用中,進(jìn)程是一等公民。12-Factor 應(yīng)用的進(jìn)程主要借鑒于 unix 守護(hù)進(jìn)程模型 。開發(fā)人員可以運(yùn)用這個(gè)模型去設(shè)計(jì)應(yīng)用架構(gòu),將不同的工作分配給不同的進(jìn)程類型 。例如,HTTP 請(qǐng)求可以交給 web 進(jìn)程來處理,而常駐的后臺(tái)工作則交由 worker 進(jìn)程負(fù)責(zé)。
上述進(jìn)程模型會(huì)在系統(tǒng)急需擴(kuò)展時(shí)大放異彩。 12-Factor 應(yīng)用的進(jìn)程所具備的無共享,水平分區(qū)的特性意味著添加并發(fā)會(huì)變得簡(jiǎn)單而穩(wěn)妥。這些進(jìn)程的類型以及每個(gè)類型中進(jìn)程的數(shù)量就被稱作進(jìn)程構(gòu)成 。
易處理
快速啟動(dòng)和優(yōu)雅終止可最大化健壯性
12-Factor 應(yīng)用的進(jìn)程是易處理(disposable)的,意思是說它們可以瞬間開啟或停止。這有利于快速、彈性的伸縮應(yīng)用,迅速部署變化的代碼或配置 ,穩(wěn)健的部署應(yīng)用。
進(jìn)程應(yīng)當(dāng)追求最小啟動(dòng)時(shí)間 。 理想狀態(tài)下,進(jìn)程從敲下命令到真正啟動(dòng)并等待請(qǐng)求的時(shí)間應(yīng)該只需很短的時(shí)間。更少的啟動(dòng)時(shí)間提供了更敏捷的發(fā)布以及擴(kuò)展過程,此外還增加了健壯性,因?yàn)檫M(jìn)程管理器可以在授權(quán)情形下容易的將進(jìn)程搬到新的物理機(jī)器上。
另外進(jìn)程一旦接收終止信號(hào)(SIGTERM) 就會(huì)優(yōu)雅的終止 。就網(wǎng)絡(luò)進(jìn)程而言,優(yōu)雅終止是指停止監(jiān)聽服務(wù)的端口,即拒絕所有新的請(qǐng)求,并繼續(xù)執(zhí)行當(dāng)前已接收的請(qǐng)求,然后退出。此類型的進(jìn)程所隱含的要求是HTTP請(qǐng)求大多都很短(不會(huì)超過幾秒鐘),而在長(zhǎng)時(shí)間輪詢中,客戶端在丟失連接后應(yīng)該馬上嘗試重連;對(duì)于 worker 進(jìn)程來說,優(yōu)雅終止是指將當(dāng)前任務(wù)退回隊(duì)列。
開發(fā)環(huán)境與線上環(huán)境一致
盡可能的保持開發(fā),預(yù)發(fā)布,線上環(huán)境相同
開發(fā)環(huán)境(即開發(fā)人員的本地部署)和線上環(huán)境(外部用戶訪問的真實(shí)部署)之間存在著很多差異。這些差異表現(xiàn)在以下三個(gè)方面:
●時(shí)間差異: 開發(fā)人員正在編寫的代碼可能需要幾天,幾周,甚至幾個(gè)月才會(huì)上線。
●人員差異: 開發(fā)人員編寫代碼,運(yùn)維人員部署代碼。
●工具差異: 開發(fā)人員或許使用 Nginx,SQLite,OS X,而線上環(huán)境使用 Apache,MySQL 以及 Linux。
12-Factor 應(yīng)用想要做到持續(xù)部署就必須縮小本地與線上差異。 再回頭看上面所描述的三個(gè)差異:
●縮小時(shí)間差異:開發(fā)人員可以幾小時(shí),甚至幾分鐘就部署代碼。
●縮小人員差異:開發(fā)人員不只要編寫代碼,更應(yīng)該密切參與部署過程以及代碼在線上的表現(xiàn)。
●縮小工具差異:盡量保證開發(fā)環(huán)境以及線上環(huán)境的一致性。
將上述總結(jié)變?yōu)橐粋€(gè)表格如下:
●12-Factor 應(yīng)用的開發(fā)人員應(yīng)該反對(duì)在不同環(huán)境間使用不同的后端服務(wù) ,即使適配器已經(jīng)可以幾乎消除使用上的差異。這是因?yàn)?,不同的后端服?wù)意味著會(huì)突然出現(xiàn)的不兼容,從而導(dǎo)致測(cè)試、預(yù)發(fā)布都正常的代碼在線上出現(xiàn)問題。這些錯(cuò)誤會(huì)給持續(xù)部署帶來阻力。從應(yīng)用程序的生命周期來看,消除這種阻力需要花費(fèi)很大的代價(jià)。
日志
把日志當(dāng)作事件流
日志使得應(yīng)用程序運(yùn)行的動(dòng)作變得透明。在基于服務(wù)器的環(huán)境中,日志通常被寫在硬盤的一個(gè)文件里,但這只是一種輸出格式。
12-factor應(yīng)用本身從不考慮存儲(chǔ)自己的輸出流。 不應(yīng)該試圖去寫或者管理日志文件。相反,每一個(gè)運(yùn)行的進(jìn)程都會(huì)直接的標(biāo)準(zhǔn)輸出(stdout)事件流。開發(fā)環(huán)境中,開發(fā)人員可以通過這些數(shù)據(jù)流,實(shí)時(shí)在終端看到應(yīng)用的活動(dòng)。
在預(yù)發(fā)布或線上部署中,每個(gè)進(jìn)程的輸出流由運(yùn)行環(huán)境截獲,并將其他輸出流整理在一起,然后一并發(fā)送給一個(gè)或多個(gè)最終的處理程序,用于查看或是長(zhǎng)期存檔。這些存檔路徑對(duì)于應(yīng)用來說不可見也不可配置,而是完全交給程序的運(yùn)行環(huán)境管理。類似 Logplex 和 Fluentd 的開源工具可以達(dá)到這個(gè)目的。
這些事件流可以輸出至文件,或者在終端實(shí)時(shí)觀察。最重要的,輸出流可以發(fā)送到 Splunk 這樣的日志索引及分析系統(tǒng),或 Hadoop/Hive 這樣的通用數(shù)據(jù)存儲(chǔ)系統(tǒng)。這些系統(tǒng)為查看應(yīng)用的歷史活動(dòng)提供了強(qiáng)大而靈活的功能,包括:
●找出過去一段時(shí)間特殊的事件。
●圖形化一個(gè)大規(guī)模的趨勢(shì),比如每分鐘的請(qǐng)求量。
●根據(jù)用戶定義的條件實(shí)時(shí)觸發(fā)警報(bào),比如每分鐘的報(bào)錯(cuò)超過某個(gè)警戒線。
管理進(jìn)程
后臺(tái)管理任務(wù)當(dāng)作一次性進(jìn)程運(yùn)行
進(jìn)程構(gòu)成(process formation)是指用來處理應(yīng)用的常規(guī)業(yè)務(wù)(比如處理 web 請(qǐng)求)的一組進(jìn)程。與此不同,開發(fā)人員經(jīng)常希望執(zhí)行一些管理或維護(hù)應(yīng)用的一次性任務(wù),例如:
運(yùn)行數(shù)據(jù)移植(Django 中的 manage.py migrate, Rails 中的 rake db:migrate)。
運(yùn)行一個(gè)控制臺(tái)(也被稱為 REPL shell),來執(zhí)行一些代碼或是針對(duì)線上數(shù)據(jù)庫做一些檢查。大多數(shù)語言都通過解釋器提供了一個(gè) REPL 工具(python 或 perl),或是其他命令(Ruby 使用 irb, Rails 使用 rails console)。
運(yùn)行一些提交到代碼倉庫的一次性腳本。
一次性管理進(jìn)程應(yīng)該和正常的常駐進(jìn)程使用同樣的環(huán)境。這些管理進(jìn)程和任何其他的進(jìn)程一樣使用相同的代碼和配置 ,基于某個(gè)發(fā)布版本運(yùn)行。后臺(tái)管理代碼應(yīng)該隨其他應(yīng)用程序代碼一起發(fā)布,從而避免同步問題。