前面已经搭建好了本地基于springboot
,redis
,mybatis
的项目,其中redis
,mybatis
都是在docker
中运行的,但是整个项目还是在IDEA
上运行的,不如折腾一下,让项目在docker
上跑起来。
注意:项目是在之前的项目基础上改造而来的:如何基于 Docker 快速搭建 Springboot + Mysql + Redis 项目
项目地址:https://github.com/Damaer/DemoCode/tree/main/springboot/springDocker
首先pom
文件里面需要增加docker 的maven打包插件
:
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>1.0.0</version>
<configuration>
<imageName>${docker.image.prefix}/${project.artifactId}</imageName>
<dockerDirectory>src/main/docker</dockerDirectory>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
</configuration>
</plugin>
里面配置了几个项:
${docker.image.prefix}
:镜像名字的前缀,这里我使用aphysia
${project.artifactId}
: 项目的artifactId
,坐标<dockerDirectory>src/main/docker</dockerDirectory>
:配置的是docker
生成镜像需要用到的文件路径因此,我们需要在src/main
下新建一个docker
文件夹,里面放了DockerFile
,生成docker
镜像:
FROM openjdk:8-jdk-alpine
EXPOSE 8081
VOLUME /tmp
ADD springbootdocker-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
FROM
: 要构建的镜像基于哪个镜像,基于openjdk:8-jdk-alpine
EXPOSE
: Docker
这个容器在运行时需要监听哪些端口,我们用8081
VOLUME
: 数据卷,用于保存持久化的数据,一般数据都要持久化到主机上,要不容器删除了,数据就没有了,所以VOLUME
可以把主机上的目录和容器中的目录对应起来,作为持久化目录,相当于在主机的/var/lib/docker
目录下创建一个临时文件并把它链接到容器中的/tmp
目录。ADD
:把这个jar
包(文件)拷贝到docker
中,并且改名为app.jar
ENTRYPOINT
:指定执行命令,用于运行镜像的,这里是用java -jar
命令来运行jar
包关于VOLUME
,我们可以使用docker inspect eaf13c07d079daba6ecb8f2d59019ad053d79bbce1e6c8e10a52e79637730e70
命令去查看(长长的那串是镜像的id
):
image-20211123083941908
我们可以看到里面有这么一段:
"Mounts": [
{
"Type": "volume",
"Name": "e8863daa5874f6446560673f0a260a4fd2b127b71d31318e6546843f8b283a48",
"Source": "/var/lib/docker/volumes/e8863daa5874f6446560673f0a260a4fd2b127b71d31318e6546843f8b283a48/_data",
"Destination": "/tmp",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
]
Source
是我们主机中的目录,Destination
是我们容器中的目录,两者关联起来了,也就是数据卷。
既然前面我们需要拷贝 jar
包,那么 jar
包从哪里来呢?答案是我们需要配置成以jar
包运行,才能看到:
<groupId>com.aphysia</groupId>
<artifactId>springbootdocker</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springbootdocker</name>
<packaging>jar</packaging>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<docker.image.prefix>aphysia</docker.image.prefix>
</properties>
编译运行,就会看到target
下生成了jar
包文件:
image-20211123084611668
先在IDEA
用maven
编译:
mvn clean package -U
mvn clean install
这个时候,我遇到了一些环境变量的错误,是Maven
和Java
的环境变量的,注意需要配置,本人是Mac
,用的的zsh
,所以需要配置zsh
的环境变量:
可以用open ~/.bash_profile
以及open ~/.zshrc
看看里面的配置:
export M2_HOME="/Users/aphysia/config/apache-maven-3.6.3"
export PATH="$M2_HOME/bin:$PATH"
export PATH
JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home
export JAVA_HOME
CLASS_PATH="$JAVA_HOME/lib"
PATH=".$PATH:$JAVA_HOME/bin"
修改完,记得用source ~/.bash_profile
刷新一下。
mvn
编译完,生成了jar
包,我们可以用java -jar
命令运行一下:
java -jar springBootDocker-0.0.1-SNAPSHOT.jar
如果没有问题,就可以用docker
命令编译镜像:
mvn docker:build -Dmaven.test.skip
DockerFile
文件,五步都成功了:
image-20211123090108680
查看docker
镜像:
% docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
aphysia/springbootdocker latest dde6911c8e25 47 hours ago 137MB
<none> <none> ee52277559a6 47 hours ago 137MB
redis latest 40c68ed3a4d2 5 days ago 113MB
redis <none> 84c5f6e03bf0 14 months ago 104MB
mysql latest e1d7dc9731da 14 months ago 544MB
docker/getting-started latest 1f32459ef038 16 months ago 26.8MB
openjdk 8-jdk-alpine a3562aa0b991 2 years ago 105MB
是时候到使用docker
编译运行镜像了(注意先运行Redis
和Mysql
镜像):
docker run -p 8081:8081 -t aphysia/springbootdocker
上面命令执行,看起来没啥报错,但是请求:http://localhost:8081/getUserList
的时候,报了以下的错误:
io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: /127.0.0.1:6379
Caused by: java.net.ConnectException: Connection refused
at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method) ~[na:1.8.0_212]
at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717) ~[na:1.8.0_212]
at io.netty.channel.socket.nio.NioSocketChannel.doFinishConnect(NioSocketChannel.java:330) ~[netty-transport-4.1.69.Final.jar!/:4.1.69.Final]
at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java:334) ~[netty-transport-4.1.69.Final.jar!/:4.1.69.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:707) ~[netty-transport-4.1.69.Final.jar!/:4.1.69.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655) ~[netty-transport-4.1.69.Final.jar!/:4.1.69.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581) ~[netty-transport-4.1.69.Final.jar!/:4.1.69.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493) ~[netty-transport-4.1.69.Final.jar!/:4.1.69.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986) ~[netty-common-4.1.69.Final.jar!/:4.1.69.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.69.Final.jar!/:4.1.69.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.69.Final.jar!/:4.1.69.Final]
at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_212]
2021-11-23 15:50:08.446 DEBUG 1 --- [ioEventLoop-4-1] io.lettuce.core.RedisChannelHandler : closeAsync()
2021-11-23 15:50:08.447 DEBUG 1 --- [ioEventLoop-4-1] i.lettuce.core.protocol.DefaultEndpoint : [unknown, epid=0x1] closeAsync()
2021-11-23 15:50:08.464 DEBUG 1 --- [ioEventLoop-4-1] io.lettuce.core.protocol.CommandHandler : [channel=0x6d227ef0, [id: 0x5a58ec1f] (inactive), epid=0x1, chid=0x1] channelUnregistered()
2021-11-23 15:50:08.467 DEBUG 1 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : Failed to complete request: org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to 127.0.0.1:6379
2021-11-23 15:50:08.478 ERROR 1 --- [nio-8081-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to 127.0.0.1:6379] with root cause
java.net.ConnectException: Connection refused
at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method) ~[na:1.8.0_212]
at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717) ~[na:1.8.0_212]
at io.netty.channel.socket.nio.NioSocketChannel.doFinishConnect(NioSocketChannel.java:330) ~[netty-transport-4.1.69.Final.jar!/:4.1.69.Final]
at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java:334) ~[netty-transport-4.1.69.Final.jar!/:4.1.69.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:707) ~[netty-transport-4.1.69.Final.jar!/:4.1.69.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655) ~[netty-transport-4.1.69.Final.jar!/:4.1.69.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581) ~[netty-transport-4.1.69.Final.jar!/:4.1.69.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493) ~[netty-transport-4.1.69.Final.jar!/:4.1.69.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986) ~[netty-common-4.1.69.Final.jar!/:4.1.69.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.69.Final.jar!/:4.1.69.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.69.Final.jar!/:4.1.69.Final]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_212]
这是什么原因呢?
原因是docker中的每一个容器之间的网络是相互隔离的,我们在应用中用的是127.0.0.1,在IDEA里面运行当然用的是本机网络,但是都部署到Docker容器中的时候,localhost用的肯定是容器本身的,但是应用容器本身没有redis,也没有mysql,所以请求就失败了
简单来说:因为容器和容器之间的ip
是隔离的,无法互相通过本地端口访问。
那我们怎么解决呢?
我们可以去看看容器是不是真的有自己的ip
,可以先用docker images
查看镜像,再用docker exec -it c178e8998e68 bash
(c178e8998e68
是容器id
),然后用cat -etc/hosts
查看ip
:
% docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3f27e28aa3f6 aphysia/springbootdocker "java -jar /app.jar" 9 minutes ago Up 9 minutes 0.0.0.0:8081->8081/tcp, :::8081->8081/tcp epic_mendeleev
c178e8998e68 mysql "docker-entrypoint.s…" 21 hours ago Up 21 hours 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp mysql
7a5056f4c28b redis "docker-entrypoint.s…" 21 hours ago Up 21 hours 0.0.0.0:6379->6379/tcp, :::6379->6379/tcp redis
% docker exec -it c178e8998e68 bash
root@c178e8998e68:/# cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.3 c178e8998e68
或者直接从docker
中点击cli
也可以,用cat -etc/hosts
查看ip
,可以看到该容器对外的ip
是172.17.0.3
:
image-20211124230444387
同样的我们可以获取到mysql
容器的ip
,在项目里重新配置链接,然后重新打包,docker
运行,就可以了,再访问http://localhost:8081/getUserList
,容器的日志也可以看出来正常了。
image-20211124230907588
在用docker
命令build
镜像的时候,出现了以下的报错:
Failed to execute goal com.spotify:docker-maven-plugin:1.0.0:build (default-cli) on project dockerDemo: Exception caught: java.util.concurrent.ExecutionException: com.spotify.docker.client.shaded.javax.ws.rs.ProcessingException: org.apache.http.client.ClientProtocolException: Cannot retry request with a non-repeatable request entity: Connection reset by peer -> [Help 1]
遭遇了人生的滑铁卢,咋一看,貌似是打包插件的docker-maven-plugin
问题,一顿搜索,换了版本,居然不行,我的版本:
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>1.0.0</version>
<configuration>
<imageName>${docker.image.prefix}/${project.artifactId}</imageName>
<dockerDirectory>src/main/docker</dockerDirectory>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
</configuration>
</plugin>
</plugins>
最后发现了问题,我之前的项目中有大写字母,而<imageName>
github
上有对应的讨论:https://github.com/spotify/docker-maven-plugin/issues/357
image-20211124231536247
【作者简介】
秦怀,技术之路不在一时,山高水长,纵使缓慢,驰而不息。个人写作方向:Java源码解析,JDBC,Mybatis,Spring,Redis,分布式,剑指Offer,LeetCode等,认真写好每一篇文章,不喜欢标题党,不喜欢花里胡哨,大多写系列文章,不能保证我写的都完全正确,但是我保证所写的均经过实践或者查找资料。遗漏或者错误之处,还望指正。