本篇概览
本文通过实战演示了如何将一个基于gradle构建的springboot应用制作成docker镜像,相关的软件版本信息如下:
- 操作系统:macOS Big Sur 11.2.2
- JDK:1.8.0_211
- gradle:6.8.3
- docker:20.10.5
- springboot:2.4.4
新建java工程
为了更接近实际项目,本次实战的java工程为多模块的父子结构:
- 新建名为java-demo的工程,其build.gradle内容如下:
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'
- 新建名为democlient的模块作为二方库,对外提供服务时,数据结构和接口都放在这里,其build.gradle内容如下:
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()
}
- 新建名为demowebapp的模块,这是个springboot应用,其build.gradle内容如下:
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()
}
- demowebapp的模块中的启动类是DemoWebAppApplication.java:
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);
}
}
- 新增一个controller类,后面用来验证服务是否正常:
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项目;
构建镜像实战
- 在demowebapp目录下新建Dockerfile文件,可见非常简单,仅仅指定了帐号和群组,以及复制镜像所需文件:
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"]
- 准备完毕,现在可以开始制作镜像了,第一步是编译构建整个项目,在java-demo目录下执行以下命令编译构建项目:
chmod x gradlew && ./gradlew build
- 编程成功后,需要把jar中的内容提取出来(就是Dockerfile中COPY命令所需的那些文件):
mkdir -p demowebapp/build/dependency
&& (cd demowebapp/build/dependency; jar -xf ../libs/*.jar)
- 去demowebapp/build/dependency看看,内容已经准备好了:
- 执行以下命令即可构建镜像,镜像的tag是当前的年月日时分秒:
cd demowebapp
&& docker build
-t bolingcavalry/demowebapp-docker:`date " %Y%m%d%H%M%S"` .
- 等待执行完毕,控制台提示信息如下:
(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
- 检查本地镜像,新建的如下图红框所示:
验证
- 执行以下命令即可启动镜像(镜像名字请按照您的实际情况修改):
docker run --rm -p 8080:8080 bolingcavalry/demowebapp-docker:20210406080915
- 浏览器访问地址java-demo,如下图,可以正常工作:
参考资料:
官方说明文档:https://spring.io/guides/gs/spring-boot-docker/
- 至此,gradle构建的springboot应用制作成docker镜像的操作就完成了,如果您正在将自己的应用做成docker镜像,希望本文能给您一些参考;