持續部署 Microservices 的實踐和準則

本篇為 Thoughtworks 洞見博客的投稿: http://insights.thoughtworkers.org/the-practices-and-principles-of-continuous-deployment-microservices/。 感謝 TW 編輯們幫我校稿和排版

當我們討論 Microservice 架構時,我們通常會和 Monolithic 架構(單體架構 ) 構進行比較。

Monolithic and Miroservice

在 Monolithic 架構中,一個簡單的應用會隨著功能的增加、時間的推移變得越來越龐大。當 Monoltithic App 變成一個龐然大物,就沒有人能夠完全理解它究竟做了什么。此時無論是添加新功能,還是修復 Bug ,都是一個非常痛苦、異常耗時的過程。

Microservices 架構漸漸被許多公司采用(AmazoneBayNetflix),用于解決 Monolithic 架構帶來的問題。其思路是將應用分解為小的、可以相互組合的 Microservices。 這些 Microservices通過輕量級的機制進行交互,通常會采用基于 HTTP 協議的服務。

每個 Microservices 完成一個獨立的業務邏輯,它可以是一個 HTTP API 服務,提供給其他服務或者客戶端使用。也可以是一個 ETL 服務,用于完成數據遷移工作。每個 Microservices 除了在業務獨立外,也會有自己獨立的運行環境,獨立的開發、部署流程。

這種獨立性給服務的部署和運營帶來很大的挑戰。因此持續部署(Continuous Deployment)是 Microservices 場景下一個重要的技術實踐。本文將介紹持續部署 Microservices 的實踐和準則:

實踐:

  1. 使用 Docker 容器化服務
  2. 采用 Docker Compose 運行測試

準則:

  1. 構建適合團隊的持續部署流水線
  2. 版本化一切
  3. 容器化一切

使用 Docker 容器化服務

我們在構建和發布服務的時候,不僅要發布服務本身,還需要為其配置服務器環境。使用 Docker 容器化微服務,可以讓我們不僅發布服務,同時還發布其需要的運行環境。容器化之后,我們可以基于 Docker 構建我們的持續部署流水線:

dockerize

上圖描述了一個基于 Ruby on Rails (簡稱:Rails) 服務的持續部署流水線。我們用 Dockerfile 配置 Rails 項目運行所需的環境,并將 Dockerfile 和項目同時放在 Git 代碼倉庫中進行版本管理。下面 Dockerfile 可以描述一個 Rails 項目的基礎環境:

FROM ruby:2.3.3
RUN apt-get update -y && \
    apt-get install -y libpq-dev nodejs git
WORKDIR /app
ADD Gemfile /app/Gemfile
ADD Gemfile.lock /app/Gemfile.lock
RUN bundle install
ADD . /app
EXPOSE 80
CMD ["bin/run"]

在持續集成服務器上會將項目代碼和 Dockerfile 同時下載(git clone)下來進行構建(Build Image)、單元測試(Testing)、最終發布(Publish)。此時整個構建過程都基于 Docker 進行,構建結果為 Docker Image,并且將最終發布到 Docker Registry

在部署階段,部署機器只需要配置 Docker 環境,從 Docker Registry 上 Pull Image 進行部署。

在服務容器化之后,我們可以讓整套持續部署流水線只依賴 Docker,并不需要為環境各異的服務進行單獨配置。

使用 Docker Compose 運行測試

在整個持續部署流水線中,我們需要在持續集成服務器上部署服務、運行單元測試和集成測試。Docker Compose 為我們提供了很好的解決方案。

Docker Compose 可以將多個 Docker Image 進行組合。在服務需要訪問數據庫時,我們可以通過 Docker Compose 將服務的 Image 和 數據庫的 Image 組合在一起,然后使用 Docker Compose 在持續集成服務器上進行部署并運行測試。

docker-compose

上圖描述了 Rails 服務和 Postgres 數據庫的組裝過程。我們只需在項目中額外添加一個 docker-compose.yml 來描述組裝過程:

db:
  image: postgres:9.4
  ports:
    - "5432"
service:
  build: .
  command: ./bin/run
  volumes:
    - .:/app
  ports:
    - "3000:3000"
dev:
  extends:
    file: docker-compose.yml
    service: service
  links:
    - db
  environment:
    - RAILS_ENV=development
ci:
  extends:
    file: docker-compose.yml
    service: service
  links:
    - db
  environment:
    - RAILS_ENV=test

采用 Docker Compose 運行單元測試和集成測試:

docker-compose run -rm ci bundle exec rake

構建適合團隊的持續部署流水線

當我們的代碼提交到代碼倉庫后,持續部署流水線應該能夠對服務進行構建、測試、并最終部署到生產環境。

為了讓持續部署流水線更好的服務團隊,我們通常會對持續部署流水線做一些調整,使其更好的服務于團隊的工作流程。例如下圖所示的,一個敏捷團隊的工作流程:

agile team workflow

通常團隊會有業務分析師(BA)做需求分析,業務分析師將需求轉換成適合工作的用戶故事卡(Story Card),開發人員(Dev)在拿到新的用戶故事卡時會先做分析,之后和業務分析師、技術主管(Tech Lead)討論需求和技術實現方案(Kick off)。

開發人員在開發階段會在分支(Branch)上進行開發,采用 Pull Request 的方式提交代碼,并且邀請他人進行代碼評審(Review)。在 Pull Request 被評審通過之后,分支會被合并到 Master 分支,此時代碼會被自動部署到測試環境(Test)

在 Microservices 場景下,本地很難搭建一整套集成環境,通常測試環境具有完整的集成環境,在部署到測試環境之后,測試人員(QA)會在測試環境上進行測試。

測試完成后,測試人員會跟業務分析師、技術主管進行驗收測試(User Acceptance Test),確認需求的實現和技術實現方案,進行驗收。驗收后的用戶故事卡會被部署到生產環境(Production)

在上述團隊工作的流程下,如果持續部署流水線僅對 Master 分支進行打包、測試、發布。在開發階段 (即:代碼還在分支) 時,無法從持續集成上得到反饋,直到代碼被合并到 Master 并運行構建后才能得到反饋,通常會造成“本地測試成功,但是持續集成失敗”的場景。

因此,團隊對僅基于 Master 分支的持續部署流水線做一些改進。使其可以支持對 Pull Request 代碼的構建:

team workflow

如上圖所示:

  • 持續部署流水線區分 Pull Request 和 Master。 Pull Request 上只運行單元測試, Master 運行完成全部構建并自動將代碼部署到測試環境。
  • 為生產環境部署引入手動操作,在驗收測試完成之后再手動觸發生產環境部署。

經過調整后的持續部署流水線可以使團隊在開發階段快速從持續集成上得到反饋,并且對生產環境的部署有更好的控制。

版本化一切

版本化一切,即將服務開發、部署相關的系統都版本化控制。我們不僅將項目代碼納入版本管理,同時將項目相關的服務、基礎設施都進行版本化管理。

對于一個服務,我們一般會為它單獨配置持續部署流水線,為它配置獨立的用于運行的基礎設施。此時會涉及兩個非常重要的技術實踐:

  • 構建流水線即代碼
  • 基礎設施即代碼

構建流水線即代碼。通常我們使用 Jenkins 或者 Bamboo 來搭建配置持續部署流水線,每次創建流水線需要手動配置,這些手動操作不易重用,并且可讀性很差,每次對流水線配置的改動并不會保存在歷史記錄中,也就是說我們無從追蹤配置的改動。

在今年上半年,團隊將所有的持續部署流水線從 Bamboo 遷移到了 BuildKite,BuildKite 對構建流水線即代碼有很好的支持。下圖描述了 BuildKite 的工作方式:

build pipeline as code

在 BuildKite 場景下,我們會在每個服務代碼庫中新增一個 pipeline.yml 來描述構建步驟。構建服務器(CI Service)會從項目的 pipeline.yml 中讀取配置,生成構建步驟。例如,我們可以使用如下代碼描述流水線:

steps:
  -
    name: "Run my tests"
    command: "shared_ci_script/bin/test"
    agents:
      queue: test
  - wait
  -
    name: "Push docker image"
    command: "shared_ci_script/bin/docker-tag"
    branches: "master"
    agents:
      queue: test
  - wait
  -
    name: "Deploy To Test"
    command: "shared_ci_script/bin/deploy"
    branches: "master"
    env:
      DEPLOYMENT_ENV: test
    agents:
      queue: test
  - block
  - name: "Deploy to Production"
    command: "shared_ci_script/bin/deploy"
    branches: "master"
    env:
      DEPLOYMENT_ENV: prod
    agents:
      queue: production

在上述配置中, command 中的步驟 ( 即:test、docker-tag、deploy ) 分別是具體的構建腳本,這些腳本被放在一個公共的 shared_ci_script 代碼庫中,shared_ci_script 會以 git submodule 的方式被引入到每個服務代碼庫中。

經過構建流水線即代碼方式的改造,對于持續部署流水線的任何改動都會在 Git 中被追蹤,并且有很好的可讀性。

基礎設施即代碼。對于一個基于 HTTP 協議的 API 服務基礎設施可以是:

  • 用于部署的機器
  • 機器的 IP 和網絡配置
  • 設備硬件監控服務(CPU,Memory 等)
  • 負載均衡(Load Balancer)
  • DNS 服務
  • AutoScaling Service (自動伸縮服務)
  • Splunk 日志收集
  • NewRelic 性能監控
  • PagerDuty 報警

這些基礎設施我們可以使用代碼進行描述,AWS Cloudformation 在這方面提供了很好的支持。我們可以使用 AWS Cloudformation 設計器或者遵循 AWS Cloudformation 的語法配置基礎設施。下圖為一個服務的基礎設施構件圖,圖中構建了上面提到的大部分基礎設施:

infrastruture as code

在 AWS Cloudformation 中,基礎設施描述代碼可以是 JSON 文件,也可以是 YAML 文件。我們將這些文件也放到項目的代碼庫中進行版本化管理。

所有對基礎設施的操作,我們都通過修改 AWS Cloudformation 配置進行修改,并且所有修改都應該在 Git 的版本化控制中。

由于我們采用代碼描述基礎設施,并且大部分服務遵循相通的部署流程和基礎設施,基礎設施代碼的相似度很高。 DevOps 團隊會為團隊創建屬于自己的部署工具來簡化基礎設施配置和部署流程。

容器化一切

通常在部署服務時,我們還需要一些輔助服務,這些服務我們也將其容器化,并使用 Docker 運行。下圖描述了一個服務在 AWS EC2 Instance 上面的運行環境:

deploy via docker

在服務部署到 AWS EC2 Instance 時,我們需要為日志配置收集服務,需要為服務配置 Nginx 反向代理。

按照 12-factors 原則,我們基于 fluentd,采用日志流的方式處理日志。其中 logs-router 用來分發日志、splunk-forwarder 負責將日志轉發到 Splunk

在容器化一切之后,我們的服務啟動只需要依賴 Docker 環境,相關服務的依賴也可以通過 Docker 的機制運行。

總結

Microservices 給業務和技術的擴展性帶來了極大的便利,同時在組織和技術層面帶來了極大的挑戰。由于在架構的演進過程中,會有很多新服務產生,持續部署是技術層面的挑戰之一,好的持續部署實踐和準則可以讓團隊從基礎設施抽離出來,關注與產生業務價值的功能實現。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,412評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,514評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,373評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,975評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,743評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,199評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,262評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,414評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,951評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,780評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,527評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,218評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,649評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,889評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,673評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容