Skip to content

zongwu's blog

使用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 最后加上:

apply from: 'docker.gradle'

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

添加:

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

都是一些基础配置。


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

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

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,我们只需要简单配置:

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:


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)更快。。。

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:

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

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

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

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

gradle pushImage

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