本篇為 Thoughtworks 洞見博客的投稿: http://insights.thoughtworkers.org/the-practices-and-principles-of-continuous-deployment-microservices/绒极。 感謝 TW 編輯們幫我校稿和排版
當(dāng)我們討論 Microservice 架構(gòu)時,我們通常會和 Monolithic 架構(gòu)(單體架構(gòu) ) 構(gòu)進(jìn)行比較凿蒜。
在 Monolithic 架構(gòu)中宾袜,一個簡單的應(yīng)用會隨著功能的增加、時間的推移變得越來越龐大幢泼。當(dāng) Monoltithic App 變成一個龐然大物,就沒有人能夠完全理解它究竟做了什么导匣。此時無論是添加新功能噪生,還是修復(fù) Bug ,都是一個非常痛苦月培、異常耗時的過程。
Microservices 架構(gòu)漸漸被許多公司采用(Amazon、eBay瞭稼、Netflix),用于解決 Monolithic 架構(gòu)帶來的問題腻惠。其思路是將應(yīng)用分解為小的环肘、可以相互組合的 Microservices。 這些 Microservices通過輕量級的機(jī)制進(jìn)行交互集灌,通常會采用基于 HTTP 協(xié)議的服務(wù)廷臼。
每個 Microservices 完成一個獨立的業(yè)務(wù)邏輯,它可以是一個 HTTP API 服務(wù)绝页,提供給其他服務(wù)或者客戶端使用荠商。也可以是一個 ETL 服務(wù),用于完成數(shù)據(jù)遷移工作续誉。每個 Microservices 除了在業(yè)務(wù)獨立外莱没,也會有自己獨立的運行環(huán)境,獨立的開發(fā)酷鸦、部署流程饰躲。
這種獨立性給服務(wù)的部署和運營帶來很大的挑戰(zhàn)。因此持續(xù)部署(Continuous Deployment)是 Microservices 場景下一個重要的技術(shù)實踐臼隔。本文將介紹持續(xù)部署 Microservices 的實踐和準(zhǔn)則:
實踐:
- 使用 Docker 容器化服務(wù)
- 采用 Docker Compose 運行測試
準(zhǔn)則:
- 構(gòu)建適合團(tuán)隊的持續(xù)部署流水線
- 版本化一切
- 容器化一切
使用 Docker 容器化服務(wù)
我們在構(gòu)建和發(fā)布服務(wù)的時候嘹裂,不僅要發(fā)布服務(wù)本身,還需要為其配置服務(wù)器環(huán)境摔握。使用 Docker 容器化微服務(wù)寄狼,可以讓我們不僅發(fā)布服務(wù),同時還發(fā)布其需要的運行環(huán)境。容器化之后泊愧,我們可以基于 Docker 構(gòu)建我們的持續(xù)部署流水線:
上圖描述了一個基于 Ruby on Rails (簡稱:Rails) 服務(wù)的持續(xù)部署流水線伊磺。我們用 Dockerfile 配置 Rails 項目運行所需的環(huán)境,并將 Dockerfile 和項目同時放在 Git 代碼倉庫中進(jìn)行版本管理删咱。下面 Dockerfile 可以描述一個 Rails 項目的基礎(chǔ)環(huán)境:
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"]
在持續(xù)集成服務(wù)器上會將項目代碼和 Dockerfile 同時下載(git clone)下來進(jìn)行構(gòu)建(Build Image)屑埋、單元測試(Testing)、最終發(fā)布(Publish)痰滋。此時整個構(gòu)建過程都基于 Docker 進(jìn)行摘能,構(gòu)建結(jié)果為 Docker Image,并且將最終發(fā)布到 Docker Registry敲街。
在部署階段团搞,部署機(jī)器只需要配置 Docker 環(huán)境,從 Docker Registry 上 Pull Image 進(jìn)行部署聪富。
在服務(wù)容器化之后莺丑,我們可以讓整套持續(xù)部署流水線只依賴 Docker,并不需要為環(huán)境各異的服務(wù)進(jìn)行單獨配置墩蔓。
使用 Docker Compose 運行測試
在整個持續(xù)部署流水線中梢莽,我們需要在持續(xù)集成服務(wù)器上部署服務(wù)、運行單元測試和集成測試奸披。Docker Compose 為我們提供了很好的解決方案昏名。
Docker Compose 可以將多個 Docker Image 進(jìn)行組合。在服務(wù)需要訪問數(shù)據(jù)庫時阵面,我們可以通過 Docker Compose 將服務(wù)的 Image 和 數(shù)據(jù)庫的 Image 組合在一起轻局,然后使用 Docker Compose 在持續(xù)集成服務(wù)器上進(jìn)行部署并運行測試。
上圖描述了 Rails 服務(wù)和 Postgres 數(shù)據(jù)庫的組裝過程样刷。我們只需在項目中額外添加一個 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
構(gòu)建適合團(tuán)隊的持續(xù)部署流水線
當(dāng)我們的代碼提交到代碼倉庫后仑扑,持續(xù)部署流水線應(yīng)該能夠?qū)Ψ?wù)進(jìn)行構(gòu)建、測試置鼻、并最終部署到生產(chǎn)環(huán)境镇饮。
為了讓持續(xù)部署流水線更好的服務(wù)團(tuán)隊,我們通常會對持續(xù)部署流水線做一些調(diào)整箕母,使其更好的服務(wù)于團(tuán)隊的工作流程储藐。例如下圖所示的,一個敏捷團(tuán)隊的工作流程:
通常團(tuán)隊會有業(yè)務(wù)分析師(BA)做需求分析嘶是,業(yè)務(wù)分析師將需求轉(zhuǎn)換成適合工作的用戶故事卡(Story Card)钙勃,開發(fā)人員(Dev)在拿到新的用戶故事卡時會先做分析,之后和業(yè)務(wù)分析師聂喇、技術(shù)主管(Tech Lead)討論需求和技術(shù)實現(xiàn)方案(Kick off)辖源。
開發(fā)人員在開發(fā)階段會在分支(Branch)上進(jìn)行開發(fā),采用 Pull Request 的方式提交代碼,并且邀請他人進(jìn)行代碼評審(Review)同木。在 Pull Request 被評審?fù)ㄟ^之后浮梢,分支會被合并到 Master 分支跛十,此時代碼會被自動部署到測試環(huán)境(Test)彤路。
在 Microservices 場景下,本地很難搭建一整套集成環(huán)境芥映,通常測試環(huán)境具有完整的集成環(huán)境洲尊,在部署到測試環(huán)境之后,測試人員(QA)會在測試環(huán)境上進(jìn)行測試奈偏。
測試完成后坞嘀,測試人員會跟業(yè)務(wù)分析師、技術(shù)主管進(jìn)行驗收測試(User Acceptance Test)惊来,確認(rèn)需求的實現(xiàn)和技術(shù)實現(xiàn)方案丽涩,進(jìn)行驗收。驗收后的用戶故事卡會被部署到生產(chǎn)環(huán)境(Production)裁蚁。
在上述團(tuán)隊工作的流程下矢渊,如果持續(xù)部署流水線僅對 Master 分支進(jìn)行打包、測試枉证、發(fā)布矮男。在開發(fā)階段 (即:代碼還在分支) 時,無法從持續(xù)集成上得到反饋室谚,直到代碼被合并到 Master 并運行構(gòu)建后才能得到反饋毡鉴,通常會造成“本地測試成功,但是持續(xù)集成失敗”的場景秒赤。
因此猪瞬,團(tuán)隊對僅基于 Master 分支的持續(xù)部署流水線做一些改進(jìn)。使其可以支持對 Pull Request 代碼的構(gòu)建:
如上圖所示:
- 持續(xù)部署流水線區(qū)分 Pull Request 和 Master入篮。 Pull Request 上只運行單元測試陈瘦, Master 運行完成全部構(gòu)建并自動將代碼部署到測試環(huán)境。
- 為生產(chǎn)環(huán)境部署引入手動操作崎弃,在驗收測試完成之后再手動觸發(fā)生產(chǎn)環(huán)境部署甘晤。
經(jīng)過調(diào)整后的持續(xù)部署流水線可以使團(tuán)隊在開發(fā)階段快速從持續(xù)集成上得到反饋,并且對生產(chǎn)環(huán)境的部署有更好的控制饲做。
版本化一切
版本化一切线婚,即將服務(wù)開發(fā)、部署相關(guān)的系統(tǒng)都版本化控制盆均。我們不僅將項目代碼納入版本管理塞弊,同時將項目相關(guān)的服務(wù)、基礎(chǔ)設(shè)施都進(jìn)行版本化管理。
對于一個服務(wù)游沿,我們一般會為它單獨配置持續(xù)部署流水線饰抒,為它配置獨立的用于運行的基礎(chǔ)設(shè)施。此時會涉及兩個非常重要的技術(shù)實踐:
- 構(gòu)建流水線即代碼
- 基礎(chǔ)設(shè)施即代碼
構(gòu)建流水線即代碼诀黍。通常我們使用 Jenkins 或者 Bamboo 來搭建配置持續(xù)部署流水線袋坑,每次創(chuàng)建流水線需要手動配置,這些手動操作不易重用眯勾,并且可讀性很差枣宫,每次對流水線配置的改動并不會保存在歷史記錄中,也就是說我們無從追蹤配置的改動吃环。
在今年上半年也颤,團(tuán)隊將所有的持續(xù)部署流水線從 Bamboo 遷移到了 BuildKite,BuildKite 對構(gòu)建流水線即代碼有很好的支持郁轻。下圖描述了 BuildKite 的工作方式:
在 BuildKite 場景下翅娶,我們會在每個服務(wù)代碼庫中新增一個 pipeline.yml 來描述構(gòu)建步驟。構(gòu)建服務(wù)器(CI Service)會從項目的 pipeline.yml 中讀取配置好唯,生成構(gòu)建步驟竭沫。例如,我們可以使用如下代碼描述流水線:
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 ) 分別是具體的構(gòu)建腳本替蛉,這些腳本被放在一個公共的 shared_ci_script 代碼庫中贯溅,shared_ci_script 會以 git submodule 的方式被引入到每個服務(wù)代碼庫中。
經(jīng)過構(gòu)建流水線即代碼方式的改造躲查,對于持續(xù)部署流水線的任何改動都會在 Git 中被追蹤它浅,并且有很好的可讀性。
基礎(chǔ)設(shè)施即代碼镣煮。對于一個基于 HTTP 協(xié)議的 API 服務(wù)基礎(chǔ)設(shè)施可以是:
- 用于部署的機(jī)器
- 機(jī)器的 IP 和網(wǎng)絡(luò)配置
- 設(shè)備硬件監(jiān)控服務(wù)(CPU姐霍,Memory 等)
- 負(fù)載均衡(Load Balancer)
- DNS 服務(wù)
- AutoScaling Service (自動伸縮服務(wù))
- Splunk 日志收集
- NewRelic 性能監(jiān)控
- PagerDuty 報警
這些基礎(chǔ)設(shè)施我們可以使用代碼進(jìn)行描述,AWS Cloudformation 在這方面提供了很好的支持典唇。我們可以使用 AWS Cloudformation 設(shè)計器或者遵循 AWS Cloudformation 的語法配置基礎(chǔ)設(shè)施镊折。下圖為一個服務(wù)的基礎(chǔ)設(shè)施構(gòu)件圖,圖中構(gòu)建了上面提到的大部分基礎(chǔ)設(shè)施:
在 AWS Cloudformation 中介衔,基礎(chǔ)設(shè)施描述代碼可以是 JSON 文件恨胚,也可以是 YAML 文件。我們將這些文件也放到項目的代碼庫中進(jìn)行版本化管理炎咖。
所有對基礎(chǔ)設(shè)施的操作赃泡,我們都通過修改 AWS Cloudformation 配置進(jìn)行修改寒波,并且所有修改都應(yīng)該在 Git 的版本化控制中。
由于我們采用代碼描述基礎(chǔ)設(shè)施升熊,并且大部分服務(wù)遵循相通的部署流程和基礎(chǔ)設(shè)施俄烁,基礎(chǔ)設(shè)施代碼的相似度很高。 DevOps 團(tuán)隊會為團(tuán)隊創(chuàng)建屬于自己的部署工具來簡化基礎(chǔ)設(shè)施配置和部署流程级野。
容器化一切
通常在部署服務(wù)時页屠,我們還需要一些輔助服務(wù),這些服務(wù)我們也將其容器化勺阐,并使用 Docker 運行卷中。下圖描述了一個服務(wù)在 AWS EC2 Instance 上面的運行環(huán)境:
在服務(wù)部署到 AWS EC2 Instance 時矛双,我們需要為日志配置收集服務(wù)渊抽,需要為服務(wù)配置 Nginx 反向代理。
按照 12-factors 原則议忽,我們基于 fluentd懒闷,采用日志流的方式處理日志。其中 logs-router 用來分發(fā)日志栈幸、splunk-forwarder 負(fù)責(zé)將日志轉(zhuǎn)發(fā)到 Splunk 愤估。
在容器化一切之后,我們的服務(wù)啟動只需要依賴 Docker 環(huán)境速址,相關(guān)服務(wù)的依賴也可以通過 Docker 的機(jī)制運行玩焰。
總結(jié)
Microservices 給業(yè)務(wù)和技術(shù)的擴(kuò)展性帶來了極大的便利,同時在組織和技術(shù)層面帶來了極大的挑戰(zhàn)芍锚。由于在架構(gòu)的演進(jìn)過程中昔园,會有很多新服務(wù)產(chǎn)生,持續(xù)部署是技術(shù)層面的挑戰(zhàn)之一并炮,好的持續(xù)部署實踐和準(zhǔn)則可以讓團(tuán)隊從基礎(chǔ)設(shè)施抽離出來默刚,關(guān)注與產(chǎn)生業(yè)務(wù)價值的功能實現(xiàn)。