相关文章:

《Linux 虚拟网络设备 veth-pair》 linux基础
《Linux虚拟网络设备之veth(arp incomplete)》

Docker网络(veth、网桥、host、container、none) docker上网络概述

Docker的网络配置 1 初识 docker 精讲
Docker的网络配置 2 配置 DNS和主机名
Docker的网络配置 3 user-defined网络
Docker的网络配置 4 内嵌的DNS server
Docker的网络配置 5 将容器与外部世界连接
Docker的网络配置 6 docker-proxy
我们在上个章节 Docker的网络配置 5 将容器与外部世界连接 首次提到docker-proxy作用是提供端口映射,以便外部可以访问容器内部,但其实是不准确的,因为同样是访问容器内部,不一定走docker-proxy。

开启docker-proxy

docker-proxy功能对应docker配置参数userland-proxy,如果不显示配置该参数,默认为开启状态,具体的配置信息,可以参考之前的 Docker配置文件daemon.json解析

docker-proxy与iptables nat

众所周知,在Docker的桥接bridge网络模式下,Docker容器是通过宿主机上的NAT模式,建立与宿主机之外世界的通信。然而在宿主机上,一般情况下,进程可以通过三种方式访问容器,分别为:

  • <eth0IP>:<hostPort> 走iptables nat映射方式

  • <containerIP>:<containerPort> 走iptables nat 映射方式

  • <0.0.0.0>:<hostPort> 走docker-proxy映射方式

  • 容器互相访问

    实际上,最后一种方式的成功访问完全得益于userland-proxy,即Docker Daemon在启动一个Docker容器时,每次为容器在宿主机上映射一个端口,都会启动一个docker-proxy进程,实现宿主机上0.0.0.0地址上对容器的访问代理。

通过docker-proxy配置来验证,在开启docker-proxy时,新增的监听如下:

[root@EMS3 ~]# ps -ef|grep docker-proxy
root      2837   983  0 18:53 ?        00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8081 -container-ip 172.17.0.14 -container-port 8080

我们发现docker-proxy生成的映射规则是-host-ip 0.0.0.0,也就是说只有以<0.0.0.0>:<hostPort>形式访问时,才会走代理路径。为什么是这样的逻辑呢?根源在于iptalbes的缺陷,对于0.0.0.0支持很差(或者压根就不支持,待研究),因此通过docker-proxy来实现访问的补充。

详细例子:

场景开启docker-proxy时刻关闭docker-proxy时刻
外部主机访问目标主机192.168.126.222:8080通过iptables nat规则访问通过iptables nat规则访问
在主机上访问容器192.168.126.222:8080通过iptables nat规则访问通过iptables nat规则访问
在主机上访问目标容器127.0.0.1:8080通过docker-proxy 转发通过iptables nat规则访问
在主机上其他容器内部访问目标容器 172.17.0.4:8080通过docker proxy转发通过iptables nat规则访问

docker-proxy如何工作

docker-proxy 通过-host-ip指定了docker-proxy在主机上监听的网络接口,通过-host-port指定了监听的端口号;通过-container-ip和-container-port 指定了docker-proxy链接到容器内部的容器ip和端口号。在上例中docker-proxy监听0.0.0.0:8080,那么当主机任何网络接口上有netfliter模块处理后input链到达的目标端口为8080的tcp数据包时刻,docker-proxy会接受这个链接(accept,记为input链接),并主动在连接container-ip+container-port建立一个tcp链接(记为output链接)。当此新建的与容器的链接建立后,docker-proxy会将所有来自input链接的包 发送给output链接。

docker-proxy是否有必要存在

在网上大量的容器最佳实践中都建议关闭docker-proxy,“原因是docker会为每个容器每个暴露的端口都启动一个docker-proxy进程,这个docker-proxy会消耗大概2M的RSS内存。当宿主机环境上有几百上千个容器的时刻,那么可能有几百上千个docker-proxy,其对物理内存消耗的是非常可观的。而docker-proxy的功能完全可以被docker配置的iptables nat规则替代。所以没有docker-proxy就没有必要开启。”
上述论断是否是正确的呢?答案是,在大多数场景下此答案正确。只有如下场景docker-proxy才是刚需:

1、ipv6场景
docker启动时刻可以通过ipv6参数开启docker ipv6支持功能。开启后所有docker容器都在ipv6下工作。但是此时docker在ipv6上的工作并为完善,docker并未在ipv6table上为容器添加相应的DNAT规则。如果此时关闭docker-proxy,那么容器外部无法访问到容器内部网络。在不借助任何外部手段的情况下(可以使用一个叫ipv6nat工具实现ip6table nat规则的自动添加,但即便如此ipv6场景下,docker-proxy依然有存在意义详见后文场景3),所以此场景下docker-proxy需要开启。

作者:marshalzxy
链接:https://www.jianshu.com/p/91002d316185
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2、在老内核下(2.6.x),容器内部通过hairpin 方式访问自己暴露的服务
3、在内核无法开启route_localnet的情况下
正常情况下,内核不会对地址为localnet(127.0.0.0/8)的地址做forwarding,因为这部分地址被认为为martian 。但是在内核中可以通过配置开启。

echo “1”>/proc/sys/net/ipv4/conf/$BridgeName/route_localnet

docker在启动阶段会配置此配置项。但是对于低版本内核无此参数或对于ipv6地址场景(ipv6内核无此配置项,无此功能)内核依然不会对localhost(ipv6下地址为[::1])进行forwarding;所以在此部分场景下,如果需要在主机上,使用localhost:Port 与容器通讯依然需要依赖于docker-proxy

总结
1、在9102年的今天,如果不使用ipv6,那么完全可以关闭docker-proxy。
2、在ipv6场景下,如果使用了ipv6nat工具配置容器的dnat,且不在主机上使用localhost:port方式与容器通讯,那么也可以关闭docker-proxy

尾声:关闭docker-proxy的真实收益其实没有那么大
在第一章中,我们的例子中看到笔者机器上docker-proxy进程rss是1592KB,如果有100个容器,关闭了docker-proxy是否真实节省了100*1592KB 约等于1.5GB物理内存?答案是否定的!docker-proxy其实逻辑很简单,它的rss占用约1.5MB是因为docker-proxy是golang语言编写,golang默认采用静态链接方式将所有的库都静态链接到可执行程序中。所以docker-proxy RSS看起来比较大。但是这里1.5MB中有很大的部分都是docker-proxy可执行程序的代码段,这部分在linux上以map方式映射到docker-proxy可执行文件上的。当多个docker-proxy进程存在时刻,这部分maps实际上是通过文件缓存在整个系统共享的。所以在真实系统上多个docker-proxy消耗的真实物理内存,其实只有docker-proxy的堆和栈,这部分大概只有几百KB,所以关闭docker-proxy的收益并没有想象的那么大。可以通过cat
/proc/$docker-proxy-pid/smaps 中每个map段的pss,随着docker-proxy进程数目增加反而下降证明。通过下面命令可以统计docker-proxy真实的物理内存消耗:

cat /proc/$docker-proxy-pid/smaps |grep -i Private |gawk ‘BEGIN{sum=0}{sum=sum+$2} END{print sum}

在笔者的环境上,统计出来docke-proxy在有负荷(通过nginx镜像下载10G的文件)情况下,docker-proxy内存消耗在500KB左右。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐