# The Twelve-Factor App [The Twelve-Factor App](https://12factor.net/) 文章主要是探討 SaaS(Software as a Service) 開發的方法論,主要作者為 **Heroku** cofounder 及 CTO 的 **Adam Wiggins**,,曾參與過上百個專案開發與部署,並透過 Heroku 平台見證了數十萬應用程式的開發、營運、擴展。 適合所有 Service 開發者及維運人員閱讀。 :::info 下面就針對筆者的閱讀提供簡易的整理,方便大家理解。 ::: ## 前言 作者提出的方法論 1. Use declarative formats for setup automation, to minimize time and cost for new developers joining the project; 2. Have a clean contract with the underlying operating system, offering maximum portability between execution environments; 3. Are suitable for deployment on modern cloud platforms, obviating the need for servers and systems administration; 4. Minimize divergence between development and production, enabling continuous deployment for maximum agility; 5. And can scale up without significant changes to tooling, architecture, or development practices. :::info 作者提到以上方法論沒有限定的程式語言與後端開發服務組合 ::: 這邊引用 <https://dzone.com> 整理的圖,整理的很不錯,可以先對整個方法論有個概覽 ![cheetsheet](https://i.imgur.com/5qI8WSD.png) ## I. Codebase ### One codebase tracked in revision control, many deploys ![codebase](https://i.imgur.com/V37xUBg.png) :::info - 使用版本控制工具對 Source Code 進行控管,可以使用 Git, Mercurial 或是 Sbuversion - 一份 codebase 就是指一個 code repo - 每個 App 都應該只對應一個 codebase - 如果一個程式有多個 codebase,那他就不是一個 App,他應該是一個分散式系統,系統中每個部件即為一個 App,而每個部件都可以各自遵守 **twelve-factor** ::: :::danger - 多個 Apps 共享同一份 code 違反了 twelve-facor,解法為將共同的程式碼部分抽出透過 [Dependency Manager](#II-Dependencies) 作為 Library 引入 ::: ## II. Dependencies ### Explicitly declare and isolate dependencies 目前大多數的程式語言都有提供套件(package)管理工具,例如 python 使用 pip、golang 使用 go mod。 而 package 又可分為兩種 scope,**system scope**(site package) 及 **local scope** (vendoring or bundling) :::info - 使用相依套件管理工具管理你的專案套件 - 套件管理工具最好可以保證相依套件都是使用 local scope,避免隱式使用 system scope package,例如 python 使用 virtualenv 做隔離 - ::: :::danger - 千萬不要讓你的 App 依賴 system scope 的套件,例如 curl 指令,雖然他存在於大數的系統中,但無法被保證,你應該想辦法將它包進 local package,例如 python 可以使用 requests package 替代。 ::: ## III. Config ### Store config in the environment 部署 App 時,我們常常透過 config 去控制 app 運作行為,例如 - 開發時指定為 dev 模式,輸出更多 debug log 方便開發與除錯。*staging production 亦然* - 相依服務的憑證,如 Amazon S3、database - 部署的實例名稱,例如 k8s 指定 nodename :::info - 將 config 獨立於 codebase,每次部署時 config 可能都有很大的變化,但 codebase 並不會 - 想知道 config 有沒有正確地從 codebase 獨立出來的標準是,該 codebase 是否可以隨時 open source,而不會洩漏任何憑證或機敏資訊 - 建議使用環境變數而不是 config file 管理,config file 可以有很多種格式,不同程式語言之間的慣例又不同,使用起來相對麻煩,也容易不小心被 add 到 repo 裡,相反環境變數有著良好的規範,針對不同程式語言以及作業系統都有著相同的格式 ::: :::danger - 不要將 config 寫死在 code 裡 ::: ## IV. Backing services ### Treat backing services as attached resources ![backing services](https://i.imgur.com/xPf8vIr.png) :::info - App 不應該區分本地或第三方服務,對 App 而言都是透過 URL 或其他憑證存取 - App 應該可以切換本地 database 到第三方 database (如 Amazon RDS),只需要更改 config,而不需要更改 code - 每個連結的服務都是一份資源(resource),這些資源跟其連結的 App 保持松耦合關係 - 資源可以隨意被附加或分離,例如 database 出現異常,管理員可以從先前的備份啟動一個新的 database 並連結上 App,異常 database 則先斷開連結 ::: ## V. Build, release, run ### Strictly separate build and run stages ![Build release run](https://i.imgur.com/sg8W1My.png) 要將 codebase 轉換為可部署資源需要經過以下三個步驟 1. Build stage: 將 codebase 轉換為可執行程式,包含相依套件下載、連結、編譯等等,最後產出二進制檔或是資源或是其他資源 2. Release stage: 將 Build stage 產出的資源,與此次部署相對應的 config 打包起來,產出可以立即在部署環境上執行的包 3. Run stage: 又稱為 Runtime,意指將 release 包運作在部署環境上 :::info - 應該嚴格的拆分 build, release 跟 run stage - 每份 Release 都應該有一個唯一的 ID,例如 timestamp 或是 git hash - 要更動 Release,應該要建立一份新的 Release 版本,任何對現有 Release 的修改都是不應該的 ::: :::danger - 不要在 runtime 階段修改程式碼,你應該修改 codebase 再出一個版本 ::: ## VI. Processes ### Execute the app as one or more stateless processes :::info - Processes 最好是**無狀態**且**資源獨立** - 任何需要保存的資料必須被保存到一個有狀態的後端服務,通常是 database ::: :::danger - web system 不應該依賴 **sticky sessions**,是指把 user session data 保存在 process 的記憶體中並希望未來的請求也會被送到同一個 process 處理,相反你應該保存在帶有過期機制的緩存中(Memcached or Redis) ::: ## VII. Port binding ### Export services via port binding :::info - App 應該可以自我監聽端口以提供服務,如 <http://localhost:8080> - 並不僅限於 HTTP,其他的還有 grpc 或是 redis 的 Redis serialization protocol (RESP) - Port binding 方法代表你的 App 可以容易的成為其他 App 的 backing service,你只需要在你的 config 裡加入 backing service 的連結資訊即可 ::: :::danger - 如[Dependency Manager](#II-Dependencies)提到,盡量不要使用 system scope 的 webserver 應用來提供服務,你應該要將 webserver library 加入到 local scope 依賴來使用(如 python 的 Tornado) ::: ## VIII. Concurrency ### Scale out via the process model ![concurrency](https://i.imgur.com/lXDsJPi.png) :::info - Process are a first class citizen - 使用 [Unix process model](https://adam.herokuapp.com/past/2011/5/9/applying_the_unix_process_model_to_web_apps/) 將不同類型的工作分配給對應的 Process Type 處理,如 HTTP 請求交給 web process、需要長時間運行的後台任務交由 worker process 處理 - 對 share-nothing、stateless 的 process 增加 concurrency 會非常的容易且可靠 ::: :::danger - App process 不應該 daemonize,應該交由其他如系統 process manager(systemd),或是雲平台如 kubernetes ::: ## IX. Disposability ### Maximize robustness with fast startup and graceful shutdown :::info - Process 應盡量縮減啟動時間,這將會增加 scaling 的彈性及速度,理想上是幾秒鐘 - 當 Process 接收到 SIGTERM signal 時,應該進行 gracefully shutdown - Process 應該足夠健全以面對 crash 的發生 ::: ## X. Dev/prod parity ### Keep development, staging, and production as similar as possible 從過往經驗來看,開發跟正式環境總是會有很大的差距,比如說開發人員會在開發環境做一些 live patch 或是一些暫時性的操作,這些差異主要分為三個區塊 1. 時間差距:開發者通常會寫 幾天、幾星期、甚至幾個月的程式碼,才將程式交付到正式環境 2. 個人差距:開發者寫程式碼,維運人員部署 3. 工具差距:開發者可能會使用像是 Nginx、SQLite,或是不同的作業系統如 OSX,但是部署的時候卻是使用別的工具,如 Apache、MySQL、Linux :::info - 建立 CD 流程,減少開發和正式環境的差距 - 減少時間差距:開發人員每幾個小時或是幾分鐘就部署一次程式碼 - 減少個人差距:開發人員密切參與部署工作,並觀察程式在正式環境的行為 - 減少工具差異:盡可能保持開發及正式環境的一致性 | | 傳統應用 | 12Factor應用 | | ---------- | ------- | ----------- | | 每次部署間隔 | 幾星期 | 幾小時 | | 開發及部署人員| 不同人 | 同一人 | | 開發及正式環境| 差異化 | 盡可能一致 | ::: :::danger - 不建議在開發和正式環境使用不同的後端服務,如開發使用 SQLite 正式環境使用 MySQL,雖然現在大多程式語言的 Library 做了良好的抽象,讓你可以輕易地切換後端服務,但實務上還是會有微小的相容性問題 ::: ## XI. Logs ### Treat logs as event streams :::info - 將事件流直接輸出到 stdout 即可 - 讓執行環境負責將每個 process 的 log 彙整起來並導向最終的位置,這樣方便長期保存以及後續觀看。 - log 導向可以使用 Logplex 和 Fluentd - 搜集好的 log 可以使用 Splunk 或 Hadoop/Hive 之類的工具來做分析 ::: :::danger - 不要讓 App 自己處理事件流的導向及存儲 ::: ## XII. Admin processes ### Run admin/management tasks as one-off processes :::info - 開發人員常常使用一次性指令或腳本來管理或維護 App - 這可能包含:Database Migration、REPL shell 或其他維護腳本 - 一次性管理程式應該和你的 App 使用相同的環境 :::