做者簡介:黃慶兵,網易蜂巢首席技術佈道師,浙大碩士畢業,從事雲計算、Docker、Go等相關開發及技術佈道工做;喜歡開源,樂於分享,勤於佈道,折騰過開源小工具,製做過Docker課程,分享過 Gopher Meetup。歡迎一塊兒來探討技術!我的主頁:http://bingohuang.com/css
如下爲正文:html
在雲的時代,應用會更多的遷移到雲端,基於雲的架構設計和開發模式須要一套全新的理念去承載,因而云原生思想應運而生,而針對雲原生應用開發的最佳實踐原則,12-Factor脫穎而出,同時也帶來了新的解讀。本文將介紹在Cloud Native時代下,結合Docker等技術,如何一一實踐12-Factor原則。node
雲原生(Cloud Native)是由 Pivotal 的Matt Stine在2013年提出的一個概念,是他多年的架構和諮詢總結出來的一個思想的集合。python
那怎麼去理解雲原生應用?我以爲能夠從三個角度來講明,這和雲計算平臺的三個層次不謀而合,以下圖:git
][2]github
IaaS看作基礎 雲 設施,用來提供各類基礎資源(Infrastructure)web
PaaS做爲開發 平臺,用來提供各類平臺服務(Platform)mongodb
SaaS交付 應用 或 服務,直面用戶,提供應用價值(Application)docker
雲原生應用,正好契合了雲、平臺和服務,一層層構建,因此我一般就把它理解爲面向雲(平臺)來設計咱們的應用。網易三拾衆籌的架構師陳曉輝,還爲它起了一個小清新的名字——向雲而生,我以爲很是貼切,再通俗一點講,也能夠叫作雲平臺應用。shell
12-Factor,是由Heroku創始人Adam Wiggins首次提出並開源,並由衆多經驗豐富的開發者共同完善,這綜合了他們關於SaaS應用幾乎全部的經驗和智慧,是開發此類應用的理想實踐標準。
12-Factor 全稱叫 The Twelve-Factor App,它定義了一個優雅的互聯網應用在設計過程當中,須要遵循的一些基本原則,和 Cloud-Native 有殊途同歸之處。其中文翻譯很多,我以爲「十二要素」或「十二原則」比較貼切,
那具體有哪十二原則了,見下圖:
在接下來的 應用和實踐 當中,咱們會一一實踐每條原則。
注:雖然 12-Factor 的原文書籍都是發佈在其官網上,但由於網絡問題和格式問題,不是很方便閱讀,我將其轉化了爲 GitBook 格式,並架設在網易蜂巢平臺上,同時開源在 GitHub 上,方便你們閱讀和下載:
在線閱讀地址:http://12.bingohuang.com/zh_c...
GitHub 開源地址:https://github.com/bingohuang...
pdf/epub下載地址:https://github.com/bingohuang...
GitBook 地址:https://www.gitbook.com/book/...
應用與實踐
既然12-factor做爲SaaS開發的最佳實踐原則,固然脫離不了實踐,接下來咱們就來設計一款雲原生應用,並依照12-factor,一步步驗證和升級咱們的應用。從中,咱們將講解每一個Factor的要點,以及如何在咱們的應用中實踐Factor。
這是一個面向雲平臺設計的簡單Web應用,它將暴露一個HTTP REST風格的接口,能夠實現對 user 的增刪改查功能,將用到如下技術棧:
下載安裝 Node.js (包含 npm):https://nodejs.org/zh-cn/down...
注:Node 版本只要 v4.4 以上就夠用,當前最新的穩定版是v6.9.5, 我本地的版本是v5.12.0
安裝 Sails 框架最新版:npm install sails -g
安裝 Docker:https://docs.docker.com/engin...
$ sails new 12factor-app
info: Created a new Sails app 12factor-app
!
$ cd 12factor-app
$ sails generate api user
info: Created a new api!
$ npm start
僅需4條命令就搞定了應用的框架代碼,並自動生成了基於user的CRUD接口,咱們已經將應用啓動起來,能夠經過如下方式本地調試接口:
控制檯輸出正常,在瀏覽器中訪問下面連接,便可看到Sails應用的首頁
接着,就能夠經過本地curl命令或者http工具來作接口調試,這裏以常規的增刪改查爲例:
1.增長一個新用戶
$ curl -XPOST http://localhost:1337/user?na...
{
"name": "bingo",
"createdAt": "2017-02-13T06:13:53.791Z",
"updatedAt": "2017-02-13T06:13:53.791Z",
"id": 58a41d952f53291200b9e065
}
2.獲取用戶列表
$ curl http://localhost:1337/user
[
{
"name": "bingo", "createdAt": "2017-02-13T06:13:53.791Z", "updatedAt": "2017-02-13T06:13:53.791Z", "id": 58a41d952f53291200b9e065
}
]
3.修改一個用戶
curl -XPUT http://localhost:1337/user/58...
{
"name": "bingohuang",
"createdAt": "2017-02-13T06:13:53.791Z",
"updatedAt": "2017-02-13T06:14:13.460Z",
"id": 58a41d952f53291200b9e065
}
4.刪除一個用戶
curl -XDELETE http://localhost:1337/user/58...
我已經將該應用部署到了網易蜂巢在線平臺,若是您對這個應用感興趣,直接將localhost替換爲59.111.110.95,同樣能夠體驗CRUD操做,以下所示,只要把yourname換成您的名字便可(建議在PC端操做):
註冊你本身
curl -XPOST http://59.111.110.95:1337/use...
查看全部註冊過的用戶
curl http://59.111.110.95:1337/user
或者PC瀏覽器直接訪問 http://59.111.110.95:1337/user
接下來開始就讓咱們開始一一實踐12-Factor中的每條原則吧,每一個原則中咱們將分爲Factor解說和Factor實踐兩塊。
Factor解說:
12-Factor應用只有一份基準代碼(Codebase),能夠多份部署(deploy)。意思就是說一個應用只有一份用來跟蹤全部修訂版本的代碼倉庫,基準代碼和應用之間老是保持一一對應的關係,由於:
一旦有多個基準代碼,就不能稱爲一個應用,而是一個分佈式系統。分佈式系統中的每個組件都是一個應用,每個應用能夠分別使用 12-Factor 進行開發。
多個應用共享一份基準代碼是有悖於 12-Factor 原則的。解決方案是將共享的代碼拆分爲獨立的類庫,而後使用 依賴管理(第二個原則) 策略去加載它們。
多份部署至關因而運行了該應用的多個實例,好比開發環境一個實例,測試環境、生產環境都有一個實例
一個代碼倉庫,確保了單一的信任源,從而保證了更少的配置錯誤和更強的容錯和復原能力
Factor實踐:
使用Git做爲應用的版本管理系統,使用GitHub咱們的在線倉庫
在剛剛建立好的應用目錄下執行:
$ echo "# 12factor-app" >> README.md
$ git init
$ git add .
$ git commit -m "first commit"
$ git remote add origin git@github.com:bingohuang/12factor-app.git
$ git push -u origin master
Factor解說:
12-Factor規則下的應用程序不會隱式依賴系統級的類庫。意思就是說:經過 依賴清單 聲明全部依賴項,經過 依賴隔離 工具確保程序不會調用系統中存在但清單中未聲明的依賴項。並通通一應用到生產和開發環境。
雲平臺根據這些聲明管理依賴,確保雲應用所需的庫和服務。
Factor實踐:
package.json 就是咱們的 依賴清單,全部應用程序的依賴都聲明在此。
{
"name": "12factor-app",
"private": true,
"version": "0.0.0",
"description": "a Sails application",
"keywords": [],
"dependencies": {
"ejs": "2.3.4",
"grunt": "1.0.1",
"grunt-contrib-clean": "1.0.0",
"grunt-contrib-coffee": "1.0.0",
"grunt-contrib-concat": "1.0.1",
"grunt-contrib-copy": "1.0.0",
"grunt-contrib-cssmin": "1.0.1",
"grunt-contrib-jst": "1.0.0",
"grunt-contrib-less": "1.3.0",
"grunt-contrib-uglify": "1.0.1",
"grunt-contrib-watch": "1.0.0",
"grunt-sails-linker": "~0.10.1",
"grunt-sync": "0.5.2",
"include-all": "^1.0.0",
"rc": "1.0.1",
"sails": "~0.12.11",
"sails-disk": "~0.10.9"
},
"scripts": {
"debug": "node debug app.js",
"start": "node app.js"
},
"main": "app.js",
"repository": {
"type": "git",
"url": "git://github.com/bingo/12factor-app.git"
},
"author": "bingo",
"license": ""
}
接下來咱們加入 mongodb 的庫依賴(後續會用到),只須要執行:
npm install sails-mongo --save
同時 package.json 中會有相應的變動
{
...
"dependencies": {
... "sails-mongo": "^0.12.2" //最新加入的依賴
}
...
}
應用程序須要用到的依賴庫都安裝在node_modules文件夾下,該文件夾就是做爲應用的 依賴隔離,而且和系統的庫是隔離的
Factor解說:
12-Factor推薦將應用的配置存儲於 環境變量 中,保證配置排除在代碼以外,有以下好處:
環境變量是一種清楚、容易理解和標準化的配置方法
環境變量能夠很是方便地在不一樣的部署間作修改,卻不動一行代碼
與配置文件不一樣,不當心把它們簽入代碼庫的機率微乎其微
與一些傳統的解決配置問題的機制(好比 Java 的屬性配置文件)相比,環境變量與語言和系統無關
存儲在環境變量中的另外一個好處是,方便和Docker配合使用
一個技巧:判斷一個應用是否正確地將配置排除在代碼以外,一個簡單的方法是看該應用能夠馬上開源,而不用擔憂會暴露任何敏感的信息:)
Factor實踐:
在應用程序的 config/connections.js 文件中,咱們使用 MONGO_URL 這個環境變量來定義 mongo 的鏈接方式
module.exports.connections = {
mongo: {
adapter: 'sails-mongo', url: process.env.MONGO_URL
}
};
在 文件中,指定module所使用的鏈接
module.exports.models = {
connection: mongo,
migrate: 'safe'
};
若是你在本地起了一個mongodb測試服務,就能夠用這個命令驗證應用是否正常配置
MONGO_URL=mongodb://localhost:27017/12factor-app npm start
Factor解說:
12-Factor 應用不會區別對待本地或第三方服務,統一把後端服務(backing services)看成附加資源或者說是遠程的資源
所謂後端服務是指程序運行所須要的經過網絡調用的各類服務,如數據庫(MySQL),消息/隊列系統(RabbitMQ),SMTP 郵件發送服務( Postfix),以及緩存系統(Memcached)等
除了本地服務以外,應用程序有可能使用了第三方發佈和管理的服務,如 SMTP(例如 Postmark),數據收集服務,數據存儲服務(如 Amazon S3),以及使用 API 訪問的服務(例如 Twitter)等
對應用程序而言,本地或第三方服務都是附加資源,經過一個 url 或是其餘存儲在 配置 中的設置來獲取數據,僅需修改配置中的資源地址便可
應用也所以具備容錯和復原能力,由於它一方面要求編碼時就要考慮資源不可用的狀況,另一方面也加強微服務方法的好處
Factor實踐:
對咱們的應用程序來講,用到的後端服務就是 MongoDB 數據庫。咱們正是經過 MONGO_URL 來傳遞 MongoDB 的資源地址,從而實現了後端服務和應用程序的解耦。
若是當前這個 MongoDB 實例出問題了,咱們能夠經過設置 MONGO_URL 這個環境變量,很方便的切換一個新的實例
Factor解說:
12-facfor 應用嚴格區分構建,發佈,運行這三個步驟:
Cloud Native應用的構建流程把大部分發布配置挪到開發階段,包括實際的代碼構建和運行應用所需的生產環境配置
舉例來講,直接修改處於運行狀態的代碼是很是不可取的作法,由於這些修改很難再同步回構建步驟
發佈的版本就像一本只能追加的帳本,一旦發佈就不可修改,任何的變更都應該產生一個新的發佈版本
Factor實踐:
針對這條原則,強烈推薦使用Docker及其組件(Compose),它的核心理念正是:Build, Ship and Run,將適合在整個構建、發佈和運行流程,咱們也將從這三個方面進行講解。
構建:
書寫構建腳本:Dockerfile
FROM hub.c.163.com/library/node:5.12.0
MAINTAINER bingohuang <me@bingohuang .com>
# 拷貝依賴清單
COPY package.json /tmp/package.json
# 安裝依賴包
RUN cd /tmp && npm install --registry=https://registry.npm.taobao.org
# 將依賴包拷貝到應用程序目錄下
RUN mkdir /app && cp -a /tmp/node_modules /app/
# 更改工做目錄
WORKDIR /app
# 拷貝應用程序代碼
COPY . /app
# 設置應用啓動端口
ENV PORT 1337
# 暴露應用程序端口
EXPOSE 1337
# 啓動應用
CMD ["npm","start"]
Docker 構建$ docker build -t message-app:v1.0 .
Docker 鏡像 推送:能夠將其 push 到指定的鏡像倉庫,好比網易蜂巢的鏡像中倉庫
docker push hub.c.163.com/bingohuang/12factor-app:1.0
發佈:
書寫發佈腳本:docker-compose.yml
version: '2'
services:
mongo:
image: hub.c.163.com/library/mongo:3.2
volumes:
- mongo-data:/data/db
ports:
- "27017:27017"
app:
image: hub.c.163.com/bingohuang/12factor-app:1.0
ports:
-"1337:1337"
links:
- mongo
depends_on:
- mongo
environment:
- MONGO_URL=mongodb://mongo/12factor-app
volumes:
mongo-data:
以上在構建好的鏡像基礎上,定義了一個發佈過程,並將配置(MONGO_URL)經過環境變量注入進去
MONGO_URL=mongodb://mongo/12factor-app
運行:
能夠經過Docker Compose在本地運行,也能夠經過雲平臺來在線編排(網易蜂巢即將支持服務編排功能)
docker-compose up -d
繼而查看日誌
docker-compose logs -f
注:爲了方便不熟悉docker和docker-compose命令的人快速運行程序和本地調試,我在源代碼中還提供了 docker.sh 腳本,方便構建、發佈和運行應用(源碼請看後續資料連接)。
Factor解說:
12-Factor 應用的進程必須無狀態且無共享。
任何須要持久化的數據都要存儲在 後端服務內,好比數據庫。
Session 中的數據應該保存在諸如 Memcached 或 Redis 這樣的帶有過時時間的緩存中。
運行環境中,應用程序一般是以一個和多個 進程 運行的。
最簡單的場景中,代碼是一個獨立的腳本,運行環境是開發人員本身的筆記本電腦,進程由一條命令行(例如python my_script.py)。另一個極端狀況是,複雜的應用可能會使用不少 進程類型 ,也就是零個或多個進程實例。
這麼作是爲了保證Cloud Native基礎設施的速度和效率。
Factor實踐:
雖然這是一個簡單的demo應用,但查看docker容器中的運行進程,發現也有4個進程在運行,其中 npm 也就是咱們的啓動進程,node app.js
是實際運行應用的進程
$ docker exec eaaa922abf08 ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.2 2.0 1076204 42024 ? Ssl 18:22 0:00 npm
root 17 0.0 0.0 4340 724 ? S 18:22 0:00 sh -c node app.js
root 18 0.9 4.5 1253808 93808 ? Sl 18:22 0:01 node app.js
root 27 1.1 3.7 962884 77076 ? Sl 18:22 0:01 grunt
在這裏,咱們的應用進程是無狀態的,持久化的數據都存儲在了後端服務 MongoDB 當中
到此,咱們已經分享完了6個Factor,而且都成功實踐在了咱們的應用上,Yes!
接下來,咱們繼續分享和實踐後續的6個Factor。
Factor解說:
12-Factor 應用經過自我加載而不依賴於任何網絡服務器就能夠建立一個面向網絡的服務。
意思就是說:Web應用經過端口綁定(Port binding)來提供服務 ,並監聽發送至該端口的請求。
Cloud Native應用的服務接口優先選擇 HTTP API 做爲通用的集成框架。
還要指出的是,端口綁定這種方式也意味着一個應用能夠成爲另一個應用的 後端服務 ,調用方將服務方提供的相應 URL 看成資源存入 配置 以備未來調用。
Factor實踐:
docker-compose文件爲咱們很好的定義了端口綁定
ports:
- "1337:1337" // 應用容器暴露1337端口在容器中,宿主機將其映射到1337端口
須要注意的是,若是在一個宿主機中部署多個應用實例,就不能將一個宿主機端口映射到多個容器端口(端口衝突),解決方法是在這之上加一個負載均衡,負載宿主機的不一樣端口服務所對應的不一樣容器。
Factor解說:
12-factor 應用經過進程模型進行擴展,把進程看做是一等公民,而且具有具有的無共享,水平分區的特性
這意味着依賴底層平臺就能實現橫向擴展,不須要技術難度高的多線程編碼。
舉例來講,HTTP 請求能夠交給 web 進程來處理,而常駐的後臺工做則交由 worker 進程負責,定時任務交由 clock 來處理,這樣擴展每一類的進程就很是方便,以下圖所示:
Factor實踐:
如第六個原則所描述,咱們的應用擁有多個進程,最主要的是 Node.js 的http server進程,進程都是無狀態並沒有共享,因此咱們能夠很是容易的水平擴展應用。
Factor解說:
12-Factor 應用的進程是易處理(disposable)的,意思是說任何進程均可以快速啓動和優雅終止,這樣作的好處是:
這有利於快速、彈性的伸縮應用,迅速部署變化的代碼或配置,提升健壯性
進程應當追求最小啓動時間,能夠提供了更敏捷的發佈以及擴展過程
進程一旦接收終止信號(SIGTERM) 就會優雅的終止
以下圖所示,就是一個優雅的應用啓動和終止流程
Factor實踐:
Docker 先天的輕量級和隔離性,就很是適合來作快速啓動和優雅終止,Docker很是適合實踐這條原則,在咱們的應用中,就加入了 Docker和Compose實踐
針對線上環境,推薦構建在容器雲平臺之上(好比網易蜂巢平臺),能夠更優雅的處理進程的啓動和中止。
Factor解說:
12-Factor 應用想要作到持續部署就必須縮小本地與線上差別,包括如下三種差別:
縮小時間差別:開發人員能夠幾小時,甚至幾分鐘就部署代碼
縮小人員差別:開發人員不僅要編寫代碼,更應該密切參與部署過程以及代碼在線上的表現
縮小工具差別:儘可能保證開發環境以及線上環境的一致性
12-Factor 應用的開發人員應該反對在不一樣環境間使用不一樣的後端服務
這是由於,不一樣的後端服務意味着會忽然出現的不兼容,從而致使測試、預發佈都正常的代碼在線上出現問題。
Factor實踐:
咱們的應用程序中,使用了docker-compose做爲咱們的發佈腳本,它使得應用既能夠在本地運行,也能夠在任何支持 Docker 的雲平臺上運行,應用無需變化,只需修改配置文件,很好的解除了不一樣環境的差別化
從以往經驗來看,傳統應用和12-Factor應用會存在以下差別:
Factor解說:
12-factor應用自己從不考慮存儲本身的輸出流。相反,每個運行的進程都會直接的標準輸出(stdout)事件流。
當日志是由雲平臺而不是應用包含的庫處理時,日誌處理機制必須保持簡單。
Factor實踐:
許多服務都能提供日誌集中管理,好比ELK、Splunk、Logentries,並且大多數都能方便的和Docker集成在一塊兒
這裏以 Logentries 爲例來爲應用集成日誌服務,須要在 docker-compose 文件中加入 log 服務,以下:
log:
command: '-t a80277ea-4233-7785203ae328'
image: 'logentries/docker-logentries’
restart: always
tags:
- development
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
一個典型的 Logentries 面板界面以下:
Factor解說:
開發人員常常但願執行一些管理或維護應用的一次性任務,例如:
運行數據移植
運行一個控制檯也被稱爲 REPL shell),來執行一些代碼或是針對線上數據庫作一些檢查。
運行一些提交到代碼倉庫的一次性腳本。
12-Factor應用中,一次性管理進程應該和正常的常駐進程(應用進程)使用一樣的環境,而且使用相同的代碼和配置,基於某個發佈版本運行,隨着其餘的應用程序一塊兒發佈
在Cloud Native中,管理任務也是一個進程,而不是特別的工具;一樣重要的是,管理任務的進程不該使用祕密的 API 或者內部機制。
Factor實踐:
咱們能夠在 docker-compose 文件中定義管理服務,和程序一塊兒執行
咱們能夠經過經過docker exec命令執行一些管理任務,好比:docker exec -ti ADMIN_CONTAINER_ID bash
若是多個容器處在相同的網絡下,能夠經過一個容器來管理其它容器
至此,12-Factor一一實踐完畢,從中能夠看出,12-Factor並不是相互獨立,而是一個總體,有的涉及代碼和框架(Node和Rails),有的涉及工具(Docker和Compose)有的涉及架構和平臺。在雲原生時代,12-Factor仍然具備強大的生命力,每一條原則都是應用開發的珠璣,並且每個原則也不是一成不變的,隨着新的理念出現,原有的Factor會獲得延伸和發展,也會出現新的原則,有興趣的同窗,不妨讀一讀《Beyond the 12 Factor App》這本書,還會有更大的收穫。最後,但願這次分享對你理解雲原生應用、實踐12-Factor有所幫助。
12-Factor 在線書籍:http://12.bingohuang.com/zh_c...
12-Factor 開源地址:https://github.com/bingohuang...
Demo開源地址:https://github.com/bingohuang...