1.5 十二要素應用
十二要素應用程序方法是Heroku的開發者起草的。十二要素中提到的特征并不特定于云提供者、平臺或語言。這些要素代表了針對云環境中蓬勃發展的可移植彈性應用程序(特別是“軟件即服務”應用程序)的一組準則或最佳實踐。下面列出了這十二要素。
(1)一份基準代碼(Codebase),多份部署(Deploy)
企業一般會采用代碼版本控制系統來跟蹤管理所有修訂版本的代碼庫,這樣就只需要一份代碼,卻可以同時存在多份部署,如圖1-6所示。每份部署相當于運行了一個應用的實例。例如作者所在學校的Portal應用,學校有一個生產環境的部署,也有一個用于測試的預發布環境部署。學校將同一套代碼先在預發布服務器上部署,進行初步評估,然后再發布到正式生產服務器上。

圖1-6 一份基準代碼多份部署
(2)顯式聲明依賴關系(Dependency)
應用程序不會隱式依賴系統級類庫。它一定通過依賴清單確切地聲明所有依賴項。此外,應用程序在運行過程中通過依賴隔離工具來確保程序不會調用系統中存在但清單中未聲明的依賴項。這一做法在生產和開發環境中都是統一的。例如Node.js應用程序通過package.json來聲明依賴項,Maven項目使用pom.xml來聲明依賴項。遵循該要素的應用程序也不會隱式依賴系統級工具,例如ImageMagick和curl等工具,即使這些工具在Linux不同版本中都存在,但無法保證所有未來的系統都部署了該工具,例如Docker中的Linux就不一定存在這兩個工具。如果要使用這些工具,則必須包括在應用程序中。
例如Drupal中的命令工具drush就包含在bin目錄下。
(3)在環境中存儲配置
十二要素應用推薦將應用的配置存儲于環境變量中,如圖1-7所示。這允許應用程序非常方便地在不同的部署間修改,而不需要改動一行代碼。例如wget用的環境變量HTTP_PROXY。還有一種做法是使用配置文件但把該配置文件從版本控制中排除。例如Drupal站點的settings.php文件,里面含有數據庫連接信息,為了保證安全,必須將該文件排除在版本控制外。但是有時仍然難免將該文件加入版本控制,從而造成了數據庫連接信息泄露。

圖1-7 在環境中存儲配置
在Kubernetes中,系統會自動將服務根據名稱創建多個不同的細粒度環境變量。
(4)把后端服務(backing services)當作附加資源
后端服務是指程序運行所需要的通過網絡調用的各種服務,如數據庫(MySQL、CouchDB),消息/隊列系統(RabbitMQ、Beanstalkd),以及緩存系統(Memcached)。符合規則的應用程序應該可以在不進行任何代碼改動的情況下,將本地數據庫切換至異地或者云上的數據庫服務。
(5)嚴格分離構建、發布和運行
基準代碼通過構建、發布和運行三個階段轉化成一份部署。構建階段是指將代碼進行編譯、打包等操作,生成可執行文件。而發布則是將構建的結果和相關配置發布到運行環境中投入使用。運行階段則是在執行環境中啟動一系列應用程序。
例如Node.js應用程序,其構建步驟較為簡單,只需要復制相關文件即可。而發布到運行環境時,則通過npm install安裝相關依賴項;在運行階段可以通過Node.js進程管理工具pm2安裝或者重啟服務。
(6)以一個或多個無狀態進程運行應用
符合十二要素的應用程序的進程必須是無狀態且無共享的。任何需要持久化的數據都需要存儲在后端服務內。例如Apereo CAS,所有認證的Ticket均保存在后端數據庫中,如memcached集群。而需要處理的是session狀態,這個也是需要通過后端memcached或者redis進行統一存儲,或者通過前端負載均衡粘性路由到同一個應用進程中。
(7)通過端口綁定(Port binding)來提供服務
符合十二要素的應用程序可以自我加載而不依賴于任何網絡服務器。例如Java代碼可以直接使用JVM的Jetty,而不依賴于Tomcat。這一點就像Node.js不需要Apache一樣。還是以Apereo CAS為例,最新版本的CAS可以自我加載運行,而不依賴Tomcat。如果查看常用軟件的Dockerfile,會發現這些Docker的執行命令不再是以后端程序的方式運行,而是直接以前端運行。例如PHP的Docker鏡像,命令為apache2-foreground。

(8)通過進程模型進行擴展
在十二要素應用中的進程主要借鑒了UNIX守護進程模型,不同的工作分配給不同類型的進程處理。尤其是無共享、水平分區的特性讓并發處理更加簡單。十二要素應用的進程不需要守護進程,也不需要寫入PID文件,而是借助操作系統的進程管理器(如systemd)進行輸出流控制、進程崩潰響應,以及進程的重啟和關閉的請求。
(9)快速啟動和優雅終止可最大化健壯性
十二要素應用的進程是可分解的(disposable),它可以瞬間開啟或者停止。這有利于快速、彈性伸縮應用,迅速部署變化的代碼或配置。進程應當追求最短的啟動時間,一旦接收到終止信號則會優雅地終止。進程還應當合理處理意外終止,例如可以在客戶端斷開或者超時連接后自動退回任務。
(10)盡可能地保持開發、預發布和線上環境相同
開發人員可能使用Macintosh開發,也可能使用Windows開發,這就造成各種環境的不一致,尤其是開發環境和生產環境。即便同樣的操作系統,也有可能隨著時間變化、工具差異、人員差異等出現不一致。十二要素應用要求必須縮小本地和生產環境的差異,企業可以使用Docker來進行環境的統一,無論是開發環境還是生產環境,都使用Docker來進行測試和正式運行。
十二要素應用要求不同環境下的后端服務也要一致,例如開發環境使用MariaDB,則生產環境亦應使用MariaDB。
(11)把日志當作事件流
日志使得應用程序運行的動作變得透明。在基于服務器的環境中,日志通常被寫在硬盤的一個文件里,但這只是一種輸出格式。在十二要素應用中則不應該考慮存儲到自己的輸出流,不應該試圖去寫或者管理日志文件。相反,每一個運行的進程都會直接將日志存儲到標準輸出(stdout)事件流。開發環境中,開發人員可以通過這些數據流,實時在終端看到應用的活動。
這點在Docker環境下尤其如此。每個Docker中的應用不應該自己進行日志的管理,而應該直接提交給標準輸出和標準錯誤事件流。下面是PHP鏡像的Dockerfile代碼:

這段代碼改造了Apache2的日志,默認情況下,Apache2將訪問日志寫入access.log文件,錯誤日志寫入error.log,而通過ln軟鏈接命令,實現了將這些日志流直接重定向為標準事件流。這樣,這些應用的日志流將被Docker或者Kubernetes捕獲。
(12)后臺管理任務當作一次性進程運行
進程構成(process formation)是指用來處理應用的常規業務(如處理Web請求)的一組進程。與此不同,開發人員經常希望執行一些管理或維護應用的一次性任務,例如開源軟件OwnCloud。升級OwnCloud可以在Web界面下操作,但更建議通過occ upgrade命令進行升級。

在這個命令中,使用了Apache2進程的用戶www-data,保證了命令行和Web方式的環境一致。這也是十二要素的要求:一次性管理進程應該和正常的常駐進程使用同樣的環境。這些管理進程和任何其他進程一樣使用相同的代碼和配置,基于某個發布版本運行。后臺管理代碼應該隨其他應用程序代碼一起發布,從而避免同步問題。
要實現高質量的微服務環境,可以不用嚴格遵循這些要素。但是,通過牢記這些要素,用戶可以在持續交付環境中構建和維護可移植應用程序或服務。這是非常重要的,讀者一定要充分理解這十二要素。在本書后續的描述和案例中,讀者可以看到十二要素的實戰。