Spring Boot 应用 Docker 化 《Spring Boot 2.0极简教程》(陈光剑)

Spring Boot 应用 Docker 化

《Spring Boot 2.0极简教程》(陈光剑)
—— 基于 Gradle + Kotlin的企业级应用开发最佳实践

前面的章节中,我们都是在IDE环境中开发运行测试 Spring Boot 应用程序。在开发测试发布整个软件生命周期的过程中,我们通常需要完成打包部署发布到日常、预发、线上机器运行等运维相关工作。
本章前半部分介绍 Spring Boot 应用的打包和部署,后半部分重点介绍如何使用 Docker 来构建部署运行 Spring Boot 应用。

1.1 准备工作

首先,使用http://start.spring.io/ 创建一个打包方式为 war 的 Spring Boot Kotlin 应用,采用 Gradle 构建。点击 Generate Project 等待创建完毕,下载 zip 包,导入 IDEA 中。可以看到,相比于项目打成jar 包方式,打成 war 包的项目中多了一个用于初始化Servlet的ServletInitializer类。代码如下

class ServletInitializer : SpringBootServletInitializer() {      override fun configure(application: SpringApplicationBuilder) : SpringApplicationBuilder {         return application.sources(DemoPackageAndDeployApplication::class.java)     }  }

我们知道Spring Boot 默认集成了内嵌web容器(例如 Tomcat、Jetty 等),这个时候,Spring Boot 应用支持“一键启动”,像一个普通Java程序一样,从main函数入口开始启动。现在,我们是将项目打包成war包,放到独立的web容器中。
而如果我们这个 war 包中没有配置Spring MVC 的 DispatcherServlet 的 web.xml 文件或者初始化 Servlet的类,那么这个 war 包就不会被 Tomcat识别启动 。这个时候,我们需要告诉 Tomcat 这个 war 包的启动入口。而SpringBootServletInitializer就是来完成这件事情的。
通过重写configure (SpringApplicationBuilder) 方法,使用SpringApplicationBuilder 来配置应用程序的sources类。为了测试应用运行的效果,我们在DemoPackageAndDeployApplication.kt 中添加HelloWorld REST接口方便测试

@SpringBootApplication open class DemoPackageAndDeployApplication  fun main(args: Array<String>) {     runApplication<DemoPackageAndDeployApplication>(*args) }  @RestController class HelloWorld {     @GetMapping(value = ["", "/"])     fun hello(): Map<String, Any> {         val result = mutableMapOf<String, Any>()         result["msg"] = "Hello,World"         result["time"] = Date()         return result     } }

1.2 项目打包成可执行 jar

在 IDEA 的右边的 Gradle 工具栏中列出了 Gradle 构建项目的命令,如下图

图16-1 Gradle 构建项目的命令
我们可以直接点击 bootJar 把项目打成 jar 包。当然,在运维部署脚本中通常使用命令行: gradle bootJar 。执行日志如下

17:44:21: Executing task 'bootJar'...  :compileKotlin UP-TO-DATE :compileJava NO-SOURCE :processResources UP-TO-DATE :classes UP-TO-DATE :bootJar UP-TO-DATE  BUILD SUCCESSFUL in 1s 3 actionable tasks: 3 up-to-date 17:44:22: Task execution finished 'bootJar'.

执行完毕,我们可以在项目的build/libs 目录下看到打好的 jar 包,如下图所示

图16-2 项目的build/libs 目录下打好的 jar 包
然后,我们就可以直接使用 java –jar 命令执行该 jar 包了
$ java -jar build/libs/demo_package_and_deploy-0.0.1-SNAPSHOT.jar
此时,我们浏览器访问 http://127.0.0.1:8080/ , 可以看到输出

{   "msg": "Hello,World",   "time": "2018-02-09T09:38:31.933+0000" }

不过,使用java –jar 命令行来启动系统的这种方式
java -jar build/libs/demo_package_and_deploy-0.0.1-SNAPSHOT.jar
只要控制台关闭,服务就不能访问了。我们可以使用nohup 与 & 命令让进程在后台运行:
nohup java -jar build/libs/demo_package_and_deploy-0.0.1-SNAPSHOT.jar &

1.3 定制配置文件启动应用

我们也可以在启动的时候选择读取不同的配置文件。例如,在项目src/main/resources 目录下面有不同环境下的配置文件。如下图所示:

图16-3 不同环境的属性配置文件
其中,application-dev.properties中配置服务器端口号为9000:

server.port=9000

执行 bootJar重新打jar 包,执行下面的命令:

java -jar  build/libs/demo_package_and_deploy-0.0.1-SNAPSHOT.jar        --spring.profiles.active=dev

可以看到应用成功启动,并监听9000端口:

… o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 9000 (http) with context path '' 2018-02-09 18:18:47.336  INFO 69156 --- [           main] .e.s.d.DemoPackageAndDeployApplicationKt : Started DemoPackageAndDeployApplicationKt in 6.493 seconds (JVM running for 7.589)

1.4 项目打包成 war 包

在上面创建的项目中,Gradle 构建配置文件 build.gradle 内容如下:

buildscript {     … } … apply plugin: 'war' … configurations {     providedRuntime } dependencies {     …     providedRuntime('org.springframework.boot:spring-boot-starter-tomcat') }

其中,apply plugin: 'war' 是使用 war 插件来完成项目的打包工作。
直接使用 gradle bootWar,即可把项目打成 war包。然后,就可以像普通J2EE项目一样部署到web容器。同样的,war 包的路径默认也是放在 build/libs 下面。
另外,如果下面这行代码还在:

@SpringBootApplication open class DemoPackageAndDeployApplication  fun main(args: Array<String>) {     runApplication<DemoPackageAndDeployApplication>(*args) }

项目打成的war包,依然支持java –jar 运行:

$ java -jar build/libs/demo_package_and_deploy-0.0.1-SNAPSHOT.war 

这个 war 包很不错,既可以直接扔到 Tomcat 容器中执行,也可以直接命令行启动运行。
提示:项目打 war包的示例项目源代码:https://github.com/EasySpring...

1.5 Spring Boot应用运维

本节简单介绍一些 Spring Boot 应用的生产运维的一些内容。

1.5.1 查看JVM参数的值

使用命令:

ps -ef|grep java 

拿到对于Java程序的pid (第2列):

  501 69156 68678   0  6:18PM ttys002    0:21.59 /usr/bin/java -jar build/libs/demo_package_and_deploy-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev

可以根据java自带的jinfo命令:

jinfo -flags 69156

来查看jar 启动后使用的是什么gc、新生代、老年代,分批的内存都是多少,示例如下:

$ jinfo -flags 69156 Attaching to process ID 69156, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.40-b25 Non-default VM flags: -XX:CICompilerCount=3 -XX:InitialHeapSize=134217728 -XX:MaxHeapSize=2147483648 -XX:MaxNewSize=715653120 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=44564480 -XX:OldSize=89653248 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC 

其中的参数简单说明如表16-1所示。

表16-1 JVM参数
参数说明
-XX:CICompilerCount
最大的并行编译数
-XX:InitialHeapSize 和 -XX:MaxHeapSize
指定JVM的初始堆内存和最大堆内存大小
-XX:MaxNewSize
JVM堆区域新生代内存的最大可分配大小
-XX:+UseParallelGC
垃圾回收使用Parallel收集器
我们可以在 Java 命令行中配置我们需要的JVM参数指标。

提示:更多关于 JVM 选项参数配置参考:http://www.oracle.com/technet...

1.5.2 应用重启

要想重启应用,要首先找到该应用的 java进程,然后kill掉 java 进程。完成这个逻辑的shell 脚本如下:

kill -9 $(ps -ef|grep java|awk '{print $2}')

然后,再使用命令行重新启动应用即可。

1.6 使用 Docker 构建部署运行Spring Boot应用

本节介绍如何使用 Docker 来构建部署 Spring Boot 应用。

1.6.1 Docker 简介

Docker 是一个Go语言开发的开源的轻量级应用容器引擎,诞生与2013年。Docker的核心概念是:镜像、容器、仓库。关键字是: 分布式应用(distributed applications), 微服务( microservices), 容器( containers ), 虚拟化(docker virtualization)。
Docker容器“轻量级”的含义主要是跟传统的虚拟机方式的对比而言。如下图所示:

图16-4 Docker “轻量级”容器VS.传统的虚拟机方式
传统的虚拟机技术是在硬件层面实现虚拟化,需要额外的虚拟机管理软件跟虚拟机操作系统这层。而 Docker 是在操作系统层面上的虚拟化,直接使用的是本地操作系统资源,因此更加轻量级。
Docker 的主要目标是通过对应用组件的封装、分发、部署、运行等生命周期的管理,做到“一次封装,到处运行”。
Docker 是实现微服务( microservices )应用程序开发的理想选择。开发、部署和回滚都将变成“一键操作”。传统的在服务器上进行各种软件包的安装、环境配置、应用程序的打包部署、启动进程等零散的运维操作——被更高层次的“抽象”,放到了一个“集装箱”中,我们只是“开箱即用”。Docker把交付运行环境比作“海运”:OS如同一个货轮,每一个在OS上运行的软件都如同一个集装箱,用户可以通过标准化手段自由组装运行环境,同时集装箱的内容可以由用户自定义,也可以由专业人员制造——这样交付一个软件,就是一系列标准化组件集的交付,如同乐高积木,用户只需要选择合适的积木组合,最后个标准化组件就是给用户的应用程序。这就是基于docker的PaaS()产品的原型。
一个完整的Docker有以下几个部分组成:

 DockerClient客户端
 Docker Daemon守护进程
 Docker Image镜像
 DockerContainer容器
 在docker的网站上介绍了使用docker的典型场景:
 Automating the packaging and deployment of applications(应用打包部署自动化)
 Creation of lightweight, private PAAS environments(创建轻量、私有的PaaS环境)
 Automated testing and continuous integration/deployment(实现自动化测试和持续的集成/部署)
 Deploying and scaling web apps, databases and backend services(部署与扩展web app、数据库和后端服务)

由于Docker 基于LXC的轻量级虚拟化的特点,相比 KVM 之类虚拟机而言,最明显的特点就是启动快,资源占用小(轻量级)——这正是构建隔离的标准化的运行环境,轻量级的PaaS,构建自动化测试和持续集成环境,以及一切可以横向扩展的应用等场景的最佳选择。
提示:更多关于 Docker 的介绍参考: https://docs.docker.com 。Dockers Github 项目空间是:https://github.com/docker

1.6.2 环境搭建

本小节介绍如何搭建 Docker 环境。
安装 Docker
去 docker 官网 https://docs.docker.com/install/ 下载对应的操作系统上的安装包。安装完毕,打开Docker运行,可以看到Mac 系统菜单栏上的显示的 Docker 应用信息如下

图16-5 Mac 系统菜单栏上的 Docker 图标
想知道 docker 提供了哪些命令行操作吗?执行docker help即可看到一个详细的命令说明。例如,在命令行查看 Docker 版本信息:

$ docker version Client:  Version: 17.12.0-ce  API version: 1.35  Go version:  go1.9.2  Git commit:  c97c6d6  Built: Wed Dec 27 20:03:51 2017  OS/Arch: darwin/amd64  Server:  Engine:   Version:  17.12.0-ce   API version:  1.35 (minimum version 1.12)   Go version: go1.9.2   Git commit: c97c6d6   Built:  Wed Dec 27 20:12:29 2017   OS/Arch:  linux/amd64   Experimental: false 

查看详细的 docker 信息

$ docker info Containers: 0  Running: 0  Paused: 0  Stopped: 0 Images: 1 Server Version: 17.12.0-ce …

从仓库 pull Java 环境镜像
使用sudo docker pull java命令从 Docker 官方仓库获取 Java 运行环境镜像:

$ sudo docker pull java Password: Using default tag: latest latest: Pulling from library/java ... bb9cdec9c7f3: Pull complete  Digest: sha256:c1ff613e8ba25833d2e1940da0940c3824f03f802c449f3d1815a66b7f8c0e9d Status: Downloaded newer image for java:latest

下载完毕之后,可以通过docker images命令查看镜像列表:

$ docker images REPOSITORY TAG     IMAGE ID            CREATED         SIZE Java       latest  d23bdf5b1b1b        12 months ago   643MB

可以看到,本地镜像中已经有了 java 运行环境。

1.7 Spring Boot 项目 Docker化实战

本节介绍如何把上面的 Spring Boot 项目 Docker 容器化。过程主要分为如下3步:
1)添加 docker构建插件。
2)配置Dockerfile文件创建自定义的镜像。
3)构建Docker镜像。
下面我们就来分别详细介绍。

1.7.1 添加 docker 构建插件

在 Gradle 项目构建配置文件build.gradle 中添加com.palantir.docker插件:

buildscript {     ext {         kotlinVersion = '1.2.20'         springBootVersion = '2.0.0.RC1'     }     repositories {         // gradle-docker plugin repo         maven { url "https://plugins.gradle.org/m2/" }         ...     }     dependencies {         ...         classpath('gradle.plugin.com.palantir.gradle.docker:gradle-docker:0.17.2')     } }   apply plugin: 'com.palantir.docker'  ...  docker {     name "${project.group}/${jar.baseName}"     files jar.archivePath     buildArgs(['JAR_FILE': "${jar.archiveName}"]) }

其中,buildArgs(['JAR_FILE': "${jar.archiveName}"]) 中配置的'JAR_FILE': "${jar.archiveName}" 是我们的 Spring Boot 项目打成 jar包的名称,会传递到Dockerfile文件中使用(下一步骤中将会看到)。
提示:关于Docker 插件com.palantir.docker的介绍参考文档: https://github.com/palantir/g...

这个插件发布在https://plugins.gradle.org/m2...,所以我们添加 maven 仓库的依赖

    repositories {         // gradle-docker plugin repo         maven { url "https://plugins.gradle.org/m2/" }         ...     }

gradle-docker提供的版本有:
https://plugins.gradle.org/m2...

1.7.2 配置 Dockerfile 文件创建自定义的镜像

Dockerfile文件放置在项目根目录:

图16-6 Dockerfile文件放置在项目根目录
Dockerfile文件内容如下:

FROM java:latest VOLUME /tmp ARG JAR_FILE ADD ${JAR_FILE} app.jar ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

配置构建参数JAR_FILE,这里的JAR_FILE是在 build.gradle 中buildArgs中配置的

docker {     name "${project.group}/${jar.baseName}"     files jar.archivePath     buildArgs(['JAR_FILE': "${jar.archiveName}"]) }  ADD ${JAR_FILE} app.jar

将文件${JAR_FILE}拷贝到docker container的文件系统对应的路径app.jar

ENTRYPOINT ["java", "-Djava.security.egd= file:/dev/./urandom", "-jar", "/app.jar"]

Docker container启动时执行的命令。注意:一个Dockerfile中只能有一条ENTRYPOINT命令。如果多条,则只执行最后一条。

-Djava.security.egd=file:/dev/./urandom

配置 JRE 使用非阻塞的 Entropy Source。SecureRandom generateSeed 使用 /dev/random 生成种子。但是 /dev/random 是一个阻塞数字生成器,如果它没有足够的随机数据提供,它就一直等,这迫使 JVM 等待。通过在 JVM 启动参数中配置这么一行:-Djava.security.egd=file:/dev/./urandom 解决这个阻塞问题。

Dockerfile是一个文本格式的配置文件,我们可以使用Dockerfile文件快速创建自定义的镜像。Dockerfile支持的丰富的运维指令。这些指令分为4部分:

 基础镜像信息
 维护者信息
 镜像操作指令
 容器启动时的执行指令
...

1.7.5 启动 Docker 应用镜像运行

直接在命令行执行:

$ docker run -p 8080:9000 -t com.easy.springboot/demo_package_and_deploy

即可启动我们构建发布在 Docker 镜像仓库中的Spring Boot 应用镜像了。

1.7.6 端口映射

我们的 Spring Boot 应用镜像运行在 Docker容器沙箱环境中,端口号是9000,作为外部Host OS环境要访问这个服务, 需要添加TCP端口映射:把本机8080端口映射到 Docker 容器端口9000,如下图所示:

图16-7 把本机8080端口映射到 Docker 容器端口9000
其中:
 -p 是将容器的端口9000映射到 docker 所在操作系统的端口8080;
 -t 是打开一个伪终端,以便后续可以进入查看控制台 log。
使用 docker ps 命令查看运行中的容器:

$ docker ps CONTAINER ID        IMAGE                                         COMMAND                  CREATED             STATUS              PORTS                    NAMES 36fbfaf05359        com.easy.springboot/demo_package_and_deploy   "java -Djava.securit…"   25 minutes ago      Up 25 minutes       0.0.0.0:8080->9000/tcp   infallible_kare

……
然后,执行 push 命令即可:

$ docker push com.easy.springboot/demo_package_and_deploy

提示:本节项目源代码:https://github.com/EasySpring...

1.8 本章小结

本章简单介绍了Spring Boot项目的打包、分环境运行、生产运维等操作。通常,在企业项目实践中,会实现一套 Spring Boot应用部署发布的自动化运维平台工具。本章还给出了一个完整的 Spring Boot项目 Docker 化的实战案例。
经过前面的学习,相信您已经对如何使用基于 Kotlin 编程语言的 Spring Boot项目开发有了一个比较好的掌握。

脚本宝典为你提供优质服务
脚本宝典 » Spring Boot 应用 Docker 化 《Spring Boot 2.0极简教程》(陈光剑)

发表评论

提供最优质的资源集合

立即查看 了解详情