一、背景
之前分享关于NAT网络地址转换的相关文章,docker中的网络正好使用到了NAT,顺带着把这个分享一下,分析docker容器的网络数据包流出、数据流入原理分析。
知识回顾:
docker运行一个容器之后,会给这个容器一个独立的netns网络命名空间和其他网络命名空间进行隔离,形成自己独立的、隔离的网络栈。
并且,会生成1个veth paris虚拟网卡对(就像一根虚拟网线,有2个头, 2个veth虚拟设备接口), 一头插入容器的网络命名空间,一头插入docker0网桥(默认不创建自己的网桥,则会连接到docker0网桥)。 容器内部的路由表则将网关指向docker0的IP地址作为"下一跳", 非docker0内网网段的IP数据包会通过网关转发,否则直接通过ARP广播,拿到对方MAC地址,通过二层网络直接通信。
宿主机查看网络设备:
ip l
看到veth45c97 位于15号网络接口索引,连接if14号网络接口。 并且后面看到关键词 master docker0, 这个就代表这个veth桥接到了docker0网桥接口。
容器内查看网络设备:
ip l
看到容器自己的eth0位于14号网络接口, 连接if15好网络接口。两者都是veth类型的虚拟网络设备,是不是很像1根网线的2个插口,一边插入docker0网桥, 一边插入容器的网络命名空间,从而实现通信。
二、运行nginx容器-分析网络数据包流向
实验信息:
1、宿主机内网IP地址: 192.168.2.116
2、我电脑内网IP地址: 192.168.2.104
1、运行nginx容器,并且8080:80端口映射
docker run -d -p 8080:80 nginx
查看容器ip地址为: 172.17.0.4
2、宿主机tcpdump监听网卡数据包、docker0数据包
我电脑使用curl访问宿主机192.168.2.116:8080,抓包结果如下:
1、网卡enp0s3的抓包结果
请求数据包:源IP地址、端口: 192.168.2.104:52140 => 目的IP地址、端口: 192.168.2.116:8080再看响应数据包:源IP地址、端口: 192.168.2.116:8080 => 目的IP地址、端口: 192.168.2.104:52140
2、docker0网桥的抓包结果
请求数据包:源IP地址、端口: 192.168.2.104:52140 => 目的IP地址、端口: 172.17.0.4:80再看响应数据包:源IP地址、端口: 172.17.0.4:80 => 目的IP地址、端口: 192.168.2.104:52140
3、IP数据包差异分析
很明显,我们发现, 目的IP数据包在enp0s3还是192.168.2.116:8080, 但是监听docker0进来的数据,目的IP数据包变成了172.17.0.4:80。
数据包被修改了! 这个就是docker底层通过iptables做了DNAT的结果。 就是将IP数据包为192.168.2.116:8080 做DNAT,修改为172.17.0.4:80, 再通过docker0转发,docker0发现是自己的网桥桥接的内网IP,再将数据包转发给nginx容器,完成数据传输。
4、DNAT验证猜想,查看iptables规则表
iptables -t nat -L -n
嘿嘿,果然不出所料,确实添加了一条DNAT规则, 将访问宿主机8080端口数据包,改为转发到172.17.0.4:80(nginx容器所在IP和端口)
5、SNAT原理也是如此
容器返回给客户端的数据包,也经过了SNAT的过程,数据包经过SNAT,将源IP地址改为宿主机IP地址和宿主机端口, 最后客户端才能得到✅正确响应。
6、conntrack查看NAT映射关系记录
yum install conntrack -yconntrack -Lconntrack -L | grep 172
这里就能清晰看到源客户端IP是192.168.2.104、源客户端端口是54640, 宿主机目的IP是192.168.2.116、宿主机目的端口8080 DNAT之后,目的IP为容器的172.17.0.2、容器端口变为80。 因为要存在维持这么一条映射关系, 后面容器进行回包的时候,才能利用知道源地址用哪个宿主机IP、宿主机端口。
并且NAT记录维持是有过期时间的,多久这个连接没活跃状态,则就会被回收。和我们使用家庭宽带上网一样,ISP做的NAT映射也有这么一条记录,连接都很久没活跃了,就会回收,要不然这个出口公网IP端口长期都占着茅坑不拉屎,那就造成了极大的资源浪费。
三、iptables后台服务与docker是否能正常运行-误区
1、误区解释
这里应该很多人有这个误区。都说docker是依靠iptables规则做到NAT转换,但是我查看宿主机进程,iptables没有、firewalld也没有,这到底是怎么实现的哟。 iptables服务都没开启,也能让NAT生效?
之前我对此也是有严重误解。我的宿主机也没有iptables、firewalld服务运行,但是docker服务正常运行。
原理: Docker默认通过iptables规则实现容器网络通信和端口映射(如DNAT/SNAT),即使系统未显式启动iptables.service,只要内核加载了iptables模块,Docker仍能自动管理规则。但若内核模块未加载或iptables被完全禁用,Docker的NAT功能将失效。
iptables的服务,只是用户态的一个工具,用来操作内核netfilter规则的一个工具,所以只要内核级别的iptables模块加载了,docker作为客户端(与iptables用户态工具级别一样)也可以直接操作内核的iptables规则,从而实现NAT功能。 和用户态iptables管理工具启不启用没有任何关系。
2、docker依赖的内核模块
lsmod | grep -E 'iptable|nf_|br_netfilter'
四、总结
docker的容器之间相互访问,可以通过veth和docker0网桥实现,源地址、目的地址都不需要做变化,就是在二层网络进行传输,不需要网关的参与。
但是如果涉及到数据出宿主机则使用SNAT做源地址转换,源数据包转换为宿主机IP地址才能通过宿主机的网卡路由出去,反之,如果想访问我容器的服务,则经过宿主机的时候要做DNAT,将数据包的目的IP和端口,改为容器的内网IP和端口,容器才能正常响应。
流程大致如下图所示: