V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Goodapp
V2EX  ›  推广

#Docker 网络构造: Docker 如何使用 Linux iptables 和 Interfaces

  •  
  •   Goodapp · 2016-12-23 12:39:41 +08:00 · 1773 次点击
    这是一个创建于 2922 天前的主题,其中的信息可能已经有所发展或是发生改变。

    #Docker 网络构造: Docker 如何使用 Linux iptables 和 Interfaces

    我使用 docker 至今已有一段时间了,与绝大部分的人一样,我被 docker 强大的功能和易用性深深的折服。简单方便是 docker 的核心之一,它强大的功能被抽象成了非常简单的命令。当我在使用和学习 docker 的时候,我很想知道 docker 在后台都做了一些什么事情,特别是在网络这一块(我最感兴趣的一块)

    我找到了很多关于创建和操作容器网络的文档,但是关于 docker 如何使网络工作的却没有那么多。 Docker 广泛使用 linux iptables 和网桥接口,这篇文章是我如何用于创建容器网络的总结,大部分信息来自 github 上的讨论,演示文稿,以及我自己的测试。文章结尾我会给出我认为非常有用的资料链接。

    我写这篇文章使用的是 docker 1.12.3,但这不是作为对 docker 网络的全面描述,也不作为 docker 网络的介绍。我只希望这篇文章能给大家开拓视野,也非常感谢所有对文章错误,缺失的反馈和批评。

    ###Docker 网络概览 Docker 的网络建立在允许任何一方编写自己的网络驱动程序的容器网络模型( CNM )之上。这允许不同的网络类型可用于在 docker 引擎上运行的容器,并且容器可以同时连接到多个网络。除了各种第三方网络驱动程序可用, docker 自带四个内置网络驱动程序:

    • Bridge: 这是启动容器的默认网络。通过 docker 主机上的网桥接口实现连接。 使用相同网桥的容器有自己的子网,并且可以相互通信(默认情况下)。
    • Host:这个驱动程序允许容器访问 docker 主机自己的网络空间(容器将看到和使用与 docker 主机相同的接口)。
    • Macvlan:此驱动程序允许容器直接访问主机的接口或子接口( vlan )。 它还允许中继链接。
    • Overlay:此驱动程序允许在运行 docker 的多个主机(通常是 docker 群集群)上构建网络。 容器还具有自己的子网和网络地址,并且可以直接相互通信,即使它们在不同的物理主机上运行。

    Bridge 和 Overlay 可能是最常用的网络驱动程序,在本文和下一篇文章中我将主要关注这两个驱动程序。

    ####Docker Bridge 网络 在 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
    [
    
        {
            <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/24

    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

    [云盟认证成员] : 超儿哥

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2282 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 16:09 · PVG 00:09 · LAX 08:09 · JFK 11:09
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.