使用Gradle 构建SpringBoot应用的Docker镜像

应用发布流程

通过结合docker容器,目前我们应用的发布流程大致如下:

应用发布流程.png

Gradle脚本构建镜像

我们的工程是:

  • 基于SpringBoot 2.0.3.RELEASE
  • 采用JDK8编译
  • gradle 构建

根据我们发布流程的要求,构建出最终的镜像需要满足几个目标:

  • 尽可能构建体积小的镜像
  • 通过执行简单的gradle命令,构建镜像并push到阿里云的dockerHub仓库
  • 构建的镜像版本,有唯一标识,方便发布。比如版本中含有日期和 最后一次git commit的hash值

我们最终选用了 https://github.com/bmuschko/gradle-docker-plugin 的gradle-docker-plugin来实现构建docker镜像。该插件支持java-applicationspring-boot-application 两种方式,很明显我们选取spring-boot-applicaiton插件。

在原工程的build.gradle 最后加上:

1
apply from: 'docker.gradle'

在build.gradle的同一级目录下新建文件docker.gradle。

添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
buildscript {
repositories {

mavenCentral()
gradlePluginPortal()
}
dependencies {
classpath 'com.bmuschko:gradle-docker-plugin:4.0.1'
}
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: DockerRemoteApiPlugin
apply plugin: com.bmuschko.gradle.docker.DockerSpringBootApplicationPlugin

都是一些基础配置。

1
2
3
4

def projectname = "${project.getName()}"
def dockerVer = getGitVersion()
def port = 8990

这里定义一些全局变量使用。port可以根据实际情况定义。其中getGitVersion()的定义:

1
2
3
4
5
6
7
8
import java.text.SimpleDateFormat
def getGitVersion() {
def logTime = 'git log'.execute() | 'grep Date:'.execute() | 'head -n 1'.execute()
logTime.waitFor()
Date date = new Date(logTime.text.replace("Date:", "").trim())
String pushTime = new SimpleDateFormat("yyyyMMddHHmmss").format(date)
return pushTime + "." + ('git rev-parse --short HEAD'.execute().text.trim())
}

可以看到拼接了git仓库的最后一个commit 的提交时间和hash值。

gradle-docker-plugin 预定义了一些task,我们只需要简单配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
docker { 
url = getDefaultDockerUrl()
registryCredentials {
url = "registry.cn-hangzhou.aliyuncs.com"
username = 'yourRepoUsername'
password = 'yourRepopswd'
}
springBootApplication {
baseImage = 'openjdk:8-alpine'
ports = [port, port]
}

}

getDefaultDockerUrl 是本地docker 的url,plugin对windows、linux、mac都已经有了相应实现。

registryCredentials 是docker Hub的认证信息配置。 阿里云的dockerHub配置如上。

springBootApplication 只需要配置baseImage 和端口映射。可以看到我们基于openjdk:8-alpine ,alpine linux 镜像只有4.4M,不过 openjdk:8-alpine镜像体积赫然有103MB那么大了。

配置这些就可以构建镜像,但是有些小问题需要解决。

  1. openjdk:8-alpine` 默认是标准时区,而不是+8时区,需要修改。
  2. 该插件对私有docker仓库的tag支持存在bug,需要特殊处理。

接下来我们需要重写其中的 createDockerFile,buildImage,PushImage Task 来解决这两个问题。

其中 createDockerFile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

task createDockerfile(type: Dockerfile) {
dependsOn dockerSyncArchive
from(docker.springBootApplication.baseImage.get())
copyFile(bootWar.archiveName, "/app/${bootWar.archiveName}".toString())
//https://wiki.alpinelinux.org/wiki/Setting_the_timezone
//https://github.com/gliderlabs/docker-alpine/issues/428
runCommand(' echo \'http://mirrors.ustc.edu.cn/alpine/v3.8/main\' > /etc/apk/repositories ' +
' && echo \'http://mirrors.ustc.edu.cn/alpine/v3.8/community\' >>/etc/apk/repositories ' +
' && apk update && apk add tzdata ' +
' && ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime ' +
' && echo "Asia/Shanghai" > /etc/timezone ' +
' && date '+
' && rm -rf /var/cache/apk/* ' +
' && rm -rf /usr/local/share/.cache')
entryPoint("java")
defaultCommand("-jar", "/app/${bootWar.archiveName}".toString())
exposePort(docker.springBootApplication.ports)


}

上面主要是创建Dockerfile,编写指令来配置合适的timezone。使用中科大的apk安装源(mirrors.ustc.edu.cn)更快。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
task buildImage(type: DockerBuildImage) {
dependsOn createDockerfile
inputDir = createDockerfile.destFile.get().asFile.parentFile
tag = "zongwu233/${projectname}:${dockerVer}".toLowerCase()
}

//https://github.com/bmuschko/gradle-docker-plugin/issues/209
task dockerTag(type: com.bmuschko.gradle.docker.tasks.image.DockerTagImage) {
dependsOn buildImage
imageId = "zongwu233/${projectname}:${dockerVer}".toLowerCase()
tag = "${dockerVer}".toLowerCase()
repository = "registry.cn-hangzhou.aliyuncs.com/zongwu233/${projectname}".toLowerCase()
}

解决私有DockerHub 的tag问题。更详细的讨论见https://github.com/bmuschko/gradle-docker-plugin/issues/209

其中zongwu233 是在阿里云DockerHub中的命名空间,${projectname} 是仓库名称。

最后是push task:

1
2
3
4
task pushImage(type: DockerPushImage) {
dependsOn dockerTag
imageName = "registry.cn-hangzhou.aliyuncs.com/zongwu233/${projectname}".toLowerCase()
}

另外要在createDockerFile,buildImage 等task之前 加上import 声明:

1
2
3
4
import com.bmuschko.gradle.docker.DockerRemoteApiPlugin
import com.bmuschko.gradle.docker.tasks.image.*
import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage
import com.bmuschko.gradle.docker.tasks.image.DockerPushImage

这样,在控制台输入命令:

1
gradle pushImage

即可完成应用的docker镜像构建并且push到阿里云仓库。