1、微服务架构的部署本文从以下几个方面简要说明微服务架构项目的实践经验: 架构选型、 开发测试环境下的相关工具支持、人员分工及开发部署流程、相关设计及注意事项。 最后, 将根据实践经验讨论提高微服架构下的开发和运维效率的切实需求, 进一步理清本项目所实现的容器服务管理平台的完善性需求。本项目是一个企业级的容器服务管理平台, 该平台的功能是基于容器实现的应用运行环境管理, 以及应用开发阶段的持续集成和持续发布。 简单的理解该平台的核心功能之一就是管理复杂应用的开发和运维环境,提高微服务架构下的开发和运维效率。项目的开发背景如下:首先,该系统具有典型分布式应用系统特征:该平台所运行的服务器配置不高,例
2、如华为 RH1288 这类低配置服务器,允许硬件失败;系统平台要求可根据实际用户数的规模进行伸缩部署,保证硬件资源的合理利用;由于系统平台之上需要运行若干企业应用的开发和运行环境, 可靠性是非常重要的, 不允许单点失效。其次, 本系统功能复杂, 从架构的角度需要将系统分成多个层次和若干个子系统。 不同的层次、子系统根据具体情况需要采用不同的开发语言,由不同的开发小组完成。第三, 项目组成员由几个城市的异地团队协同开发, 统一的开发环境和协同工具是必不可少的。针对上述项目背景的考虑,本项目选择基于微服务架构进行项目开发。开发、测试、部署使用到的工具集“工欲善其事、必先利其器”,借助适合的流程和相
3、关工具集,才能提高微服务架构下的应用开发效率。本项目利用 DevOPs流程并选用一套相关工具集实现应用开发管理,提高开发、测试、部署的效率。代码库:本项目使用分布式代码库 Gitlab ,它的功能不限于代码仓库,还包括 reviews( 代码审查 ), issue tracking( 问题跟踪 ) 、 wiki 等功能,是代码管理和异地团队沟通、协作工具的首选。Docker 镜像仓库、 Docker :本项目用容器贯穿整个软件开发流程,以容器作为应用发布的载体,应用的开发环境和测试发版环境都运行在 Docker 容器中。对于复杂的开发和运维环境管理 Docker 具有先天的优势, 目前国内外的
4、互联网公司有大多数都已经将 Docker 应用到了他们的开发或者生产环境中了。K8s: 本项目采用 Kubernates 作为容器调度管理的基础环境, 开发环境、 测试环境的 Docker容器都由 K8s 负责调度管理。Jenkins :快速的部署发布离不开老牌持续集成明星 Jenkins ,本项目通过 Jenkins 任务构建代码、将应用打包成 Docker 镜像,最终发布到 K8s 环境中将容器运行起来。Shell 脚本:编写 Shell 脚本将项目打分支、发布应用等开发阶段的配置管理工作自动化,降低运维门槛、提高配置管理和运维的效率。WIKI : Gitlib 上的 WIKI 功能相对简
5、陋,因此项目组选择 dokuwiki 作为异地团队协作和沟通的工具 , 团队成员可以将设计文档、 知识分享文档、 公告信息等信息可以更新到 wiki 上, 便与协同开发。禅道:为了便于开发计划、开发任务和 bug 关联起来,本项目使用禅道进行开发任务和 bug管理。人员分工及开发流程微服务架构应用的开发、 部署的复杂度都是远大于单体式应用的, 靠运维人员手工的配置管理显然是难于应付了。 DevOps 主张以自动化任务处理方式实现软件交付及基础设施更新,可以说是微服务架构应用开发和运维的必要条件,本项目采用 DevOps的理念的开发流程进行开发。实现部署和运维的自动化需要工具, 同时 DevOp
6、s强调软件开发者与其他 IT 员工及管理层间的协作与沟通, 因此明确的人员分工和开发流程是与工具同样重要的因素。 通俗的说, 就是有了工具,大家要知道怎么使用工具,并且愿意使用工具才能真正达到提高研发效率的目的。项目组的主要工作成员无非也是做开发、 测试和系统管理三类工作, 这里只说明与传统的企业应用开发过程中三类人员所做的工作略有不同的工作内容。开发人员:a) 开发者做开发设计,需要将涉及到接口部分设计更新到 wiki 上,供调用者评审和调用。b) 开发者除了编写程序逻辑外,还需要注意编写单元测试用例,因为分布式应用联调相对复杂,先做在编写单服务时做好了测试再联调能够提高开发效率。c) 由于
7、本项目是采用 Docker 容器作为发布载体的,开发者可能需要修改 DockerFile 模板里的部分参数, 便于部署阶段能将编译后的代码打包到镜像中。 相对于传统的开发方式, 这是对开发者额外的要求。让所有开发者懂 Dockerfile 似乎要求也有点高,其实每个子项目中的DockerFile 及脚本一般是在搭建项目框架时,主要系统配置管理员编写的好的模板,若开发人员不懂相关技术,也可以跟配置管理员沟通需求,由配置管理员修改相关文件。测试人员:测试人员的工作没有什么特别,只是需要注意除了每个 Sprint 阶段的测试外,还需要配合开发人员持续集成的测试;系统配置管理人员:一般 DevOps的
8、开发方式是依赖于云基础平台以及自动化发布工具的,因此相对于传统开发方式, 对系统配置管理者的技术要求会比较低。 但是, 我们的项目开发目的就是构建一个能支撑 DevOps 流程的平台,其开发本身还不具备相应的平台基础。因此,我们项目最初的系统配置管理工作是由架构师来做的,主要需要做如下这些事:a) 部署运行项目组开发需要用到公共的服务组件、例如 zookeeper 注册中心、 Docker Registry 镜像仓库、数据库等;b) 为子项目编写在 git 上打分支的脚本,便于测试发版的时候打分支;c) 编写各类型应用发布部署成镜像的 Dockerfile ;d) 制作或者在网上找到现成的开发
9、所需环境的 Docker 镜像,并且 Push 到项目开发使用的私有镜像库中;e) 编写 Shell 脚本实现将子项目打包成 Docker 镜像,并且 Push 到镜像仓库中。f) 在 Jenkins 上配置自动编译或者部署任务,实现持续集成和部署。本文将对项目的开发、 部署联调以及测试发版流程和规范做简要说明, 并提供项目各个阶段使用到的部分自动化脚本工具示例。图 1 项目持续集成和部署流程代码分支管理:如图所示,在 git 上创建的每一个项目都需要至少建立 develop 和 master 两个分支。开发人员只有权限把代码提交到 develop 分支上, 平时的持续集成和联调都从 deve
10、lop 分支上获取代码。 每个 Sprint 阶段测试发版时,配置管理员从 develop 分支上创建一个用于测试的 release分支。 当测试修改 bug 时, 开发人员只把修改后的代码提交到对应的测试 Release 分支上。 当测试版本稳定后,由配置管理员将代码合并到 Master 分支中。自动部署和发布:项目借助于 Shell 脚本、 Dockerfile 、 K8s 配置文件和 Jenkins 任务实现了自动化的持续集成和部署。配置管理员在项目目录下编写的脚本文件结构如图 2 所示。a) 创建分支的 shell 脚本,示例见附件 1;#!/bin/bash if -z “$1“ ;
11、 then cat EOF exit 1 fi DEPLOY_VERSION=$1 RP_FILES=(subproject1/kube-rc.yaml subproject1/pom.xml subproject1/Makefile) if -z $(git branch -a | grep -e /$DEPLOY_VERSION$) ; then git branch $DEPLOY_VERSION git checkout $DEPLOY_VERSION else git checkout $DEPLOY_VERSION git pull fi #替换 k8s 配置文件中环境指向 , 从
12、开发切换到测试#替换掉 pom.xml 文件中的 SNAPSHOT为 release 版本号#替换掉 makefile 中发布的镜像 Tag 的 latest 为 release 版本号for f in $RP_FILES; do sed -i -e “s###g“ -e “s#0.0.1-SNAPSHOT#$DEPLOY_VERSION-SNAPSHOT#g“ -e “s#latest#$DEPLOY_VERSION#g“ $f done git commit -a -m “Create Branch $DEPLOY_VERSION“ git push origin $DEPLOY_VERS
13、ION b) Dockerfile 示例文件,将 Java dubbo 服务发布为镜像为例,示例见附件 2:FROM MAINTAINER zhangsan ENV spring.profiles.active=“production“ ENV JAVA_OPTS=“-Xmx1024m“ RUN mkdir -p /app COPY target/subproject1.war /app/ COPY ./startup.sh /app/ RUN chmod +x /app/startup.sh WORKDIR /app CMD “./startup.sh“ EXPOSE 8080 c) Ma
14、kefile 文件: 包括编译项目、 将项目打包成 Docker 镜像、 将镜像 Push 到镜像仓库、在 K8s 上创建 ReplicationController 、在 K8s 上创建 service 的命令脚本:IMAGE_PREFIX = COMPONENT = subproject1 ifndef BUILD_TAG BUILD_TAG = latest endif IMAGE = $(IMAGE_PREFIX)$(COMPONENT):$(BUILD_TAG) ifndef KUBE_OPS KUBE_OPS = -server=https:/ -namespace=projec
15、t1 endif clean: mvn clean compile: clean mvn -U -DskipTests=true -Dmaven.javadoc.skip=true package #将当前程序打包成 Docker 镜像build: docker build -t $(IMAGE) . #将当前镜像 Push 到镜像仓库push: docker push $(IMAGE) run: docker run -rm -it -e spring.profiles.active=application -p 8080:8080 $(IMAGE) #部署 RelicationContro
16、ller deploy: kubectl create -f kube-rc.yaml $(KUBE_OPS) redeploy: kubectl replace -f kube-rc.yaml $(KUBE_OPS) undeploy: kubectl delete -f kube-rc.yaml $(KUBE_OPS) #创建 service deploy-svc: kubectl create -f kube-svc.yaml $(KUBE_OPS) undeploy-svc: kubectl delete -f kube-svc.yaml $(KUBE_OPS) d) K8s 部署配置
17、文件,创建 ReplicationController 、创建 service 示例见附件 4:#创建 ReplicationController apiVersion: v1 kind: ReplicationController metadata: name: subproject1 spec: replicas: 1 selector: name: subproject1 template: metadata: labels: name: subproject1 spec: containers: - name: subproject1 image: imagePullPolicy:
18、Always env: - name: DUBBO_REGISTRY_ADDRESS value: “kube:/zookeeper:2181“ - name: DUBBO_REGISTRY_REGISTER value: “true“ ports: - containerPort: 8888 #创建 Service apiVersion: v1 kind: Service metadata: name: subproject1 labels: component: subproject1 spec: ports: - port: 8888 nodePort: 16888 selector:
19、name: svc-subproject1 type: NodePor e) 配置管理员在 Jenkins 上配置自动或手动触发的任务,在 jenkins 任务中配置 shell 脚本,可实现应用的一键部署,示例见附件 5。#!/bin/bash -e IMAGE= make compile if $build = “true“ ; then echo “docker build -t $IMAGE“ docker build -t $IMAGE . echo “docker push $IMAGE“ docker push $IMAGE fi if $undeploy = “true“ ;
20、then make undeploy fi if $deploy = “true“ ; then make deploy fi if $deploysvc = “true“ ; then make deploy-svc fi 具体的过程说如下:i. 从 Git 上拉取代码,编译、发布项目;ii. 将编译好的程序包,打包成 Docker 镜像;iii. 将打包好的 Docker 镜像 Push 到镜像仓库;iv. Jenkins 执行 Shell 脚本命令,从镜像仓库拉取镜像在 K8s 环境中创建 pod 和 RC,将应用程序及其运行环境所在的容器在 K8s 平台上运行起来。测试与发版:从图中可
21、以看到,项目的开发环境和测试环境是相互隔离的两套环境。a) 部署在开发环境的应用代码,来自 develop 分支,对应的 Docker 镜像 Tag 用 latest ,供开发人员调试、以及测试人员随时协助做集成测试;b) 部署在测是环境的应用代码,来自每到一个 Sprint 阶段发版测试时配置管理员从develop 分支中打出的测试发版分支,分支名对应的版本号不同,相应的 Docker 镜像的 tag 也会随是版本号改变。测试环境中部署的应用主要用于测试验证。部署联调:项目分为四层:前端 UI 、 WEB层有若干个 web 应用、 Service 层包括若干个分布式服务、基础底层。这里简要说
22、明一下各层之间的调试方式:a) 前端和 Web层联调:前端开发人员本地启动一个 Nginx ,配置 nginx.conf 文件将localhost 代理指向 web server 的地址,即可在本地调试与动态 Web端的交互。b) WEB 层与服务层联调、服务层之间联调、服务层与基础层联调,分为两种方式:本地调试:部署一个专用的 zookeeper 注册中心,开发者可以把本机地址注册到注册中心,供相关人员临时调用服务调试。集成环境调试: 提交代码触发 Jenkins 任务, 将服务打包成容器镜像, 部署到 K8s 上在完整的系统运行环境中联合调试。具体的集成环境编排依赖于 k8s 完成。微服务
23、的分层和服务交互设计关于微服架构的利弊以及设计原则有很多著名的文章有介绍,例如 MarinFowler 的博文 Microservices:a definition of this new architectural term 和来自 DZone community社区的 Microservices in Practice: From Architecture to Deployment 在 InfoQ 等技术网站都有中文翻译, 本文就不对概念和设计原则做过多赘述。 本小节主要是说明关于项目的逻辑分层结构和服务交互方面的设计。本项目遵守以下微服务架构的主要基本原则,但是也会根据具体项目情况有所
24、保留。i. 单一责任原则( Single Responsibility Principle , SRP)ii. 保证微服务设计能支持服务的敏捷 / 独立地开发和部署。图 2 分层结构及通信机制架构分层设计如图 2 所示, 项目的架构是分为四层: 静态 UI 层、 动态 WEB层、 业务服务层、 基础业务层。i. 静态 UI 层, 直接面向用户的操作展示界面, 包括静态 UI 设计和 JS 交互代码, 主要采用Angulars 框架;ii. 动态 WEB层是各业务服务的 “门面” , 根据前端设计的需要调用、 组装业务服务层的 API ,相对来说,这一层变动的频率较高,例如系统需要进行流程优化或者前端 UE改造,相应的都要变更这一层。动态 WEB层采用 Java Spring 或者 python Django 框架实现;