详解Docker使用Linux iptables 和 Interfaces管理容器网络
我使用docker至今已有一段时间了,与绝大部分的人一样,我被docker强大的功能和易用性深深的折服。简单方便是docker的核心之一,它强大的功能被抽象成了非常简单的命令。当我在使用和学习docker的时候,我很想知道docker在后台都做了一些什么事情,特别是在网络这一块(我最感兴趣的一块)
我找到了很多关于创建和操作容器网络的文档,但是关于docker如何使网络工作的却没有那么多。Docker广泛使用linuxiptables和网桥接口,这篇文章是我如何用于创建容器网络的总结,大部分信息来自github上的讨论,演示文稿,以及我自己的测试。文章结尾我会给出我认为非常有用的资料链接。
我写这篇文章使用的是docker1.12.3,但这不是作为对docker网络的全面描述,也不作为docker网络的介绍。我只希望这篇文章能给大家开拓视野,也非常感谢所有对文章错误,缺失的反馈和批评。
Docker网络概览
Docker的网络建立在允许任何一方编写自己的网络驱动程序的容器网络模型(CNM)之上。这允许不同的网络类型可用于在docker引擎上运行的容器,并且容器可以同时连接到多个网络。除了各种第三方网络驱动程序可用,docker自带四个内置网络
驱动程序:
Bridge:这是启动容器的默认网络。通过docker主机上的网桥接口实现连接。使用相同网桥的容器有自己的子网,并且可以相互通信(默认情况下)。
Host:这个驱动程序允许容器访问docker主机自己的网络空间(容器将看到和使用与docker主机相同的接口)。
Macvlan:此驱动程序允许容器直接访问主机的接口或子接口(vlan)。它还允许中继链接。
Overlay:此驱动程序允许在运行docker的多个主机(通常是docker群集群)上构建网络。容器还具有自己的子网和网络地址,并且可以直接相互通信,即使它们在不同的物理主机上运行。
Bridge和Overlay可能是最常用的网络驱动程序,在本文和下一篇文章中我将主要关注这两个驱动程序。
DockerBridge网络
在docker主机上运行的容器的默认网络是。Docker在首次安装时创建一个名为“bridge”的默认网络。我们可以列出所有docker网络来查看此网络dockernetworkls:
要检查其属性,运行dockernetworkinspectbridge
$dockernetworkinspectbridge [ { "Name":"bridge", "Id":"3e8110efa04a1eb0923d863af719abf5eac871dbac4ae74f133894b8df4b9f5f", "Scope":"local", "Driver":"bridge", "EnableIPv6":false, "IPAM":{ "Driver":"default", "Options":null, "Config":[ { "Subnet":"172.18.0.0/16", "Gateway":"172.18.0.1" } ] }, "Internal":false, "Containers":{}, "Options":{ "com.docker.network.bridge.default_bridge":"true", "com.docker.network.bridge.enable_icc":"true", "com.docker.network.bridge.enable_ip_masquerade":"true", "com.docker.network.bridge.host_binding_ipv4":"0.0.0.0", "com.docker.network.bridge.name":"docker0", "com.docker.network.driver.mtu":"1500" }, "Labels":{} } ]
你还可以使用dockernetworkcreate命令并指定选项--driverbridge创建自己的网络,例如
dockernetworkcreate--driverbridge--subnet192.168.100.0/24--ip-range192.168.100.0/24my-bridge-network创建另一个网桥网络,名称为“my-bridge-network”,子网为192.168.100.0/24。
Linux网桥接口
docker创建的每个网桥网络由docker主机上的网桥接口呈现。、默认桥网络“bridge”通常具有与其相关联的接口docker0,并且使用dockernetworkcreate命令创建的每个后续网桥网络将具有与其相关联的新接口。
$ifconfigdocker0 docker0Linkencap:EthernetHWaddr02:42:44:88:bd:75 inetaddr:172.18.0.1Bcast:0.0.0.0Mask:255.255.0.0 UPBROADCASTMULTICASTMTU:1500Metric:1 RXpackets:0errors:0dropped:0overruns:0frame:0 TXpackets:0errors:0dropped:0overruns:0carrier:0 collisions:0txqueuelen:0 RXbytes:0(0.0B)TXbytes:0(0.0B)
要找到与你创建的docker网络关联的linux接口,可以使用ifconfig列出所有接口,然后找到你指定了子网的接口,例如,我们想查看我们之前创建的网桥接口my-bridge-network我们可以这样:
$ifconfig|grep192.168.100.-B1 br-e6bc7d6b75f3Linkencap:EthernetHWaddr02:42:bc:f1:91:09 inetaddr:192.168.100.1Bcast:0.0.0.0Mask:255.255.255.0
linux桥接接口与交换机的功能类似,因为它们将不同的接口连接到同一子网,并根据MAC地址转发流量。我们将在下面看到,连接到网桥网络的每个容器将在docker主机上创建自己的虚拟接口,并且docker引擎将同一网络中的所有容器连接到同一个网桥接口,这将允许它们与彼此进行通信。您可以使用brctl获取有关网桥状态的更多详细信息。
$brctlshowdocker0 bridgenamebridgeidSTPenabledinterfaces docker08000.02424488bd75no
一旦我们有容器运行并连接到这个网络,我们将看到interfaces列下面列出的每个容器的接口。并且在桥接器接口上运行流量捕获将允许我们看到同一子网上的容器之间的相互通信。
Linux虚拟网络接口(veth)
容器网络模型(CNM)允许每个容器具有其自己的网络空间。从容器内部运行ifconfig将显示容器内部的网络接口:
$dockerrun-tiubuntu:14.04/bin/bash root@6622112b507c:/# root@6622112b507c:/#ifconfig eth0Linkencap:EthernetHWaddr02:42:ac:12:00:02 inetaddr:172.18.0.2Bcast:0.0.0.0Mask:255.255.0.0 inet6addr:fe80::42:acff:fe12:2/64Scope:Link UPBROADCASTRUNNINGMULTICASTMTU:1500Metric:1 RXpackets:9errors:0dropped:0overruns:0frame:0 TXpackets:6errors:0dropped:0overruns:0carrier:0 collisions:0txqueuelen:0 RXbytes:766(766.0B)TXbytes:508(508.0B) loLinkencap:LocalLoopback inetaddr:127.0.0.1Mask:255.0.0.0 inet6addr:::1/128Scope:Host UPLOOPBACKRUNNINGMTU:65536Metric:1 RXpackets:0errors:0dropped:0overruns:0frame:0 TXpackets:0errors:0dropped:0overruns:0carrier:0 collisions:0txqueuelen:0 RXbytes:0(0.0B)TXbytes:0(0.0B)
然而,上面看到的eth0只能从那个容器中可用,而在Docker主机的外部,docker会创建一个与其对应的双虚拟接口,并作为到容器外的链接。这些虚拟接口连接到上面讨论的桥接器接口,以便于在同一子网上的不同容器之间的连接。
我们可以通过启动连接到默认网桥的两个容器来查看此过程,然后查看docker主机上的接口配置。
在运行启动任何容器之前,docker0桥接接口没有连接的接口:
然后我从ubuntu:14.04镜像启动2个容器
您能马上看到现在有两个接口连接到docker0网桥接口(每个容器一个)
从其中一个容器ping到google,然后从docker主机对容器的虚拟接口进行流量捕获,将显示容器流量
$dockerexeca754719db594pinggoogle.com PINGgoogle.com(216.58.217.110)56(84)bytesofdata. 64bytesfromiad23s42-in-f110.1e100.net(216.58.217.110):icmp_seq=1ttl=48time=0.849ms 64bytesfromiad23s42-in-f110.1e100.net(216.58.217.110):icmp_seq=2ttl=48time=0.965ms ubuntu@swarm02:~$sudotcpdump-iveth2177159icmp tcpdump:verboseoutputsuppressed,use-vor-vvforfullprotocoldecode listeningonveth2177159,link-typeEN10MB(Ethernet),capturesize262144bytes 20:47:12.170815IP172.18.0.3>iad23s42-in-f14.1e100.net:ICMPechorequest,id14,seq55,length64 20:47:12.171654IPiad23s42-in-f14.1e100.net>172.18.0.3:ICMPechoreply,id14,seq55,length64 20:47:13.170821IP172.18.0.3>iad23s42-in-f14.1e100.net:ICMPechorequest,id14,seq56,length64 20:47:13.171694IPiad23s42-in-f14.1e100.net>172.18.0.3:ICMPechoreply,id14,seq56,length64
同样,我们可以从一个容器平到另一个容器。
首先,我们需要获取容器的IP地址,这可以通过在容器中运行ifconfig或使用dockerinspect命令检查容器来完成:
$dockerinspect-f'{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}'a754719db594 172.18.0.3
然后我们从一个容器ping另一个容器
$dockerexec976041ec420fping172.18.0.3 PING172.18.0.3(172.18.0.3)56(84)bytesofdata. 64bytesfrom172.18.0.3:icmp_seq=1ttl=64time=0.070ms 64bytesfrom172.18.0.3:icmp_seq=2ttl=64time=0.053ms
要从docker主机看到这个流量,我们可以在对应于容器的任何一个虚拟接口上捕获,或者我们可以在桥接口(在这个实例中为docker0)上捕获,显示所有的容器间通信子网:
$sudotcpdump-nidocker0host172.18.0.2andhost172.18.0.3 tcpdump:verboseoutputsuppressed,use-vor-vvforfullprotocoldecode listeningondocker0,link-typeEN10MB(Ethernet),capturesize262144bytes 20:55:37.990831IP172.18.0.2>172.18.0.3:ICMPechorequest,id14,seq200,length64 20:55:37.990865IP172.18.0.3>172.18.0.2:ICMPechoreply,id14,seq200,length64 20:55:38.990828IP172.18.0.2>172.18.0.3:ICMPechorequest,id14,seq201,length64 20:55:38.990866IP172.18.0.3>172.18.0.2:ICMPechoreply,id14,seq201,length64
定位一个容器的vet接口
没有直接的方法来找到docker主机上的哪个veth接口链接到容器内的接口,但是在各种docker论坛和github中讨论了几种方法。在我看来最简单的是以下(基于这个解决方案做了稍微的修改),这也取决于ethtool在容器中可访问
例如:我的系统上运行了3个容器
首先我运行如下命令来获得peer_ifindex号
$dockerexec77d9f02d61f2sudoethtool-Seth0 NICstatistics: peer_ifindex:16
然后在docker主机上,通过peer_ifindex找到接口名称
$sudoiplink|grep16 16:veth7bd3604@if15:mtu1500qdiscnoqueuemasterdocker0stateUPmodeDEFAULTgroupdefault
所以,在目前的情况下,接口名称是:veth7bd3604
iptables
Docker使用linuxiptables来控制与它创建的接口和网络之间的通信。Linuxiptables由不同的表组成,但我们主要关注两个:filter和nat。过滤器是网络或接口的流量的安全规则表,用于允许或拒绝IP地址,而nat包含负责屏蔽IP地址或端口的规则。Docker使用nat允许桥接网络上的容器与docker主机之外的目的地进行通信(否则指向容器网络的路由必须在docker主机的网络中添加)
iptables:filter
iptables中的表由对应于处理docker主机上的数据包的不同条件或阶段的不同链组成。默认情况下,过滤器表具有3个链:用于处理到达主机并且去往同一主机的分组的输入链,用于发送到外部目的地的主机的分组的输出链,以及用于进入主机但具有目的地外部主机。每个链由一些规则组成,这些规则规定对分组采取一些措施(例如拒绝或接受分组)以及匹配规则的条件。顺序处理规则,直到找到匹配项,否则应用链的默认策略。也可以在表中定义自定义链。
要查看过滤器表中链的当前配置的规则和默认策略,可以运行iptables-tfilter-L(或iptables-L,如果未指定表,则默认使用过滤器表)
突出显示的是不同的链,以及每个链的默认策略(没有自定义链的默认策略)。我们还可以看到Docker已经添加了两个自定义链:Docker和Docker-Isolation,并且在Forward链中插入了以这两个新链作为目标的规则。
Docker-isolationchain
Docker-isolation包含限制不同容器网络之间的访问的规则。要查看更多详细信息,请在运行iptables时使用-v选项
您可以在上面看到一些删除规则,阻止任何由docker创建的桥接接口之间的流量,从而确保容器网络不能通信。
icc=false
可以传递到dockernetworkcreate命令的选项之一是com.docker.network.bridge.enable_icc,它代表容器间通信。将此选项设置为false会阻止同一网络上的容器彼此通信。这是通过在前向链中添加一个丢弃规则来实现的,该丢弃规则匹配来自与去往同一接口的网络相关联的桥接器接口的分组。
举个例子,我们用以下命令创建一个新的网络
dockernetworkcreate--driverbridge--subnet192.168.200.0/24--ip-range192.168.200.0/24-o"com.docker.network.bridge.enable_icc"="false"no-icc-network
$ifconfig|grep192.168.200-B1 br-8e3f0d353353Linkencap:EthernetHWaddr02:42:c4:6b:f1:40 inetaddr:192.168.200.1Bcast:0.0.0.0Mask:255.255.255.0 $sudoiptables-tfilter-SFORWARD -PFORWARDACCEPT -AFORWARD-jDOCKER-ISOLATION -AFORWARD-obr-8e3f0d353353-jDOCKER -AFORWARD-obr-8e3f0d353353-mconntrack--ctstateRELATED,ESTABLISHED-jACCEPT -AFORWARD-ibr-8e3f0d353353!-obr-8e3f0d353353-jACCEPT -AFORWARD-odocker0-jDOCKER -AFORWARD-odocker0-mconntrack--ctstateRELATED,ESTABLISHED-jACCEPT -AFORWARD-idocker0!-odocker0-jACCEPT -AFORWARD-idocker0-odocker0-jACCEPT -AFORWARD-obr-e6bc7d6b75f3-jDOCKER -AFORWARD-obr-e6bc7d6b75f3-mconntrack--ctstateRELATED,ESTABLISHED-jACCEPT -AFORWARD-ibr-e6bc7d6b75f3!-obr-e6bc7d6b75f3-jACCEPT -AFORWARD-ibr-e6bc7d6b75f3-obr-e6bc7d6b75f3-jACCEPT -AFORWARD-odocker_gwbridge-jDOCKER -AFORWARD-odocker_gwbridge-mconntrack--ctstateRELATED,ESTABLISHED-jACCEPT -AFORWARD-idocker_gwbridge!-odocker_gwbridge-jACCEPT -AFORWARD-olxcbr0-jACCEPT -AFORWARD-ilxcbr0-jACCEPT -AFORWARD-idocker_gwbridge-odocker_gwbridge-jDROP -AFORWARD-ibr-8e3f0d353353-obr-8e3f0d353353-jDROP
iptables:nat
NAT允许主机更改数据包的IP地址或端口。在这种情况下,它用于屏蔽源IP地址来自docker网络(例如172.18.0.0/24子网中的主机),目的地为容器外,位于docker主机的IP地址之后的数据包。此功能由com.docker.network.bridge.enable_ip_masquerade选项控制,可以在dockernetworkcreate(如果未指定,则默认为true)命令中使用。
你可以在iptables的nat表中看到此命令的效果
在postrouting链中,您可以看到在与自己网络外部的任何主机通信时,通过应用伪装操作创建的所有docker网络。
总结
网桥网络在docker主机上具有对应的linux网桥接口,其作为layer2交换机,并且连接在同一子网上的不同容器。
容器中的每个网络接口在Docker主机上具有在容器运行时创建的对应虚拟接口。
桥接接口上来自Docker主机的流量捕获等效于在交换机上配置SPAN端口,可以在该网络上查看所有集群间通信。
在虚拟接口(veth-*)上来自docker主机的流量捕获将显示容器在特定子网上发送的所有流量
Linuxiptables规则用于阻止不同的网络(有时网络中的主机)使用过滤器表进行通信。这些规则通常添加在DOCKER-ISOLATION链中。
容器通过桥接接口与外部通信,其IP被隐藏在docker主机的IP地址后面。这是通过向iptables中的nat表添加规则来实现的。
结束语
以上就是本文关于详解Docker使用Linuxiptables和Interfaces管理容器网络的全部内容,希望对大家有所帮助。有兴趣的朋友可以参阅:浅谈Docker安全机制内核安全与容器之间的网络安全等以及本站其他专题。感谢大家对毛票票的支持!