The Twelve-Factor APP & Microservice & Docker

請參看 https://12factor.net/ ,記錄我的一些解讀和實踐故事。

我的前言

在微服務(wù)如此流行的今日,我們大量的使用獨立責(zé)任的微服務(wù)來完成我們的業(yè)務(wù)邏輯以及基礎(chǔ)設(shè)施,組織架構(gòu)由大組向兩個披薩餅團(tuán)隊轉(zhuǎn)換,由單一的 monolithic 應(yīng)用轉(zhuǎn)向由微服務(wù)組成的網(wǎng),使用更多的工具幫助我們完成 CI/CD 來確保時時發(fā)布,使用 Docker 等來幫助我們整理維護(hù)開發(fā)與部署環(huán)境等等。這一切都是為了適應(yīng)產(chǎn)品的快速變動以及釋放工程師的生產(chǎn)能力。

這篇 The Twelve-Factor App 很好的描述了在構(gòu)建 microservice 日常中的情景,在更高的層級上描述了我們該如何建設(shè) microservice。請一定不要進(jìn)行以下的斷言:微服務(wù)等于 docker 化,或是就是拆分成小服務(wù)罷了。

Docker 不論是在日常開發(fā)中為我們提供了便利,也使得部署十分簡單。image 為我們提供了標(biāo)準(zhǔn)化的部屬單位,同時多個 image 所產(chǎn)生的容器可以完成復(fù)雜的任務(wù)。在我們的項目中,一個 instance 中可能有多個 containers 協(xié)作完成,但是往往只有一個 container 運行業(yè)務(wù)相關(guān)的應(yīng)用,其他的可以提供 reverse proxy,log forwarder 以及 APM 性能監(jiān)控等。

Docker 同時還為基礎(chǔ)設(shè)施建設(shè)提供了便利,想象一下我們的城市,有醫(yī)院、學(xué)校、工廠等等設(shè)施,里面的人們所做的工作就是我們的業(yè)務(wù)層的表述,醫(yī)院只負(fù)責(zé)治療病人就如同一個庫存服務(wù)只負(fù)責(zé)管理庫存,而不在乎商品入庫是因為進(jìn)貨還是退貨。而這些設(shè)施實體之間的關(guān)鍵卻像網(wǎng)一樣,而不是直線的數(shù)據(jù)流動。再可以聯(lián)想一下,我們的城市污水系統(tǒng)、電力系統(tǒng)、公共交通等都是統(tǒng)一的,而所有的城市設(shè)施都是基于統(tǒng)一的基礎(chǔ)設(shè)施之上的。

I. Codebase

總體來說 12-Factory App 嚴(yán)格要求一個 app 一個 repo (對于 SVN 管理可能有些困難),但是會有多個用于 deploy 的 repo。但是在 dockerized app 的今日,我們的應(yīng)用對于的一個 repo 并且產(chǎn)生一個相應(yīng)的 docker image,而 deploy 的 repo 也是唯一的,只是在 CI 上的參數(shù)配置不同來區(qū)分不同的運行環(huán)境。對于本地運行環(huán)境可以在 code repo 中定義 docker-compose,特別是當(dāng) app 對其他系統(tǒng)有依賴的情況下,可以直接使用其他系統(tǒng)的 image 支持本地環(huán)境。

當(dāng)然,deploy repo 盡量包含所有的參數(shù),我們不想讓 CI 上的命令、腳本(過于復(fù)雜的 CI Task 或者 pipeline 是十分危險的)等失去版本控制。也可以將 deploy repo 與 code repo 結(jié)合在一起(當(dāng)你認(rèn)為該 repo 沒有開源必要的時候),特別是你使用了代碼即是 CI 的集成工具后。但是中心是:分離部署環(huán)境,盡量讓 CI 中的配置可被代碼管理。

II. Dependency

12-Factor App 希望我們顯示的處理應(yīng)用中的依賴,目前有大量的依賴管理工具可以幫助我們,bundle gradle sbt 等等。當(dāng)然,這些工具可以幫助我們建立一個可運行的本地開發(fā)環(huán)境,你可以很方便的使用各種 IDE 去集成,并且在 IDE 中啟動服務(wù)等。但是問題是,我們的服務(wù)也有其他依賴,并且我們希望本地能提供一種運行環(huán)境與線上環(huán)境相差無幾,這時候就可以使用 docker-compose 這種工具。我們可以使用 volumn 掛載代碼、緩存 packages,并且將單元測試運行、代碼質(zhì)量檢測等全部放到 compose 容器中,同時,通過 compose 我們可以方便的進(jìn)行 database,redis 等等依賴服務(wù)的集成。

在日常中,復(fù)雜的開發(fā)工作你可以使用 IDE 啟動應(yīng)用或者 debug,當(dāng)然你有 database 依賴的時候,只需要使用 docker-compose 啟動 database 即可。

12-Factory App 不希望我們依賴于底層的系統(tǒng)應(yīng)用,比如 curl,wget 等,這一點是非常重要的。我們的解決辦法是生產(chǎn)一個特別的 image 用于運行部署 task,這樣 CI agent 只需要 docker 環(huán)境就可以了。

III. Config

將所有的配置放在環(huán)境變量中!這一點非常重要,因為在不同環(huán)境下(local,stage,production),我們的代碼都是一致的,而環(huán)境變量是最方便去修改并且不需要我們更新 app 代碼。你也可以通過標(biāo)記 dev test 或者 prod 來區(qū)分配置。

特別是在本地開發(fā)的環(huán)境中,靈活的修改環(huán)境變量可以方便的在不同環(huán)境中切換,例如集成測試時也許需要 stage 環(huán)境,只需要修改變量就行。

在 java 開發(fā)中,我經(jīng)常使用 -D 的方式來加入 properties 用來本地測試,并且連接 docker-compose 啟動的數(shù)據(jù)庫。

當(dāng)然你可以將 config 放在其他服務(wù)中,在此章節(jié)中核心點是使用環(huán)境變量在程序運行時 apply config??梢詤⒖?External Configuration Pattern 來實現(xiàn) external configuration。

IV. Backing Services

我們的 app 經(jīng)常依賴于上游與下游中各類的服務(wù),database, queue, service 與 caching。在 12-Factory App 中,我們都需要將其視為同類的資源。在我們的 code repo 中我們不應(yīng)該區(qū)別對待本地資源與三方服務(wù),例如在 sidekid 中, 用于調(diào)度任務(wù)的 redis 也是通過存儲在 config 中的 url 來訪問的。

Docker 可以很方便的為我們做到這些,數(shù)據(jù)庫、緩存等有豐富的 image 可供選擇,對于三方服務(wù),如果其為 dockerized 服務(wù),只需要配置 image 即可。但是對于一些非 dockerized 服務(wù),可以使用替代品用于本地測試,或者直接配置在 config 中。

V. Build, Release, Run

我們經(jīng)?;煜?build,release 以及 run 。在 12-Factor App 中,這三項是截然不同的,傳統(tǒng)的做法是 build code,例如在 java 中,往往是將代碼、依賴、資源生成 jar 包則認(rèn)為是 build,而 release 則是將 config 加入其中,使其能夠快速的投入使用。運行時則是選擇版本,并且在運行環(huán)境中使用。

在開發(fā) dockerized app 時,build 往往是指生成無配置的 image,并且使用版本號作為 tag,然后發(fā)布在 docker repository 中。而 release 則會制定不同的 config 作為環(huán)境變量輸入其中,最后在運行前,由 CI 工具來確定在什么環(huán)境中應(yīng)用哪些 config。

我們十分同意文中對于 build 和 run 的區(qū)別對待,build 中往往會做很多工作,例如 unit test,integration test,style check,coverage check 等等,讓開發(fā)人員能盡早的發(fā)現(xiàn)問題。一旦 image 創(chuàng)建,只需要組織 config 并且這一部分并不需要人工干預(yù)。微服務(wù)的魅力就在于此,不是某周、某日發(fā)布新的應(yīng)用,對于一個系統(tǒng),任何時候內(nèi)部的微服務(wù)都有可能在 release。當(dāng)然,正因為如此,清晰的 contract 以及集成測試才是萬分重要的。

VI. Processes

Twelve-factor processes are stateless and share-nothing.

十分重要的觀點,stateless 與 share nothing 是保證 app 可以被 scale up 的根本。所有需要被 persist 的數(shù)據(jù)都應(yīng)該保存在數(shù)據(jù)庫或者其他服務(wù)中。

避免使用 session,或者將 session 存放在 Memcached 與 Redis 中,我們需要避免在 processes 中傳遞 session 數(shù)據(jù),或者依賴等待 session 數(shù)據(jù)。

怎么確定自己的服務(wù)是由 stateless 和 share nothing 的呢?很簡單, 1 個 instance 可以解決問題,100 個 instances 也是可以達(dá)到同樣的效果的,只是時間消耗的差異。

VII. Port binding

對于 web app 或者 SAAS,一個端口一個地址就是我們需要的全部。在日常中,我們的 service 邏輯往往會運行在 tomcat、unicron 等等容器中,暴露出一個端口,而所有依賴的服務(wù)都可以通過機(jī)器名與端口找到該服務(wù)。

需要注意的是,所有在互聯(lián)網(wǎng)上的服務(wù)都可以使用端口來指明 service,例如支持 MySQL Protocol 的 sphinx,調(diào)用者無需知道 sphinx 的實現(xiàn)細(xì)節(jié),只需要知道該服務(wù)的協(xié)議與端口,在開發(fā)中,我甚至可以用一個 MySQL 的 image 替代它。這樣做還有一個好處,當(dāng)我們的服務(wù)依賴于其他時,我們可以很方便的將 endpoint 作為配置。

同理,我們在本地開發(fā)中,測試的本地 MySQL 與 RDS 中的 MySQL 略有不同,但是對于 app,是不需要感知到不同的。

VIII. Concurrency

按照負(fù)載動態(tài)擴(kuò)展是我們的追求,最重要的目的是為了避免浪費!12-Facto App 認(rèn)為進(jìn)程是一等公民,并且進(jìn)程之間是相互隔離的。當(dāng)然在 build 一個 docker image 時你的 container,只能有一個進(jìn)程在運行(對于 docker 來說這是十分重要 one process per container)。

運氣好的是,docker 為我們提供了天然的隔離進(jìn)程的方式,并且由于部署簡單,在 scale up 時,只需要達(dá)到了設(shè)定的 threshold 就可以開啟多個 instances,并且 pull 下 image 運行。當(dāng)然,現(xiàn)在有更強(qiáng)悍的 container 管理服務(wù),我們可以告別 instances 而直接使用 container 進(jìn)行擴(kuò)展,從而使得應(yīng)用的 scale up 更快,參看 AWS ESC。

得益于微服務(wù)的業(yè)務(wù)分割,每個服務(wù)的職責(zé)是簡單的、有邊界的,所以考慮單個服務(wù)的 concurrency 是相對簡單的。我所在的小組(5人)維護(hù)了十多個應(yīng)用,提供 webservice 的應(yīng)用有 7 個,每個服務(wù)都是能夠輕松的動態(tài)擴(kuò)展的,并且 concurrency 問題得到了較好的處理。這么來講,只有三個 repositories 訪問一張數(shù)據(jù)表的 concurrency 情況是非常簡單的,而不是滿世界的 join 你那張可憐的數(shù)據(jù)表。

IX. Disposability

我們的應(yīng)用很好的達(dá)到了優(yōu)雅的啟動,部分系統(tǒng)達(dá)到了優(yōu)雅的終止。我們都希望自己的應(yīng)用在關(guān)閉時也保持優(yōu)雅,在關(guān)閉完端口后,不接收任何請求,并且完成線程池內(nèi)的任務(wù),然后再關(guān)閉自己。因為在負(fù)載變化較大的情況下,關(guān)閉一個 instanes 是常常發(fā)生的,我們并不希望在處理中的任務(wù)失敗,丟失用戶信息或是產(chǎn)生臟數(shù)據(jù)。

X. Dev/prod parity

在日常的工作中,service bug 將是每個人的噩夢,很多分配到 fix 的同事第一個問題是:我該怎么重現(xiàn) bug 呢?12-Factor App 推薦我們等價開發(fā)環(huán)境與生產(chǎn)環(huán)境,這是非常重要的,在業(yè)務(wù)上很多 bug 是可以依靠等價的環(huán)境在本地重現(xiàn)的,從而方便修復(fù)。

我所在的小組只有 5 名開發(fā)人員,卻維護(hù)這十多個獨立的,相互耦合的應(yīng)用(感謝微服務(wù)?。瑖?yán)格的控制開發(fā)環(huán)境與線上環(huán)境的等價是我們的基礎(chǔ)之一,按照文中的維度,我可以描述我們做到了什么:

  • 縮小時間差異:盡快部署,修改后的代碼可以在幾分鐘內(nèi)上線
  • 縮小人員差異:Dev 人員不僅需要開發(fā),還需要參與 QA,監(jiān)控部署以及服務(wù)狀態(tài)
  • 縮小工具差異:幾乎本地環(huán)境可以完全視為線上環(huán)境

這里可以多說說 integration first,當(dāng)一個新的項目啟動后,第一件事情就是部署,我們需要一套本地的 docker-compose 環(huán)境來表示項目的環(huán)境,同時也需要構(gòu)建 CI task 將其部署在產(chǎn)品環(huán)境。從此以后,任何的修改,都必須通過 CI。在開發(fā)中,我們會慢慢的加入 dummy response、unit test、integration test 等,并且會更新 CI pipeline。永遠(yuǎn)維護(hù)一個可用的線上環(huán)境,這樣可以確保以后的更改不會破壞任何東西。當(dāng)然在開發(fā)過程中我們有可能需要調(diào)整環(huán)境,這些事情往往是第一優(yōu)先級的。

XI. Log

大約兩年前,有人告訴我說在進(jìn)行大數(shù)據(jù)開發(fā)中,你都不知道 log 在哪里看,或者你都不知道是哪臺機(jī)器出了問題!我承認(rèn)當(dāng)時我被嚇到了,直到我擺脫了傳統(tǒng) log pattern 之后。重點是:永遠(yuǎn)永遠(yuǎn)永遠(yuǎn)不要將 log 存放在本地。引申為你的 app process 是無狀態(tài),它并不染指本地的存儲資源。

我們可以使用 stdout 流優(yōu)雅的解決這個問題,12-Factor App 將 log 放入輸出流,誰關(guān)心誰去使用,不論是存檔或是發(fā)送到日志中心這是與 app 無關(guān)的。得益于強(qiáng)大的日志分析工具,我們可以輕松的查看異常、統(tǒng)計 request time 或者分析流量。

Docker 又一個好處是天生的支持我們需要的 12-Factor App Log,參看 Docker Log。是時候結(jié)束那種 ssh 到環(huán)境上,tail -f logfile 的悲慘日子了。

XII. Admin processes

我們需要那種只運行一次的管理任務(wù),日常中可能最重要的是做數(shù)據(jù)庫 migration,我們希望這種任務(wù)只運行一次,并且只在一個實例中運行。舉個例子,在一個 ruby 的項目中,發(fā)布的 image 是包含有能夠運行 migration 的任務(wù)的,這時,CI 只需要開啟一個實例并且運行該任務(wù)即可,在完成后,由于程序正常退出,該任務(wù)所在的實例也會被正常關(guān)閉掉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Docker — 云時代的程序分發(fā)方式 要說最近一年云計算業(yè)界有什么大事件?Google Compute Engi...
    ahohoho閱讀 15,828評論 15 147
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評論 19 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,810評論 25 709
  • 一奇跡 1.今天邊聽彼尚功課宏燕老師微課邊干活,非常亨受光與愛的進(jìn)入和滋養(yǎng)頭腦變得空無,感覺很好棒棒滴完美完整。 ...
    和平感恩閱讀 294評論 0 0
  • 1、旋律 當(dāng)我行走在這座小城的街頭時,暮色中隱約有股桂花的味道。我知道,秋天已...
    老仙兒閱讀 1,227評論 2 39

友情鏈接更多精彩內(nèi)容