关于nginx:从Docker容器内部,如何连接到本机的本地主机?

From inside of a Docker container, how do I connect to the localhost of the machine?

所以我有一个nginx在docker容器中运行,我有一个mysql在localhost上运行,我想从nginx中连接到mysql。MySQL运行在本地主机上,不会向外部世界公开端口,因此它绑定在本地主机上,而不是绑定在计算机的IP地址上。

有没有办法从这个docker容器中连接到这个mysql或者本地主机上的其他程序?


编辑:如果您使用Docker for Mac或Docker for Windows 18.03+,只需使用主机host.docker.internal连接到您的MySQL服务即可。

从Docker 18.09.3开始,这在Linux的Docker上不起作用。已于2019年3月8日提交了修复程序,有望合并到代码库中。在此之前,解决方法是使用Qoomon答案中描述的容器。

TLDR

在您的docker run命令中使用--network="host",那么docker容器中的127.0.0.1将指向您的docker主机。

注意:根据文档,此模式仅适用于Linux的Docker。

Docker容器联网模式说明

Docker在运行容器时提供不同的网络模式。根据您选择的模式,您将以不同的方式连接到运行在Docker主机上的MySQL数据库。

docker run--network="bridge"(默认)

默认情况下,Docker创建一个名为docker0的桥。Docker主机和Docker容器在该桥上都有一个IP地址。

在Docker主机上,键入sudo ip addr show docker0,您将得到如下输出:

1
2
3
4
5
6
7
[vagrant@docker:~] $ sudo ip addr show docker0
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 56:84:7a:fe:97:99 brd ff:ff:ff:ff:ff:ff
    inet 172.17.42.1/16 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::5484:7aff:fefe:9799/64 scope link
       valid_lft forever preferred_lft forever

所以这里我的docker主机在docker0网络接口上有IP地址172.17.42.1

现在,启动一个新的容器并在其上获得一个shell:docker run --rm -it ubuntu:trusty bash和在容器类型ip addr show eth0内,以了解如何设置其主网络接口:

1
2
3
4
5
6
7
root@e77f6a1b3740:/# ip addr show eth0
863: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 66:32:13:f0:f1:e3 brd ff:ff:ff:ff:ff:ff
    inet 172.17.1.192/16 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::6432:13ff:fef0:f1e3/64 scope link
       valid_lft forever preferred_lft forever

这里我的容器有IP地址172.17.1.192。现在查看路由表:

1
2
3
4
5
root@e77f6a1b3740:/# route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         172.17.42.1     0.0.0.0         UG    0      0        0 eth0
172.17.0.0      *               255.255.0.0     U     0      0        0 eth0

所以docker主机172.17.42.1的IP地址被设置为默认路由,可以从容器中访问。

1
2
3
4
5
root@e77f6a1b3740:/# ping 172.17.42.1
PING 172.17.42.1 (172.17.42.1) 56(84) bytes of data.
64 bytes from 172.17.42.1: icmp_seq=1 ttl=64 time=0.070 ms
64 bytes from 172.17.42.1: icmp_seq=2 ttl=64 time=0.201 ms
64 bytes from 172.17.42.1: icmp_seq=3 ttl=64 time=0.116 ms

docker run--network="主机"

或者,可以运行网络设置设置为host的Docker容器。这样一个容器将与docker主机共享网络堆栈,从容器的角度来看,localhost127.0.0.1将指docker主机。

请注意,在Docker容器中打开的任何端口都将在Docker主机上打开。这不需要-p-pdocker run选项。

我的Docker主机上的IP配置:

1
2
3
4
5
6
7
[vagrant@docker:~] $ ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:98:dc:aa brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe98:dcaa/64 scope link
       valid_lft forever preferred_lft forever

从主机模式下的Docker容器:

1
2
3
4
5
6
7
[vagrant@docker:~] $ docker run --rm -it --network=host ubuntu:trusty ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:98:dc:aa brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe98:dcaa/64 scope link
       valid_lft forever preferred_lft forever

如您所见,Docker主机和Docker容器共享完全相同的网络接口,因此具有相同的IP地址。

从容器连接到MySQL桥式模式

要从处于网桥模式的容器访问Docker主机上运行的MySQL,需要确保MySQL服务正在侦听172.17.42.1IP地址上的连接。

为此,请确保在mysql配置文件(my.cnf)中有bind-address = 172.17.42.1bind-address = 0.0.0.0

如果需要使用网关的IP地址设置环境变量,可以在容器中运行以下代码:

1
export DOCKER_HOST_IP=$(route -n | awk '/UG[ \t]/{print $2}')

然后在应用程序中,使用DOCKER_HOST_IP环境变量打开到MySQL的连接。

注意:如果您使用bind-address = 0.0.0.0,您的mysql服务器将监听所有网络接口上的连接。这意味着您的MySQL服务器可以从Internet访问;请确保相应地设置防火墙规则。

注2:如果使用bind-address = 172.17.42.1,MySQL服务器将不会监听与127.0.0.1的连接。在Docker主机上运行的想要连接到MySQL的进程必须使用172.17.42.1IP地址。

宿主模式

要从主机模式下的容器访问docker主机上运行的mysql,可以在mysql配置中保留bind-address = 127.0.0.1,只需从容器连接到127.0.0.1

1
2
3
4
5
6
7
8
9
10
11
12
13
[vagrant@docker:~] $ docker run --rm -it --network=host mysql mysql -h 127.0.0.1 -uroot -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 36
Server version: 5.5.41-0ubuntu0.14.04.1 (Ubuntu)

Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

注意:不要使用mysql -h 127.0.0.1,不要使用mysql -h localhost,否则mysql客户端会尝试使用unix套接字进行连接。


对于MacOS和Windows

Docker V 18.03及以上(自2018年3月21日起)

使用内部IP地址或连接到特定的DNS名称host.docker.internal,该名称将解析为主机使用的内部IP地址。

Linux支持挂起https://github.com/docker/for-linux/issues/264

带有早期版本Docker的MacOS

Mac V 17.12至V 18.02的Docker

同上,但使用docker.for.mac.host.internal

Mac V 17.06至V 17.11的Docker

同上,但使用docker.for.mac.localhost

Mac 17.05及以下的Docker

要从Docker容器访问主机,必须在网络接口上附加一个IP别名。你可以绑定任何你想要的IP,只需确保你没有把它用于任何其他东西。

sudo ifconfig lo0 alias 123.123.123.123/24

然后确保您的服务器正在监听上面提到的IP或0.0.0.0。如果它在本地主机EDOCX1上监听(5),它将不接受连接。

然后把你的Docker容器指向这个IP,你就可以访问主机了!

为了测试,您可以在容器中运行类似于curl -X GET 123.123.123.123:3000的东西。

每次重新启动时,别名都会重置,因此如果需要,请创建一个启动脚本。

这里的解决方案和更多文档:https://docs.docker.com/docker for mac/networking/用例和解决方案


我做了一个类似于上面文章的黑客,让本地IP映射到容器中的别名(DNS)。主要的问题是动态获取一个简单的脚本,该脚本在Linux和OSX(主机IP地址)中都可以工作。我编写的这个脚本在两种环境中都可以使用(即使在配置了"$LANG" !="en_*"的Linux发行版中):

1
ifconfig | grep -E"([0-9]{1,3}\.){3}[0-9]{1,3}" | grep -v 127.0.0.1 | awk '{ print $2 }' | cut -f2 -d: | head -n1

因此,使用docker compose,完整配置将是:

启动脚本(docker run.sh):

1
2
export DOCKERHOST=$(ifconfig | grep -E"([0-9]{1,3}\.){3}[0-9]{1,3}" | grep -v 127.0.0.1 | awk '{ print $2 }' | cut -f2 -d: | head -n1)
docker-compose -f docker-compose.yml up

docker-compose.yml:

1
2
3
4
5
6
myapp:
  build: .
  ports:
    -"80:80"
  extra_hosts:
    -"dockerhost:$DOCKERHOST"

然后将代码中的http://localhost改为http://dockerhost

有关如何自定义DOCKERHOST脚本的更高级指南,请阅读本文并解释它的工作原理。


这在nginx/php-fpm堆栈上对我很有效,不需要接触任何代码或网络,因为应用程序只希望能够连接到localhost

mysqld.sock从主机安装到容器内部。

在运行mysql的主机上查找mysql.sock文件的位置:netstat -ln | awk '/mysql(.*)?\.sock/ { print $9 }'

将该文件安装到Docker中预期的位置:docker run -v /hostpath/to/mysqld.sock:/containerpath/to/mysqld.sock

mysqld.sock的可能位置:

1
2
3
4
/tmp/mysqld.sock
/var/run/mysqld/mysqld.sock
/var/lib/mysql/mysql.sock
/Applications/MAMP/tmp/mysql/mysql.sock # if running via MAMP


Windows 10解决方案

Docker社区版17.06.0-ce-win18 2017-06-28(稳定)

您可以使用主机docker.for.win.localhost的DNS名称解析为内部IP。(警告:有些消息来源提到了windows,但应该是win)

概览
我需要做一些类似的事情,即从Docker容器连接到本地主机,该主机运行Azure Storage EmulatorCosmosDB Emulator

默认情况下,Azure Storage Emulator监听127.0.0.1,虽然您也可以更改IP的绑定,但我正在寻找一种可以使用默认设置的解决方案。

这也适用于从Docker容器连接到SQL ServerIIS,两者都在主机上以默认端口设置本地运行。


Linux解决方案(内核>=3.6)。

好的,本地主机服务器有默认的Docker接口Docker0,IP地址为172.17.0.1。容器从默认网络设置开始--net="bridge"。

  • 为Docker0接口启用路由本地网:$ sysctl -w net.ipv4.conf.docker0.route_localnet=1
  • 将此规则添加到IPtables:$ iptables -t nat -I PREROUTING -i docker0 -d 172.17.0.1 -p tcp --dport 3306 -j DNAT --to 127.0.0.1:3306$ iptables -t filter -I INPUT -i docker0 -d 127.0.0.1 -p tcp --dport 3306 -j ACCEPT
  • 从"%"创建具有访问权限的MySQL用户,这意味着-从任何人(不包括localhost)创建:CREATE USER 'user'@'%' IDENTIFIED BY 'password';
  • 将脚本中的mysql服务器地址更改为172.17.0.1
  • 从内核文档中:

    route_localnet - BOOLEAN: Do not consider loopback addresses as martian source or destination while routing. This enables the use of 127/8 for local routing purposes (default FALSE).


    对于Windows上的那些,假设您使用的是网桥网络驱动程序,那么您需要将MySQL具体绑定到Hyper-V网络接口的IP地址。

    这是通过通常隐藏的c:programdatamysql文件夹下的配置文件完成的。

    绑定到0.0.0.0将不起作用。所需的地址也显示在Docker配置中,在我的例子中是10.0.75.1。


    在host.docker.internal为每个平台工作之前,您可以使用我的容器作为NAT网关,而无需任何手动设置https://github.com/qoomon/docker-host


    编辑:我最终在Github上制作了这个概念的原型。查看:https://github.com/sivabudh/system-in-a-box

    首先,我的答案面向两组人:使用Mac的人和使用Linux的人。

    主机网络模式在Mac上不工作。您必须使用IP别名,请参阅:https://stackoverflow.com/a/43541681/2713729

    什么是主机网络模式?请参见:https://docs.docker.com/engine/reference/run//network settings

    其次,对于那些使用Linux的用户(我的直接经验是在Ubuntu 14.04 LTS上,我很快就要升级到16.04 LTS的生产环境中),可以让在Docker容器内运行的服务连接到Docker主机上运行的localhost服务(如笔记本电脑)。

    怎么用?

    关键是当您运行Docker容器时,必须以主机模式运行它。命令如下:

    docker run --network="host" -id

    当您在容器中执行ifconfig操作(您需要apt-get install net-tools您的ifconfig容器可调用)时,您将看到网络接口与Docker主机(例如您的笔记本电脑)上的网络接口相同。

    重要的是要注意,我是一个Mac用户,但我在Parallels下运行Ubuntu,所以使用Mac并不是一个缺点。;-)

    这就是如何将nginx容器连接到运行在localhost上的mysql。


    Mac OSX的最简单解决方案

    只需使用你的Mac的IP地址。在Mac上运行此命令以获取IP地址并从容器中使用它:

    1
    $ ifconfig | grep 'inet 192'| awk '{ print $2}'

    只要在Mac或其他Docker容器中本地运行的服务器正在监听0.0.0.0,Docker容器就可以在该地址进行访问。

    如果您只想访问另一个正在监听0.0.0.0的Docker容器,可以使用172.17.0.1


    这是我的解决方案:它适用于我的案件

    • 通过注释将本地MySQL服务器设置为公共访问
      #bind-address = 127.0.0.1
      在/etc/mysql/mysql.conf.d中

    • 重新启动MySQL服务器sudo /etc/init.d/mysql restart

    • 运行以下命令以打开用户根目录访问任何主机mysql -uroot -proot
      GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'root' WITH
      GRANT OPTION;
      FLUSH PRIVILEGES;

    • 创建sh脚本:rundocker.sh

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
        #!bin/bash

        HOSTIP=`ip -4 addr show scope global dev eth0 | grep inet | awk '{print \$2}' | cut -d / -f 1`


          docker run -it -d --name web-app \
                      --add-host=local:${HOSTIP} \
                      -p 8080:8080 \
                      -e DATABASE_HOST=${HOSTIP} \
                      -e DATABASE_PORT=3306 \
                      -e DATABASE_NAME=demo \
                      -e DATABASE_USER=root \
                      -e DATABASE_PASSWORD=root \
                      sopheamak/springboot_docker_mysql
    • 与Docker Composer一起运行

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      version: '2.1'
      </p>

      <p>
      services:
        tomcatwar:
           extra_hosts:
              -"local:10.1.2.232"
           image: sopheamak/springboot_docker_mysql
           ports:
               - 8080:8080
           environment:
                  - DATABASE_HOST=local
                  - DATABASE_USER=root
                  - DATABASE_PASSWORD=root
                  - DATABASE_NAME=demo
                  - DATABASE_PORT=3306
      </p>
      </li>


      </ul>


      <p>


      您可以使用Alpine映像获取主机IP

      1
      docker run --rm alpine ip route | awk 'NR==1 {print $3}'

      这将更加一致,因为您总是使用阿尔卑斯山来运行命令。

      与Mariano的答案类似,您可以使用相同的命令来设置环境变量

      1
      DOCKER_HOST=$(docker run --rm alpine ip route | awk 'NR==1 {print $3}') docker-compose up

      我不同意托马斯列维尔的回答。

      将mysql绑定到172.17.42.1将阻止其他程序使用主机上的数据库访问它。只有当您的所有数据库用户都是Dockerized时,这才有效。

      将MySQL绑定到0.0.0.0将使数据库向外部世界开放,这不仅是一件非常糟糕的事情,而且与原始问题的作者想要做的相反。他明确表示,"MySQL运行在本地主机上,不向外部世界公开端口,因此它绑定在本地主机上。"

      回答伊万特的评论

      "Why not bind mysql to docker0 as well?"

      这是不可能的。mysql/mariadb文档明确表示不可能绑定到多个接口。只能绑定到0、1或所有接口。

      最后,我还没有找到任何方法从Docker容器访问主机上的(仅限本地主机)数据库。这似乎是一个非常常见的模式,但我不知道该怎么做。


      CGroups和名称空间在容器生态系统中发挥着重要作用。

      命名空间提供了一个隔离层。每个容器在单独的命名空间中运行,其访问权限仅限于该命名空间。cgroups控制每个容器的资源利用率,而namespace控制进程可以看到和访问各自资源的内容。

      下面是您可以遵循的解决方案方法的基本理解,

      使用网络命名空间

      当容器从图像中生成时,定义并创建网络接口。这将为容器提供唯一的IP地址和接口。

      1
      $ docker run -it alpine ifconfig

      通过将命名空间更改为主机,cotainers网络不会保持与其接口的隔离,进程将可以访问主机网络接口。

      1
      $ docker run -it --net=host alpine ifconfig

      如果进程在端口上侦听,那么它们将在主机接口上侦听并映射到容器。

      使用PID命名空间通过更改pid名称空间,容器可以与超出其正常范围的其他进程进行交互。

      此容器将在其自己的命名空间中运行。

      1
      $ docker run -it alpine ps aux

      通过将命名空间更改为主机,容器还可以看到系统上运行的所有其他进程。

      1
      $ docker run -it --pid=host alpine ps aux

      共享命名空间

      在生产中这样做是一个糟糕的做法,因为您打破了容器安全模型,这可能会打开漏洞,并容易访问窃听器。这只是为了调试工具和了解容器安全中的漏洞。

      第一个容器是nginx服务器。这将创建一个新的网络和进程命名空间。此容器将自身绑定到新创建的网络接口的端口80。

      1
      $ docker run -d --name http nginx:alpine

      另一个容器现在可以重用此命名空间,

      1
      $ docker run --net=container:http mohan08p/curl curl -s localhost

      此外,此容器还可以看到与共享容器中进程的接口。

      1
      $ docker run --pid=container:http alpine ps aux

      这将允许您在不更改或重新启动应用程序的情况下授予容器更多权限。以类似的方式,您可以在主机上连接到MySQL,运行和调试您的应用程序。但是,不建议这样做。希望它有帮助。


      对于Windows计算机:

      运行以下命令在构建期间随机公开Docker端口

      1
      $docker run -d --name MyWebServer -P mediawiki

      enter image description here

      enter image description here

      在上面的容器列表中,您可以看到分配为32768的端口。尝试访问

      1
      localhost:32768

      您可以看到Mediawiki页面


      在Windows10 Home上使用Docker工具箱时,没有一个答案对我有效,但10.0.2.2确实有效,因为它使用的是将主机暴露给这个地址上的虚拟机的virtualbox。


      非常简单和快速,使用ifconfig(Linux)或ipconfig(Windows)检查主机IP,然后创建一个

      docker-compose.yml公司

      1
      2
      3
      4
      5
      6
      7
      8
      9
      version: '3' # specify docker-compose version

      services:
        nginx:
          build: ./ # specify the directory of the Dockerfile
          ports:
            -"8080:80" # specify port mapping
          extra_hosts:
            -"dockerhost:<yourIP>"

      这样,容器就可以访问主机。访问数据库时,请记住使用前面指定的名称,在本例中是"dockerhost",以及运行数据库的主机的端口。


      直到fix未合并到master分支中,才能从容器内部运行主机IP:

      1
      ip -4 route list match 0/0 | cut -d' ' -f3

      (如@mahoney建议)。


      您可以使用ngrok创建一个到本地主机的安全隧道,然后将该隧道暴露到Docker容器中。

      截至2017年5月22日,NGROK可免费使用。

      步骤:

      1)去NGROK

      2)下载ngrok客户端,按照安装说明操作

      3)注册一个帐户,他们将提供一个身份验证令牌。注册是必需的,因为ngrok只在注册后提供TCP端口隧道。注册不需要成本或信用卡。

      4)在您的终端上执行ngrok tcp 33063306是MySQL在本地运行的端口,您也可以使用其他任何端口。

      5)您将收到来自步骤4的地址,如:tcp://0.tcp.ngrok.io:10117。这是到本地计算机的隧道连接。0.tcp.ngrok.io映射到您的localhost10117端口映射到您的本地端口3306。现在,您可以从任何使用此地址的地方访问本地主机端口3306,包括在此计算机上运行的任何Docker容器。在Docker容器(无论它在哪里)中,假设已经安装了MySQL客户机,请执行以下操作:

      mysql --host 0.tcp.ngrok.io --port 10117 -u root

      您可以从Docker容器内部登录您本地机器的root帐户!

      我已经在博客中介绍了这个解决方案,请在这里查看更多详细信息