meleto 2019-09-16
Spring Boot目前流行的java web应用开发框架,相比传统的spring开发,spring boot极大简化了配置,并且遵守约定优于配置的原则即使0配置也能正常运行,这在spring中是难以想象的。spring boot应用程序可以独立运行,框架内嵌web容器,使得web应用程序可以像本地程序一样启动和调试,十分的方便,这种设计方式也使得spring boot应用程序非常适合容器化进行大规模部署。生态方面,spring boot提供了非常丰富的组件,目前流行的java web框架基本都有spring boot版本,生态十分庞大,是目前java web开发最好的方案。
Springboot应用程序有两种运行方式
两种方式应用场景不一样,各有优缺点
通过maven插件spring-boot-maven-plugin
,在进行打包时,会动态生成jar的启动类org.springframework.boot.loader.JarLauncher
,借助该类对springboot应用程序进行启动。
以war包方式运行,通过maven插件spring-boot-maven-plugin
进行相关配置后,最终生成一个可运行在tomcat,weblogic等java web容器中的war包。
在实际的项目中,并没有哪一种方式是最好的,根据客户不同的需求制定不同的部署方案,比如有些客户比较看中管理功能,要求数据源和tomcat相关配置必须由管理员进行管理,那么选择war包方式,有些客户希望借助容器化进行大规模部署,那么jar方式更适合。不管选择哪种方式,在部署时都会遇到下面的问题
spring.profiles.active
配置设置不同的环境,但一方面需要人为修改配置文件,只要是人为的就有可能出错,另一方面,客户有时出于安全考虑不会提供生产环境配置信息,那么这时候就无法指定prifiles.active
。玩具
,随处运行的jar包,缺少统一管理,是达不到生产的要求,那么如果从jar包到容器也是一个问题。早期碰到这些问题,都是人工解决,不仅效率十分低下,部署一次都需要十几分钟,而且很容易出错,一百次出错一次算是概率低了,但是生产出错一次都是重大事件,所以我们也在思考如何通过自动化解决以上问题,如何将开发和部署分离,开发人员只关心开发,开发完提交代码,打包和部署都是后台透明的完成。以下就是我们的解决方案。
spring boot打war包的步骤如下
pom.xml
中将打包方式改为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>
spring-boot-starter-tomcat
范围为provided
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency>
SpringBootServletInitializer
public class DemoApplication extends SpringBootServletInitializer{ @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(DemoApplication.class); } }
每打包一次都要修改pom.xml
和启动类,打包完再修改回来,十分的繁琐,因为,我们提出以下整改方案
pom.xml
复制一个pom-war.xml
文件,将pom.xml
修改为war包配置shell脚本打包过程为
pom-war.xml
文件进行打包。以下就是参考脚本
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文件和启动类文件都需要预先准备好,未实现完全的自动化,通过优化方案做到完全自动化。
*Application.java
结尾的文件,作为启动类文件,读取文件名获取类名,通过字符串替换方式动态生成war包启动类文件。这里的多模块指的是maven中的多模块,项目工程中的代码多模块,一个项目按功能划分模块后,在创建工程时一般也按照功能层面上的模块进行创建,这样避免一个模块代码过于庞大,也利于任务的分工,但打包却出现问题。
通过优化项目结构解决以上问题
start
项目。install
后再进入start项目进行打包,脚本参考如下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程序读取外部配置文件。
这里讨论的是线上环境配置文件方案,本地调试参考子模块打包相关内容,可以将配置文件统一写在start项目中。
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
则会读取`/Users/asan/workspace/config/appliction-prod.properties'文件作为配置文件。
以tomcat
为例,需要在tomcat启动时指定-Dspring.config.location
参数,可以设置服务器环境变量CATALINA_OPTS
达到目的。可以编辑用户 prifile文件
export CATALINA_OPTS=/Users/asan/workspace/config
同样,也可以通过-Dspring.profiles.active
指定配置文件名称。
spring boot借助容器化,可以如虎添翼,发挥出更大的威力,也只有通过容器化,才能体会到spring boot开发的高效。通过以上的介绍,你可以很顺利的打好一个jar包或者war包,那么可以通过编写dockerfile文件进行镜像的构建。spring boot在构建镜像时有两个地方需要考虑
UTC
,比北京时间早8小时,需要指定镜像时区。app-jar-dockerfile.Dockerfile
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"]
app-war.dockerfile.Dockerfile
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
早期我们采用的是以下部署过程
每一次发布都是一个新的镜像,但这种方式有个问题就是如何保证前一个环境验证没问题,后一个环境就一定没问题,因为两个镜像是不一样的,虽然可能两次构建都是基于同一版本代码,但因为是重新构建,中间可能因为各种原因,如maven包版本更新等,无法保证两次构建就是完全一样的镜像。因此我们优化了构建的流程,如下:
所有的环境都是用同一个镜像,环境之间只有配置文件不同,文件通过configmap或者外部配置文件方式进行挂载,这样保证了配置文件没问题的前提下,每个环境的程序一定是一样的。
打包和部署在本地进行也是有问题的,本地jdk版本取决于个人电脑,甚至有黑客污染jdk导致编译的class文件自带后门,个人电脑环境也是随着用户不同操作可能改变,构建出来的包不能保证是稳定的包。因此需要一个远程服务器用于打包和部署,能够实现从源码到镜像过程。jenkins是一个基于java开发的持续集成工具,通过配置插件和编写脚本实现程序从代码到制品再到线上运行的过程。jenkins在spring boot开发中主要完成了以下工作。
jenkins在构建镜像时需要借助docker工具,但jenkins本身也是有docker版本的,所以就面临着docker in docker
的问题,这里选择的方案是用二进制文件安装jenkin而非镜像方式,虽然丧失了docker的便利性,但可以简化docker方案,降低集成的复杂度。