SaaS应用12原则
SaaS應用12原則
- 1、What
- 1.1 什么是 "12 Factor App" ?
- 1.2 "12 Factor App" 分別是什么?
- 2、Why
- 3、How
- 3.1 基準代碼
- 3.2 依賴
- 3.3 配置
- 3.4 后端服務
- 3.5 構建,發布,運行
- 3.6 進程
- 3.7 端口綁定
- 3.8 并發
- 3.9 易處理
- 3.10 開發環境與線上環境等價
- 3.11 日志
- 3.12 管理進程
- 4、Sample
1、What
1.1 什么是 “12 Factor App” ?
目前,軟件通常作為一種服務來進行交付,他們被稱之為網絡應用程序,或軟件即服務(Saas), 12-Factor 為構建Saas 應用提供了方法論。
1.2 “12 Factor App” 分別是什么?
| 1 | 基準代碼 | 一份基準代碼,可以進行多份部署 |
| 2 | 依賴 | 顯示聲明所有的依賴關系 |
| 3 | 配置 | 在環境中存儲配置 |
| 4 | 后端服務 | 把后端服務當做附加資源 |
| 5 | 構建,發布,運行 | 嚴格分離構建和運行 |
| 6 | 進程 | 以一個或多個無狀態進程運行應用 |
| 7 | 端口綁定 | 通過端口綁定提供服務 |
| 8 | 并發 | 通過進程模型進行擴展 |
| 9 | 易處理 | 快速啟動和優雅終止可最大化健壯性 |
| 10 | 開發環境與線上環境等價 | 盡可能的保持開發,預發布,線上的環境相同 |
| 11 | 日志 | 把日志當做事件流 |
| 12 | 管理進程 | 后臺管理任務當做一次性進程運行 |
2、Why
“12-factor app” 是為構建可擴展、高性能、獨立且最具彈性的云原生應用程序而定義的一種方法或一組原則:
The 12-factor app is a methodology or set of principles defined for building the scalable and performant, independent, and most resilient cloud-native applications:
- 使用聲明性格式進行設置自動化,從而最大限度地減少新進開發人員加入項目的時間和成本
Use declarative formats for setup automation, which minimizes time and cost for new developers joining the project - 與底層操作系統松耦合的狀態,為執行環境之間提供了最大的可移植性
Have a clean contract with the underlying operating system, offering maximum portability between execution environments - 適合部署在現代云平臺上
Are suitable for deployment on modern cloud platforms - 最大限度地減少開發環境和生產環境之間的差異,實現持續部署以獲得最大的敏捷性
Minimize divergence between development and production, enabling continuous deployment for maximum agility - 無需對工具、架構或開發實踐進行重大更改即進行擴展
Can scale up without significant changes to tooling, architecture, or development practices
3、How
3.1 基準代碼
軟件開發中,版本控制尤為重要,常用的如Git,SVN等。一份用來跟蹤代碼所有修訂版本的數據庫被稱作:代碼庫。
在類似 SVN 這樣的集中式版本控制系統中,基準代碼 就是指控制系統中的這一份代碼庫;而在 Git 那樣的分布式版本控制系統中,基準代碼 則是指最上游的那份代碼庫。
基準代碼和應用之間總是保持一一對應的關系:
- 一旦有多個基準代碼,就不能稱為一個應用,而是一個分布式系統。分布式系統中的每一個組件都是一個應用,每一個應用可以分別使用 12-Factor 進行開發。
- 多個應用共享一份基準代碼是有悖于 12-Factor 原則的。解決方案是將共享的代碼拆分為獨立的類庫,然后使用依賴管理策略去加載它們。
盡管每個應用只對應一份基準代碼,但可以同時存在多份部署。每份部署相當于運行了一個應用的實例。通常會有一個生產環境,一個或多個預發布環境。此外,每個開發人員都會在自己本地環境運行一個應用實例,這些都相當于一份部署。
所有部署的基準代碼相同,但每份部署可以使用其不同的版本。比如,開發人員可能有一些提交還沒有同步至預發布環境;預發布環境也有一些提交沒有同步至生產環境。但它們都共享一份基準代碼,我們就認為它們只是相同應用的不同部署而已。
3.2 依賴
大多數編程語言都會提供一個打包系統,用來為各個類庫提供打包服務。12-Factor規則下的應用程序不會隱式依賴系統級的類庫。 它一定通過依賴清單,確切地聲明所有依賴項。此外,在運行過程中通過依賴隔離工具來確保程序不會調用系統中存在但清單中未聲明的依賴項。這一做法會統一應用到生產和開發環境。
顯式聲明依賴的優點之一是為新進開發者簡化了環境配置流程。新進開發者可以檢出應用程序的基準代碼,安裝編程語言環境和它對應的依賴管理工具,只需通過一個構建命令來安裝所有的依賴項,即可開始工作。
12-Factor 應用同樣不會隱式依賴某些系統工具,如 ImageMagick 或是curl。即使這些工具存在于幾乎所有系統,但終究無法保證所有未來的系統都能支持應用順利運行,或是能夠和應用兼容。如果應用必須使用到某些系統工具,那么這些工具應該被包含在應用之中。
3.3 配置
通常,引用的配置在不同部署環境中會有很大差異,有些應用在代碼中使用了常量保存配置,這就與12-Factor所要求的代碼的代碼和配置嚴格分離大相徑庭。配置文件在各部署間存在大幅差異,代碼卻完全一致。判斷一個應用是否正確地將配置排除在代碼之外,一個簡單的方法是看該應用的基準代碼是否可以立刻開源,而不用擔心會暴露任何敏感的信息。
一個解決方法是使用配置文件,但不把它們納入版本控制系統,就像 Rails 的 config/database.yml 。這相對于在代碼中使用常量已經是長足進步,但仍然有缺點:總是會不小心將配置文件簽入了代碼庫;配置文件也可能會分散在不同的目錄,并有著不同的格式,這讓找出一個地方來統一管理所有配置變的不太現實。更糟的是,這些格式通常是語言或框架特定的。
12-Factor推薦將應用的配置存儲于 環境變量 中( env vars, env )。環境變量可以非常方便地在不同的部署間做修改,卻不動一行代碼;與配置文件不同,不小心把它們簽入代碼庫的概率微乎其微;與一些傳統的解決配置問題的機制(比如 Java 的屬性配置文件)相比,環境變量與語言和系統無關。
目前特別流行的docker是可以很容易地滿足這個需求
12-Factor推薦將應用的配置存儲于 環境變量 中,保證配置排除在代碼之外,有如下好處:
- 環境變量是一種清楚、容易理解和標準化的配置方法
- 環境變量可以非常方便地在不同的部署間做修改,卻不動一行代碼
- 與配置文件不同,不小心把它們簽入代碼庫的概率微乎其微
- 與一些傳統的解決配置問題的機制(比如 Java 的屬性配置文件)相比,環境變量與語言和系統無關
- 存儲在環境變量中的另一個好處是,方便和Docker配合使用
配置管理的另一個方面是分組。有時應用會將配置按照特定部署進行分組(或叫做“環境”),隨著項目的不斷深入,開發人員可能還會添加他們自己的環境,這將導致各種配置組合的激增,從而給管理部署增加了很多不確定因素。
2-Factor 應用中,環境變量的粒度要足夠小,且相對獨立。它們永遠也不會組合成一個所謂的“環境”,而是獨立存在于每個部署之中。當應用程序不斷擴展,需要更多種類的部署時,這種配置管理方式能夠做到平滑過渡。
3.4 后端服務
后端服務是指程序運行所需要的通過網絡調用的各種服務,如數據庫(MySQL,CouchDB),消息/隊列系統(RabbitMQ,Beanstalkd),SMTP 郵件發送服務(Postfix),以及緩存系統(Memcached)。
類似數據庫的后端服務,通常由部署應用程序的系統管理員一起管理。除了本地服務之外,應用程序有可能使用了第三方發布和管理的服務。12-Factor 應用不會區別對待本地或第三方服務。 對應用程序而言,兩種都是附加資源,通過一個 url 或是其他存儲在 配置 中的服務定位/服務證書來獲取數據。
每個不同的后端服務是一份資源 。例如,一個 MySQL 數據庫是一個資源,兩個 MySQL 數據庫(用來數據分區)就被當作是 2 個不同的資源。12-Factor 應用將這些數據庫都視作附加資源,這些資源和它們附屬的部署保持松耦合。例如,如果應用的數據庫服務由于硬件問題出現異常,管理員可以從最近的備份中恢復一個數據庫,卸載當前的數據庫,然后加載新的數據庫,整個過程僅需修改配置中的資源地址,都不需要修改代碼即可完成。
3.5 構建,發布,運行
基準代碼 轉化為一份部署(非開發環境)需要以下三個階段:
- 構建階段 是指將代碼倉庫轉化為可執行包的過程。構建時會使用指定版本的代碼,獲取和打包 依賴項,編譯成二進制文件和資源文件。
- 發布階段 會將構建的結果和當前部署所需配置相結合,并能夠立刻在運行環境中投入使用。
- 運行階段 (或者說“運行時”)是指針對選定的發布版本,在執行環境中啟動一系列應用程序進程。
12-factor 應用嚴格區分構建,發布,運行這三個步驟。直接修改處于運行狀態的代碼是非常不可取的做法,因為這些修改很難再同步回構建步驟。部署工具通常都提供了發布管理工具,最引人注目的功能是退回至較舊的發布版本。并且,每一個發布版本必須對應一個唯一的發布 ID,發布的版本就像一本只能追加的賬本,一旦發布就不可修改,任何的變動都應該產生一個新的發布版本。
新的代碼在部署之前,需要開發人員觸發構建操作。但是,運行階段不一定需要人為觸發,而是可以自動進行。如服務器重啟,或是進程管理器重啟了一個崩潰的進程。因此,運行階段應該保持盡可能少的模塊,這樣假設半夜發生系統故障而開發人員又捉襟見肘也不會引起太大問題。構建階段是可以相對復雜一些的,因為錯誤信息能夠立刻展示在開發人員面前,從而得到妥善處理。
3.6 進程
運行環境中,應用程序通常是以一個或多個進程運行的。12-Factor 應用的進程必須無狀態且無共享。任何需要持久化的數據都要存儲在后端服務內,比如數據庫。
內存區域或磁盤空間可以作為進程在做某種事務型操作時的緩存,例如下載一個很大的文件,對其操作并將結果寫入數據庫的過程。12-Factor應用根本不用考慮這些緩存的內容是不是可以保留給之后的請求來使用,這是因為應用啟動了多種類型的進程,將來的請求多半會由其他進程來服務。即使在只有一個進程的情形下,先前保存的數據(內存或文件系統中)也會因為重啟(如代碼部署、配置更改、或運行環境將進程調度至另一個物理區域執行)而丟失。
一些互聯網系統依賴于粘性 session(sticky session), 這是指將用戶 session 中的數據緩存至某進程的內存中,并將同一用戶的后續請求路由到同一個進程。粘性 session 是 12-Factor 極力反對的,因為我們不應該在進程內存中存放數據,而且也無法確保客戶端下次的請求也會被路由到該進程中去。Session 中的數據應該保存在諸如 Memcached 或 Redis 這樣的帶有過期時間的緩存中。
3.7 端口綁定
互聯網應用有時會運行于服務器的容器之中。12-Factor 應用完全自我加載而不依賴于任何網絡服務器就可以創建一個面向網絡的服務。互聯網應用通過端口綁定來提供服務,并監聽發送至該端口的請求。
端口綁定這種方式也意味著一個應用可以成為另外一個應用的后端服務,調用方將服務方提供的相應 URL 當作資源存入配置以備將來調用。
原則總結:
- 應用內嵌 HTTP 庫,如 Tomcat、Jetty等,例如 Spring Boot 的應用
- 直接綁定端口對外提供服務,不依賴外部服務容器運行,例如 Dubbo
- 通過在環境變量中聲明,服務可以作為其他服務的依賴
需要注意的是,如果在一個宿主機中部署多個應用實例,就不能將一個宿主機端口映射到多個容器端口(端口沖突),解決方法是在這之上加一個負載均衡,負載宿主機的不同端口服務所對應的不同容器
3.8 并發
任何計算機程序,一旦啟動,就會生成一個或多個進程。在Java開發中,在Java程序啟動之初 JVM 就提供了一個超級進程儲備了大量的系統資源(CPU 和內存),并通過多線程實現內部的并發管理。
在 12-factor 應用中,進程是一等公民。12-Factor 應用的進程主要借鑒于 unix 守護進程模型。開發人員可以運用這個模型去設計應用架構,將不同的工作分配給不同的進程類型。
舉例來說,HTTP 請求可以交給 web 進程來處理,而常駐的后臺工作則交由 worker 進程負責,定時任務交由 clock 來處理,這樣擴展每一類的進程就非常方便,如下圖所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-6asQqlnE-1664186699924)(/.attachments/12-Factor_并發模型-8f89d4af-8796-48c8-8502-2f24a54f36d5.jpg)]
上述進程模型會在系統急需擴展時大放異彩。 12-Factor 應用的進程所具備的無共享,水平分區的特性 意味著添加并發會變得簡單而穩妥。這些進程的類型以及每個類型中進程的數量就被稱作進程構成 。
總結:
- 進程內仍然可以進行多線程或時間模型
- 因為服務都是無狀態的,所以橫向擴展很容易,依賴底層平臺就能實現,不需要技術難度高的多線程編碼
- 進程永遠都不應該讓自己變成守護進程
3.9 易處理
12-Factor 應用的進程是易處理(disposable)的,意思是說它們可以瞬間開啟或停止。這有利于快速、彈性的伸縮應用,迅速部署變化的代碼或配置,穩健的部署應用。
進程應當追求最小啟動時間 。理想狀態下,進程從敲下命令到真正啟動并等待請求的時間應該只需很短的時間。更少的啟動時間提供了更敏捷的發布以及擴展過程,此外還增加了健壯性,因為進程管理器可以在授權情形下容易的將進程搬到新的物理機器上。
進程一旦接收到終止信號(SIGTERM) 就會優雅的終止。就網絡進程而言,優雅終止是指停止監聽服務的端口,即拒絕所有新的請求,并繼續執行當前已接收的請求,然后退出。此類型的進程所隱含的要求是HTTP請求大多都很短(不會超過幾秒鐘),而在長時間輪詢中,客戶端在丟失連接后應該馬上嘗試重連。
進程還應當在面對突然死亡時保持健壯,例如底層硬件故障。雖然這種情況比起優雅終止來說少之又少,但終究有可能發生。一種推薦的方式是使用一個健壯的后端隊列,例如 Beanstalkd ,它可以在客戶端斷開或超時后自動退回任務。無論如何,12-Factor 應用都應該可以設計能夠應對意外的、不優雅的終結。Crash-only design 將這種概念轉化為合乎邏輯的理論。
3.10 開發環境與線上環境等價
從以往經驗來看,開發環境(即開發人員的本地 部署)和線上環境(外部用戶訪問的真實部署)之間存在著很多差異。這些差異表現在以下三個方面:
- 時間差異: 開發人員正在編寫的代碼可能需要幾天,幾周,甚至幾個月才會上線。
- 人員差異: 開發人員編寫代碼,運維人員部署代碼。
- 工具差異: 開發人員或許使用 Nginx,SQLite,OS X,而線上環境使用 Apache,MySQL 以及 Linux。
12-Factor 應用想要做到持續部署就必須縮小本地與線上差異。
后端服務是保持開發與線上等價的重要部分,例如數據庫,隊列系統,以及緩存。許多語言都提供了簡化獲取后端服務的類庫,例如不同類型服務的適配器。開發人員有時會覺得在本地環境中使用輕量的后端服務具有很強的吸引力,而那些更重量級的健壯的后端服務應該使用在生產環境。
12-Factor 應用的開發人員應該反對在不同環境間使用不同的后端服務 ,即使適配器已經可以幾乎消除使用上的差異。這是因為,不同的后端服務意味著會突然出現不兼容,從而導致測試、預發布都正常的代碼在線上出現問題。這些錯誤會給持續部署帶來阻力。從應用程序的生命周期來看,消除這種阻力需要花費很大的代價。
不同后端服務的適配器仍然是有用的,因為它們可以使移植后端服務變得簡單。但應用的所有部署,這其中包括開發、預發布以及線上環境,都應該使用同一個后端服務的相同版本。
3.11 日志
日志使得應用程序運行的動作變得透明。在基于服務器的環境中,日志通常被寫在硬盤的一個文件里,但這只是一種輸出格式。日志應該是事件流的匯總,將所有運行中進程和后端服務的輸出流按照時間順序收集起來。盡管在回溯問題時可能需要看很多行,日志最原始的格式確實是一個事件一行。日志沒有確定開始和結束,但隨著應用在運行會持續的增加。
12-factor 應用本身從不考慮存儲自己的輸出流。不應該試圖去寫或者管理日志文件。相反,每一個運行的進程都會直接的標準輸出(stdout)事件流。開發環境中,開發人員可以通過這些數據流,實時在終端看到應用的活動。
在預發布或線上部署中,每個進程的輸出流由運行環境截獲,并將其他輸出流整理在一起,然后一并發送給一個或多個最終的處理程序,用于查看或是長期存檔。這些存檔路徑對于應用來說不可見也不可配置,而是完全交給程序的運行環境管理。這些事件流可以輸出至文件,或者在終端實時觀察。最重要的,輸出流可以發送到 Splunk 這樣的日志索引及分析系統,或 Hadoop/Hive 這樣的通用數據存儲系統。這些系統為查看應用的歷史活動提供了強大而靈活的功能,包括:
- 找出過去一段時間特殊的事件。
- 圖形化一個大規模的趨勢,比如每分鐘的請求量。
- 根據用戶定義的條件實時觸發警報,比如每分鐘的報錯超過某個警戒線。
3.12 管理進程
進程構成(process formation)是指用來處理應用的常規業務(比如處理 web 請求)的一組進程。與此不同,開發人員經常希望執行一些管理或維護應用的一次性任務,例如:
- 運行數據移植
- 運行一個控制臺(也被稱為 REPL shell),來執行一些代碼或是針對線上數據庫做一些檢查。大多數語言都通過解釋器提供了一個 REPL 工具,或是其他命令
- 運行一些提交到代碼倉庫的一次性腳本。
一次性管理進程應該和正常的 常駐進程 使用同樣的環境。這些管理進程和任何其他的進程一樣使用相同的 代碼 和 配置 ,基于某個 發布版本 運行。后臺管理代碼應該隨其他應用程序代碼一起發布,從而避免同步問題。
所有進程類型應該使用同樣的依賴隔離 技術。12-factor 尤其青睞那些提供了 REPL shell 的語言,因為那會讓運行一次性腳本變得簡單。在本地部署中,開發人員直接在命令行使用 shell 命令調用一次性管理進程。在線上部署中,開發人員依舊可以使用ssh或是運行環境提供的其他機制來運行這樣的進程。
4、Sample
以之前的項目為例:
- 基準代碼: 項目采用 Git 進行代碼管理,采用 Feature Branch 的分支管理策略。 dev 分支作為開發分支,每次的開發任務都從dev 分支切出任務分支進行開發,開發完成后,將任務分支代碼再合并到dev 分支中。
- 依賴: 采用了 Maven 管理我們項目中用到的所有依賴,沒有其他私有依賴。只需要根據分支拉取對應的代碼,導入到開發工具中,就會自動下載項目用到的所有依賴,啟動我們的服務。
- 配置: 項目采用配置文件的方式管理項目中用到的一些公共配置信息。單獨創建了一個的 config_file 的工程項目,專門存儲項目中用到的配置文件信息,當新增或修改時,直接修改對應的配置信息即可,不需要改動代碼。使用Sring Cloud Config 構建運行。
- 后端服務: 項目中用到的數據庫,MQ,緩存等信息都是一種附加的資源,他們與我們的開發的項目之間應該是個松耦合的關系。例如當我們需要更換MySQL的數據庫時,我們可以直接通過更改配置文件中對應的 MySQL 服務的配置信息來更換我們項目中連接使用的數據庫。
- 構建,發布,運行: 當開發完畢后,會在 Jenkins 上根據自己的開發分支進行打包成docker鏡像,然后將服務發布到測試環境中,經過測試沒有問題后,我們會將我們分支上的代碼統一合并到dev分支中,然后 Jenkins 重新使用dev分支進行打包,將服務發布到生產環境中。如果有新的需求任務或者bug的出現,我們會創建另外的任務,然后根據任務進行開發,測試,部署等一系列操作。我們不能直接修改已經發布的服務信息,可以繼續追加一個版本,將問題修改后重新測試發布即可。
- 進程: 每一個發布的服務都是一個進程
- 端口綁定: 每一個服務在環境中都對一個一個唯一的端口號,請求到服務器上的請求數據,會根據特定的解析方式分發到不同的服務中去。服務通過 k8s 職工的service 配置服務的端口信息,最后通過 Ingress 將端口映射出來供前端請求調用。
- 并發: 一個環境可以有多個服務進行部署,通常我們的一個請求可能會涉及到多個服務之間的調用。或者當我們需要一個新的服務時,只需要端口號等特殊信息不同,即可再次進行部署,橫向擴展十分方便。
- 易處理: 項目部署采用 docker 鏡像部署的方式發布運行,開發完成后,先通過 Jenkins 將服務打包成可執行的鏡像,在對應的服務中,選擇將發布的鏡像版本即可。這種發布服務的當時,可以快速的部署我們的項目,實現易處理的特點。
- 開發環境與線上環境等價: 開發過程中,我們有專門的開發環境供我們開發使用,通過DockFile 配置環境信息,當開發完成后,將對應的服務發布到測試環境中,供測試人員進行測試,驗證。所有功能都測試通過后,就可以將測試環境中運行的相同版本的服務在生產環境中發布運行。首先,保證了測試環境與生產環境要相同,當在測試環境中測試無誤后,就可以向生產環境發布了。
- 日志: 每一個發布的服務都會有自己的日志記錄,當我們的程序發生錯誤或者服務宕機的時候,我們可以通過 Ranach 平臺查看日志文件來快速定位發生的問題。 同時,項目中,我們采用了 elasticsearch + Kanba 的方式集成了日志系統,可以直接通過網頁來查看我們需要的日志信息。
- 管理進程: 當需要手動進行一些操作的時候,通過 k8s 直接進入相關的 pod 中,直接通過命令進行操作。
總結
以上是生活随笔為你收集整理的SaaS应用12原则的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于freeswitch1.6的IVR智
- 下一篇: spring注解开发配置spring父子