Docker 網絡構造:Docker如何使用Linux iptables 和 Interfaces

#Docker 網絡構造:Docker如何使用Linux iptables 和 Interfacesphp

我使用docker至今已有一段時間了,與絕大部分的人同樣,我被docker強大的功能和易用性深深的折服。簡單方即是docker的核心之一,它強大的功能被抽象成了很是簡單的命令。當我在使用和學習docker的時候,我很想知道docker在後臺都作了一些什麼事情,特別是在網絡這一塊(我最感興趣的一塊)html

我找到了不少關於建立和操做容器網絡的文檔,可是關於docker如何使網絡工做的卻沒有那麼多。 Docker普遍使用linux iptables和網橋接口,這篇文章是我如何用於建立容器網絡的總結,大部分信息來自github上的討論,演示文稿,以及我本身的測試。文章結尾我會給出我認爲很是有用的資料連接。linux

我寫這篇文章使用的是docker 1.12.3,但這不是做爲對docker網絡的全面描述,也不做爲docker網絡的介紹。我只但願這篇文章能給你們開拓視野,也很是感謝全部對文章錯誤,缺失的反饋和批評。git

###Docker網絡概覽 Docker的網絡創建在容許任何一方編寫本身的網絡驅動程序的容器網絡模型(CNM)之上。這容許不一樣的網絡類型可用於在docker引擎上運行的容器,而且容器能夠同時鏈接到多個網絡。除了各類第三方網絡驅動程序可用,docker自帶四個內置網絡驅動程序:github

  • Bridge: 這是啓動容器的默認網絡。經過docker主機上的網橋接口實現鏈接。 使用相同網橋的容器有本身的子網,而且能夠相互通訊(默認狀況下)。
  • Host:這個驅動程序容許容器訪問docker主機本身的網絡空間(容器將看到和使用與docker主機相同的接口)。
  • Macvlan:此驅動程序容許容器直接訪問主機的接口或子接口(vlan)。 它還容許中繼連接。
  • Overlay:此驅動程序容許在運行docker的多個主機(一般是docker羣集羣)上構建網絡。 容器還具備本身的子網和網絡地址,而且能夠直接相互通訊,即便它們在不一樣的物理主機上運行。

Bridge和Overlay多是最經常使用的網絡驅動程序,在本文和下一篇文章中我將主要關注這兩個驅動程序。docker

####Docker Bridge 網絡 在docker主機上運行的容器的默認網絡是。 Docker在首次安裝時建立一個名爲「bridge」的默認網絡。 咱們能夠列出全部docker網絡來查看此網絡 docker network lsubuntu

$ 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
[

    {
        <font color="red">"Name": "bridge",</font>
        "Id": "3e8110efa04a1eb0923d863af719abf5eac871dbac4ae74f133894b8df4b9f5f",
        "Scope": "local",
        <font color="red">"Driver": "bridge",</font>
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    <font color="red">"Subnet": "172.18.0.0/16",</font>
                    "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/24bash

Linux 網橋接口

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
<font color="red">br-e6bc7d6b75f3</font> 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列下面列出的每一個容器的接口。 而且在橋接器接口上運行流量捕獲將容許咱們看到同一子網上的容器之間的相互通訊。

####Linux 虛擬網絡接口(veth)

容器網絡模型(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
bridge name     bridge id               STP enabled     <font color="red">interfaces</font>
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     <font color="red">interfaces</font>
docker0         8000.02424488bd75       no              <font color="red">veth2177159</font>
                                                        <font color="red">vethd8e05dd</font>

從其中一個容器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 <font color="red">veth2177159</font> 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 
<font color="red">172.18.0.3</font>

而後咱們從一個容器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 <font color="red">docker0</font> 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

#####定位一個容器的vet接口 沒有直接的方法來找到docker主機上的哪一個veth接口連接到容器內的接口,可是在各類docker論壇和github中討論了幾種方法。在我看來最簡單的是如下(基於這個解決方案作了稍微的修改),這也取決於ethtool在容器中可訪問

例如:個人系統上運行了3個容器

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
<font color="red">ccbf97c72bf5</font>        ubuntu:14.04        "/bin/bash"         3 seconds ago       Up 3 seconds                            admiring_torvalds
<font color="red">77d9f02d61f2</font>        ubuntu:14.04        "/bin/bash"         4 seconds ago       Up 4 seconds                            goofy_borg
<font color="red">19743c0ddf24</font>        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: <font color="red">16</font>

而後在docker主機上,經過peer_ifindex找到接口名稱

$ sudo ip link | grep <font color="red">16</font>
16: <font color="red">veth7bd3604</font>@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default

因此,在目前的狀況下,接口名稱是:veth7bd3604

iptables

Docker使用linux iptables來控制與它建立的接口和網絡之間的通訊。 Linux iptables由不一樣的表組成,但咱們主要關注兩個:filternat。過濾器是網絡或接口的流量的安全規則表,用於容許或拒絕IP地址,而nat包含負責屏蔽IP地址或端口的規則。Docker使用nat容許橋接網絡上的容器與docker主機以外的目的地進行通訊(不然指向容器網絡的路由必須在docker主機的網絡中添加)

iptables:filter

iptables中的表由對應於處理docker主機上的數據包的不一樣條件或階段的不一樣鏈組成。默認狀況下,過濾器表具備3個鏈:用於處理到達主機而且去往同一主機的分組的輸入鏈,用於發送到外部目的地的主機的分組的輸出鏈,以及用於進入主機但具備目的地外部主機。每一個鏈由一些規則組成,這些規則規定對分組採起一些措施(例如拒絕或接受分組)以及匹配規則的條件。 順序處理規則,直到找到匹配項,不然應用鏈的默認策略。 也能夠在表中定義自定義鏈。

要查看過濾器表中鏈的當前配置的規則和默認策略,能夠運行iptables -t filter -L(或iptables -L,若是未指定表,則默認使用過濾器表)

$ sudo iptables -t filter -L
<font color="red">Chain INPUT (policy ACCEPT)</font>
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
<font color="red">Chain FORWARD (policy ACCEPT)</font>
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
<font color="red">Chain OUTPUT</font> (policy ACCEPT)
target     prot opt source               destination
<font color="red">Chain DOCKER</font> (3 references)
target     prot opt source               destination
<font color="red">Chain DOCKER-ISOLATION</font> (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已經添加了兩個自定義鏈:DockerDocker-Isolation,而且在Forward鏈中插入了以這兩個新鏈做爲目標的規則。

Docker-isolation chain

Docker-isolation包含限制不一樣容器網絡之間的訪問的規則。 要查看更多詳細信息,請在運行iptables時使用-v選項

$ sudo iptables -t filter -L -v
….
Chain <font color="red">DOCKER-ISOLATION</font> (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
<font color="red">br-8e3f0d353353</font> Link encap:Ethernet  HWaddr 02:42:c4:6b:f1:40
          <font color="red">inet addr:192.168.200.1</font>  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
<font color="red">-A FORWARD -i br-8e3f0d353353 -o br-8e3f0d353353 -j DROP</font>

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
<font color="red">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</font>


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表添加規則來實現的。

###相關鏈接

Docker networking concepts

Deep dive into Docker 1.12 Networking

Docker container networking user guide

Linux iptables overview

原文:docker-networking-internals-how-docker

【雲盟認證成員】: 超兒哥

相關文章
相關標籤/搜索