SpringBoot(2.4)应用制作Docker镜像(Gradle版官方方案)

2021-12-07 10:20:57 浏览数 (1)

本篇概览

本文通过实战演示了如何将一个基于gradle构建的springboot应用制作成docker镜像,相关的软件版本信息如下:

  1. 操作系统:macOS Big Sur 11.2.2
  2. JDK:1.8.0_211
  3. gradle:6.8.3
  4. docker:20.10.5
  5. springboot:2.4.4

新建java工程

为了更接近实际项目,本次实战的java工程为多模块的父子结构:

  1. 新建名为java-demo的工程,其build.gradle内容如下:
代码语言:javascript复制
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter

// gradle自身会用到的相关设置
buildscript {
    // 仓库
    repositories {
        // 本地
        mavenLocal()
        // 中央仓库
        mavenCentral()
        // grandle插件
        maven {
            url 'https://plugins.gradle.org/m2/'
        }
    }

    // 子模块会用到的变量
    ext {
        springBootVersion = '2.4.4'
    }
}

// 插件
plugins {
    id 'java'
    id 'java-library'
    // 有这个声明,子模块可以使用org.springframework.boot插件而无需指定版本,但是apply=false表示当前模块不使用此插件
    id 'org.springframework.boot' version "${springBootVersion}" apply false
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
}

// gradle wrapper指定版本
wrapper {
    gradleVersion = '6.8.3'
}

// 取当前时间
def buildTimeAndDate = OffsetDateTime.now()

// 根据时间生成字符串变量
ext {
    projectVersion = project.version
    buildDate = DateTimeFormatter.ISO_LOCAL_DATE.format(buildTimeAndDate)
    buildTime = DateTimeFormatter.ofPattern('HH:mm:ss.SSSZ').format(buildTimeAndDate)
}


// 针对所有project的配置,包含根项目
allprojects {
    group 'com.bolingcavalry'
    version '1.0-SNAPSHOT'

    apply plugin: 'java'
    apply plugin: 'idea'
    apply plugin: 'io.spring.dependency-management'

    // 制作设置参数的一个闭包,后面compileJava和compileTestJava都会用到
    def compileSetUp = {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
        options.encoding = 'UTF-8'
        options.compilerArgs =  [
                '-Xlint:all', '-Xlint:-processing'
        ]
    }

    compileJava (compileSetUp)

    compileTestJava (compileSetUp)

    // Copy LICENSE
    tasks.withType(Jar) {
        from(project.rootDir) {
            include 'LICENSE'
            into 'META-INF'
        }
    }

    // 生成jar文件时,MANIFEST.MF的内容如下
    jar {
        manifest {
            attributes(
                    'Created-By': "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']})".toString(),
                    'Built-By': 'travis',
                    'Build-Date': buildDate,
                    'Build-Time': buildTime,
                    'Built-OS': "${System.properties['os.name']}",
                    'Specification-Title': project.name,
                    'Specification-Version': project.version,
                    'Specification-Vendor': 'Will Zhao',
                    'Implementation-Title': project.name,
                    'Implementation-Version': project.version,
                    'Implementation-Vendor': 'Will Zhao'
            )
        }
    }

    // 仓库
    repositories {
        // 本地
        mavenLocal()
        // 中央仓库
        mavenCentral()
        // grandle插件
        maven {
            url "https://plugins.gradle.org/m2/"
        }
    }
}

// 类似maven的dependencyManagement,这里将所有jar的版本指定好,子模块在依赖时可以不用指定版本
allprojects { project ->
    buildscript {
        dependencyManagement {
            imports {
                mavenBom "org.springframework.boot:spring-boot-starter-parent:${springBootVersion}"
                mavenBom "org.junit:junit-bom:5.7.0"
            }

            dependencies {
                dependency 'org.projectlombok:lombok:1.16.16'
                dependency 'org.apache.commons:commons-lang3:3.11'
                dependency 'commons-collections:commons-collections:3.2.2'
                dependency 'org.slf4j:slf4j-log4j12:1.7.30'
            }
        }
    }
}

// 坐标信息
group 'com.bolingcavalry'
version '1.0-SNAPSHOT'
  1. 新建名为democlient的模块作为二方库,对外提供服务时,数据结构和接口都放在这里,其build.gradle内容如下:
代码语言:javascript复制
plugins {
    id 'java-library'
}

// 子模块自己的依赖
dependencies {
    api 'org.projectlombok:lombok'
    // annotationProcessor不会传递,使用了lombok生成代码的模块,需要自己声明annotationProcessor
    annotationProcessor 'org.projectlombok:lombok'
    // slf4j的包自己用就行了,不要继承到其他工程中去,否则容易和其他日志包起冲突
    implementation 'org.slf4j:slf4j-log4j12'
    testImplementation('org.junit.jupiter:junit-jupiter')
}

test {
    useJUnitPlatform()
}
  1. 新建名为demowebapp的模块,这是个springboot应用,其build.gradle内容如下:
代码语言:javascript复制
plugins {
    id 'org.springframework.boot'
}

// 用了插件org.springframework.boot之后,jar task会失效,可用bootJar取代
bootJar {
    archiveBaseName = project.name
    archiveVersion = project.version

    manifest {
        attributes(
                'Created-By': "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']})".toString(),
                'Built-By': 'travis',
                'Build-Date': buildDate,
                'Build-Time': buildTime,
                'Built-OS': "${System.properties['os.name']}",
                'Specification-Title': project.name,
                'Specification-Version': projectVersion,
                'Specification-Vendor': 'Will Zhao',
                'Implementation-Title': project.name,
                'Implementation-Version': projectVersion,
                'Implementation-Vendor': 'Will Zhao'
        )
    }
}

// 子模块自己的依赖
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    // 二方库依赖
    implementation project(':democlient')
    // annotationProcessor不会传递,使用了lombok生成代码的模块,需要自己声明annotationProcessor
    annotationProcessor 'org.projectlombok:lombok'
    implementation 'commons-collections:commons-collections'
    implementation 'org.apache.commons:commons-lang3'
    testImplementation('org.junit.jupiter:junit-jupiter')
}

test {
    useJUnitPlatform()
}
  1. demowebapp的模块中的启动类是DemoWebAppApplication.java:
代码语言:javascript复制
package com.bolingcavalry;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoWebAppApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoWebAppApplication.class, args);
    }
}
  1. 新增一个controller类,后面用来验证服务是否正常:
代码语言:javascript复制
package com.bolingcavalry.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;

@RestController
@Slf4j
public class Hello {

    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String hello() {
        log.info("execute hello");
        return "hello "   new Date();
    }
}
  • 工程创建完毕了,可见这是个非常简单且典型的父子结构的springboot项目;

构建镜像实战

  1. 在demowebapp目录下新建Dockerfile文件,可见非常简单,仅仅指定了帐号和群组,以及复制镜像所需文件:
代码语言:javascript复制
FROM openjdk:8-jdk-alpine
# 增加群组和用户
RUN addgroup -S spring && adduser -S spring -G spring
# 指定容器运行时1号进程的用户和群组
USER spring:spring
# 指定镜像的内容的来源位置
ARG DEPENDENCY=build/dependency
# 复制内容到镜像
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app
# 指定启动命令
ENTRYPOINT ["java","-cp","app:app/lib/*","com.bolingcavalry.DemoWebAppApplication"]
  1. 准备完毕,现在可以开始制作镜像了,第一步是编译构建整个项目,在java-demo目录下执行以下命令编译构建项目:
代码语言:javascript复制
chmod  x gradlew && ./gradlew build
  1. 编程成功后,需要把jar中的内容提取出来(就是Dockerfile中COPY命令所需的那些文件):
代码语言:javascript复制
mkdir -p demowebapp/build/dependency 
&& (cd demowebapp/build/dependency; jar -xf ../libs/*.jar)
  1. 去demowebapp/build/dependency看看,内容已经准备好了:
  1. 执行以下命令即可构建镜像,镜像的tag是当前的年月日时分秒:
代码语言:javascript复制
cd demowebapp 
&& docker build 
-t bolingcavalry/demowebapp-docker:`date " %Y%m%d%H%M%S"` .
  1. 等待执行完毕,控制台提示信息如下:
代码语言:javascript复制
(base) zhaoqindeMBP:java-demo zhaoqin$ cd demowebapp 
> && docker build 
> -t bolingcavalry/demowebapp-docker:`date " %Y%m%d%H%M%S"` .
[ ] Building 1.7s (10/10) FINISHED
 => [internal] load build definition from Dockerfile                                                                  0.1s
 => => transferring dockerfile: 542B                                                                                  0.0s
 => [internal] load .dockerignore                                                                                     0.0s
 => => transferring context: 2B                                                                                       0.0s
 => [internal] load metadata for docker.io/library/openjdk:8-jdk-alpine                                               0.0s
 => [1/5] FROM docker.io/library/openjdk:8-jdk-alpine                                                                 0.1s
 => [internal] load build context                                                                                     0.8s
 => => transferring context: 20.05MB                                                                                  0.7s
 => [2/5] RUN addgroup -S spring && adduser -S spring -G spring                                                       1.1s
 => [3/5] COPY build/dependency/BOOT-INF/lib /app/lib                                                                 0.1s
 => [4/5] COPY build/dependency/META-INF /app/META-INF                                                                0.0s
 => [5/5] COPY build/dependency/BOOT-INF/classes /app                                                                 0.0s
 => exporting to image                                                                                                0.1s
 => => exporting layers                                                                                               0.1s
 => => writing image sha256:765d7a01490aaf2bd301e1cee68b3dc90b22768a5e7c957cde280cc2215d2848                          0.0s
 => => naming to docker.io/bolingcavalry/demowebapp-docker:20210406080915
  1. 检查本地镜像,新建的如下图红框所示:

验证

  • 执行以下命令即可启动镜像(镜像名字请按照您的实际情况修改):
代码语言:javascript复制
docker run --rm -p 8080:8080 bolingcavalry/demowebapp-docker:20210406080915
  • 浏览器访问地址java-demo,如下图,可以正常工作:

参考资料:

官方说明文档:https://spring.io/guides/gs/spring-boot-docker/

  • 至此,gradle构建的springboot应用制作成docker镜像的操作就完成了,如果您正在将自己的应用做成docker镜像,希望本文能给您一些参考;

0 人点赞