常州专业做网站公司,小企业网站建设的大品牌,app开发用到的技术,软件项目外包网站基于 SpringBoot 的微服务开发完成之后#xff0c;现在到了把它们发布并部署到相应的环境去运行的时候了。 SpringBoot 框架只提供了一套基于可执行 jar 包#xff08;executable jar#xff09;格式的标准发布形式#xff0c;但并没有对部署做过多的界定#xff0c;而且为… 基于 SpringBoot 的微服务开发完成之后现在到了把它们发布并部署到相应的环境去运行的时候了。 SpringBoot 框架只提供了一套基于可执行 jar 包executable jar格式的标准发布形式但并没有对部署做过多的界定而且为了简化可执行 jar 包的生成SpringBoot 提供了相应的 Maven 项目插件 buildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactId/plugin !-- 其他插件定义 --/plugins
/build 然后只要我们运行 mvn package当前 SpringBoot 项目就会被打包成一个包含了其所有项目依赖以及该项目本身的可执行 jar 包通过 scp 或者 rsync 等方式将这个可执行 jar 包部署到目标环境的服务器之后就可以通过 java-jar your-project.jar 启动 SpringBoot 应用了。 整个流程看起来很简单也很符合大部分开发人员的认知但是相对于一套较为严谨的软件交付流程来说以上流程则难免过于粗糙了。 软件的发布和部署可以有多种不同的形式这更多由软件项目的属性决定比如 这个项目使用的是什么语言这个项目属于类库项目还是可独立运行的项目这个项目是面向什么平台和环境的项目 此外我们希望使用什么样的形式进行软件的交付这里则涉及生态管理以及技术选型的喜好等因素所以为了降低讲解的复杂度我们还是先将发布和部署分开来说吧。 首先大家应该都知道发布并不等于部署这是两个阶段的事情如图 1 所示。 发布与部署示意图 图 1 发布与部署示意图 发布一般是将项目以指定的格式打包成某种可直接交付的形式然后放置到预先指定的交付地点。 比如对于 Java 类库Java Library来说我们一般将其打包成 jar 包然后 mvn deploy 到公司内部的 Maven 仓库中Maven Repository像 Nexus Repository Manager 或者 JFrog Artifactory 以及 Apache Archiva。 而对于可独立运行的程序比如 SpringBoot 微服务或者一般的 Java Standalone 程序我们既可以将它们打包成 RPM、DEB 等面向特定目标系统的发布形式也可以将它们制作成一个个的 docker images然后将制作完成的发布成品存储到相应的仓库中Repository去。 部署一般紧接着发布完成之后进行它的主要职能就是将已经发布好的成品从仓库中拿出来然后分发到目标环境的指定资源池比如物理机结点虚拟机结点docker 宿主机等并最终启动服务。 软件成品分发的手段和工具可以有很多种从最常见的 scp、rsync到 Chef、Puppet进而再到最新的 saltstack、ansible 等一般根据团队对这些工具的把控力度和喜好进行选型。 下面我们就几种典型的发布和部署形式跟大家一起探索相应的实践。 spring-boot-starter 的发布与部署方式 spring-boot-starters属于 Java 类库性质的组件只被其他可独立运行的程序依赖使用自身不可独立运行对于这种性质的软件实体我们一般将其发布到公司内部的软件仓库或者以开源形式发布到 Maven 的中央仓库Maven Central Repository。 下面我们就以 spring-boot-starter-metrics 为例向大家展示如何将类似 spring-boot-starter-metrics 这样的 Java 类库发布到自己公司内部搭建的 Nexus 服务器上。 首先你要有一套已经搭建完成并运行的 Nexus 服务器然后我们需要对 spring-boot-starter-metrics 的 pom.xml 附加一点儿发布相关的内容 ?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersiongroupIdcom.keevol/groupIdartifactIdspring-boot-starter-metrics/artifactIdversion0.0.1-SNAPSHOT/versionpackagingjar/packagingnamespring-boot-starter-metrics/namedescriptionauto configuration module for dropwizard metrics/descriptionparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion1.3.0.RELEASE/versionrelativePath / !-- lookup parent from repository --/parentdistributionManagementrepositoryiddeployment/idnameinternal repository for releases/nameurlhttp://{内部nexus服务器地址}/nexus/content/repositories/ releases//url/repositorysnapshotRepositoryiddeployment/idnameinternal repository for snapshots/nameurlhttp://{内部nexus服务器地址}/nexus/content/repositories/ snapshots//url/snapshotRepository/distributionManagementpropertiesproject.build.sourceEncodingUTF-8/project.build.sourceEncodingjava.version1.8/java.versionmetrics.version3.1.2/metrics.version/properties !--其他配置 --dependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependencydependencygroupIdio.dropwizard.metrics/groupIdartifactIdmetrics-core/artifactIdversion${metrics.version}/version/dependencydependencygroupIdio.dropwizard.metrics/groupIdartifactIdmetrics-annotation/artifactIdversion${metrics.version}/version/dependencydependencygroupIdorg.aspectj/groupIdartifactIdaspectjrt/artifactIdversion1.8.7/version/dependency/dependencies
/project主要关注我们添加的 distributionManagement 相关内容用于将我们的当前项目与内部的 Nexus 服务器进行关联这样就可以将当前项目不同阶段的成品比如 SNAPSHOT 版本或者 RELEASE 版本发布到特定的仓库路径下。 但是只是在项目的 pom.xml 中添加 distributionManagement 相关内容还不够对发布服务器的安全管控等敏感信息不能与 pom.xml 一同公开所以还需要在 ~/.m2/settings.xml 配置文件中添加 Nexus 服务器访问和认证信息 serveriddeployment/idusernamedeployment/usernamepassword********/password
/server因为我们前面 distributionManagement 定义的 repository 和 snapshotRepository 的 id 都是 deployment所以这里的 server 的 id 也是匹配性地指定为 deployment至于 username 和 password则完全是我们内部的 nexus 服务器对应的安全认证用的用户名和密码啦。 将内部 Nexus 服务器的认证信息放到 maven 的 settings.xml 中并非什么好的实践纯粹是为了便利性而牺牲安全性二者之间需要根据情况做出权衡如果对安全性要求比较高的公司或者组织最好将这些认证信息移除并只在管控的范围内使用。 比如将这些认证信息回收到发布和部署平台这一可控的小范围环境中而所有开发人员使用的 settings.xml 属于“消毒”后无安全认证等敏感信息的版本。 当 pom.xml 中的 distributionManagement 以及 settings.xml 中对应的 server 设定都准备好之后我们就可以直接 mvn deploy 将 spring-boot-starter-metrics 或者类似的 Java Library 项目发布到内部 Nexus 仓库了。 对于 Java 类库类型的项目来说并无明确的部署过程如果说有也是存在于可独立运行项目的开发过程中比如使用 lib 目录或者结合 Ant“部署”为项目的依赖或者直接享受 maven、gradle、sbt 等编译工具提供的“透明”的依赖部署过程。 基于RPM的发布与部署方式 部署的目标服务器从硬件到系统软件一般情况下都应该是尽量相同这与软件的标准化目的相同一个是可以减少应对不同类型实体的复杂度另一个就是标准化硬件和软件之后就可以通过工具批量化以“边际成本递减”近乎为 0 的做法来提升效率减少成本。 所以对于大部分互联网公司来说在硬件标准化的基础上还会使操作系统尽量统一比如大多数都是使用稳定性和可靠性经过长期验证过的 Red Hat CentOS 系统而 CentOS 本身经过长期的沉淀也有一套自己的系统管理工具比如像 YUM 或者 RPM 这样的系统包依赖管理器Debian/Ubuntu 等 Linux 发行版也有对应的 deb 形式的包管理器。 像 RPM 这样的包管理器对系统软件包的依赖和配置提供了很好的支持如果我们的微服务等可独立执行实体要部署到像 CentOS 这样的目标环境中使用 RPM 完成微服务的发布和部署对于运维人员来说几乎就是无缝衔接的。 而且对于 SpringBoot 微服务来说单单一个可执行的 jar 包实际上是远远无法达到发布和部署要求的如果只是发布一个可执行的 jar 包那就意味着在部署阶段运维要做更多的事情来弥补某些缺失比如 启动参数是否调整配置文件是否修改安装部署结构如何规范资源的对接和映射要不要做 但是如果我们能够将整个软件交付体系标准化和规范化然后通过 RPM 这样的发布形式将这些标注和规范固化到发布包中那么整个部署过程就可以简化为一条命令这其实也是使用 RPM 这种系统原生包管理工具完成交付部署的好处自动化的 orcherstration减少不必要的人工干预中间环节。 使用 RPM 发布 SpringBoot 微服务我们简单将这一个过程划分为几步如图 2 所示。 使用RPM交付的SpringBoot微服务发布流程图 图 2 使用 RPM 交付的 SpringBoot 微服务发布流程图 首先我们需要有一个特定的编译和项目构建环境可以不管这个编译和项目构建环境是搭建在本地比如你的开发机上还是搭建在特定的一台服务器上但这个编译和项目构建环境需要安装 rpmbuild用来构建 rpm 包。 其次我们不推荐 RPM 编译和构建的过程使用某些定义在项目编译脚本中的插件来完成这样会让一些通用的逻辑散落在所有需要发布的项目中而不好治理。所以我们建议使用一个外部化的独立的编译服务器完成整个 SpringBoot 微服务的 RPM 发布和部署。 SpringBoot 微服务的 rpmbuild 脚本构建过程主要分几个主要步骤如图 2 中 1 所标注 1调用标准的 mvn package 完成可执行 jar 包的打包。 2根据软件交付规范构建标准发布格式的 rpm 包如下所示 使用 bin 目录存放根据脚本模板以及环境变量生成的启停脚本。使用 config 目录SpringBoot 默认文件系统中的配置目录名或者 conf 目录存放特定的配置文件。使用 docs 目录存放文档。使用 agents 目录存放某些 javaagent。…… 3在标准发布格式的基础上生成从标准发布格式到具体目标环境的映射。 比如原来应用的日志是默认打印到当前项目部署目录而根据要求我们希望打印到 /var/logs/{projectId}/ 或者其他服务器磁盘容量分配更大的分区这个时候可以在 rpmbuild 过程中指定安装类似的安装规则。 下面是一个简化的 SpringBoot 微服务的 rpmbuild 脚本定义 Summary: metrics autoconfigure module for spring boot
applicationName: spring-boot-starter-metricsVersion: {version}Release: 1Copyright: ...Group: Applications/ProductivitySource: ...URL: ...Distribution: Vendor: KEEp eVOLution, Inc.Packager: Darren afookeevol.com%description%prep%buildgit clone ....
# 检出代码到本地cd {project folder}mvn package ...%install%clean%files/{install_location}/{projectId}/bin/start.sh/{install_location}
/{projectId}/bin/stop.sh/{install_location}/{projectId}/agents/jolokia-jvm-1.3.1-agent.jar...%attr(755, user, group) /{install_location}/{projectId}/agents/jolokia-jvm-1.3.1-agent.jar...%doc%changelog然后需要调用 rpmbuild 的 spec 定义完成最终 rpm 包的构建 rpmbuild -bb {projectId}.spec#然后 scp or sftp 生成的 rpm 包到指定的 rpm 仓库 我们可以像上面那样直接执行 rpmbuild 命令完成最终的 rpm 包构建也可以将这些逻辑纳入编译构建脚本并部署到像 Jenkins 这样现成的持续集成服务器上总之执行完成后打包好的 rpm 就发布到目标环境对应的 rpm 仓库了。 rpm 包发布到 rpm 仓库之后就可以执行部署比如通过 Salt 或者 ansible 在目标环境执行 rpm 或者 yum 命令但具体的部署行为可能因为不同开发者的习惯和理念而有所不同。 有的开发者喜欢将不同目标环境的配置都一股脑地打包到发布包中然后通过配置文件的命名和启动程序时单独指定一个环境变量来决定如何启用哪一个配置文件对于这种做法只需要打一个 rpm 包同时也只需要搭建一个内部的 rpm 仓库部署的时候则需要运维人员根据具体的操作环境传递相应的环境变量来启停程序。 有的开发者则认为一个软件实体发布的时候就应该是针对目标环境“装配”完备的rpm 包中的各项配置都是针对特定目标环境配置好的只要将 rpm 包部署到目标环境就可以直接启动启停完全无差别操作唯一的差别是rpm 包分别是根据目标环境发布和部署到不同的 rpm 仓库的如图 3 所示。 “根据不同交付目标环境设置不同RPM仓库作为交付地点”示意图 图 3 “根据不同交付目标环境设置不同RPM仓库作为交付地点”示意图 以上两种策略并无优劣之分但却有各自适合的场景 1在团队小以人为本的时候前者更适合原因在于整个软件交付链路更多是通过开发人员来协调和完成的。所以开发、测试、运维一把抓即使是不同环境的配置文件也都是为了开发人员方便直接放到了项目目录下一起管理和修改。 当到了线上开发人员同时担当运维人员的角色启停程序的可控性也很高所以可以在熟知自身程序的前提下很好地完成整个链路的工作。 2随着团队规模的扩张职能更加明确交付链路要承担的关注点也更多的时候为了保证整个软件的交付质量需要引入规范化的流程来关联和约束整个链路上各个环节和团队之间的工作。 这个时候开发人员的职责范围将缩小到明确的范围测试团队、安全团队、应用运维团队等也将加入并根据流程各司其职每个团队之间的工作需要横向关联的同时又需要垂直隔离。 这个时候我们就需要从交付的源头一直到发布和部署根据环境进行隔离每个人即使只关注自己负责的事情也可以让整个软件交付链路很好地工作这考虑的是规范和流程对整体粒度上的把控和支撑。 对于小团队来说微服务并不是什么太好的选择高内聚的应用开发和部署单元对整个交付链路的要求没那么高也不需要更多自动化和平台化层面的投入和支持。 而一旦你选择了微服务的软件交付策略数量庞大的微服务治理将耗费更多资源在支撑整个交付链路的自动化和平台化建设层面。否则如此数量上的差异化的实体管理单纯还靠人工拼苦劳是“捞不着好果子吃的”。 所以我们要标准化和规范化微服务的开发、交付、部署以及运维从而收敛整条链路的治理复杂度以近乎无差别的方式完成各个环节上的工作。这个时候规范、流程、平台是核心对人的要求则适当降低。 说了这么多其实就一点如果团队要转向微服务的交付策略那么标准化、单一化的微服务发布和部署行为是大家努力的方向虽然我们在说基于 RPM 这种特定的微服务发布和部署形式但并不意味着我们只应该关注这一点或者这单一环节只有系统的从整体的微服务交付链路和体系层面考虑才能够在各个单一环节落地的时候选择合适的方案。 基于 RPM 的发布与部署方式就说这么多希望对大家有所启发。 基于 Docker 的发布与部署方式 随着资源虚拟化技术的持续精进一种基于容器container的方式开始风行Docker 就属于当下这个风口上最耀眼的明星而且很多微服务相关的文章也是言必提 Docker好像没有 Docker 的微服务就不是正宗的微服务了所以我们自然也要适当提及一下如何结合 Docker 发布和部署我们的 SpringBoot 微服务。 我们知道RPM 包的构建是使用系统的 rpmbuild 工具完成的该工具需要一个构建描述文件即 .spec 文件rpmbuild 工具读取 .spec 构建描述文件之后根据构建描述文件生成一个 rpm 包然后我们就可以把 rpm 包发布到相应的 RPM 仓库RPM Repository。 使用 Docker 其实也是类似的过程如图 4 所示。 基于Docker的SpringBoot微服务发布流程图 图 4 基于Docker的SpringBoot微服务发布流程图 我们需要提供一个 Dockerfile 用于描述 Docker 发布成品的构建过程这类似于 rpmbuild 需要的 .spec 文件。 在编写好要使用的 Dockerfile 之后我们使用 docker build 命令读取 Dockerfile 开始构建一个 Docker 的 imagedocker build 完成 rpmbuild 类似的功能Docker 的 image 则类似于 rpm 包即 Docker 的软件发布成品。 有了 docker image 之后我们就可以将其发布到一个 Docker 的 image registry这里的 image registry 就类似于 RPM 仓库RPM Repository而将 docker image 发布到 image registry 的过程可以通过 docker push 完成。 所以假设要以 Docker 的形式发布我们的汇率查询 SpringBoot 微服务首先需要编写一个对应的 Dockerfile 来构建相应的 docker image FROM java:8MAINTAINER AFOO afooafoo.meLABEL
groupId...LABEL artifactId...LABEL
version...LABEL ...USER deployerEXPOSE 8080ENV
{key}{value}...VOLUME ...RUN mkdir /{group}/{projectId}/configRUN mkdir /{group}/{projectId}/agentRUN mkdir /{group}/{projectId}/docsRUN mkdir /{group}/{projectId}/libCOPY target/docker-springboot-chapter4-0.0.1-SNAPSHOT.jar /{group}/{projectId}/lib/docker-springboot-chapter4-0.0.1-SNAPSHOT.jarCOPY conf/application.properties /{group}/{projectId}/config/application.propertiesCOPY agent/jolokia-jvm-1.3.3-agent.jar /{group}/{projectId}/jolokia-jvm-1.3.3-agent.jar...ENTRYPOINT [java, ..., -jar, lib/docker-springboot-chapter4-0.0.1-SNAPSHOT.jar] 之后我们就可以使用这个 dockerfile 来进行构建并发布了 $ docker build . -t {groupId}/{artifactId}:{version}$ docker push 基于 Docker 的微服务部署与 RPM 类似都是从发布仓库中拉取发布的成品并在目标环境安装部署一般情况下我们也同样是使用 Salt 或者 Ansible 之类的工具执行如下类似的命令完成基于 Docker 的微服务的部署 $ ansible {cluster} -m shell -a cd /microservices/{groupId}/{artifactId}; docker pull为了简化基于 Docker 的发布和部署流程实际上以上演示的只是单人单微服务项目的方法在讲究集团军作战的微服务场景下我们希望的是能够快速、批量且标准化的形式完成数量巨大的微服务发布和部署。 这就要求不能只盯着单一项目内部去思考如何实现发布和部署而应该将视线从单一项目内部抽取出来以更高的视角来审视如何快速地完成批量微服务的发布和部署。 笔者建议的一个思路是适当地弱化 Docker 属性将发布和部署逻辑外部化到发布脚本中。 外部化后的发布脚本将集中协调 Docker 基础设施要发布的微服务上下文信息以及其他中间步骤将微服务项目与 Docker 挂钩的唯一纽带也仅仅是一个模板化、标注化后的 Dockerfile整个过程如图 5 所示。 外部化的基于Docker的SpringBoot微服务发布脚步逻辑流程图 图 5 外部化的基于Docker的SpringBoot微服务发布脚步逻辑流程图 如此一来对于所有希望以 Docker 形式发布的标准化的微服务来说一套发布脚本即可完成所有微服务的发布和部署而不需要每一个微服务自己去编写 Dockerfile 甚至发布脚本。 上面的 Docker 实践并非 Docker 社区建议的最佳实践方式这里更多只是为了简化说明和对比Docker 背后是一套更为庞大完备的体系比如 Docker 容器的注册和发现容器的编排及调度等功能和系统限于篇幅和内容定位这里不再赘述。 总的来说其实基于 Docker 的微服务发布和部署与其他形式从本质上来说没有太大差别唯一的差别只是各自方案特定的实现不同而已。 大部分人选择和认同 Docker 方案更多是从系统资源利用率以及 Docker 对整个软件交付链路的支撑体系比较完备这些角度考虑的而微服务自身的很多特点以及需求比如隔离、轻量恰好与 Docker 能提供的相互吻合或许这就是二者经常被“相提并论”的原因。 不过对于 Java 应用和微服务来说Docker 能给予的好处可能没有想象的那么多。 © 著作权归作者所有,转载或内容合作请联系作者 喜欢的朋友记得点赞、收藏、关注哦