1. 程式人生 > >雲原生時代下的12-factor應用與實踐

雲原生時代下的12-factor應用與實踐

在雲的時代,應用會更多地遷移到雲端,基於雲的架構設計和開發模式需要一套全新的理念去承載,於是雲原生思想應運而生,而針對雲原生應用開發的最佳實踐原則,12-Factor脫穎而出,同時也帶來了新的解讀。本次分享將結合 Docker等技術,介紹在 Cloud Native時代下,如何一一實踐 12-Factor原則。

雲原生


雲原生(Cloud Native)是由  Pivotal 的 Matt Stine在 2013年提出的一個概念,是他多年的架構和諮詢總結出來的一個思想的集合。

那怎麼去理解雲原生應用?我覺得可以從三個角度來說明,這和雲端計算平臺的三個層次不謀而合,如下圖:

  • IaaS看做基礎雲設施,用來提供各種基礎資源 (Infrastructure)

  • PaaS作為開發平臺,用來提供各種平臺服務 (Platform)

  • SaaS交付應用或服務,直面使用者,提供應用價值 (Application)

雲原生應用,正好契合了雲、平臺和服務,一層層建構,所以我通常就把它理解為面向雲(平臺)來設計我們的應用。網易三拾眾籌的架構師陳曉輝還為它起了一個小清新的名字——向雲而生,我覺得非常貼切,再通俗一點講,也可以叫做雲平臺應用。

 

12-Factor


12-Factor,是由 Heroku創始人 Adam Wiggins首次提出並開源,並由眾多經驗豐富的開發者共同完善,這綜合了他們關於 SaaS應用幾乎所有的經驗和智慧,是開發此類應用的理想實踐標準。

12-Factor 全稱叫 The Twelve-Factor App,它定義了一個優雅的網際網路應用在設計過程中,需要遵循的一些基本原則,和 Cloud-Native 有異曲同工之處。其中文翻譯不少,我覺得“十二要素”或“十二原則”比較貼切。

那具體有哪十二原則了,見下圖:

在接下來的應用和實踐當中,我們會一一實踐每條原則。

注:雖然 12-Factor 的原文書籍都是釋出在其官網上,但因為網路問題和格式問題,不是很方便閱讀,我將其轉化為了 GitBook 格式,並架設在網易雲基礎服務(蜂巢)平臺上,同時開源在 GitHub 上,方便大家閱讀和下載:

線上閱讀地址:

http://12.bingohuang.com/zh_cn/index.html

GitHub 開源地址:

https://github.com/bingohuang/12factor-gitbook

pdf/epub下載地址:

https://github.com/bingohuang/12factor-gitbook/download

GitBook 地址:

https://www.gitbook.com/book/bingohuang/12factor/details

 

應用與實踐


既然 12-factor作為 SaaS開發的最佳實踐原則,當然脫離不了實踐,接下來我們就來設計一款雲原生應用,並依照 12-factor,一步步驗證和升級我們的應用。從中,我們將講解每個 Factor的要點,以及如何在我們的應用中實踐 Factor。

 

應用準備

這是一個面向雲平臺設計的簡單 Web應用,它將暴露一個 HTTP REST風格的介面,可以實現對 user 的增刪改查功能,將用到以下技術棧:

1.  基於 Node.js,用 Node.js 寫 Web應用非常方便,而且是當今最火的程式設計平臺之一。

下載安裝 Node.js (包含 npm):https://nodejs.org/zh-cn/download/

注:Node 版本只要 v4.4 以上就夠用,當前最新的穩定版是 v6.9.5, 我本地的版本是 v5.12.0

2.  基於 Sails,類似 Rails 框架,用於快速開發 Node.js 應用:http://sailsjs.com/

安裝 Sails 框架最新版:

npm install sails -g

3.  基於 mongo 3.2 :https://docs.mongodb.org/manual/installation/

4.  基於 Docker,非常契合 12-Factor理念,作為我們打包、釋出、執行的工具。

安裝 Docker:https://docs.docker.com/engine/installation/

5.  以上環境安裝好之後,就開始初始化我們的應用並執行,應用的名稱就叫:12factor-app。

$ 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

注:本文原始碼放在 GitHub上,請參考文後參考連結

僅需 4條命令就搞定了應用的框架程式碼,並自動生成了基於 user的 CRUD介面,我們已經將應用啟動起來,可以通過以下方式本地除錯介面:

控制檯輸出正常,在瀏覽器中訪問下面連結,即可看到 Sails應用的首頁:http://localhost:1337

接著,就可以通過本地 curl命令或者 http工具來做介面除錯,這裡以常規的增刪改查為例:

1. 增加一個新使用者

$ curl -XPOST http://localhost:1337/user?name=bingo
{
  "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/58a41d952f53291200b9e065?name=bingohuang
{
  "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/58a41d952f53291200b9e065

我已經將該應用部署到了網易雲基礎服務(蜂巢)線上平臺,如果您對這個應用感興趣,直接將 localhost替換為 59.111.110.95,一樣可以體驗 CRUD操作,如下所示,只要把 yourname換成您的名字即可(建議在 PC端操作):

# 註冊你自己
curl -XPOST http://59.111.110.95:1337/user?name=yourname
# 檢視所有註冊過的使用者
curl http://59.111.110.95:1337/user
# 或者 PC瀏覽器直接訪問 http://59.111.110.95:1337/user

接下來開始就讓我們開始一一實踐 12-Factor中的每條原則吧,每個原則中我們將分為 Factor解說和 Factor實踐兩塊。

 

1 基準程式碼

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 [email protected]:bingohuang/12factor-app.git
$ git push -u origin master

 

2 依賴

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資料夾下,該資料夾就是作為應用的依賴隔離,並且和系統的庫是隔離的。

 

3 配置

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

 

4 後端服務

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 這個環境變數,很方便的切換一個新的例項。

 

5 構建,釋出,執行

Factor解說:

12-Factor 應用嚴格區分構建,釋出,執行這三個步驟。

Cloud Native應用的構建流程把大部分發布配置挪到開發階段,包括實際的程式碼構建和執行應用所需的生產環境配置。

舉例來說,直接修改處於執行狀態的程式碼是非常不可取的做法,因為這些修改很難再同步回構建步驟。

釋出的版本就像一本只能追加的賬本,一旦釋出就不可修改,任何的變動都應該產生一個新的釋出版本。

Factor實踐:

針對這條原則,強烈推薦使用 Docker及其元件(Compose),它的核心理念正是:Build, Ship and Run,將適合在整個構建、釋出和執行流程,我們也將從這三個方面進行講解。

① 構建:

書寫構建指令碼:Dockerfile

FROM hub.c.163.com/library/node:5.12.0
MAINTAINER bingohuang <[email protected]>
# 拷貝依賴清單
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 12factor-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 指令碼,方便構建、釋出和執行應用(原始碼請看後續資料連結)。

 

6 程序

Factor解說:

12-Factor 應用的程序必須無狀態且無共享。

任何需要持久化的資料都要儲存在後端服務內,比如資料庫。Session 中的資料應該儲存在諸如 Memcached 或 Redis 這樣的帶有過期時間的快取中。

執行環境中,應用程式通常是以一個和多個程序 執行的。

最簡單的場景中,程式碼是一個獨立的指令碼,執行環境是開發人員自己的膝上型電腦,程序由一條命令列(例如 python my_script.py)。另外一個極端情況是,複雜的應用可能會使用很多程序型別 ,也就是零個或多個程序例項。

這麼做是為了保證 Cloud Native基礎設施的速度和效率。

Factor實踐:

雖然這是一個簡單的 demo應用,但檢視 docker容器中的執行程序,發現也有4個程序在執行,其中 npm也就是我們的啟動程序,`node app.js` 是實際執行應用的程序。

$ docker  exec 12-factor 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 當中。

 

7 埠繫結

Factor解說:

12-Factor 應用通過自我載入而不依賴於任何網路伺服器就可以建立一個面向網路的服務。

意思就是說:Web應用通過埠繫結 (Port binding)來提供服務 ,並監聽傳送至該埠的請求。Cloud Native應用的服務介面優先選擇 HTTP API 作為通用的整合框架。

還要指出的是,埠繫結這種方式也意味著一個應用可以成為另外一個應用的後端服務 ,呼叫方將服務方提供的相應 URL 當作資源存入 配置 以備將來呼叫。

Factor實踐:

docker-compose檔案為我們很好的定義了埠繫結。

ports: "1337:1337" // 應用容器暴露 1337埠在容器中,宿主機將其對映到 1337埠。

需要注意的是,如果在一個宿主機中部署多個應用例項,就不能將一個宿主機埠對映到多個容器埠(埠衝突),解決方法是在這之上加一個負載均衡,負載宿主機的不同埠服務所對應的不同容器。

 

8 併發

Factor解說:

12-factor 應用通過程序模型進行擴充套件,把程序看作是一等公民,並且具備無共享,水平分割槽的特性。

這意味著依賴底層平臺就能實現橫向擴充套件,不需要技術難度高的多執行緒編碼。

舉例來說,HTTP 請求可以交給 web 程序來處理,而常駐的後臺工作則交由 worker 程序負責,定時任務交由 clock 來處理,這樣擴充套件每一類的程序就非常方便,如下圖所示:

Factor實踐:

如第六個原則所描述,我們的應用擁有多個程序,最主要的是 Node.js 的http server程序,程序都是無狀態並無共享,所以我們可以非常容易的水平擴充套件應用。

 

9 易處理

Factor解說:

12-Factor 應用的程序是易處理(disposable)的,意思是說任何程序都可以快速啟動和優雅終止,這樣做的好處是:

  • 這有利於快速、彈性的伸縮應用,迅速部署變化的程式碼或配置,提高健壯性;

  • 程序應當追求最小啟動時間,可以提供了更敏捷的釋出以及擴充套件過程;

  • 程序一旦接收終止訊號(SIGTERM) 就會優雅的終止。

如下圖所示,就是一個優雅的應用啟動和終止流程。

Factor實踐:

Docker 先天的輕量級和隔離性,就非常適合來做快速啟動和優雅終止,Docker非常適合實踐這條原則,在我們的應用中,就加入了 Docker和Compose實踐。

針對線上環境,推薦構建在容器雲平臺之上(比如網易雲基礎服務平臺),可以更優雅的處理程序的啟動和停止。

 

10 環境等價

Factor解說:

12-Factor 應用想要做到持續部署就必須縮小本地與線上差異,包括以下三種差異:

  • 縮小時間差異:開發人員可以幾小時,甚至幾分鐘就部署程式碼;

  • 縮小人員差異:開發人員不只要編寫程式碼,更應該密切參與部署過程以及程式碼在線上的表現;

  • 縮小工具差異:儘量保證開發環境以及線上環境的一致性。

12-Factor 應用的開發人員應該反對在不同環境間使用不同的後端服務。

這是因為,不同的後端服務意味著會突然出現的不相容,從而導致測試、預釋出都正常的程式碼在線上出現問題。

Factor實踐:

我們的應用程式中,使用了 docker-compose作為我們的釋出指令碼,它使得應用既可以在本地執行,也可以在任何支援 Docker 的雲平臺上執行,應用無需變化,只需修改配置檔案,很好的解除了不同環境的差異化。

從以往經驗來看,傳統應用和 12-Factor應用會存在如下差異:

 

11 日誌

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 面板介面如下:

 

12 管理程序

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有所幫助。

 

參考連結


原始碼:

https://github.com/bingohuang/12factor-app

文章地址:

http://talks.bingohuang.com/2017/cloud-native-12factor.article

12-Factor 線上書籍:

http://12.bingohuang.com/zh_cn/index.html

12-Factor 書籍開源:

https://github.com/bingohuang/12factor-gitbook