熟悉docker的朋友也许都知道在使用docker默认情况下为所有的容器准备了一个网络(docker0),并且可以通过-p参数将将主机上某个端口映射到容器内部的固定端口上。例如: docker run -name zxy-nginx -itd -p 8080:80 mynginx:v1 /bin/bash 上面命令运行后,可以在主机上通过docker ps命令查看到多了一个叫zxy-nginx的容器(上例中假设我们主机上有一个叫mynginx:v1的nginx 容器镜像)。且docker在主机上将任意网络接口的8080访问导入到zxy-nginx容器内部。
在zxy-nginx内部启动nginx服务,并在主机上通过curl 访问nginx服务。 #docker exec -it zxy-nginx /usr/local/nginx/sbin/nginx -c /usr/local/nginx/nginx.conf #curl http://192.168.126.222:8080 <html> <head> <title>Welcome to nginx!</title> </head> <body bgcolor=“white“ text=“black”> <center><h1>Welcome to nginx!</h1></center> </body> </html> 在上例中,我在主机上通过主机外部ip地址192.168.126.222+端口8080 访问到容器zxy-nginx内部的nginx服务。当然也可以在另外一台与主机相连的机器上,通过curl 192.168.126.222:8080访问到主机上的zxy-nginx容器内部。这是如何达成的呢?在docker默认配置时刻,docker端口暴露是通过docker-proxy加适当的iptables规则实现的。通过下面命令可以看到docker-proxy的存在:
但是docker-proxy却不是一定需要的,通过下面命令可以看到是否使用docker-proxy也是可以配置的: #dockerd --help|grep proxy --userland-proxy Use userland proxy for loopback traffic (default true) --userland-proxy-path Path to the userland proxy binary 在centos 7.x上docker rpm安装方式下,可以通过/etc/docker/daemon.json里添加 “userland-proxy”:false 关闭docker-proxy docker-proxy如何工作在docker的源码中,docker-proxy代码位于vendor目录的proxy包内部,感兴趣的读者可以自行阅读proxy实现代码。本文绕开代码实现只讲实现原理,本文以tcp链接为例,udp效果等同。 在上一章例子中,看到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 设置的iptables nat规则其实在目前的docker-proxy实现中,并不是所有的数据包都由docker-proxy完成包转发。docker配置的iptables nat也参与其中。可以通过下面命令查看到docker配置的iptables nat规则
通过上述命令我们将docker-proxy开启/关闭情况下,将iptables nat表导出可以得出下面的规则表
表3-1 下面我们以第一章出现的例子,看看docker-proxy和iptables如何共同作用访问docker容器内部
表3-2 细心的读者也许会有这样的疑惑,即使在开启docker-proxy的配置下,也不是所有的包都直接由docker-proxy处理。对于外部主机访问目标主机192.168.126.222:8080和在主机上访问容器192.168.126.222:8080时刻依然会通过iptables nat表处理。其实这两种场景下iptables DNAT规则确实多余了,可以通过下面命令手动将iptables nat表中DOCKER链规则直接绕开: #iptables -t nat -I DOCKER 1 -p tcp -j RETURN 添加这条规则后iptables nat中所有到达DOCKER链的tcp包都会直接不做dnat处理返回,此时docker-proxy起作用将数据包转发到容器内部。手动添加上述iptables规则后,表3-1中通过192.168.126.222:8080访问容器内部的场景依然可达。对于这种冗余功能逻辑的情况,我们尚未找到原因。
docker-proxy是否有必要存在在网上大量的容器最佳实践中都建议关闭docker-proxy,“原因是docker会为每个容器每个暴露的端口都启动一个docker-proxy进程,这个docker-proxy会消耗大概2M的RSS内存。当宿主机环境上有几百上千个容器的时刻,那么可能有几百上千个docker-proxy,其对物理内存消耗的是非常可观的。而docker-proxy的功能完全可以被docker配置的iptables nat规则替代。所以没有docker-proxy就没有必要开启。”
可以看到在容器zxy-nginx内通过主机ip+容器映射主机端口方式一样可以访问到zxy-nginx容器自己暴露的nginx服务。
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。 尾声:关闭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
在笔者的环境上,统计出来docke-proxy在有负荷(通过nginx镜像下载10G的文件)情况下,docker-proxy内存消耗在500KB左右。 作者:marshalzxy |
|