Jenkins+Docker(docker-compose、Dockerfile)+Gitee实现自动化部署
项目目录结构
project-root/
├── pom.xml
├── docker
│ ├── copy.sh
│ ├── file
│ │ ├── jar
│ │ │ └── 存放执行copy.sh以后jar包的位置
│ │ └── Dockerfile
│ └── docker-compose.yml
├── docker-only-test
│ ├── src
│ ├── target
│ └── pom.xml
├── docker-maven-test
│ ├── src
│ ├── target
│ └── pom.xml
└── docker-compose-test├── src├── target└── pom.xml
写在前文
本文主要是做一个总结,包含三个内容:
一:单独的使用docker+maven插件实现自动化编译;
二:使用docker+maven插件结合jenkins实现自动化编译部署;
三:基于docker-compose+Dockerfile结合jenkins实现自动化编译部署;
本文默认已经安装好了gitee、jenkins、Docker、并且已经将项目上传到Gitee上(如果没有安装好的,可以查看我上一篇...)
案例一和案例二,在上一篇已经做了详细说明,安装Jenkins并配置等操作,不做详解,本文主要做一个总结,有需要直接看上一篇。
关于本文的案例三,jenkins项目采用maven版本,通过Jenkins执行shell脚本指令去操作docker-compose读取docker-compose.yml文件,在文件中读取Dockerfile内容,然后通过docker-compose来build;
上一篇位置:Jenkins+Gitee+Docker容器化部署-CSDN博客
案例一:仅使用Docker+Maven插件实现自动化编译
创建项目-模块docker-only-test
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.lanting</groupId><artifactId>docker-test</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>docker-only-test</artifactId><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><!-- 添加maven命令:clean package -Pprod,test docker:removeImage docker:build --><!-- 添加maven命令:clean install -Dmaven.test.skip=true docker:removeImage docker:build --><!-- 添加maven命令:clean package -Pprod,test,dev -Dmaven.test.skip=true docker:removeImage docker:build --><groupId>com.spotify</groupId><artifactId>docker-maven-plugin</artifactId><version>1.2.2</version><configuration><!-- <imageName>${docker.image.prefix}/app</imageName>--><!-- 镜像名称:[DockerTest:1.0-SNAPSHOT]。镜像名称必须小写...否在要报错。会报错:on project DockerTest: Exception caught: Request error: DELETE ... 400, body: {"message":"invalid reference format: repository name (library/DockerTest) must be lowercase"}: HTTP 400 Bad Request -> [Help 1] --><imageName>${project.artifactId}:${project.version}</imageName><!-- 基于这个镜像构建镜像 --><baseImage>my-jdk17:17</baseImage><!-- JAR 文件路径:docker-test-1.0-SNAPSHOT.jar 必须位于容器的根目录(/)--><!-- <entryPoint>["java","-jar","/${project.build.finalName}.jar"]</entryPoint>--><!-- 下面sh -c 这个命令可以解析JVM(-Xmx、-Xms等)参数,不然我们只能使用默认JVM参数,即在docker启动中可以使用“-e JAVA_OPTS="-Xms=512m -Dlanting.name=张三"”来设置启动参数,不然系统无法通过“@Value(${lanting.name}})”解析参数, --><entryPoint>["sh", "-c","java $JAVA_OPTS -jar /${project.build.finalName}.jar \"$@\""]</entryPoint><!-- <dockerDirectory>src/main/docker</dockerDirectory>--><!--<dockerDirectory>${project.basedir}/src/main/resources/docker</dockerDirectory>--><!-- 制作的镜像放在哪里 --><dockerHost>http://ip:2375</dockerHost><resources><resource><targetPath>/</targetPath><directory>${project.build.directory}</directory><include>${project.build.finalName}.jar</include></resource></resources></configuration></plugin></plugins></build>
</project>
项目内容
可以不使用一致的。
application.yml
server:port: ${SERVER_PORT:8090}
#Spring Boot 的 Profile 激活优先级(从高到低):
### 命令行参数 (--spring.profiles.active=prod)
### 环境变量 (SPRING_PROFILES_ACTIVE=prod)
### JVM 参数 (-Dspring.profiles.active=prod)
### application.yml 中的静态配置
### 默认 Profile(如果没有设置)
spring:profiles:# 注意大小写...。配置了这个就可以在docekr run中传入
# active: ${SPRING_PROFILES_ACTIVE:dev}active: dev2application:name: @project.artifactId@
application-dev.yml
lanting:application:key: only-dev版本
DockerOnlyController.java
package com.lanting.only.controller;import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.RuntimeMXBean;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;@RestController
@RequestMapping("/only")
public class DockerOnlyController {@Value("${lanting.application.key}")private String key;@Value("${lanting.name.args}")private String args;@Value("${lanting.name.env}")private String env;@Value("${lanting.name.jvm}")private String jvm;@Value("${spring.application.name}")private String appName;@Value("${spring.profiles.active:Unknown}")private String activeProfile;private StringBuilder builder = new StringBuilder();@PostConstructpublic void init() {builder.append("args:").append(args).append("\n").append("key:").append(key).append("\n").append("env:").append(env).append("\n").append("jvm:").append(jvm).append("\n").append("appName:").append(appName).append("\n").append("ARGS_1:").append(System.getenv("ARGS_1")).append("\n").append("activeProfile:").append(activeProfile).append("\n").append("System.getenv(\"SPRING_PROFILES_ACTIVE\"):").append(System.getenv("SPRING_PROFILES_ACTIVE")).append("\n");System.out.println(builder);}@GetMapping("/get/{name}")public String getKey(@PathVariable("name") String name) {return builder.append(name).toString();}@GetMapping("/jvm")public Map<String, Object> getJvmDetails() {Map<String, Object> details = new LinkedHashMap<>();// 1. 获取内存信息MemoryMXBean memoryMxBean = ManagementFactory.getMemoryMXBean();details.put("HeapMemory", memoryMxBean.getHeapMemoryUsage());details.put("NonHeapMemory", memoryMxBean.getNonHeapMemoryUsage());// 2. 获取GC信息List<GarbageCollectorMXBean> gcMxBeans = ManagementFactory.getGarbageCollectorMXBeans();List<String> gcInfo = gcMxBeans.stream().map(bean -> bean.getName() + " (count=" + bean.getCollectionCount() + ", time=" + bean.getCollectionTime() + "ms)").collect(Collectors.toList());details.put("GarbageCollectors", gcInfo);// 3. 获取运行时参数(包含-X -XX参数)RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();details.put("JVMArguments", runtimeMxBean.getInputArguments());// 4. 获取系统属性(-D参数)details.put("SystemProperties", runtimeMxBean.getSystemProperties());return details;}
}
配置Jenkins
注意:本模块仅使用项目直接添加mvn命令编译即可将编译好的docker镜像jar上传到远程docker,无需使用Jenkins和Gitee;
但是此种方法只能实现自动编译,无法实现自动部署,要实现自动部署,需要如下操作:
注意本文此案例只做记录,具体操作参考上一篇:
Step1、git配置、触发时机Triggers等不做介绍;
Step2、配置Maven的Build
Root POM:docker-only-test/pom.xml(找到子模块下面的具体某一个pom.xml)
Goals and options:clean package -Dmaven.test.skip=true docker:removeImage docker:build
Step3、编写Post Steps>>>Execute shell
#!/bin/bash
cd $WORKSPACE
echo "当前工作目录WORKSPACE:$WORKSPACE"
# 固定写死 - 激活环境/对外暴露的端口
# 与上面的mvn命令保持激活的一致即可。不要使用-P激活多个环境
# clean package -Pprod -Dmaven.test.skip=true docker:removeImage docker:build
ACTIVE_PROFILE=prod
CONTAINER_NAME=only-web-$ACTIVE_PROFILE # 容器名称
# 子模块名称
MODEL_NAME=docker-only-test
# 对外暴露的端口,默认和容器启动端口一样---需要和server.port保持一致。
HOST_IMAGE_PORT=8090# 切换目录
cd $MODEL_NAME
echo "切换后的目录:$(pwd)"
IMAGE_NAME=$(mvn help:evaluate -Dexpression=project.artifactId -q -DforceStdout):$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)# 停止并删除旧容器
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; thenecho "停止并删除容器: $CONTAINER_NAME"docker stop $CONTAINER_NAME >/dev/null 2>&1docker rm $CONTAINER_NAME >/dev/null 2>&1
fi# 清理悬空镜像(可选)
docker image prune -f >/dev/null 2>&1# 启动新容器
echo "启动容器: $CONTAINER_NAME (镜像: $IMAGE_NAME) (对外端口:$HOST_IMAGE_PORT,容器内端口:$HOST_IMAGE_PORT)(激活环境:$ACTIVE_PROFILE)"
# 设置环境优先级:(前面的会覆盖后面的,也就是最后生效的是prod)
### -Dspring.profiles.active=prod
### >>> SPRING_PROFILES_ACTIVE=dev
### >>> --spring.profiles.active=test
### >>> mvn命令通过-Ptest,dev,prod指定的,默认为第一个test
### >>> pom.xml通过activeByDefault指定的dev# 以下会ACTIVE_PROFILE对应的prod生效:
docker run -d \
--name $CONTAINER_NAME \
--log-driver json-file \
--log-opt max-size=10m \
-p $HOST_IMAGE_PORT:$HOST_IMAGE_PORT \
-e ARGS_1="DockerOnly" \
-e LANTING_NAME_ARGS="Args参数DockerOnly" \
-e LANTING_NAME_ENV="Env参数DockerOnly" \
-e SPRING_PROFILES_ACTIVE="test" \
-e SERVER_PORT=$HOST_IMAGE_PORT \
-e JAVA_OPTS="-Xms128m -Xmx128m -Dlanting.name.jvm=JVM参数DockerOnly -Dspring.profiles.active=$ACTIVE_PROFILE -XX:+UseZGC -XX:MaxMetaspaceSize=128m -XX:NewSize=128m -XX:MaxNewSize=128m -XX:SurvivorRatio=6 -XX:MaxTenuringThreshold=15 -XX:+AlwaysPreTouch" \
$IMAGE_NAME \
--spring.profiles.active=devsleep 5
echo "🔍 查看容器日志(尾部):"
docker logs $CONTAINER_NAME --tail=50
案例二:使用docker+maven插件结合jenkins实现自动化编译部署
本案例与案例一的区别主要是在于“本案例的多环境配置是在pom.xml环境中的profile中进行配置,而案例一是在application.yml中”
创建项目-模块docker-maven-test
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.lanting</groupId><artifactId>docker-test</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>docker-maven-test</artifactId><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><!-- 添加maven命令:clean package -Pprod,test docker:removeImage docker:build --><!-- 添加maven命令:clean install -Dmaven.test.skip=true docker:removeImage docker:build --><!-- 添加maven命令:clean package -Pprod,test,dev -Dmaven.test.skip=true docker:removeImage docker:build --><groupId>com.spotify</groupId><artifactId>docker-maven-plugin</artifactId><version>1.2.2</version><configuration><!-- <imageName>${docker.image.prefix}/app</imageName>--><!-- 镜像名称:[DockerTest:1.0-SNAPSHOT]。镜像名称必须小写...否在要报错。会报错:on project DockerTest: Exception caught: Request error: DELETE ... 400, body: {"message":"invalid reference format: repository name (library/DockerTest) must be lowercase"}: HTTP 400 Bad Request -> [Help 1] --><imageName>${project.artifactId}-${my.profiles.active}:${project.version}</imageName><!-- 基于这个镜像构建镜像 --><baseImage>my-jdk17:17</baseImage><!-- JAR 文件路径:docker-test-1.0-SNAPSHOT.jar 必须位于容器的根目录(/)--><!-- <entryPoint>["java","-jar","/${project.build.finalName}.jar"]</entryPoint>--><!-- 下面sh -c 这个命令可以解析JVM(-Xmx、-Xms等)参数,不然我们只能使用默认JVM参数,即在docker启动中可以使用“-e JAVA_OPTS="-Xms=512m -Dlanting.name=张三"”来设置启动参数,不然系统无法通过“@Value(${lanting.name}})”解析参数, --><entryPoint>["sh", "-c","java $JAVA_OPTS -jar /${project.build.finalName}.jar \"$@\""]</entryPoint><!-- <dockerDirectory>src/main/docker</dockerDirectory>--><!--<dockerDirectory>${project.basedir}/src/main/resources/docker</dockerDirectory>--><!-- 制作的镜像放在哪里 --><dockerHost>http://ip:2375</dockerHost><resources><resource><targetPath>/</targetPath><directory>${project.build.directory}</directory><include>${project.build.finalName}.jar</include></resource></resources></configuration></plugin><!-- <plugin><groupId>com.spotify</groupId><artifactId>dockerfile-maven-plugin</artifactId><version>1.4.2</version><configuration><repository>docker_storage/${project.artifactId}</repository><buildArgs><JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE></buildArgs></configuration></plugin>--><!-- 这个如果不配置,只能在application.yml中只能通过@...@带入,docker又无法通过@...@带入 --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-resources-plugin</artifactId><configuration><delimiters><!-- 使用${..}作为占位符 --><delimiter>${*}</delimiter></delimiters><!-- 同时还支持使用默认的占位符(@..@) --><useDefaultDelimiters>true</useDefaultDelimiters></configuration></plugin></plugins><!-- 要使用下面的多环境,就需要配置 --><resources><resource><directory>src/main/resources</directory><filtering>true</filtering> <!-- 启用资源过滤 --><includes><include>**/application*.yml</include> <!-- 过滤所有环境配置文件 --></includes></resource></resources></build><!-- 多环境配置:可以在application.yml中使用@profiles.active@获取,在jenkins脚本中也可以使用@server.port@获取端口 --><profiles><!-- 开发环境 --><profile><id>dev</id><properties>
<!-- <profiles.active>dev</profiles.active>--><my.profiles.active>dev</my.profiles.active><server.port>10084</server.port> <!-- 开发环境端口 --></properties><activation><activeByDefault>true</activeByDefault> <!-- 默认激活 --></activation></profile><profile><id>dev2</id><properties>
<!-- <profiles.active>dev</profiles.active>--><my.profiles.active>dev2</my.profiles.active><server.port>10085</server.port> <!-- 开发环境端口 --></properties></profile><!-- 生产环境 --><profile><id>prod</id><properties>
<!-- <profiles.active>prod</profiles.active>--><my.profiles.active>prod</my.profiles.active><server.port>10086</server.port> <!-- 生产环境端口 --></properties></profile><!-- 测试环境 --><profile><id>test</id><properties>
<!-- <profiles.active>test</profiles.active>--><my.profiles.active>test</my.profiles.active><server.port>10087</server.port> <!-- 生产环境端口 --></properties></profile></profiles>
</project>
项目内容
项目内容,可以不一样。
application.yml
通过maven的pom.xml配置的多环境选择,在application.yml中通过配置“MY_PROFILES_ACTIVE”来读取pom.xml中的profile对象中的<my.profiles.active>XX</my.profiles.active>属性的值;
server: # 可以不指定,直接读取yml对应的端口即可
# port: @server.port@ # 读取的是固定的值,在maven编译时,我们通过-Pxxx指定编译环境,默认读取的是第一个的值,无法根据后续“active”选择的环境进行更改。port: ${server.port} # 读取的是固定的值,在maven编译时,我们通过-Pxxx指定编译环境,默认读取的是第一个的值,无法根据后续“active”选择的环境进行更改。
# port: ${SERVER_PORT} # 使用环境变量来覆盖 -e SERVER_PORT=xxx
#Spring Boot 的 Profile 激活优先级(从高到低):
### 命令行参数 (--spring.profiles.active=prod)
### 环境变量 (SPRING_PROFILES_ACTIVE=prod)
### JVM 参数 (-Dspring.profiles.active=prod)
### application.yml 中的静态配置
### 默认 Profile(如果没有设置)
spring:profiles:# 注意大小写...。配置了这个就可以在docekr run中传入active: ${my.profiles.active}
# active: ${MY_PROFILES_ACTIVE} # 可以通过启动时设置环境变量覆盖
# active: @profiles.active@
# active: ${profiles.active:dev}
# active: ${SPRING_PROFILES_ACTIVE:dev}application:name: @project.artifactId@
application-dev.yml版本
# 如果使用这个就无法在maven命令中通过:“IMAGE_PORT=$(mvn help:evaluate -P$ACTIVE_PROFILE -Dexpression=server.port -q -DforceStdout)”获取maven激活的端口
#server:
# port: 10084
lanting:application:key: maven-dev版本
注意事项
如何选择pom.xml的profiles?
可以在编译时,通过传递“-P”参数来指定,如果只传递一个参数“-Ptest”,那么maven在编译的时候会将“MY_PROFILES_ACTIVE”的值设置为固定的test;
如果传递N个参数“-Ptest,prod,...”,那么maven在编译的时候会将“test,prod,...”打包进jar包,后续需要通过指定“--spring.profiles.active”或者JVM参数或者启动参数或者环境变量来选择具体的环境。
多环境编译时的注意事项
如果我们在Jenkins中配置了编译命令为:clean package -Ptest,dev2,dev,prod -Dmaven.test.skip=true docker:removeImage docker:build 我们在打包编译的时候,设置了多个“-Ptest,prod”,那么我们在执行maven命令时 “docker:removeImage”时,系统会默认获取第一个“test”(如果我们在编译时test,prod,dev等环境的镜像image名称根据环境的不同而不同时,会删除-P的第一个环境的镜像) 。所以,要解决这个问题,我们针对于每一个环境都单独设置-P,或者将镜像名称设置为一样的也可以。 本项目的镜像images名称会根据选择的环境不同后缀不同,所以会出现这个问题。
启动端口和运行时端口不一致
此时注意一个问题,如果我们在编译时使用了“mvn clean package -Ptest....”,然后我们在运行时通过“--spring.profiles.active=prod”设置了运行时环境为prod时,此时可能会出现运行时的目标端口不一致但是里面的内容却是prod的内容这个问题。(也就是如果test指定端口为9999,lanting.name=zhangsan;而prod指定的端口为9998,lanting.name=lisi时,我们通过mvn clean package -Ptest...编译后的包在运行时的端口应该是9999而里面的内容lanting=lisi的这种情况。)
编译环境和运行时环境
之所以会有这个问题,是因为环境指定不一致导致(也就是编译环境和运行时环境不一致导致)
编译环境和运行时环境:Maven 的资源过滤(Resource Filtering)是在编译阶段进行的,而 spring.profiles.active 是运行时参数。
@server.port@是Maven的资源过滤功能实现的,也就是我们在执行mvn命令时,Maven 会将 @server.port@ 替换为当前激活的 Maven profile 中定义的值。
而spring.profiles.active 是运行时参数变量,对 Maven 编译时的行为没有任何影响。
如果期望在运行时通过 spring.profiles.active 动态控制 server.port 的值,但 @server.port@ 已经在编译阶段被固定成了某个值(比如 dev 对应的 8090),无法再变了。
实现端口隔离
1、可以使用“${server.port:8080}”搭配在application-XXX.yml中定义不同的“server.port: xxx”来控制即可。但是这种方法在后续操作中无法通过“IMAGE_PORT=$(mvn help:evaluate -P$ACTIVE_PROFILE -Dexpression=server.port -q -DforceStdout)动态获取maven激活的端口”
2、依然在maven中通过<server.port>来设置对应的端口,但是我们可以在后续的启动命令中添加-e SERVER_PORT来覆盖端口,或者在执行“mvn package -P命令时只激活一个环境,后续就不单独设置环境”
DockerMavenController.java
package com.lanting.maven.controller;import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.RuntimeMXBean;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;@RestController
@RequestMapping("/maven")
public class DockerMavenController {@Value("${lanting.application.key}")private String key;@Value("${lanting.name.args}")private String args;@Value("${lanting.name.env}")private String env;@Value("${lanting.name.jvm}")private String jvm;@Value("${spring.application.name}")private String appName;@Value("${spring.profiles.active:Unknown}")private String activeProfile;private StringBuilder builder = new StringBuilder();@PostConstructpublic void init() {builder.append("args:").append(args).append("\n").append("key:").append(key).append("\n").append("env:").append(env).append("\n").append("jvm:").append(jvm).append("\n").append("appName:").append(appName).append("\n").append("ARGS_1:").append(System.getenv("ARGS_1")).append("\n").append("activeProfile:").append(activeProfile).append("\n").append("System.getenv(\"SPRING_PROFILES_ACTIVE\"):").append(System.getenv("SPRING_PROFILES_ACTIVE")).append("\n");System.out.println(builder);}@GetMapping("/get/{name}")public String getKey(@PathVariable("name") String name) {return builder.append(name).toString();}@GetMapping("/jvm")public Map<String, Object> getJvmDetails() {Map<String, Object> details = new LinkedHashMap<>();// 1. 获取内存信息MemoryMXBean memoryMxBean = ManagementFactory.getMemoryMXBean();details.put("HeapMemory", memoryMxBean.getHeapMemoryUsage());details.put("NonHeapMemory", memoryMxBean.getNonHeapMemoryUsage());// 2. 获取GC信息List<GarbageCollectorMXBean> gcMxBeans = ManagementFactory.getGarbageCollectorMXBeans();List<String> gcInfo = gcMxBeans.stream().map(bean -> bean.getName() + " (count=" + bean.getCollectionCount() + ", time=" + bean.getCollectionTime() + "ms)").collect(Collectors.toList());details.put("GarbageCollectors", gcInfo);// 3. 获取运行时参数(包含-X -XX参数)RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();details.put("JVMArguments", runtimeMxBean.getInputArguments());// 4. 获取系统属性(-D参数)details.put("SystemProperties", runtimeMxBean.getSystemProperties());return details;}
}
配置Jenkins
本例和案例一一样,Jenkins的各类配置,安装等不介绍,可以参考上一篇文章;
Step1、创建项目
Step2、配置git
Step3、配置拉取方式
Step4、编译命令Build
Root POM:docker-maven-test/pom.xml(子模块的具体的pom.xml)
Goals and options:clean package -Pprod -Dmaven.test.skip=true docker:removeImage docker:build
Step5、执行脚本Post Steps
#!/bin/bash
cd $WORKSPACE
echo "当前工作目录WORKSPACE:$WORKSPACE"
# 固定写死 - 激活环境/对外暴露的端口
# 与上面的mvn命令保持激活的一致即可。不要使用-P激活多个环境
# clean package -Pprod -Dmaven.test.skip=true docker:removeImage docker:build
ACTIVE_PROFILE=prod
CONTAINER_NAME=maven-web-$ACTIVE_PROFILE # 容器名称
# 子模块名称
MODEL_NAME=docker-maven-test# 动态获取当前激活的主profile(从构建参数中提取)
# --只能获取“clean package -Pprod,test -Dmaven.test.skip=true docker:removeImage docker:build”中的 test环境
# 如果要启用这个,那么我们可以只启动一个-Pprod即可。
# ACTIVE_PROFILE=$(echo "$MAVEN_GOALS" | grep -oP '(?<=-P)[^ ]+' | cut -d',' -f1)# 获取镜像名称和端口(关联激活的profile)
# 项目结构是,获取到的是“project.artifactId为project-root”
# project-root/
# └── pom.xml
#IMAGE_NAME=$(mvn help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-$ACTIVE_PROFILE:$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
#IMAGE_PORT=$(mvn help:evaluate -P$ACTIVE_PROFILE -Dexpression=server.port -q -DforceStdout)
#echo "激活端口:$IMAGE_PORT;激活镜像:$IMAGE_NAME"# 项目结构是:
# project-root/
# ├── pom.xml
# ├── project-child1
# │ └── pom.xml
# └── docker-maven-test
# └── pom.xml
# 切换目录
cd $MODEL_NAME
echo "切换后的目录:$(pwd)"
IMAGE_NAME=$(mvn help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-$ACTIVE_PROFILE:$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
IMAGE_PORT=$(mvn help:evaluate -P$ACTIVE_PROFILE -Dexpression=server.port -q -DforceStdout)
echo "激活端口:$IMAGE_PORT;激活镜像:$IMAGE_NAME"# 不切换目录,直接指定子模块 POM 文件
#IMAGE_NAME=$(mvn -f docker-maven-test/pom.xml help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-$ACTIVE_PROFILE:$(mvn -f docker-maven-test/pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout)
#IMAGE_PORT=$(mvn -f docker-maven-test/pom.xml help:evaluate -P$ACTIVE_PROFILE -Dexpression=server.port -q -DforceStdout)
#echo "激活端口:$IMAGE_PORT;激活镜像:$IMAGE_NAME"# 对外暴露的端口,默认和容器启动端口一样
HOST_IMAGE_PORT=$IMAGE_PORT# 停止并删除旧容器
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; thenecho "停止并删除容器: $CONTAINER_NAME"docker stop $CONTAINER_NAME >/dev/null 2>&1docker rm $CONTAINER_NAME >/dev/null 2>&1
fi# 清理悬空镜像(可选)
docker image prune -f >/dev/null 2>&1# 启动新容器
echo "启动容器: $CONTAINER_NAME (镜像: $IMAGE_NAME) (对外端口:$HOST_IMAGE_PORT,容器内端口:$IMAGE_PORT)(激活环境:$ACTIVE_PROFILE)"
# 设置环境优先级:(前面的会覆盖后面的,也就是最后生效的是prod)
### -Dspring.profiles.active=prod
### >>> SPRING_PROFILES_ACTIVE=dev
### >>> --spring.profiles.active=test
### >>> mvn命令通过-Ptest,dev,prod指定的,默认为第一个test
### >>> pom.xml通过activeByDefault指定的dev# 同时激活多个环境时----不推荐:
#docker run -d \
#--name $CONTAINER_NAME \
#--log-driver json-file \
#--log-opt max-size=10m \
#-p $HOST_IMAGE_PORT:$IMAGE_PORT \
#-e ARGS_1=DockerMaven \
#-e LANTING_NAME_ARGS="Args参数DockerMaven" \
#-e LANTING_NAME_ENV="Env参数DockerMaven" \
#-e SPRING_PROFILES_ACTIVE="test" \
#-e JAVA_OPTS="-Xms128m -Xmx128m -Dlanting.name.jvm=JVM参数DockerMaven -Dspring.profiles.active=$ACTIVE_PROFILE -XX:+UseZGC -XX:MaxMetaspaceSize=128m -XX:NewSize=128m -XX:MaxNewSize=128m -XX:SurvivorRatio=6 -XX:MaxTenuringThreshold=15 -XX:+AlwaysPreTouch" \
#$IMAGE_NAME \
#--spring.profiles.active=dev# 只激活一个环境时:
docker run -d \
--name $CONTAINER_NAME \
--log-driver json-file \
--log-opt max-size=10m \
-p $HOST_IMAGE_PORT:$IMAGE_PORT \
-e JAVA_OPTS="-Xms128m -Xmx128m -Dlanting.name.jvm=JVM参数DockerMaven -XX:+UseZGC -XX:MaxMetaspaceSize=128m -XX:NewSize=128m -XX:MaxNewSize=128m -XX:SurvivorRatio=6 -XX:MaxTenuringThreshold=15 -XX:+AlwaysPreTouch" \
-e ARGS_1=DockerMaven \
-e LANTING_NAME_ARGS="Args参数DockerMaven" \
-e LANTING_NAME_ENV="Env参数DockerMaven" \
$IMAGE_NAMEsleep 5
echo "🔍 查看容器日志(尾部):"
docker logs $CONTAINER_NAME --tail=50
案例三:docker-compose+Dockerfile实现自动化编译并部署
安装Jenkins
docker run --name jenkins \-p 8099:8080 -p 50000:50000 \-v /var/run/docker.sock:/var/run/docker.sock \-v $(which docker):/bin/docker \-v $(which docker-compose):/bin/docker-compose \-v /app/jenkins:/var/jenkins_home \--group-add=$(getent group docker | cut -d: -f3) \-d jenkins/jenkins:lts参数解释:
$(which docker-compose):/bin/docker-compose:将宿主机的docker-compose安装目录映射到Jenkins容器内部,这样在Jenkins容器内部就可以使用宿主机的docker-compose;其余参数解释见上一篇
注意:本文只展示和上一篇不同的地方,可以参考上一篇对Docker的镜像制作以及Jenkins的安装和配置环境...
创建项目-模块docker-compose-test
docker-compose.yml
version: "3.8"
services:compose-web:build:context: .. # 将上下文设为项目根目录(我的yml文件在docker目录下,所以“..”是为了回到根目录)dockerfile: ./docker/file/Dockerfileimage: docker-compose-test:1.0-SNAPSHOT # 镜像名称 --- 不知道怎么从pom.xml中取出来,所以写死。container_name: compose-web-prodenvironment:- ARGS_1=DockerCompose- LANTING_NAME_ENV=Env参数DockerCompose # 系统自动解析成“@Value("${lanting.name.env}")”- JAVA_OPTS=-Xms64m -Xmx64m -Dlanting.name.jvm=JVM参数DockerCompose
# - JAVA_OPTS=-Xms64m -Xmx64m -Dlanting.name=张三- ACTIVE_PROFILE=prod # 传递到了application.yml中,通过${ACTIVE_PROFILE}户区- PARAMS=--lanting.name.args=Args参数DockerCompose
# volumes: #挂载目录
# - /app/lanting:/home/lantingports:- "9091:8090"logging:driver: json-fileoptions:max-size: "10m"
Dockerfile
# 使用官方 OpenJDK 镜像
FROM my-jdk17:17MAINTAINER lanting# 挂载目录--- 宿主机的目录
VOLUME /app/lanting# 在这个容器中创建目录并设置权限 --- 容器内目录
RUN mkdir -p /home/lanting && \chmod 777 /home/lanting# 镜像中创建一个工作目录 --- 容器内目录
WORKDIR /home/lanting# 环境变量(可选)
#ENV JAVA_OPTS="-Xms256m -Xmx256m -XX:+UseZGC -XX:MaxMetaspaceSize=256m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:SurvivorRatio=6 -XX:MaxTenuringThreshold=15 -XX:+AlwaysPreTouch -Dlanting.name=\"张三\""
#ENV TZ=Asia/Shanghai
#ENV PARAMS=""# 我们将jar移动到了当前目录下的jar包后 --- 容器内目录
COPY ./docker/file/jar/*.jar /home/lanting/app.jar# 使用 JAVA_OPTS 与 ACTIVE_PROFILE 都可在 docker run 通过 -e 注入。
# 启动命令
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /home/lanting/app.jar $PARAMS --spring.profiles.active=${ACTIVE_PROFILE}"]
项目内容
--- 注意:只需要保持项目结构一致就行,不需要项目内容一致。
application.yml
server:port: 8090
spring:profiles:active: ${ACTIVE_PROFILE}application:name: docker-compose-test
application-prod.yml
lanting:application:key: compose-prod版本
DockerComposeController.java
package com.lanting.compose.controller;import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.RuntimeMXBean;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;@RestController
@RequestMapping("/maven")
public class DockerComposeController {@Value("${lanting.application.key}")private String key;@Value("${lanting.name.args}")private String args;@Value("${lanting.name.env}")private String env;@Value("${lanting.name.jvm}")private String jvm;@Value("${spring.application.name}")private String appName;@Value("${spring.profiles.active:Unknown}")private String activeProfile;private StringBuilder builder = new StringBuilder();@PostConstructpublic void init() {builder.append("args:").append(args).append("\n").append("key:").append(key).append("\n").append("env:").append(env).append("\n").append("jvm:").append(jvm).append("\n").append("appName:").append(appName).append("\n").append("ARGS_1:").append(System.getenv("ARGS_1")).append("\n").append("activeProfile:").append(activeProfile).append("\n").append("System.getenv(\"SPRING_PROFILES_ACTIVE\"):").append(System.getenv("SPRING_PROFILES_ACTIVE")).append("\n");System.out.println(builder);}@GetMapping("/get/{name}")public String getKey(@PathVariable("name") String name) {return builder.append(name).toString();}@GetMapping("/jvm")public Map<String, Object> getJvmDetails() {Map<String, Object> details = new LinkedHashMap<>();// 1. 获取内存信息MemoryMXBean memoryMxBean = ManagementFactory.getMemoryMXBean();details.put("HeapMemory", memoryMxBean.getHeapMemoryUsage());details.put("NonHeapMemory", memoryMxBean.getNonHeapMemoryUsage());// 2. 获取GC信息List<GarbageCollectorMXBean> gcMxBeans = ManagementFactory.getGarbageCollectorMXBeans();List<String> gcInfo = gcMxBeans.stream().map(bean -> bean.getName() + " (count=" + bean.getCollectionCount() + ", time=" + bean.getCollectionTime() + "ms)").collect(Collectors.toList());details.put("GarbageCollectors", gcInfo);// 3. 获取运行时参数(包含-X -XX参数)RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();details.put("JVMArguments", runtimeMxBean.getInputArguments());// 4. 获取系统属性(-D参数)details.put("SystemProperties", runtimeMxBean.getSystemProperties());return details;}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.lanting</groupId><artifactId>docker-test</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>docker-compose-test</artifactId><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
配置Jenkins
Step1、创建Item
Step2、备注
Step3、配置git(git配置方法查看上一篇)
在这之前,我们应该是在Gitee上上传了项目仓库,具体方法见上一篇
Step4、配置编译时间
其余的webhook、remote、SCM等配置方法自行查看上一篇
Step5、配置Maven-build
Root POM:docker-compose-test/pom.xml
Goals and options:clean package -Pprod -DskipTests
Step6、配置执行脚本Post Steps
Execute shell内容如下:
#!/bin/bash -x
cd $WORKSPACE
echo "当前工作目录:$(pwd)"
# 子模块名称
MODEL_NAME=docker-compose-test
# Docker-compose.yml文件位置
DOCKER_COMPOSE_DIR='docker'# 检查生成的JAR文件
if ! ls $MODEL_NAME/target/*.jar >/dev/null 2>&1; thenecho "❌ 错误:未找到JAR文件,请检查Maven构建结果"exit 1
fi
echo "✅ JAR包位置:$(ls -l $MODEL_NAME/target/*.jar)"# 进入docker目录执行操作
cd $DOCKER_COMPOSE_DIR
echo "当前工作目录:$(pwd)"
sh copy.sh
echo "✅ 移动后的JAR包位置:$(ls -l ./file/jar/*.jar)"# 清除所有构建缓存
docker builder prune -af# 清理旧容器和镜像(忽略可能的错误)
docker-compose down --rmi local || true# 强制重新构建镜像(不使用缓存)
echo "🔨 开始构建镜像(不使用缓存)..."
docker-compose build --no-cache# 启动容器并强制重建
echo "🚀 启动容器..."
docker-compose up -d --force-recreate# 检查容器状态
sleep 5
echo "📊 容器状态检查:"
docker-compose ps# 输出日志检查(可选)
echo "🔍 查看容器日志(尾部):"
docker-compose logs --tail=50