两个 docker 容器之间的通信问题

Posted

技术标签:

【中文标题】两个 docker 容器之间的通信问题【英文标题】:Trouble communicating between two docker containers 【发布时间】:2018-07-15 03:55:54 【问题描述】:

我是 docker 新手,我正在尝试将运行在 boot-example docker 容器中的 spring boot 应用程序连接到运行在 mymysql 中的 mysql 服务器6603 端口上的 docker 容器,两者都在同一台物理机器上运行。 事实是:如果我将我的 spring-boot 应用程序连接到我的 mymysql docker 容器以便与数据库通信,我不会收到任何错误,并且一切正常。

我将 Spring Boot 应用程序 移动到我的 boot-example 容器中并尝试(通过 Hibernate)与我的 mymysql 容器进行通信时,然后我得到这个错误:

2018-02-05 09:58:38.912 ERROR 1 --- [           main] o.a.tomcat.jdbc.pool.ConnectionPool      : Unable to create initial connections of pool.

com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_111]

我的spring boot application.properties是:

server.port=8083
spring.jpa.hibernate.ddl-auto=create-drop
spring.datasource.url=jdbc:mysql://localhost:6603/mydockerdb
spring.datasource.username=root
spring.datasource.password=mypassword

在我的 spring boot 应用程序在端口 8082 上的 docker 容器中运行之前,它可以正常工作,(在正确构建 docker 映像之后):

docker run -it -p 8082:8083 boot-example 

【问题讨论】:

【参考方案1】:

您不能在容器内使用localhost,它是容器本身。因此,您将始终收到连接被拒绝错误。

你可以做以下事情 -

    在您的 Spring Boot 应用程序的 application.properties 文件中添加您的主机 IP。 (不推荐,因为它破坏了 docker 可移植性逻辑)

    如果您想使用localhost,请在启动容器时使用--net=host(不推荐用于生产,因为不存在逻辑网络层)

    使用--links 与 DNS 名称进行容器通信。 (已弃用/旧版)

    创建一个 compose 文件并使用服务名称从 Spring Boot 应用程序调用您的数据库,因为它们将位于同一网络中并且彼此高度集成。 (推荐)

PS - 每当您需要将多个容器集成在一起时,请始终使用docker-compose version 3+。使用docker run|build 了解基本原理并进行试运行。

【讨论】:

我建议使用链接进行容器通信。内部 Spring Boot application.yml 可以准备使用 MySQL 数据库的特定主机名,例如“jdbc:mysql://mydatabase:6603/mydockerdb”。如果我们想在开发过程中拥有一个本地数据库,可能会有一个特殊的“application-dev.yml”文件,其中包含数据库的本地主机 URL。可以通过启用“dev”Spring 配置文件来激活此配置。 我使用过 --net=host 并且它可以工作但是......即使我将 java 容器映射为 -p 8082:8083 我的 spring boot 服务在端口 8083 上可用而不是在 8082使用 Docker 端口...为什么?谢谢! 由于您的容器是在主机网络上启动的,它使用主机 TCP 端口空间来暴露在容器内运行的服务。当您开始使用--net=host 时,Docker 网络的工作方式不同。将其视为在您的主机上运行的另一个进程,没有任何隔离网络。【参考方案2】:

正如@vivekyad4v 建议的那样 - 实现您的愿望的最简单方法是使用具有更好容器通信集成的docker-compose

Docker-compose 是一个用于管理单个或多个 docker 容器的工具。它使用一个名为docker-compose.yml的配置文件。

有关 docker-compose 的更多信息,请查看 documentation 和 compose file reference

根据我的经验,遵循 SRP(单一职责原则)是一种很好的做法,因此 - 为您的数据库创建一个容器,为您的应用程序创建一个容器。它们都使用您在配置中指定的网络进行通信。

以下docker-compose.yml 的示例可能会对您有所帮助:

version: '2'
 networks: 
 # your network name
  somename:
    driver: bridge

services:
   # php server
  php:
   image: dalten/php5.6-apache
   ports:
    - 80:80
   volumes:
    - .application_path:/some/application/path
   # your container network name defined at the beggining 
   networks: 
    - somename

   # Mysql server for backend
  mysql:
   image: dalten/mysql:dev
   ports:
    - 3306:3306
   # The /var/lib/mysql volume MUST be specified to achieve data persistence over container restart
   volumes:
    - ./mysql_data:/var/lib/mysql
   environment:
    MYSQL_ROOT_PASSWORD: root
    MYSQL_DATABASE: backend
   # your container network name defined at the beggining 
   networks: 
      - somename

注意:网络内容器之间的通信可以通过在容器内调用service name来实现。

从 PHP 到 MySQL 容器的连接参数,在这个例子中是:

hostname: mysql
port: 3306
database: backend
user: root
password: root

【讨论】:

【参考方案3】:

根据上述建议,Docker-compose 是一种方法,但如果您不想使用 compose/swarm 模式。

    只需使用docker network create myNet 创建您自己的网络 部署您的容器侦听创建的网络--network myNet 将 spring.datasource.url 更改为 jdbc:mysql://mymysql:6603/mydockerdb

通过使用 docker demo 的 DNS 解析,容器可以发现彼此,从而进行通信。 [默认网桥不支持DNS。使用网桥的用户定义网络。]

欲了解更多信息:https://docs.docker.com/engine/userguide/networking/

【讨论】:

感谢您的回答,使用网络您可以加入识别同一网络的两个容器。但是,如果我只想将我的 mysql 容器部署到两个单独的服务器呢?我想像所有传统连接一样连接两个容器,它们只有它们的地址和端口。 您的意思是在另一台服务器上部署 MySQL 容器,然后将它与在另一台服务器上运行的另一个容器连接起来?请说清楚。 :)

以上是关于两个 docker 容器之间的通信问题的主要内容,如果未能解决你的问题,请参考以下文章

在同一主机上的不同网络中的 Docker 容器之间进行通信

Docker容器跨主机通信之:直接路由方式

docker容器之间的通信,如何配置

docker容器之间的通信,如何配置

Docker容器之间的通信

docker笔记:docker容器通信参数 --link参数介绍