暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

无快不破,在本地 docker 运行 IDEA 里面的项目?

秦怀杂货店 2021-11-26
995
  • 💻   剑指Offer & LeetCode刷题仓库:https://github.com/Damaer/CodeSolution
  • 📒   编程知识库:https://github.com/Damaer/Coding
    • 文档地址:https://damaer.github.io/Coding/#/


  • 前言

  • Docker改造

    • 1. 插件配置

    • 2. DockerFile 文件配置

    • 3. 以 jar 包方式运行

    • 4. maven & Docker编译

    • 5. 报错解决

  • 坑点


前言

前面已经搭建好了本地基于springboot
,redis
mybatis
的项目,其中redis
,mybatis
都是在docker
中运行的,但是整个项目还是在IDEA
上运行的,不如折腾一下,让项目在docker
上跑起来。

注意:项目是在之前的项目基础上改造而来的:如何基于 Docker 快速搭建 Springboot + Mysql + Redis 项目

项目地址:https://github.com/Damaer/DemoCode/tree/main/springboot/springDocker

Docker改造

1. 插件配置

首先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
    生成镜像需要用到的文件路径

2. DockerFile 文件配置

因此,我们需要在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
是我们容器中的目录,两者关联起来了,也就是数据卷。

3. 以 jar 包方式运行

既然前面我们需要拷贝 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

4. maven & Docker编译

先在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

5. 报错解决

上面命令执行,看起来没啥报错,但是请求: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>${docker.image.prefix}/${project.artifactId}</imageName>
这里面是不能包含大写字母的,必须使用小写字母。(巨坑)

github
上有对应的讨论:https://github.com/spotify/docker-maven-plugin/issues/357


image-20211124231536247


【作者简介】

秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。个人写作方向:Java源码解析,JDBC,Mybatis,Spring,Redis,分布式,剑指Offer,LeetCode等,认真写好每一篇文章,不喜欢标题党,不喜欢花里胡哨,大多写系列文章,不能保证我写的都完全正确,但是我保证所写的均经过实践或者查找资料。遗漏或者错误之处,还望指正。

平日时间宝贵,只能使用晚上以及周末时间学习写作,关注我,我们一起成长吧~

 刷题仓库:CodeSolution
 架构实战场景面试题
 2020年我写了什么?
 剑指Offer系列文章
 JVM学习之路
 设计模式
 Java基础点点滴滴
 Mybatis学习之路
● Java集合源码分析系列
● 面试官说:你来设计一个短链接生成系统吧
● LeetCode刷题




- END -


文章转载自秦怀杂货店,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论