我使用docker至今已有一段时间了,与绝大部分的人同样,我被docker强大的功能和易用性深深的折服。简单方即是docker的核心之一,它强大的功能被抽象成了很是简单的命令。当我在使用和学习docker的时候,我很想知道docker在后台都作了一些什么事情,特别是在网络这一块(我最感兴趣的一块)php
我找到了不少关于建立和操做容器网络的文档,可是关于docker如何使网络工做的却没有那么多。 Docker普遍使用linux iptables和网桥接口,这篇文章是我如何用于建立容器网络的总结,大部分信息来自github上的讨论,演示文稿,以及我本身的测试。文章结尾我会给出我认为很是有用的资料连接。html
我写这篇文章使用的是docker 1.12.3,但这不是做为对docker网络的全面描述,也不做为docker网络的介绍。我只但愿这篇文章能给你们开拓视野,也很是感谢全部对文章错误,缺失的反馈和批评。linux
Docker的网络创建在容许任何一方编写本身的网络驱动程序的容器网络模型(CNM)之上。这容许不一样的网络类型可用于在docker引擎上运行的容器,而且容器能够同时链接到多个网络。除了各类第三方网络驱动程序可用,docker自带四个内置网络驱动程序:git
Bridge: 这是启动容器的默认网络。经过docker主机上的网桥接口实现链接。 使用相同网桥的容器有本身的子网,而且能够相互通讯(默认状况下)。github
Host:这个驱动程序容许容器访问docker主机本身的网络空间(容器将看到和使用与docker主机相同的接口)。docker
Macvlan:此驱动程序容许容器直接访问主机的接口或子接口(vlan)。 它还容许中继连接。ubuntu
Overlay:此驱动程序容许在运行docker的多个主机(一般是docker群集群)上构建网络。 容器还具备本身的子网和网络地址,而且能够直接相互通讯,即便它们在不一样的物理主机上运行。安全
Bridge和Overlay多是最经常使用的网络驱动程序,在本文和下一篇文章中我将主要关注这两个驱动程序。bash
在docker主机上运行的容器的默认网络是。 Docker在首次安装时建立一个名为“bridge”的默认网络。 咱们能够列出全部docker网络来查看此网络 docker network ls
:网络
$ docker network ls NETWORK ID NAME DRIVER SCOPE 3e8110efa04a bridge bridge local bb3cd79b9236 docker_gwbridge bridge local 22849c4d1c3a host host local 3kuba8yq3c27 ingress overlay swarm ecbd1c6c193a none null local
要检查其属性,运行docker network inspect bridge
$ docker network inspect bridge [ { "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": {} } ]
你还可使用docker network create
命令并指定选项--driver bridge
建立本身的网络,例如docker network create --driver bridge --subnet 192.168.100.0/24 --ip-range 192.168.100.0/ 24 my-bridge-network
建立另外一个网桥网络,名称为“my-bridge-network”
,子网为192.168.100.0/24
。
docker建立的每一个网桥网络由docker主机上的网桥接口呈现。、 默认桥网络“bridge”一般具备与其相关联的接口docker0,而且使用docker network create命令建立的每一个后续网桥网络将具备与其相关联的新接口。
$ ifconfig docker0 docker0 Link encap:Ethernet HWaddr 02:42:44:88:bd:75 inet addr:172.18.0.1 Bcast:0.0.0.0 Mask:255.255.0.0 UP BROADCAST MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
要找到与你建立的docker网络关联的linux接口,可使用ifconfig
列出全部接口,而后找到你指定了子网的接口,例如,咱们想查看咱们以前建立的网桥接口my-bridge-network
咱们能够这样:
$ ifconfig | grep 192.168.100. -B 1 br-e6bc7d6b75f3 Link encap:Ethernet HWaddr 02:42:bc:f1:91:09 inet addr:192.168.100.1 Bcast:0.0.0.0 Mask:255.255.255.0
linux桥接接口与交换机的功能相似,由于它们将不一样的接口链接到同一子网,并根据MAC地址转发流量。 咱们将在下面看到,链接到网桥网络的每一个容器将在docker主机上建立本身的虚拟接口,而且docker引擎将同一网络中的全部容器链接到同一个网桥接口,这将容许它们与彼此进行通讯。 您可使用brctl
获取有关网桥状态的更多详细信息。
$ brctl show docker0 bridge name bridge id STP enabled interfaces docker0 8000.02424488bd75 no
一旦咱们有容器运行并链接到这个网络,咱们将看到interfaces列下面列出的每一个容器的接口。 而且在桥接器接口上运行流量捕获将容许咱们看到同一子网上的容器之间的相互通讯。
容器网络模型(CNM)容许每一个容器具备其本身的网络空间。 从容器内部运行ifconfig
将显示容器内部的网络接口:
$ docker run -ti ubuntu:14.04 /bin/bash root@6622112b507c:/# root@6622112b507c:/# ifconfig eth0 Link encap:Ethernet HWaddr 02:42:ac:12:00:02 inet addr:172.18.0.2 Bcast:0.0.0.0 Mask:255.255.0.0 inet6 addr: fe80::42:acff:fe12:2/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:9 errors:0 dropped:0 overruns:0 frame:0 TX packets:6 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:766 (766.0 B) TX bytes:508 (508.0 B) lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
然而,上面看到的eth0只能从那个容器中可用,而在Docker主机的外部,docker会建立一个与其对应的双虚拟接口,并做为到容器外的连接。 这些虚拟接口链接到上面讨论的桥接器接口,以便于在同一子网上的不一样容器之间的链接。
咱们能够经过启动链接到默认网桥的两个容器来查看此过程,而后查看docker主机上的接口配置。
在运行启动任何容器以前,docker0 桥接接口没有链接的接口:
$ sudo brctl show docker0 $ sudo brctl show docker0 bridge name bridge id STP enabled interfaces docker0 8000.02424488bd75 no
而后我从ubuntu:14.04
镜像启动2个容器
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a754719db594 ubuntu:14.04 "/bin/bash" 5 seconds ago Up 4 seconds zen_kalam 976041ec420f ubuntu:14.04 "/bin/bash" 7 seconds ago Up 5 seconds stupefied_easley
您能立刻看到如今有两个接口链接到docker0网桥接口(每一个容器一个)
$ sudo brctl show docker0 bridge name bridge id STP enabled interfaces docker0 8000.02424488bd75 no veth2177159 vethd8e05dd
从其中一个容器ping到google,而后从docker主机对容器的虚拟接口进行流量捕获,将显示容器流量
$ docker exec a754719db594 ping google.com PING google.com (216.58.217.110) 56(84) bytes of data. 64 bytes from iad23s42-in-f110.1e100.net (216.58.217.110): icmp_seq=1 ttl=48 time=0.849 ms 64 bytes from iad23s42-in-f110.1e100.net (216.58.217.110): icmp_seq=2 ttl=48 time=0.965 ms ubuntu@swarm02:~$ sudo tcpdump -i veth2177159 icmp tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on veth2177159, link-type EN10MB (Ethernet), capture size 262144 bytes 20:47:12.170815 IP 172.18.0.3 > iad23s42-in-f14.1e100.net: ICMP echo request, id 14, seq 55, length 64 20:47:12.171654 IP iad23s42-in-f14.1e100.net > 172.18.0.3: ICMP echo reply, id 14, seq 55, length 64 20:47:13.170821 IP 172.18.0.3 > iad23s42-in-f14.1e100.net: ICMP echo request, id 14, seq 56, length 64 20:47:13.171694 IP iad23s42-in-f14.1e100.net > 172.18.0.3: ICMP echo reply, id 14, seq 56, length 64
一样,咱们能够从一个容器平到另外一个容器。
首先,咱们须要获取容器的IP地址,这能够经过在容器中运行ifconfig
或使用docker inspect
命令检查容器来完成:
$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' a754719db594 172.18.0.3
而后咱们从一个容器ping另外一个容器
$ docker exec 976041ec420f ping 172.18.0.3 PING 172.18.0.3 (172.18.0.3) 56(84) bytes of data. 64 bytes from 172.18.0.3: icmp_seq=1 ttl=64 time=0.070 ms 64 bytes from 172.18.0.3: icmp_seq=2 ttl=64 time=0.053 ms
要从docker主机看到这个流量,咱们能够在对应于容器的任何一个虚拟接口上捕获,或者咱们能够在桥接口(在这个实例中为docker0)上捕获,显示全部的容器间通讯子网:
$ sudo tcpdump -ni docker0 host 172.18.0.2 and host 172.18.0.3 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on docker0, link-type EN10MB (Ethernet), capture size 262144 bytes 20:55:37.990831 IP 172.18.0.2 > 172.18.0.3: ICMP echo request, id 14, seq 200, length 64 20:55:37.990865 IP 172.18.0.3 > 172.18.0.2: ICMP echo reply, id 14, seq 200, length 64 20:55:38.990828 IP 172.18.0.2 > 172.18.0.3: ICMP echo request, id 14, seq 201, length 64 20:55:38.990866 IP 172.18.0.3 > 172.18.0.2: ICMP echo reply, id 14, seq 201, length 64
没有直接的方法来找到docker主机上的哪一个veth接口连接到容器内的接口,可是在各类docker论坛和github中讨论了几种方法。在我看来最简单的是如下(基于这个解决方案作了稍微的修改),这也取决于ethtool
在容器中可访问
例如:个人系统上运行了3个容器
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ccbf97c72bf5 ubuntu:14.04 "/bin/bash" 3 seconds ago Up 3 seconds admiring_torvalds 77d9f02d61f2 ubuntu:14.04 "/bin/bash" 4 seconds ago Up 4 seconds goofy_borg 19743c0ddf24 ubuntu:14.04 "/bin/sh" 8 minutes ago Up 8 minutes high_engelbart
首先我运行以下命令来得到peer_ifindex
号
$ docker exec 77d9f02d61f2 sudo ethtool -S eth0 NIC statistics: peer_ifindex: 16
而后在docker主机上,经过peer_ifindex
找到接口名称
$ sudo ip link | grep 16 16: veth7bd3604@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
因此,在目前的状况下,接口名称是:veth7bd3604
Docker使用linux iptables来控制与它建立的接口和网络之间的通讯。 Linux iptables由不一样的表组成,但咱们主要关注两个:filter
和nat
。过滤器是网络或接口的流量的安全规则表,用于容许或拒绝IP地址,而nat包含负责屏蔽IP地址或端口的规则。Docker使用nat容许桥接网络上的容器与docker主机以外的目的地进行通讯(不然指向容器网络的路由必须在docker主机的网络中添加)
iptables:filter
iptables中的表由对应于处理docker主机上的数据包的不一样条件或阶段的不一样链组成。默认状况下,过滤器表具备3个链:用于处理到达主机而且去往同一主机的分组的输入链,用于发送到外部目的地的主机的分组的输出链,以及用于进入主机但具备目的地外部主机。每一个链由一些规则组成,这些规则规定对分组采起一些措施(例如拒绝或接受分组)以及匹配规则的条件。 顺序处理规则,直到找到匹配项,不然应用链的默认策略。 也能够在表中定义自定义链。
要查看过滤器表中链的当前配置的规则和默认策略,能够运行iptables -t filter -L
(或iptables -L
,若是未指定表,则默认使用过滤器表)
$ sudo iptables -t filter -L Chain INPUT (policy ACCEPT) target prot opt source destination ACCEPT tcp -- anywhere anywhere tcp dpt:domain ACCEPT udp -- anywhere anywhere udp dpt:domain ACCEPT tcp -- anywhere anywhere tcp dpt:bootps ACCEPT udp -- anywhere anywhere udp dpt:bootps Chain FORWARD (policy ACCEPT) target prot opt source destination DOCKER-ISOLATION all -- anywhere anywhere DOCKER all -- anywhere anywhere ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED ACCEPT all -- anywhere anywhere ACCEPT all -- anywhere anywhere DOCKER all -- anywhere anywhere ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED ACCEPT all -- anywhere anywhere ACCEPT all -- anywhere anywhere DOCKER all -- anywhere anywhere ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED ACCEPT all -- anywhere anywhere ACCEPT all -- anywhere anywhere ACCEPT all -- anywhere anywhere DROP all -- anywhere anywhere Chain OUTPUT (policy ACCEPT) target prot opt source destination Chain DOCKER (3 references) target prot opt source destination Chain DOCKER-ISOLATION (1 references) target prot opt source destination DROP all -- anywhere anywhere DROP all -- anywhere anywhere DROP all -- anywhere anywhere DROP all -- anywhere anywhere DROP all -- anywhere anywhere DROP all -- anywhere anywhere RETURN all -- anywhere anywhere
突出显示的是不一样的链,以及每一个链的默认策略(没有自定义链的默认策略)。 咱们还能够看到Docker已经添加了两个自定义链:Docker
和Docker-Isolation
,而且在Forward
链中插入了以这两个新链做为目标的规则。
Docker-isolation chain
Docker-isolation
包含限制不一样容器网络之间的访问的规则。 要查看更多详细信息,请在运行iptables
时使用-v
选项
$ sudo iptables -t filter -L -v …. Chain DOCKER-ISOLATION (1 references) pkts bytes target prot opt in out source destination 0 0 DROP all -- br-e6bc7d6b75f3 docker0 anywhere anywhere 0 0 DROP all -- docker0 br-e6bc7d6b75f3 anywhere anywhere 0 0 DROP all -- docker_gwbridge docker0 anywhere anywhere 0 0 DROP all -- docker0 docker_gwbridge anywhere anywhere 0 0 DROP all -- docker_gwbridge br-e6bc7d6b75f3 anywhere anywhere 0 0 DROP all -- br-e6bc7d6b75f3 docker_gwbridge anywhere anywhere 36991 3107K RETURN all -- any any anywhere anywhere
您能够在上面看到一些删除规则,阻止任何由docker建立的桥接接口之间的流量,从而确保容器网络不能通讯。
icc=false
能够传递到docker network create
命令的选项之一是com.docker.network.bridge.enable_icc
,它表明容器间通讯。 将此选项设置为false
会阻止同一网络上的容器彼此通讯。 这是经过在前向链中添加一个丢弃规则来实现的,该丢弃规则匹配来自与去往同一接口的网络相关联的桥接器接口的分组。
举个例子,咱们用如下命令建立一个新的网络
docker network create --driver bridge --subnet 192.168.200.0/24 --ip-range 192.168.200.0/24 -o "com.docker.network.bridge.enable_icc"="false" no-icc-network
$ ifconfig | grep 192.168.200 -B 1 br-8e3f0d353353 Link encap:Ethernet HWaddr 02:42:c4:6b:f1:40 inet addr:192.168.200.1 Bcast:0.0.0.0 Mask:255.255.255.0 $ sudo iptables -t filter -S FORWARD -P FORWARD ACCEPT -A FORWARD -j DOCKER-ISOLATION -A FORWARD -o br-8e3f0d353353 -j DOCKER -A FORWARD -o br-8e3f0d353353 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A FORWARD -i br-8e3f0d353353 ! -o br-8e3f0d353353 -j ACCEPT -A FORWARD -o docker0 -j DOCKER -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A FORWARD -i docker0 ! -o docker0 -j ACCEPT -A FORWARD -i docker0 -o docker0 -j ACCEPT -A FORWARD -o br-e6bc7d6b75f3 -j DOCKER -A FORWARD -o br-e6bc7d6b75f3 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A FORWARD -i br-e6bc7d6b75f3 ! -o br-e6bc7d6b75f3 -j ACCEPT -A FORWARD -i br-e6bc7d6b75f3 -o br-e6bc7d6b75f3 -j ACCEPT -A FORWARD -o docker_gwbridge -j DOCKER -A FORWARD -o docker_gwbridge -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A FORWARD -i docker_gwbridge ! -o docker_gwbridge -j ACCEPT -A FORWARD -o lxcbr0 -j ACCEPT -A FORWARD -i lxcbr0 -j ACCEPT -A FORWARD -i docker_gwbridge -o docker_gwbridge -j DROP -A FORWARD -i br-8e3f0d353353 -o br-8e3f0d353353 -j DROP
iptables:nat
NAT
容许主机更改数据包的IP地址或端口。在这种状况下,它用于屏蔽源IP地址来自docker网络(例如172.18.0.0/24子网中的主机),目的地为容器外,位于docker主机的IP地址以后的数据包。此功能由com.docker.network.bridge.enable_ip_masquerade
选项控制,能够在docker network create
(若是未指定,则默认为true)命令中使用。
你能够在iptables的nat表中看到此命令的效果
$ sudo iptables -t nat -L Chain PREROUTING (policy ACCEPT) target prot opt source destination DOCKER all -- anywhere anywhere ADDRTYPE match dst-type LOCAL Chain INPUT (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination DOCKER all -- anywhere !127.0.0.0/8 ADDRTYPE match dst-type LOCAL Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all -- 172.18.0.0/16 anywhere MASQUERADE all -- 192.168.100.0/24 anywhere MASQUERADE all -- 172.19.0.0/16 anywhere MASQUERADE all -- 10.0.3.0/24 !10.0.3.0/24 Chain DOCKER (2 references) target prot opt source destination RETURN all -- anywhere anywhere RETURN all -- anywhere anywhere RETURN all -- anywhere anywhere
在postrouting
链中,您能够看到在与本身网络外部的任何主机通讯时,经过应用假装操做建立的全部docker网络。
网桥网络在docker主机上具备对应的linux网桥接口,其做为layer2交换机,而且链接在同一子网上的不一样容器。
容器中的每一个网络接口在Docker主机上具备在容器运行时建立的对应虚拟接口。
桥接接口上来自Docker主机的流量捕获等效于在交换机上配置SPAN端口,能够在该网络上查看全部集群间通讯。
在虚拟接口(veth- *)上来自docker主机的流量捕获将显示容器在特定子网上发送的全部流量
Linux iptables规则用于阻止不一样的网络(有时网络中的主机)使用过滤器表进行通讯。 这些规则一般添加在DOCKER-ISOLATION
链中。
容器经过桥接接口与外部通讯,其IP被隐藏在docker主机的IP地址后面。 这是经过向iptables
中的nat
表添加规则来实现的。
Deep dive into Docker 1.12 Networking
Docker container networking user guide
原文:docker-networking-internals-how-docker
【云盟认证成员】: 超儿哥 译