雲原生應用開發12-Factors

英文地址:https://12factor.net/html

中文地址:https://12factor.net/zh_cn/node

文章內容python

簡介

現在,軟件一般會做爲一種服務來交付,它們被稱爲網絡應用程序,或軟件即服務(SaaS)。12-Factor 爲構建以下的 SaaS 應用提供了方法論:mysql

  • 使用標準化流程自動配置,從而使新的開發者花費最少的學習成本加入這個項目。
  • 和操做系統之間儘量的劃清界限,在各個系統中提供最大的可移植性
  • 適合部署在現代的雲計算平臺,從而在服務器和系統管理方面節省資源。
  • 將開發環境和生產環境的差別降至最低,並使用持續交付實施敏捷開發。
  • 能夠在工具、架構和開發流程不發生明顯變化的前提下實現擴展

這套理論適用於任意語言和後端服務(數據庫、消息隊列、緩存等)開發的應用程序。git

I. 基準代碼

一份基準代碼(Codebase),多份部署(deploy

12-Factor應用(譯者注:應該是說一個使用本文概念來設計的應用,下同)一般會使用版本控制系統加以管理,如GitMercurialSubversion。一份用來跟蹤代碼全部修訂版本的數據庫被稱做 代碼庫(code repository, code repo, repo)。github

在相似 SVN 這樣的集中式版本控制系統中,基準代碼 就是指控制系統中的這一份代碼庫;而在 Git 那樣的分佈式版本控制系統中,基準代碼 則是指最上游的那份代碼庫。web

 

基準代碼和應用之間老是保持一一對應的關係:redis

  • 一旦有多個基準代碼,就不能稱爲一個應用,而是一個分佈式系統。分佈式系統中的每個組件都是一個應用,每個應用能夠分別使用 12-Factor 進行開發。
  • 多個應用共享一份基準代碼是有悖於 12-Factor 原則的。解決方案是將共享的代碼拆分爲獨立的類庫,而後使用 依賴管理 策略去加載它們。

儘管每一個應用只對應一份基準代碼,但能夠同時存在多份部署。每份 部署 至關於運行了一個應用的實例。一般會有一個生產環境,一個或多個預發佈環境。此外,每一個開發人員都會在本身本地環境運行一個應用實例,這些都至關於一份部署。spring

全部部署的基準代碼相同,但每份部署可使用其不一樣的版本。好比,開發人員可能有一些提交尚未同步至預發佈環境;預發佈環境也有一些提交沒有同步至生產環境。但它們都共享一份基準代碼,咱們就認爲它們只是相同應用的不一樣部署而已。sql

II. 依賴

顯式聲明依賴關係( dependency )

大多數編程語言都會提供一個打包系統,用來爲各個類庫提供打包服務,就像 Perl 的 CPAN 或是 Ruby 的 Rubygems 。經過打包系統安裝的類庫能夠是系統級的(稱之爲 「site packages」),或僅供某個應用程序使用,部署在相應的目錄中(稱之爲 「vendoring」 或 「bunding」)。

12-Factor規則下的應用程序不會隱式依賴系統級的類庫。 它必定經過 依賴清單 ,確切地聲明全部依賴項。此外,在運行過程當中經過 依賴隔離 工具來確保程序不會調用系統中存在但清單中未聲明的依賴項。這一作法會統一應用到生產和開發環境。

例如, Ruby 的 Bundler 使用 Gemfile 做爲依賴項聲明清單,使用 bundle exec 來進行依賴隔離。Python 中則可分別使用兩種工具 – Pip 用做依賴聲明, Virtualenv 用做依賴隔離。甚至 C 語言也有相似工具, Autoconf 用做依賴聲明,靜態連接庫用做依賴隔離。不管用什麼工具,依賴聲明和依賴隔離必須一塊兒使用,不然沒法知足 12-Factor 規範。

顯式聲明依賴的優勢之一是爲新進開發者簡化了環境配置流程。新進開發者能夠檢出應用程序的基準代碼,安裝編程語言環境和它對應的依賴管理工具,只需經過一個 構建命令 來安裝全部的依賴項,便可開始工做。例如,Ruby/Bundler 下使用 bundle install,而 Clojure/Leiningen 則是 lein deps

12-Factor 應用一樣不會隱式依賴某些系統工具,如 ImageMagick 或是curl。即便這些工具存在於幾乎全部系統,但終究沒法保證全部將來的系統都能支持應用順利運行,或是可以和應用兼容。若是應用必須使用到某些系統工具,那麼這些工具應該被包含在應用之中。

III. 配置

在環境中存儲配置

一般,應用的 配置 在不一樣 部署 (預發佈、生產環境、開發環境等等)間會有很大差別。這其中包括:

  • 數據庫,Memcached,以及其餘 後端服務 的配置
  • 第三方服務的證書,如 Amazon S三、Twitter 等
  • 每份部署特有的配置,如域名等

有些應用在代碼中使用常量保存配置,這與 12-Factor 所要求的代碼和配置嚴格分離顯然截然不同。配置文件在各部署間存在大幅差別,代碼卻徹底一致。

判斷一個應用是否正確地將配置排除在代碼以外,一個簡單的方法是看該應用的基準代碼是否能夠馬上開源,而不用擔憂會暴露任何敏感的信息。

須要指出的是,這裏定義的「配置」並包括應用的內部配置,好比 Rails 的 config/routes.rb,或是使用 Spring 時 代碼模塊間的依賴注入關係 。這類配置在不一樣部署間不存在差別,因此應該寫入代碼。

另一個解決方法是使用配置文件,但不把它們歸入版本控制系統,就像 Rails 的 config/database.yml 。這相對於在代碼中使用常量已是長足進步,但仍然有缺點:老是會不當心將配置文件簽入了代碼庫;配置文件的可能會分散在不一樣的目錄,並有着不一樣的格式,這讓找出一個地方來統一管理全部配置變的不太現實。更糟的是,這些格式一般是語言或框架特定的。

12-Factor推薦將應用的配置存儲於 環境變量 中( env varsenv )。環境變量能夠很是方便地在不一樣的部署間作修改,卻不動一行代碼;與配置文件不一樣,不當心把它們簽入代碼庫的機率微乎其微;與一些傳統的解決配置問題的機制(好比 Java 的屬性配置文件)相比,環境變量與語言和系統無關。

配置管理的另外一個方面是分組。有時應用會將配置按照特定部署進行分組(或叫作「環境」),例如Rails中的 development,test, 和 production 環境。這種方法沒法輕易擴展:更多部署意味着更多新的環境,例如 staging 或 qa。 隨着項目的不斷深刻,開發人員可能還會添加他們本身的環境,好比 joes-staging ,這將致使各類配置組合的激增,從而給管理部署增長了不少不肯定因素。

12-Factor 應用中,環境變量的粒度要足夠小,且相對獨立。它們永遠也不會組合成一個所謂的「環境」,而是獨立存在於每一個部署之中。當應用程序不斷擴展,須要更多種類的部署時,這種配置管理方式可以作到平滑過渡。

IV. 後端服務

把後端服務(backing services)看成附加資源

後端服務是指程序運行所須要的經過網絡調用的各類服務,如數據庫(MySQLCouchDB),消息/隊列系統(RabbitMQBeanstalkd),SMTP 郵件發送服務(Postfix),以及緩存系統(Memcached)。

相似數據庫的後端服務,一般由部署應用程序的系統管理員一塊兒管理。除了本地服務以外,應用程序有可能使用了第三方發佈和管理的服務。示例包括 SMTP(例如 Postmark),數據收集服務(例如 New Relic 或 Loggly),數據存儲服務(如 Amazon S3),以及使用 API 訪問的服務(例如 TwitterGoogle MapsLast.fm)。

12-Factor 應用不會區別對待本地或第三方服務。 對應用程序而言,兩種都是附加資源,經過一個 url 或是其餘存儲在 配置中的服務定位/服務證書來獲取數據。12-Factor 應用的任意 部署 ,都應該能夠在不進行任何代碼改動的狀況下,將本地 MySQL 數據庫換成第三方服務(例如 Amazon RDS)。相似的,本地 SMTP 服務應該也能夠和第三方 SMTP 服務(例如 Postmark )互換。上述 2 個例子中,僅需修改配置中的資源地址。

每一個不一樣的後端服務是一份 資源 。例如,一個 MySQL 數據庫是一個資源,兩個 MySQL 數據庫(用來數據分區)就被看成是 2 個不一樣的資源。12-Factor 應用將這些數據庫都視做 附加資源 ,這些資源和它們附屬的部署保持鬆耦合。

 

部署能夠按需加載或卸載資源。例如,若是應用的數據庫服務因爲硬件問題出現異常,管理員能夠從最近的備份中恢復一個數據庫,卸載當前的數據庫,而後加載新的數據庫 – 整個過程都不須要修改代碼。

 

V. 構建,發佈,運行

嚴格分離構建和運行

基準代碼 轉化爲一份部署(非開發環境)須要如下三個階段:

  • 構建階段 是指將代碼倉庫轉化爲可執行包的過程。構建時會使用指定版本的代碼,獲取和打包 依賴項,編譯成二進制文件和資源文件。
  • 發佈階段 會將構建的結果和當前部署所需 配置 相結合,並可以馬上在運行環境中投入使用。
  • 運行階段 (或者說「運行時」)是指針對選定的發佈版本,在執行環境中啓動一系列應用程序 進程

 

12-factor 應用嚴格區分構建,發佈,運行這三個步驟。 舉例來講,直接修改處於運行狀態的代碼是很是不可取的作法,由於這些修改很難再同步回構建步驟。

部署工具一般都提供了發佈管理工具,最引人注目的功能是退回至較舊的發佈版本。好比, Capistrano 將全部發布版本都存儲在一個叫 releases 的子目錄中,當前的在線版本只需映射至對應的目錄便可。該工具的 rollback 命令能夠很容易地實現回退版本的功能。

每個發佈版本必須對應一個惟一的發佈 ID,例如可使用發佈時的時間戳(2011-04-06-20:32:17),亦或是一個增加的數字(v100)。發佈的版本就像一本只能追加的帳本,一旦發佈就不可修改,任何的變更都應該產生一個新的發佈版本。

新的代碼在部署以前,須要開發人員觸發構建操做。可是,運行階段不必定須要人爲觸發,而是能夠自動進行。如服務器重啓,或是進程管理器重啓了一個崩潰的進程。所以,運行階段應該保持儘量少的模塊,這樣假設半夜發生系統故障而開發人員又捉襟見肘也不會引發太大問題。構建階段是能夠相對複雜一些的,由於錯誤信息可以馬上展現在開發人員面前,從而獲得妥善處理。

VI. 進程

以一個或多個無狀態進程運行應用

運行環境中,應用程序一般是以一個和多個 進程 運行的。

最簡單的場景中,代碼是一個獨立的腳本,運行環境是開發人員本身的筆記本電腦,進程由一條命令行(例如python my_script.py)。另一個極端狀況是,複雜的應用可能會使用不少 進程類型 ,也就是零個或多個進程實例。

12-Factor 應用的進程必須無狀態且 無共享 。 任何須要持久化的數據都要存儲在 後端服務 內,好比數據庫。

內存區域或磁盤空間能夠做爲進程在作某種事務型操做時的緩存,例以下載一個很大的文件,對其操做並將結果寫入數據庫的過程。12-Factor應用根本不用考慮這些緩存的內容是否是能夠保留給以後的請求來使用,這是由於應用啓動了多種類型的進程,未來的請求多半會由其餘進程來服務。即便在只有一個進程的情形下,先前保存的數據(內存或文件系統中)也會由於重啓(如代碼部署、配置更改、或運行環境將進程調度至另外一個物理區域執行)而丟失。

源文件打包工具(Jammitdjango-compressor) 使用文件系統來緩存編譯過的源文件。12-Factor 應用更傾向於在 構建步驟作此動做——正如 Rails資源管道 ,而不是在運行階段。

一些互聯網系統依賴於 「粘性 session」, 這是指將用戶 session 中的數據緩存至某進程的內存中,並將同一用戶的後續請求路由到同一個進程。粘性 session 是 12-Factor 極力反對的。Session 中的數據應該保存在諸如 Memcached 或 Redis 這樣的帶有過時時間的緩存中。

VII. 端口綁定

經過端口綁定(Port binding)來提供服務

互聯網應用有時會運行於服務器的容器之中。例如 PHP 常常做爲 Apache HTTPD 的一個模塊來運行,正如 Java 運行於 Tomcat 。

12-Factor 應用徹底自我加載 而不依賴於任何網絡服務器就能夠建立一個面向網絡的服務。互聯網應用 經過端口綁定來提供服務 ,並監聽發送至該端口的請求。

本地環境中,開發人員經過相似http://localhost:5000/的地址來訪問服務。在線上環境中,請求統一發送至公共域名然後路由至綁定了端口的網絡進程。

一般的實現思路是,將網絡服務器類庫經過 依賴聲明 載入應用。例如,Python 的 Tornado, Ruby 的Thin , Java 以及其餘基於 JVM 語言的 Jetty。徹底由 用戶端 ,確切的說應該是應用的代碼,發起請求。和運行環境約定好綁定的端口便可處理這些請求。

HTTP 並非惟一一個能夠由端口綁定提供的服務。其實幾乎全部服務器軟件均可以經過進程綁定端口來等待請求。例如,使用 XMPP 的 ejabberd , 以及使用 Redis 協議 的 Redis 。

還要指出的是,端口綁定這種方式也意味着一個應用能夠成爲另一個應用的 後端服務 ,調用方將服務方提供的相應 URL 看成資源存入 配置 以備未來調用。

VIII. 併發

經過進程模型進行擴展

任何計算機程序,一旦啓動,就會生成一個或多個進程。互聯網應用採用多種進程運行方式。例如,PHP 進程做爲 Apache 的子進程存在,隨請求按需啓動。Java 進程則採起了相反的方式,在程序啓動之初 JVM 就提供了一個超級進程儲備了大量的系統資源(CPU 和內存),並經過多線程實現內部的併發管理。上述 2 個例子中,進程是開發人員能夠操做的最小單位。

 

在 12-factor 應用中,進程是一等公民。12-Factor 應用的進程主要借鑑於 unix 守護進程模型 。開發人員能夠運用這個模型去設計應用架構,將不一樣的工做分配給不一樣的 進程類型 。例如,HTTP 請求能夠交給 web 進程來處理,而常駐的後臺工做則交由 worker 進程負責。

這並不包括個別較爲特殊的進程,例如經過虛擬機的線程處理併發的內部運算,或是使用諸如 EventMachineTwistedNode.js 的異步/事件觸發模型。但一臺獨立的虛擬機的擴展有瓶頸(垂直擴展),因此應用程序必須能夠在多臺物理機器間跨進程工做。

上述進程模型會在系統急需擴展時大放異彩。 12-Factor 應用的進程所具有的無共享,水平分區的特性 意味着添加併發會變得簡單而穩妥。這些進程的類型以及每一個類型中進程的數量就被稱做 進程構成 。

12-Factor 應用的進程 不須要守護進程 或是寫入 PID 文件。相反的,應該藉助操做系統的進程管理器(例如 systemd ,分佈式的進程管理雲平臺,或是相似 Foreman 的工具),來管理 輸出流 ,響應崩潰的進程,以及處理用戶觸發的重啓和關閉超級進程的請求。

IX. 易處理

快速啓動和優雅終止可最大化健壯性

12-Factor 應用的 進程 是 易處理(disposable)的,意思是說它們能夠瞬間開啓或中止。 這有利於快速、彈性的伸縮應用,迅速部署變化的 代碼 或 配置 ,穩健的部署應用。

進程應當追求 最小啓動時間 。 理想狀態下,進程從敲下命令到真正啓動並等待請求的時間應該只需很短的時間。更少的啓動時間提供了更敏捷的 發佈 以及擴展過程,此外還增長了健壯性,由於進程管理器能夠在受權情形下容易的將進程搬到新的物理機器上。

進程 一旦接收 終止信號(SIGTERM 就會優雅的終止 。就網絡進程而言,優雅終止是指中止監聽服務的端口,即拒絕全部新的請求,並繼續執行當前已接收的請求,而後退出。此類型的進程所隱含的要求是HTTP請求大多都很短(不會超過幾秒鐘),而在長時間輪詢中,客戶端在丟失鏈接後應該立刻嘗試重連。

對於 worker 進程來講,優雅終止是指將當前任務退回隊列。例如,RabbitMQ 中,worker 能夠發送一個NACK信號。 Beanstalkd 中,任務終止並退回隊列會在worker斷開時自動觸發。有鎖機制的系統諸如 Delayed Job 則須要肯定釋放了系統資源。此類型的進程所隱含的要求是,任務都應該 可重複執行 , 這主要由將結果包裝進事務或是使重複操做 冪等 來實現。

進程還應當在面對忽然死亡時保持健壯,例如底層硬件故障。雖然這種狀況比起優雅終止來講少之又少,但終究有可能發生。一種推薦的方式是使用一個健壯的後端隊列,例如 Beanstalkd ,它能夠在客戶端斷開或超時後自動退回任務。不管如何,12-Factor 應用都應該能夠設計可以應對意外的、不優雅的終結。Crash-only design 將這種概念轉化爲 合乎邏輯的理論

X. 開發環境與線上環境等價

儘量的保持開發,預發佈,線上環境相同

從以往經驗來看,開發環境(即開發人員的本地 部署)和線上環境(外部用戶訪問的真實部署)之間存在着不少差別。這些差別表如今如下三個方面:

  • 時間差別: 開發人員正在編寫的代碼可能須要幾天,幾周,甚至幾個月纔會上線。
  • 人員差別: 開發人員編寫代碼,運維人員部署代碼。
  • 工具差別: 開發人員或許使用 Nginx,SQLite,OS X,而線上環境使用 Apache,MySQL 以及 Linux。

12-Factor 應用想要作到 持續部署 就必須縮小本地與線上差別。 再回頭看上面所描述的三個差別:

  • 縮小時間差別:開發人員能夠幾小時,甚至幾分鐘就部署代碼。
  • 縮小人員差別:開發人員不僅要編寫代碼,更應該密切參與部署過程以及代碼在線上的表現。
  • 縮小工具差別:儘可能保證開發環境以及線上環境的一致性。

將上述總結變爲一個表格以下:

  傳統應用 12-Factor 應用
每次部署間隔 數週 幾小時
開發人員 vs 運維人員 不一樣的人 相同的人
開發環境 vs 線上環境 不一樣 儘可能接近

後端服務 是保持開發與線上等價的重要部分,例如數據庫,隊列系統,以及緩存。許多語言都提供了簡化獲取後端服務的類庫,例如不一樣類型服務的 適配器 。下列表格提供了一些例子。

類型 語言 類庫 適配器
數據庫 Ruby/Rails ActiveRecord MySQL, PostgreSQL, SQLite
隊列 Python/Django Celery RabbitMQ, Beanstalkd, Redis
緩存 Ruby/Rails ActiveSupport::Cache Memory, filesystem, Memcached

開發人員有時會以爲在本地環境中使用輕量的後端服務具備很強的吸引力,而那些更重量級的健壯的後端服務應該使用在生產環境。例如,本地使用 SQLite 線上使用 PostgreSQL;又如本地緩存在進程內存中而線上存入 Memcached。

12-Factor 應用的開發人員應該反對在不一樣環境間使用不一樣的後端服務 ,即便適配器已經能夠幾乎消除使用上的差別。這是由於,不一樣的後端服務意味着會忽然出現的不兼容,從而致使測試、預發佈都正常的代碼在線上出現問題。這些錯誤會給持續部署帶來阻力。從應用程序的生命週期來看,消除這種阻力須要花費很大的代價。

與此同時,輕量的本地服務也不像之前那樣引人注目。藉助於Homebrewapt-get等現代的打包系統,諸如Memcached、PostgreSQL、RabbitMQ 等後端服務的安裝與運行也並不複雜。此外,使用相似 Chef 和 Puppet 的聲明式配置工具,結合像 Vagrant 這樣輕量的虛擬環境就可使得開發人員的本地環境與線上環境無限接近。與同步環境和持續部署所帶來的益處相比,安裝這些系統顯然是值得的。

不一樣後端服務的適配器仍然是有用的,由於它們可使移植後端服務變得簡單。但應用的全部部署,這其中包括開發、預發佈以及線上環境,都應該使用同一個後端服務的相同版本。

XI. 日誌

把日誌看成事件流

日誌 使得應用程序運行的動做變得透明。在基於服務器的環境中,日誌一般被寫在硬盤的一個文件裏,但這只是一種輸出格式。

日誌應該是 事件流 的彙總,將全部運行中進程和後端服務的輸出流按照時間順序收集起來。儘管在回溯問題時可能須要看不少行,日誌最原始的格式確實是一個事件一行。日誌沒有肯定開始和結束,但隨着應用在運行會持續的增長。

12-factor應用自己從不考慮存儲本身的輸出流。 不該該試圖去寫或者管理日誌文件。相反,每個運行的進程都會直接的標準輸出(stdout)事件流。開發環境中,開發人員能夠經過這些數據流,實時在終端看到應用的活動。

在預發佈或線上部署中,每一個進程的輸出流由運行環境截獲,並將其餘輸出流整理在一塊兒,而後一併發送給一個或多個最終的處理程序,用於查看或是長期存檔。這些存檔路徑對於應用來講不可見也不可配置,而是徹底交給程序的運行環境管理。相似 Logplex 和 Fluentd 的開源工具能夠達到這個目的。

這些事件流能夠輸出至文件,或者在終端實時觀察。最重要的,輸出流能夠發送到 Splunk 這樣的日誌索引及分析系統,或 Hadoop/Hive 這樣的通用數據存儲系統。這些系統爲查看應用的歷史活動提供了強大而靈活的功能,包括:

  • 找出過去一段時間特殊的事件。
  • 圖形化一個大規模的趨勢,好比每分鐘的請求量。
  • 根據用戶定義的條件實時觸發警報,好比每分鐘的報錯超過某個警惕線。

XII. 管理進程

後臺管理任務看成一次性進程運行

進程構成(process formation)是指用來處理應用的常規業務(好比處理 web 請求)的一組進程。與此不一樣,開發人員常常但願執行一些管理或維護應用的一次性任務,例如:

  • 運行數據移植(Django 中的 manage.py migrate, Rails 中的 rake db:migrate)。
  • 運行一個控制檯(也被稱爲 REPL shell),來執行一些代碼或是針對線上數據庫作一些檢查。大多數語言都經過解釋器提供了一個 REPL 工具(python 或 perl) ,或是其餘命令(Ruby 使用 irb, Rails 使用 rails console)。
  • 運行一些提交到代碼倉庫的一次性腳本。

一次性管理進程應該和正常的 常駐進程 使用一樣的環境。這些管理進程和任何其餘的進程同樣使用相同的 代碼 和 配置 ,基於某個 發佈版本 運行。後臺管理代碼應該隨其餘應用程序代碼一塊兒發佈,從而避免同步問題。

全部進程類型應該使用一樣的 依賴隔離 技術。例如,若是Ruby的web進程使用了命令 bundle exec thin start ,那麼數據庫移植應使用 bundle exec rake db:migrate 。一樣的,若是一個 Python 程序使用了 Virtualenv,則須要在運行 Tornado Web 服務器和任何 manage.py 管理進程時引入 bin/python 。

12-factor 尤爲青睞那些提供了 REPL shell 的語言,由於那會讓運行一次性腳本變得簡單。在本地部署中,開發人員直接在命令行使用 shell 命令調用一次性管理進程。在線上部署中,開發人員依舊可使用ssh或是運行環境提供的其餘機制來運行這樣的進程。

相關文章
相關標籤/搜索