[Docker]Mac&Windows访问Docker容器IP

2 篇文章 0 订阅
1 篇文章 0 订阅

问题

Desktop Docker for Mac and Windows无法在macOS/Windows通过IP直接访问Docker中容器,只能通过EXPOSE端口来访问,这个问题在官方文档中有描述,对于大多数情况EXPOSE是够用的。但是对于测试某些集群的时候就会有问题,比如搭建HBase集群,集群会有很多容器使用相同端口,节点注册到zookeeper上的是容器内的IP或机器名,要想在宿主机器上访问就必须能够访问节点IP。另外,除了自己访问之外,为了分享或者测试,还需临时把搭建好的环境提供给其他同事。因此,主要的问题如下:

  • 需要支持从macOS/Windows访问容器IP的能力
  • 需要支持从其他机器访问容器IP的能力

解决

先给出解决方法,如有兴趣再查看分析过程

Mac/Windows端和Docker端都默认使用了192.168.251.1/24的子网。如果Mac端已经被占用了,可在配置文件docker-connector.conf中新增addr xxx.xxx.xxx.1/24的地址(默认注释掉了);如果Docker端冲突了,需要修改启动容器的命令追加mac-receiver -addr xxx.xxx.xxx.1/24来指定地址

第一步、Mac或Windows

这取决于你使用的操作系统是Mac还是Windows。

Mac

如果是Mac系统,安装的是Docker Desktop for Mac,那么可以通过brew安装docker-connector

$ brew install wenjunxiao/brew/docker-connector

安装完成,按照提示通过命令添加路由,以下命令把所有bridge的网络都添加到路由中

$ docker network ls --filter driver=bridge --format "{{.ID}}" | xargs docker network inspect --format "route {{range .IPAM.Config}}{{.Subnet}}{{end}}" >> /usr/local/etc/docker-connector.conf

也可以手动修改/usr/local/etc/docker-connector.conf文件中的路由,格式是

route 172.100.0.0/16

路由的子网决定了你能访问那些容器,配置完成,直接启动服务(需要sudo

$ sudo brew services start docker-connector

路由配置启动之后仍然可以修改,并且无需重启服务立即生效。
为了把对应的网络共享给其他同事访问,需要对配置文件进行如下调整:

  • route后增加expose
route 172.100.0.0/16 expose
  • 增加监听地址和token及其IP分配
expose 0.0.0.0:2512
token user1 192.168.251.10
token user2 192.168.251.11

这样其他人可以使用对应的token进行连接和访问容器网络

Windows

如果是Windows系统,安装的是Docker Desktop for Windows,那么可以下载最新的docker-connector-win-x86_64.zip解压。
首次安装还需要安装驱动驱动tap-windows
在配置文件options.conf按照以下格式写入需要访问的bridge子网

route 172.17.0.0/16

可以通过脚本start-connector.bat直接启动应用,也可以通过脚本install-service.bat把应用安装成服务,然后通过脚本start-service.bat启动服务。

第二步、Docker

docker端运行wenjunxiao/mac-docker-connector(已修改为desktop-docker-connector),需要使用host网络,并且允许NET_ADMIN

$ docker run -it -d --restart always --net host --cap-add NET_ADMIN --name desktop-connector wenjunxiao/desktop-docker-connector

其他机器

其他机器如果想要访问你本地容器需要安装docker-accessor,不同的系统安装方法不同
MacOS
macOS直接用brew安装

$ brew install wenjunxiao/brew/docker-accessor

安装完成之后,需要从容器提供者获取地址和token之后使用sudo运行

$ sudo docker-accessor -remote 192.168.1.100:2512 -token user1

Windows
需要先安装tap驱动tap-windows,安装完成驱动之后直接从mac-docker-connector下载最新适合当前操作系统的docker-accessor-win-i686.zipdocker-accessor-win-x86_64.zip解压即可,运行时需要管理员权限打开控制台

D:\Downloads\>.\docker-accessor.exe -remote 192.168.1.100:2512 -token user2

Linux
Linux系统直接从mac-docker-connector下载最新的docker-accessor-linux.tar.gz解压即可

$ curl -L -o- https://github.com/wenjunxiao/mac-docker-connector/releases/download/v2.0/docker-accessor-linux.tar.gz | tar -xzf - -C /usr/local/bin

获取访问地址和token之后直接运行(需要sudo

$ sudo docker-accessor -remote 192.168.1.100:2512 -token user1

分析

阅读过一篇Mac访问容器的文章。思路很简单,宿主和Docker的虚拟机容器是两个独立的网络,使用EXPOSE端口使得macOS能够访问Docker的容器,再以使用host网络模式的Docker容器与Docker虚拟机处于同一网络中,使用转发串联两个容器。虽然达到了目的,但是过程稍微有点绕。想要简单一点的方案。

+------------+          +-----------------+
|            |          |    Hypervisor   |
|   macOS    |          |  +-----------+  |
|            |          |  | Container |  |
|            |          |  +-----------+  |
|     Client |<-------->|       Server    |
+------------+          +-----------------+

由于macOS不能直接访问容器,以及容器选择host网络模式时不允许EXPOSE端口导致需要增加一个socat容器来做转发。但是容器是可以访问macOS,通过host.docker.internal这个域名就可以访问。由于网络连接是双向的,哪个是客户端,哪个是服务端都可以。因此,我们可以反转一下角色

+------------+          +-----------------+
|            |          |    Hypervisor   |
|   macOS    |          |  +-----------+  |
|            |          |  | Container |  |
|            |          |  +-----------+  |
|     Server |<-------->|       Client    |
+------------+          +-----------------+

这样就不需要EXPOSE端口以及新增一个容器来转发了。
即使这样,对于macOS访问容器IP这个简单目的而言,安装配置证书还是稍显麻烦,而且也大材小用了。

方案

借助于上面这个思路,我们只需要实现一个支持路由转发的客户端和服务端即可。

+------------+          +-----------------+
|            |          |    Hypervisor   |
|   macOS    |          |  +-----------+  |
|            |          |  | Container |  |
|            |   udp    |  +-----------+  |
| TUN Server |<-------->|   TUN Client    |
+------------+          +-----------------+

依托于客户端和服务器端的TUN设备,把路由到虚拟网络设备的数据包转发到对端的虚拟网络设备即可实现网络的互通了。因此主要步骤有两步:
1、在macOS和容器中创建虚拟网卡
2、交换虚拟网卡的数据包
以下是GO的实现方法。
1、容器中创建虚拟网卡

import "github.com/songgao/water"
func main(){
  config := water.Config{
    DeviceType: water.TUN,
  }
  // 新建虚拟网卡
  iface, err := water.New(config)
  if err != nil {
    logger.Fatal(err)
  }
  // 启用并设置mtu
  exec.Command("ip", "link", "set","dev", iface.Name(), "up", "mtu", "1400").Run()
  // 设置IP信息
  exec.Command("ip", "addr", "add","dev", iface.Name(), "local", "192.168.251.1", "peer", "192.168.251.2").Run()
  // 添加路由
  exec.Command("ip", "route", "add","192.168.251.0/24", "via", "192.168.251.2", "dev", iface.Name()).Run()
}

2、macOS中创建虚拟网卡

import "github.com/songgao/water"
func main(){
  config := water.Config{
    DeviceType: water.TUN,
  }
  // 新建虚拟网卡
  iface, err := water.New(config)
  if err != nil {
    logger.Fatal(err)
  }
  // 启用并设置IP信息
  exec.Command("ifconfig", iface.Name(), "inet", "192.168.251.2", "192.168.251.1", "netmask", "255.255.255.255", "up").Run()
  // 添加Docker中的子网络到路由表中,可以添加多个
  exec.Command("route", "-n", "add", "-net", "172.17.0.0/16", "192.168.251.1").Run()
}

3、把虚拟网卡数据包通过udp转发到对端

// 虚拟网卡通过udp转发
go func() {
  buf := make([]byte, 2000)
  for {
    n, err := iface.Read(buf)
    if err != nil {
	  fmt.Printf("tun read error: %v\n", err)
	  continue
	}
	if _, err := conn.Write(buf[:n]); err != nil {
	  fmt.Printf("udp write error: %v\n", err)
	}
  }
}()
// 把udp收到的数据包写入虚拟网卡
data := make([]byte, 2000)
for {
  n, err := conn.Read(data)
  if err != nil {
    fmt.Println("failed read udp msg, error: " + err.Error())
  }
  if _, err := iface.Write(data[:n]); err != nil {
    fmt.Printf("tun write error: %v\n", err)
  }
}

通过以上步骤就可以完成数据交换了。
同样的为了共享给其他同事,只需要在对应的电脑上启动一个客户端进行数据交换即可。
完整的源码地址如下:
https://github.com/wenjunxiao/mac-docker-connector

安装

Mac

mac端也已经编译好,并添加了第三方的Homebrew,可以通过brew安装

$ brew install wenjunxiao/brew/docker-connector

安装完成,按照提示通过命令添加路由,以下命令把所有bridge的网络都添加到路由中

$ docker network ls --filter driver=bridge --format "{{.ID}}" | xargs docker network inspect --format "route {{range .IPAM.Config}}{{.Subnet}}{{end}}" >> /usr/local/etc/docker-connector.conf

也可以手动修改/usr/local/etc/docker-connector.conf文件中的路由,格式是

route 172.100.0.0/16

配置完成,可以直接启动服务(需要sudo

$ sudo brew services start docker-connector

启动之后如果新增了网络,或者需要删除,可以直接修改配置文件,无需要重启服务,路由会自动更新。可以通过以下命令查看路由表

$ netstat -nr -f inet

Docker

docker端已经打包成镜像,可以直接使用:
https://hub.docker.com/repository/docker/wenjunxiao/mac-docker-connector

docker pull wenjunxiao/mac-docker-connector

直接启动即可,需要使用host网络,并且允许NET_ADMIN

$ docker run -it -d --restart always --net host --cap-add NET_ADMIN --name connector wenjunxiao/mac-docker-connector

其他机器

对于需要访问共享容器网络的机器,只需要安装客户端docker-accessor并启动即可访问,不同系统上的安装方法在前面【解决】部分。

验证

选择一个容器IP测试一下,我的测试IP是172.100.0.10,并在对应的容器中启动一个HTTP服务

$ python -m SimpleHTTPServer 8080
Serving HTTP on 0.0.0.0 port 8080 ...

宿主机器访问容器

在宿主机器上直接ping和访问http服务

$ ping 172.100.0.10
PING 172.100.0.10 (172.100.0.10): 56 data bytes
64 bytes from 172.100.0.10: icmp_seq=0 ttl=63 time=0.837 ms
64 bytes from 172.100.0.10: icmp_seq=1 ttl=63 time=1.689 ms
64 bytes from 172.100.0.10: icmp_seq=2 ttl=63 time=2.793 ms
64 bytes from 172.100.0.10: icmp_seq=3 ttl=63 time=2.333 ms

再验证HTTP服务

$ curl -si -w "%{http_code}" http://172.100.0.10:8080 -o /dev/null
200

无法访问多网络容器
如果一个容器有多个网络(可以通过docker network connect添加),可能会导致ping不通,此时要在容器中增加路由表,或者修改默认路由。比如在172.100.0.10的容器中原来的路由表是

$ route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         cpe-172-100-0-1 0.0.0.0         UG    0      0        0 eth0
172.100.0.0     0.0.0.0         255.255.0.0     U     0      0        0 eth0

但是通过docker network connect brigde container-id之后,路由表变成如下

$ route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         gateway         0.0.0.0         UG    0      0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0
172.100.0.0     0.0.0.0         255.255.0.0     U     0      0        0 eth1

默认路由网关不是子网的网关,此时可以修改默认路由

$ ip route del default
$ ip route add default via 172.100.0.1

也可以把用于交换的虚拟网卡的子网(192.168.251.0/24)添加到当前容器的路由中

$ ip route add 192.168.251.0/24 via 172.100.0.1 dev eth1

这两种方式都可以保证容器可以通过172.100.0.10访问,但是只能保证一个网络被访问,要想支持多个网络可以同时访问,需要使用为不同子网指定不同的rt_table

$ ip rule add from <interface_IP> table <rt_table> prio 1
$ ip route add default via <gateway_IP> dev <interface> table <rt_table>

分别为子网创建多个rt_table

$ echo "201   rt1" >> /etc/iproute2/rt_tables
$ echo "202   rt2" >> /etc/iproute2/rt_tables

然后分别为不同的子网指定不同rt_table

$ ip route add default via 172.100.0.1 dev `ip addr show | grep 172.100.0 | awk '{print $NF}'` table rt1
$ ip rule add from `ip addr show | grep 172.100.0 | awk '{print $2}' | awk -F/ '{print $1}'` table rt1 prio 1
$ ip route add default via 172.17.0.1 dev `ip addr show | grep 172.17.0 | awk '{print $NF}'` table rt2
$ ip rule add from `ip addr show | grep 172.17.0 | awk '{print $2}' | awk -F/ '{print $1}'` table rt2 prio 1

执行以上命令之后,所有的的子网都可以正常使用

其他机器访问容器

为了验证其他机器访问,在windows启动客户端docker-accessor.exe,用管理员打开控制台

C:\Users\wenjunxiao>.\docker-accessor.exe -remote 192.168.1.107:2512 -token win10
local => 192.168.1.109:49572
remote => 192.168.1.107:2512
relogin
logged
interface => "my-tap"
command => netsh interface ip set address "my-tap" static 192.168.251.3 255.255.255.0 192.168.251.2
command => netsh interface ip show addresses "my-tap"
waiting network setup...
command => netsh interface ip show addresses "my-tap"
waiting network setup...
command => netsh interface ip show addresses "my-tap"
waiting network setup...
command => netsh interface ip show addresses "my-tap"
command => netsh interface ip delete dns my-tap all
command => netsh interface ip delete wins my-tap all
control => addr 192.168.251.3/24
control => peer 192.168.251.2
control => mtu 1400
control => route 172.100.0.0/16
command => route add 172.100.0.0 mask 255.255.0.0 192.168.251.2

已经连接成功,打开另一个控制台测试是否可以访问

C:\Users\wenjunxiao>ping 172.100.0.10
正在 Ping 172.100.0.10 具有 32 字节的数据:
来自 172.100.0.10 的回复: 字节=32 时间=11ms TTL=63
来自 172.100.0.10 的回复: 字节=32 时间=11ms TTL=63
来自 172.100.0.10 的回复: 字节=32 时间=11ms TTL=63
来自 172.100.0.10 的回复: 字节=32 时间=13ms TTL=63

172.100.0.10 的 Ping 统计信息:
    数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
    最短 = 11ms,最长 = 13ms,平均 = 11ms

再测试一下HTTP

C:\Users\wenjunxiao>curl -si -w "%{http_code}" http://172.100.0.10:8080 -o /dev/null
200

可以正常访问

  • 20
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值