Docker 网络(十一)

Docker 通过网络驱动来支持容器的网络通信,默认情况下,Docker 提供两种网络驱动供我们使用,一个是 bridge,一个是 overlay。我们也可以自己写一个网络驱动插件,如果你足够大牛的话。

Docker 安装时会自动在 host 上创建三个网络,我们可用docker network ls命令查看:

root@ubuntu:~# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
fe6e820d51e2        bridge              bridge              local
e91fa0de345b        host                host                local
9fea16767e7a        none                null                local

一、none 网络

故名思议,none 网络就是什么都没有的网络。挂在这个网络下的容器除了 lo,没有其他任何网卡。容器创建时,可以通过--network=none指定使用 none 网络。

docker run -it --network=none busybox

我们不禁会问,这样一个封闭的网络有什么用呢?

其实还真有应用场景。封闭意味着隔离,一些对安全性要求高并且不需要联网的应用可以使用 none 网络。

比如某个容器的唯一用途是生成随机密码,就可以放到 none 网络中避免密码被窃取。

当然大部分容器是需要网络的,我们接着看 host 网络。

二、host 网络

连接到 host 网络的容器共享 Docker host 的网络栈,容器的网络配置与 host 完全一样。可以通过--network=host指定使用 host 网络。

docker run -it --network=host busybox

直接使用 Docker host 的网络最大的好处就是性能,如果容器对网络传输效率有较高要求,则可以选择 host 网络。当然不便之处就是牺牲一些灵活性,比如要考虑端口冲突问题,Docker host 上已经使用的端口就不能再用了。

Docker host 的另一个用途是让容器可以直接配置 host 网路。比如某些跨 host 的网络解决方案,其本身也是以容器方式运行的,这些方案需要对网络进行配置,比如管理 iptables

三、bridge 网络

bridge 是一个很特殊的网络,Docker 安装时会创建一个 命名为 docker0 的 linux bridge。如果不指定 --network,创建的容器默认都会挂到 docker0 上。

接口 docker0 是一个虚拟的以太网桥,用于连接容器和本地宿主网络。

我们停掉所有的容器查看一下。

root@ubuntu:~# brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.0242f47922c9       no

当前 docker0 上没有任何其他网络设备,我们创建一个容器看看有什么变化。

一个新的网络接口 vethcd8f150 被挂到了 docker0 上,vethcd8f150 就是新创建容器的虚拟网卡。

下面看一下容器的网络配置。

容器有一个网卡 eth0@if39。大家可能会问了,为什么不是 vethcd8f150 呢?

Docker 每创建一个容器就会创建一组互联的网络接口。这组接口就像管道的两端(就是说,从一端发送的数据会在另一端接收到)。这组接口其中一端作为容器里的 eth0 接口,而领一端统一命名为类似 vethxxxx 这种名字,作为宿主机的一个端口。可以把 veth 接口认为是虚拟网线的一端。这个虚拟网线一端插在名为 docker0 的网桥上,另一端插到容器里。通过把每个 veth 接口绑定到 docker0 网桥,Docker 创建了一个虚拟子网,这个子网由宿主机和所有的 Docker 容器共享。

所以说 eth0@if39 和 vethcd8f150 是一对 veth pair。veth pair 是一种成对出现的特殊网络设备,可以把它们想象成由一根虚拟网线连接起来的一对网卡,网卡的一头(eth0)在容器中,另一头(vethcd8f150)挂在网桥 docker0 上,其效果就是将 eth0@if39 也挂在了 docker0 上。

我们还看到 eth0@if39 已经配置了 IP 172.17.0.2,为什么是这个网段呢?让我们通过docker network inspect bridge看一下 bridge 网络的配置信息:

root@ubuntu:~# docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "fe6e820d51e226b3262ce3bba44ebae6ec7106db36db333b59ffd09dd0608f23",
        "Created": "2017-12-01T13:53:34.169111033+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "c14ec27277f6228eff709c5b5a437214d967247e716daf7186aacb5339497ac9": {
                "Name": "unruffled_swanson",
                "EndpointID": "5f8034274333c8582645620b238e2ede7c9267a7433369017a576a3aaaa08527",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "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": {}
    }
]

原来 bridge 网络配置的 subnet 就是 172.17.0.0/16,并且网关是 172.17.0.1,网关就是 docker0。

容器创建时,docker 会自动从 172.17.0.0/16 中分配一个 IP,这里 16 位的掩码保证有足够多的 IP 可以供容器使用。

我们也可以把一个容器从一个网络中端口,使用如下命令即可。

docker network disconnect bridge <Container ID>

四、User-defined 网络

1、创建 my_bridge 网络

Docker 本身提供两种网络驱动:bridge 和 overlay。bridge 只能用于单机网络模式,overlay 用于创建跨主机的网络,我们可通过 bridge 驱动创建类似前面默认的 bridge 网络,例如:

root@ubuntu:~# docker network create -d bridge my_bridge
423b660fbdbd5cfacc4cbf591bdf2bc977e7865261349efb6482dbdcbc7e1bd3
root@ubuntu:~# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
fe6e820d51e2        bridge              bridge              local
e91fa0de345b        host                host                local
423b660fbdbd        my_bridge           bridge              local
9fea16767e7a        none                null                local

-d 就是 --bridge 的简写,也可以使用 --bridge 替换进行创建。

查看一下当前 host 的网络结构变化:

root@ubuntu:~# brctl show
bridge name     bridge id               STP enabled     interfaces
br-423b660fbdbd         8000.0242cb3347fd       no
docker0         8000.0242f47922c9       no              vethcd8f150

新增了一个网桥 br-423b660fbdbd

执行docker network inspect查看一下 my_bridge 的配置信息:

root@ubuntu:~# docker network inspect my_bridge
[
    {
        "Name": "my_bridge",
        "Id": "423b660fbdbd5cfacc4cbf591bdf2bc977e7865261349efb6482dbdcbc7e1bd3",
        "Created": "2017-12-04T16:29:23.726303365+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

这里 172.18.0.0/16 是 Docker 自动分配的 IP 网段。

2、自定义网络 IP 段

如果要自定义网络 IP 段,只需在创建网段时指定--subnet--gateway参数:

root@ubuntu:~# docker network create -d bridge --subnet 192.168.31.0/24 --gateway 192.168.31.1 my_bridge2
43043f6bbc1a74106bef92e158daec3ea376748de2f8695541c8e93964303b5b
root@ubuntu:~# docker network inspect my_bridge2
[
    {
        "Name": "my_bridge2",
        "Id": "43043f6bbc1a74106bef92e158daec3ea376748de2f8695541c8e93964303b5b",
        "Created": "2017-12-04T16:39:19.832006374+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "192.168.31.0/24",
                    "Gateway": "192.168.31.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

这里我们创建了新的 bridge 网络 my_bridge2,网段为 192.168.31.0/24,网关为 192.168.31.1。与前面一样,网关在 my_bridge2 对应的网桥 br-43043f6bbc1a 上:

root@ubuntu:~# brctl show
bridge name     bridge id               STP enabled     interfaces
br-423b660fbdbd         8000.0242cb3347fd       no
br-43043f6bbc1a         8000.02426266f83e       no
docker0         8000.0242f47922c9       no              vethcd8f150

容器要使用新的网络,需要在启动时通过--network指定:

root@ubuntu:~# docker run -it --network=my_bridge2 --name busybox1 busybox
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
42: eth0@if43: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:42:c0:a8:1f:02 brd ff:ff:ff:ff:ff:ff
    inet 192.168.31.2/24 scope global eth0
       valid_lft forever preferred_lft forever

容器分配到的 IP 为 192.168.31.2。

root@ubuntu:~# brctl show
bridge name     bridge id               STP enabled     interfaces
br-423b660fbdbd         8000.0242cb3347fd       no
br-43043f6bbc1a         8000.02426266f83e       no              vethfffb9d3
docker0         8000.0242f47922c9       no              vethcd8f150

3、给容器指定分配静态 IP

我们在启动容器的时候,可以通过参数 --ip 来指定特定的 IP,只有使用--subnet创建的 User-defined 网络才能指定静态 IP。

root@ubuntu:~# docker run -it --network=my_bridge2 --ip 192.168.31.25 --name busybox2 busybox
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
44: eth0@if45: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:42:c0:a8:1f:19 brd ff:ff:ff:ff:ff:ff
    inet 192.168.31.25/24 scope global eth0
       valid_lft forever preferred_lft forever
root@ubuntu:~# brctl show
bridge name     bridge id               STP enabled     interfaces
br-423b660fbdbd         8000.0242cb3347fd       no
br-43043f6bbc1a         8000.02426266f83e       no              veth8e0d4f7
                                                        vethfffb9d3
docker0         8000.0242f47922c9       no              vethcd8f150

4、容器联通测试

两个 busybox 容器都挂在 my_bridge2 上,应该能够互通,我们验证一下:

可见同一网络中的容器、网关之间都是可以通信的。

从拓扑图来看,my_bridge2 与默认 bridge 两个网络属于不同的网桥,应该不能通信,我们通过实验验证一下,让 busybox1 容器 ping httpd 容器:

确实 ping 不通,符合预期。

5、不同网桥下的容器互通

不同的网络如果加上路由应该就可以通信了吧?确实,如果 host 上对每个网络的都有一条路由,同时操作系统上打开了 ip forwarding,host 就成了一个路由器,挂接在不同网桥上的网络就能够相互通信。下面我们来看看 docker host 满不满足这些条件呢?
ip r 查看 host 上的路由表:

root@ubuntu:~# ip r
default via 192.168.2.1 dev enp5s0 onlink
172.17.0.0/16 dev docker0  proto kernel  scope link  src 172.17.0.1
172.18.0.0/16 dev br-423b660fbdbd  proto kernel  scope link  src 172.18.0.1 linkdown
192.168.2.0/24 dev enp5s0  proto kernel  scope link  src 192.168.2.206
192.168.31.0/24 dev br-43043f6bbc1a  proto kernel  scope link  src 192.168.31.1 

172.17.0.0/16 和 192.168.31.0/24 两个网络的路由都定义好了。再看看 ip forwarding:

root@ubuntu:~# sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1

ip forwarding 也已经启用了。
条件都满足,为什么不能通行呢?
我们还得看看 iptables:

root@ubuntu:~# iptables-save
......
-A DOCKER-ISOLATION -i br-423b660fbdbd -o br-43043f6bbc1a -j DROP
-A DOCKER-ISOLATION -i br-43043f6bbc1a -o br-423b660fbdbd -j DROP
-A DOCKER-ISOLATION -i docker0 -o br-43043f6bbc1a -j DROP
-A DOCKER-ISOLATION -i br-43043f6bbc1a -o docker0 -j DROP
-A DOCKER-ISOLATION -i docker0 -o br-423b660fbdbd -j DROP
-A DOCKER-ISOLATION -i br-423b660fbdbd -o docker0 -j DROP
......

原因就在这里了:iptables DROP 掉了网桥 docker0 与 br-43043f6bbc1a 之间双向的流量。

从规则的命名 DOCKER-ISOLATION 可知 docker 在设计上就是要隔离不同的 netwrok。

怎样才能让 busybox 与 httpd 通信呢?我们需要为 httpd 容器添加一块 net_bridge2 的网卡。这个可以通过docker network connect 命令实现。

查看 httpd 容器的 ID。

root@ubuntu:~# docker ps
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS               NAMES
488e6019f780        busybox             "sh"                 About an hour ago   Up 31 minutes                           busybox2
7bf37788f011        busybox             "sh"                 About an hour ago   Up 36 minutes                           busybox1
c14ec27277f6        httpd               "httpd-foreground"   3 hours ago         Up 3 hours          80/tcp              unruffled_swanson

为 httpd 容器添加网卡。

docker network connect my_bridge2 c14ec27277f6
root@ubuntu:~# brctl show
bridge name     bridge id               STP enabled     interfaces
br-423b660fbdbd         8000.0242cb3347fd       no
br-43043f6bbc1a         8000.02426266f83e       no              veth8e0d4f7
                                                        vethf3faf80
                                                        vethfffb9d3
docker0         8000.0242f47922c9       no              vethcd8f150

我们在 httpd 容器中查看一下网络配置。

root@ubuntu:~# docker exec -ti c14ec27277f6 /bin/bash
root@c14ec27277f6:/usr/local/apache2# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
38: eth0@if39: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 scope global eth0
       valid_lft forever preferred_lft forever
52: eth1@if53: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:c0:a8:1f:03 brd ff:ff:ff:ff:ff:ff
    inet 192.168.31.3/24 scope global eth1
       valid_lft forever preferred_lft forever

容器中增加了一个网卡 eth1,分配了 my_bridge2 的 IP 192.168.31.3。现在 busybox 应该能够访问 httpd 了,验证一下:

参考文档:https://docs.docker.com/engine/tutorials/networkingcontainers/
https://docs.docker.com/engine/userguide/networking/#related-information

(0)

相关推荐