目录

SpringBoot打包部署最佳实践

{{https://avatar-static.segmentfault.com/116/471/1164713654-5f22adf620393_big64?nolink&32}}**wls1036**发布于 2019-09-15

lg.php

spring boot介绍

Spring Boot目前流行的java web应用开发框架,相比传统的spring开发,spring boot极大简化了配置,并且遵守约定优于配置的原则即使0配置也能正常运行,这在spring中是难以想象的。spring boot应用程序可以独立运行,框架内嵌web容器,使得web应用程序可以像本地程序一样启动和调试,十分的方便,这种设计方式也使得spring boot应用程序非常适合容器化进行大规模部署。生态方面,spring boot提供了非常丰富的组件,目前流行的java web框架基本都有spring boot版本,生态十分庞大,是目前java web开发最好的方案。

spring boot部署问题

Springboot应用程序有两种运行方式

两种方式应用场景不一样,各有优缺点

jar包运行

通过maven插件spring-boot-maven-plugin ,在进行打包时,会动态生成jar的启动类org.springframework.boot.loader.JarLauncher ,借助该类对springboot应用程序进行启动。

优点

缺点

war包运行

以war包方式运行,通过maven插件spring-boot-maven-plugin 进行相关配置后,最终生成一个可运行在tomcat,weblogic等java web容器中的war包。

优点

缺点

在实际的项目中,并没有哪一种方式是最好的,根据客户不同的需求制定不同的部署方案,比如有些客户比较看中管理功能,要求数据源和tomcat相关配置必须由管理员进行管理,那么选择war包方式,有些客户希望借助容器化进行大规模部署,那么jar方式更适合。不管选择哪种方式,在部署时都会遇到下面的问题

早期碰到这些问题,都是人工解决,不仅效率十分低下,部署一次都需要十几分钟,而且很容易出错,一百次出错一次算是概率低了,但是生产出错一次都是重大事件,所以我们也在思考如何通过自动化解决以上问题,如何将开发和部署分离,开发人员只关心开发,开发完提交代码,打包和部署都是后台透明的完成。以下就是我们的解决方案。

打包

war包打包问题解决

spring boot打war包的步骤如下

''<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    ...
    <packaging>war</packaging>
    ...
</project>''
''<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
</dependency>''
''public class DemoApplication extends SpringBootServletInitializer{
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
   return builder.sources(DemoApplication.class);
    }
}''

每打包一次都要修改pom.xml 和启动类,打包完再修改回来,十分的繁琐,因为,我们提出以下整改方案

shell脚本打包过程为

  1. 备份当前启动类的java代码。
  2. 将war包启动类的代码替换掉当前启动类的代码。
  3. maven指定pom-war.xml 文件进行打包。
  4. 打包结束后恢复启动类文件。

以下就是参考脚本

app-war.sh

''#!/usr/bin/env bash

v1=src/main/java/com/definesys/demo/DemoApplication.java
v2=war/DemoApplication.java
v3=war/DemoApplication-bak.java

cp -rf $v2 $v1

mvn clean package -Dmaven.test.skip=true -f war-pom.xml

#recovery
cp -rf $v3 $v1''

通过预先配置好pom文件和启动类文件,开发人员只要运行app-war.sh 脚本无需修改任何文件即可生成war包。

更优的方案

以上方案pom文件和启动类文件都需要预先准备好,未实现完全的自动化,通过优化方案做到完全自动化。

多模块打包

这里的多模块指的是maven中的多模块,项目工程中的代码多模块,一个项目按功能划分模块后,在创建工程时一般也按照功能层面上的模块进行创建,这样避免一个模块代码过于庞大,也利于任务的分工,但打包却更麻烦了。

通过优化项目结构解决以上问题

''mvn clean install
cd start
mvn clean package''

目录结构如下

''.
├── pom.xml
├── role
│  ├── pom.xml
│   └── src
│       ├── main
│       │   ├── java
│       │   │   └── com
│       │   │       └── definesys
│       │   │     └── demo
│       │   │               └── controller
│       │   │                   └── RoleController.java
│       │   └── resources
├── start
│   ├── pom.xml
│   ├── src
│   │   ├── main
│   │   │   ├── java
│   │   │   │   └── com
│   │   │   │       └── definesys
│   │   │   │           └── demo
│   │   │   │               └── DemoApplication.java
│   │   │   └── resources
│   │   │       └── application.properties
└── user
    ├── pom.xml
    └── src
        └── main
            ├── java
            │   └── com
            │       └── definesys
            │           └── demo
            │               └── controller
            │                   └── UserController.java
            └── resources
''

start pom.xml

''<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>blog0915</artifactId>
        <groupId>com.definesys.demo</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>start</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.definesys.demo</groupId>
            <artifactId>user</artifactId>
            <version>1.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.definesys.demo</groupId>
            <artifactId>role</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>''

父项目 pom.xml

''<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <packaging>pom</packaging>
    <modules>
        <module>user</module>
        <module>role</module>
        <module>start</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.RELEASE</version>
        <relativePath/>
    </parent>

    <groupId>com.definesys.demo</groupId>
    <artifactId>blog0915</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

</project>''

子项目 pom.xml

''<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>blog0915</artifactId>
        <groupId>com.definesys.demo</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>role</artifactId>
    <version>1.0.0</version>
</project>''

配置文件问题

spring boot提供spring.profiles.active 指定配置文件,但生产环境有时候客户出于安全考虑不提供配置信息给开发人员,而是预先将配置文件上传到服务器指定路径,程序需要在运行时去引用该配置文件,如果运行环境是kubernetes ,则会提供一个config map作为配置文件,这时候就要求spring boot程序读取外部配置文件。

<blockquote>这里讨论的是线上环境配置文件方案,本地调试参考子模块打包相关内容,可以将配置文件统一写在start项目中。</blockquote>

jar包外部配置文件读取

jar运行可以通过指定参数spring.config.location 引用外部文件,命令参考如下:

''java -jar start-1.0-SNAPSHOT.jar --spring.config.location=/Users/asan/workspace/config''

config 目录存放properties配置文件

可以通过配合spring.profiles.active 参数可以指定目录下配置文件,如:

''java -jar start-1.0-SNAPSHOT.jar --spring.profiles.active=prod --spring.config.location=/Users/asan/workspace/config''

</file>

则会读取''/Users/asan/workspace/config/appliction-prod.properties'' 文件作为配置文件。

=== war包外部配置文件读取 ===

以''tomcat'' 为例,需要在tomcat启动时指定''-Dspring.config.location'' 参数,可以设置服务器环境变量''CATALINA_OPTS'' 达到目的。可以编辑用户 prifile文件

<code>''export CATALINA_OPTS=/Users/asan/workspace/config''

同样,也可以通过-Dspring.profiles.active 指定配置文件名称。

容器化

spring boot借助容器化,可以如虎添翼,发挥出更大的威力,也只有通过容器化,才能体会到spring boot开发的高效。通过以上的介绍,你可以很顺利的打好一个jar包或者war包,那么可以通过编写dockerfile文件进行镜像的构建。spring boot在构建镜像时有两个地方需要考虑

app-jar-dockerfile.Dockerfile

<code>FROM openjdk:8-jdk-alpine MAINTAINER definesys.com VOLUME /tmp ADD start-1.0-SNAPSHOT.jar app.jar RUN echo “Asia/Shanghai” > /etc/timezone EXPOSE 8080 ENTRYPOINT [“java”,“-Djava.security.egd=file:/dev/./urandom”,“-jar”,“–spring.config.location=/Users/asan/workspace/config”,“/app.jar”]

</file>

app-war.dockerfile.Dockerfile

<code>FROM tomcat MAINTAINER definesys.com ENV CATALINA_OPTS -Dspring.config.location=file:/middleware/config/ ADD start-1.0-SNAPSHOT.war /usr/local/tomcat/webapps/app.jar RUN echo “Asia/Shanghai” > /etc/timezone EXPOSE 8080 EOF

</file>

部署

早期我们采用的是以下部署过程

1460000020388317

每一次发布都是一个新的镜像,但这种方式有个问题就是如何保证前一个环境验证没问题,后一个环境就一定没问题,因为两个镜像是不一样的,虽然可能两次构建都是基于同一版本代码,但因为是重新构建,中间可能因为各种原因,如maven包版本更新等,无法保证两次构建就是完全一样的镜像。因此我们优化了构建的流程,如下:

1460000020388318

所有的环境都是用同一个镜像,环境之间只有配置文件不同,文件通过configmap或者外部配置文件方式进行挂载,这样保证了配置文件没问题的前提下,每个环境的程序一定是一样的。

jenkins自动打包部署

打包和部署在本地进行也是有问题的,本地jdk版本取决于个人电脑,甚至有黑客污染jdk导致编译的class文件自带后门,个人电脑环境也是随着用户不同操作可能改变,构建出来的包不能保证是稳定的包。因此需要一个远程服务器用于打包和部署,能够实现从源码到镜像过程。jenkins是一个基于java开发的持续集成工具,通过配置插件和编写脚本实现程序从代码到制品再到线上运行的过程。jenkins在spring boot开发中主要完成了以下工作。

jenkins在构建镜像时需要借助docker工具,但jenkins本身也是有docker版本的,所以就面临着docker in docker 的问题,这里选择的方案是用二进制文件安装jenkin而非镜像方式,虽然丧失了docker的便利性,但可以简化docker方案,降低集成的复杂度。