? 我们知道,Docker是一个容器引擎;对于开发者来说,使用Dokcer容器部署各种开发需要的中间件(比如myql、redis)会非常简单方便;效率更高。Docker天生是完美支持Linux的,但大多数开发者都是在windows环境下进行开发,虽然Docker也兼容Windows;但并不是那么完美。好在,微软推出了WSL2(全称:Windows Subsystem for Linux),即Windows的Linux子系统;子系统运行真正的 Linux 内核,可以完美支持Docker。而新版IDEA支持Docker远程连接进行开发调试。因此WSL2+Docker+IDEA进行开发可以说是最佳搭配。本文就是介绍如何使用这三者搭建开发环境的。
WSL2:安装的是CentOS7
Docker:版本20.10.14;已开启远程访问;开启方式可参考文章:docker开启远程访问
IDEA:版本为2022.1.1
为了测试环境效果,我们先简单创建一个最基础的springboot应用,打开IDEA,选择New Project,如图:

图示修改说明:
① 原URL为https://start.spring.io ;可能会因为网络问题无法访问而导致创建失败,因此改为:https://start.aliyun.com
② 自定义项目名称
③ 自定义项目存放目录
④ 按需选择JDK和JAVA版本
上面信息填写完成后,点击Next:

在这一步,只勾选 Lombok 和 Spring Web即可。然后点击Create,即可创建一个最简单的springboot应用。创建完成后的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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>dev-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>dev-demo</name> <description>dev-demo</description> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-boot.version>2.3.7.RELEASE</spring-boot.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.3.7.RELEASE</version> <configuration> <mainClass>com.example.devdemo.DevDemoApplication</mainClass> </configuration> <executions> <execution> <id>repackage</id> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build></project>我们再新建一个controller包,在包中新建一个DemoController类,在类中添加一个web响应接口:
package com.example.devdemo.controller;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.text.SimpleDateFormat;import java.util.Date;@Slf4j@RestControllerpublic class DemoController { @RequestMapping("/test") public String test(){ log.info("测试接口:Hello world, docker!"); return "Hello world, docker!" + new SimpleDateFormat(" [yyyy-MM-dd HH:mm:ss]").format(new Date()); }}以上,我们的demo应用创建完毕,点击运行,然后访问:http://localhost:8080/test 即可看到如下效果:

IDEA远程连接Docker的方式有两种:分别是IDEA自带插件方式和maven插件方式
在idea中配置docker远程连接

如图,在设置中填写好Name和Engine API URL后,如果显示Connection successful即为成功连接到远程docker。URL中的windows.wsl为我的WSL2地址,我做了host映射。
踩坑记录:如果是使用WSL2的,windows.wsl可以改为localhost;但经过测试,在win10的wsl2中,使用localhost能成功连接docker;但在win11中却不行。如果localhost无法连接到wsl2的Docker,则需要使用脚本对wsl2进行固定IP,然后像我一样做host映射即可;可参考我另一篇文章:WSL2-CenOS7固定IP,这里不再表述。
创建Dockerfile并进行运行环境配置
在项目根目录下创建Dockerfile文件,并写入如下内容:
# Docker image for springboot application# VERSION 0.0.1# Author: Lunfy### 基础镜像; 直接基于java8运行FROM anapsix/alpine-java#作者MAINTAINER Lunfy <lunfy@qq.com>#系统编码ENV LANG=C.UTF-8 LC_ALL=C.UTF-8#设置时区ENV TZ=Asia/ShanghaiRUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone#声明一个挂载点,容器内此路径会对应宿主机的某个文件夹VOLUME /tmp#应用构建成功后的jar文件被复制到镜像内,名字也改成了app.jarADD target/dev-demo-0.0.1-SNAPSHOT.jar app.jar#暴露8080端口;5005为远程调试端口,根据需要选择是否暴露EXPOSE 8080 5005#启动容器时的进程#ENTRYPOINT ["java","-jar","/app.jar"]#远程调试ENTRYPOINT ["java","-jar","-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005","app.jar"]如图所示创建一个配置文件

配置详情如下图:

上图中Run Maven Goal添加配置运行前的mavne打包命令为:clean package -U -DskipTests; 注意,不带mvn指令;表示每次在构建镜像之前,都会将当前工程清理掉并且重新编译构建
设置完成后,点击下图红框中的绿色三角按钮,运行此配置

运行结果
运行后,控制台效果如下:

打开浏览器,输入http://localhost:18080/test;效果如下:

打开WSL2,使用docker命令查看镜像和容器
查看镜像:docker images 查看容器:docker ps -a

远程debug配置
上面我们已完成了应用的容器构建和部署运行;但作为开发,也许我们还会经常用到debug功能;但应用部署在远程docker容器中,我们该怎么进行debug呢?这时,我们就需要用到IDEA的远程debug功能了。
和前面添加配置一样,点击Edit Configurations...创建配置文件


配置详情如图:

需要特别说明的是,之前dockerfile配置文件的ENTRYPOINT开启远程调试的配置,就是从Command line arguments for remote JVM这里复制的命令。另外,因为我是WSL2,所以远程主机填的是localhost;端口可根据需要自定义。
开始远程debug;

我们给应用打上断点,然后点击debug图标开始运行。之后在控制台出现“Connected to the target VM”字样,即为成功连接到远程的虚拟机上。这时,再次在浏览器访问 http://localhost:18080/test 即可看到程序进入断点:

至此,IDEA插件方式远程连接打包部署并远程调试完毕。
创建Dockerfile(在此直接使用方式一的Dockerfile文件,如没有,需创建)。
在pom.xml中配置插件信息;参考:docker-maven-plugin插件官方文档 在
<!-- docker-maven-plugin 插件 --> <plugin> <groupId>io.fabric8</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.39.1</version> <!-- 全局配置 --> <configuration> <!-- 这一部分是为了实现对远程docker容器的控制 --> <!-- docker主机地址,用于完成docker各项功能;tcp或者http均可! --> <dockerHost>tcp://localhost:2375</dockerHost> <!-- <dockerHost>http://localhost:2375</dockerHost> --> <!-- 这一部分是为了实现docker镜像的构建和推送,如果不需要可不配置; 比如我就只是在本地WSL环境开发,所以不配置 --> <!-- registry地址,用于推送,拉取镜像 <registry>registry.example.com</registry> <authConfig> <push> <username>这里填registry的用户名</username> <password>这里填registry的密码</password> </push> </authConfig> --> <!-- 镜像相关配置,支持多镜像--> <images> <!-- 单个镜像配置 --> <image> <!-- 镜像名(含标签); 标准镜像名格式为:命名空间/仓库名称:镜像标签;如标签不填,则默认为latest --> <!--例: <name>lunfy/devdemo:test</name> --> <name>lunfy/devdemo</name> <!-- 镜像别名;可用于作为容器名(服务名称) --> <alias>demo-service</alias> <build> <!-- 可指定Dockerfile目录或者指定Dockerfile文件;配置二选一即可 --> <!-- 指定dockerfile所在目录 --> <!-- <contextDir>${project.basedir}</contextDir> --> <!-- 指定dockerfile文件的位置 --> <dockerFile>${project.basedir}/Dockerfile</dockerFile> </build> <!-- 配置docker-compose文件 <external> <type>compose</type> <basedir>${project.basedir}/docker</basedir> <composeFile>docker-compose.yml</composeFile> </external> --> <run> <!-- 容器运行端口映射,对应docker的 -p 命令 --> <ports> <port>28080:8080</port> <port>5005:5005</port> </ports> <!-- 容器命名策略:alias:使用镜像别名作为容器名;none:随机命名 --> <namingStrategy>alias</namingStrategy> </run> </image> </images> </configuration> <executions> <execution> <id>docker-exec</id> <!-- 绑定mvn install阶段,当执行mvn install时 就会执行docker stop、docker build、docker start --> <phase>install</phase> <goals> <!-- 要先停止并删除容器,否则下一步删除镜像会报错 --> <goal>stop</goal> <!-- 删除镜像;此步可省略,build构建的时候会自动删除 --> <!-- <goal>remove</goal> --> <!-- 构建镜像 --> <goal>build</goal> <!-- 启动容器 --> <goal>start</goal> <!-- 运行容器: run和start的区别: run:在前台运行docker容器,容器直接与idea联动;日志会直接输出到idea控制台,可以在idea直接控制容器状态。在idea点击停止按钮,容器停止运行。 start: 在后台运行docker容器;容器与idea解耦;日志不会输出到idea控制台;和正常在命令行启动docker一样 --> <!-- <goal>run</goal> --> </goals> </execution> </executions> </plugin>配置中已有注释,这里不再做说明
运行结果
配置完pom.xml后,在应用打上断点,然后双击install即可开始运行;如果前面按照方式一构建运行过docker; 记得要先在WSL2中把容器删除(虽然是同一dockerfile构建的镜像,但maven删除无法自动删除别的插件运行产生的镜像),否则会出现如下报错:

执行命令把容器删除:

再次双击install运行

访问 http://localhost:18080/test

点击停止按钮,容器停止运行;和方式一不一样,此时去WSL2中已查看到不窗口进行。也就是说,在这种方式下,WSL2中的容器是随着IDEA的停止而销毁的。
两种方式效果差异:
方式一会随着项目不断重复构建,而产生很多个

idea dockder反复打包后,会有很多
解决办法:使用以下指令进行快速删除docker rmi -f $(docker images | grep "none" | awk '{print $3}')
或者docker rmi -f $(docker images -f "dangling=true" -q)
或者docker image prune -f
深度清理命令:docker image prune -a -f
这个命令将清理整个系统,并且只会保留真正在使用的镜像,容器,数据卷以及网络,因此需要格外谨慎。
比如,我们不能在生产环境中运行prune -a命令,因为一些备用镜像(用于备份,回滚等)有时候需要用到,如果这些镜像被删除了,则运行容器时需要重新下载。
Maven插件方式功能强大,从docker的构建、运行、推送等等全流程支持;而且经过配置,即使多次重复构建镜像,也不会像idea插件一样产生很多个
Maven插件方式部署步骤比较少,但需要对docker-maven-plugin的配置比较了解;而IDEA插件配置方式虽然步骤多一些,但都是图形化配置。比较方便。因此可根据需要进行选择。
最后附上Demo代码:https://gitee.com/lunfangyu/dev-demo