當咱們討論微服務架構時,咱們一般會和Monolithic架構(單體架構 )進行比較。html
在Monolithic架構中,一個簡單的應用會隨着功能的增長、時間的推移變得愈來愈龐大。當Monoltithic App變成一個龐然大物,就沒有人可以徹底理解它究竟作了什麼。此時不管是添加新功能,仍是修復Bug,都是一個很是痛苦、異常耗時的過程。node
Microservices架構漸漸被許多公司採用(Amazon、eBay、Netflix),用於解決Monolithic架構帶來的問題。其思路是將應用分解爲小的、能夠相互組合的Microservices。這些Microservices經過輕量級的機制進行交互,一般會採用基於HTTP協議的服務。nginx
每一個Microservices完成一個獨立的業務邏輯,它能夠是一個HTTP API服務,提供給其餘服務或者客戶端使用。也能夠是一個ETL服務,用於完成數據遷移工做。每一個Microservices除了在業務獨立外,也會有本身獨立的運行環境,獨立的開發、部署流程。git
這種獨立性給服務的部署和運營帶來很大的挑戰。所以持續部署(Continuous Deployment)是Microservices場景下一個重要的技術實踐。本文將介紹持續部署Microservices的實踐和準則。github
實踐:docker
準則:數據庫
咱們在構建和發佈服務的時候,不只要發佈服務自己,還須要爲其配置服務器環境。使用Docker容器化微服務,可讓咱們不只發佈服務,同時還發布其須要的運行環境。容器化以後,咱們能夠基於Docker構建咱們的持續部署流水線:ruby
上圖描述了一個基於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"]網絡
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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 Image進行組合。在服務須要訪問數據庫時,咱們能夠經過Docker Compose將服務的Image和數據庫的Image組合在一塊兒,而後使用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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
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
1 2 |
docker-compose run -rm ci bundle exec rake
|
當咱們的代碼提交到代碼倉庫後,持續部署流水線應該可以對服務進行構建、測試、並最終部署到生產環境。
爲了讓持續部署流水線更好的服務團隊,咱們一般會對持續部署流水線作一些調整,使其更好的服務於團隊的工做流程。例以下圖所示的,一個敏捷團隊的工做流程:
一般團隊會有業務分析師(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代碼的構建:
如上圖所示:
版本化一切,即將服務開發、部署相關的系統都版本化控制。咱們不只將項目代碼歸入版本管理,同時將項目相關的服務、基礎設施都進行版本化管理。 對於一個服務,咱們通常會爲它單獨配置持續部署流水線,爲它配置獨立的用於運行的基礎設施。此時會涉及兩個很是重要的技術實踐:
構建流水線即代碼。一般咱們使用Jenkins或者Bamboo來搭建配置持續部署流水線,每次建立流水線須要手動配置,這些手動操做不易重用,而且可讀性不好,每次對流水線配置的改動並不會保存在歷史記錄中,也就是說咱們無從追蹤配置的改動。
在今年上半年,團隊將全部的持續部署流水線從Bamboo遷移到了BuildKite,BuildKite對構建流水線即代碼有很好的支持。下圖描述了BuildKite的工做方式:
在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: production agents: queue: production
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
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: production agents: queue: production
|
在上述配置中,command中的步驟(即:test、docker-tag、deploy)分別是具體的構建腳本,這些腳本被放在一個公共的shared_ci_script代碼庫中,shared_ci_script會以git submodule的方式被引入到每一個服務代碼庫中。
通過構建流水線即代碼方式的改造,對於持續部署流水線的任何改動都會在Git中被追蹤,而且有很好的可讀性。
基礎設施即代碼。對於一個基於HTTP協議的API服務基礎設施能夠是:
這些基礎設施咱們可使用代碼進行描述,AWS Cloudformation在這方面提供了很好的支持。咱們可使用AWS Cloudformation設計器或者遵循AWS Cloudformation的語法配置基礎設施。下圖爲一個服務的基礎設施構件圖,圖中構建了上面提到的大部分基礎設施:
在AWS Cloudformation中,基礎設施描述代碼能夠是JSON文件,也能夠是YAML文件。咱們將這些文件也放到項目的代碼庫中進行版本化管理。
全部對基礎設施的操做,咱們都經過修改AWS Cloudformation配置進行修改,而且全部修改都應該在Git的版本化控制中。
因爲咱們採用代碼描述基礎設施,而且大部分服務遵循相通的部署流程和基礎設施,基礎設施代碼的類似度很高。DevOps團隊會爲團隊建立屬於本身的部署工具來簡化基礎設施配置和部署流程。
一般在部署服務時,咱們還須要一些輔助服務,這些服務咱們也將其容器化,並使用Docker運行。下圖描述了一個服務在AWS EC2 Instance上面的運行環境:
在服務部署到AWS EC2 Instance時,咱們須要爲日誌配置收集服務,須要爲服務配置Nginx反向代理。
按照12-factors原則,咱們基於fluentd,採用日誌流的方式處理日誌。其中logs-router用來分發日誌、splunk-forwarder負責將日誌轉發到Splunk。
在容器化一切以後,咱們的服務啓動只須要依賴Docker環境,相關服務的依賴也能夠經過Docker的機制運行。
Microservices給業務和技術的擴展性帶來了極大的便利,同時在組織和技術層面帶來了極大的挑戰。因爲在架構的演進過程當中,會有不少新服務產生,持續部署是技術層面的挑戰之一,好的持續部署實踐和準則可讓團隊從基礎設施抽離出來,關注與產生業務價值的功能實現。