通过docker部署系统是一个很好的实践。但是在一个复杂的系统里,有时候很难做到所有的组件都用docker封装。在这种情况下就需要让docker container和原生部署在主机上的应用通信。遗憾的是,docker本身并没有提供这种场景下的支持,要想实现这种通信,主要有两个办法:1.使用host网络模式启动docker container 2.找出docker network interface的ip地址,并且在container内用这个ip地址与主机进行通信。

第一个办法的优点是简单,因为此时docker container里的进程和主机上直接运行的进程没什么分别,可以通过localhost直接访问到主机上运行的服务。缺点是有可能遇到端口冲突。

第二个办法就是找出一个静态的ip地址,在container内部通过这个地址访问主机。虽然一开始觉得是一个比较hack的解决方案,但实际应用里其实可行。首先是这个ip地址并不会在docker engine运行的时候发生改变,另外即使主机重启,大部份情况下这个ip地址也是不变的。这里主要总结一下第二种方式的具体使用。

一般情况

首先找到docker0 interface的ip地址,执行: ifconfig,样例输出:

docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        inet6 fe80::42:7ff:fe3f:f77a  prefixlen 64  scopeid 0x20<link>
        ether 02:42:07:3f:f7:7a  txqueuelen 0  (Ethernet)
        RX packets 37  bytes 2113 (2.0 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 42  bytes 6283 (6.1 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

这里的172.17.0.1就是可以从container访问host的ip地址。

接下来测试一下,首先以host network模式启动一个nginx服务器:

docker run --name nginx --network host --rm nginx 

接下来以bridge network模式执行curl测试一下网络链接:

docker run --rm byrnedo/alpine-curl 172.17.0.1:9201

得到以下回复说明实验成功:

docker run --rm byrnedo/alpine-curl 172.17.0.1:80
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   612  100   612    0     0   <!DOCTYPE html>--:-- --:--:-- --:--:--     0
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
...

注意,如果你使用的是docker for Mac,有一个更好的方法也就是用host.docker.internal这个特殊的dns地址来访问host network。详情请看: docker for mac networking

通过ssh tunnel访问远程服务

一个特殊的情况就是,在host machine上我们使用ssh port forwarding功能连接到远程服务的时候,如何让container也能通过host上的tunnel访问远程服务?一般情况下,可能在你的~/.ssh/config里有以下配置:

Host some-tunnel
    HostName dbhost
    Port 22
    User root
    IdentityFile ~/.ssh/dbhost.key
    LocalForward 3306 localhost:3306 

如果不明白上面的配置文件是干什么的,可以看这里

如果想了解更多上面的配置格式信息,可以看这里

通过上面的配置,可以实现执行ssh -f -N some-tunnel后从本机地址localhost:3306访问远程数据库的功能。但是从container内部是无法使用这个tunnel的,因为默认情况下tunnel只绑定了localhost地址。为了能让container也使用这个tunnel,需要额外加入以下配置:

Host some-tunnel
    HostName dbhost
    Port 22
    User root
    IdentityFile ~/.ssh/dbhost.key
    LocalForward 3306 localhost:3306 
    LocalForward 172.17.0.1:3307 localhost:3306

之后,在container内部就能通过172.17.0.1:3307这个地址,通过ssh隧道访问远程数据库了。

参考资料