当咱们讨论微服务架构时,咱们一般会和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给业务和技术的扩展性带来了极大的便利,同时在组织和技术层面带来了极大的挑战。因为在架构的演进过程当中,会有不少新服务产生,持续部署是技术层面的挑战之一,好的持续部署实践和准则可让团队从基础设施抽离出来,关注与产生业务价值的功能实现。