Docker容器网络-实现篇

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Docker容器网络-实现篇相关的知识,希望对你有一定的参考价值。

参考技术A

前面介绍了: Docker容器网络-基础篇

前文说到容器网络对Linux虚拟化技术的依赖,这一篇章我们将一探究竟,看看Docker究竟是怎么做的。通常,Linux容器的网络是被隔离在它自己的Network Namespace中,其中就包括:网卡(Network Interface)、回环设备(Loopback Device)、路由表(Routing Table)和iptables规则。对于一个进程来说,这些要素,就构成了它发起和响应网络请求的基本环境。

我们在执行 docker run -d --name xxx 之后,进入容器内部:

并执行 ifconfig:

我们看到一张叫eth0的网卡,它正是一个Veth Pair设备在容器的这一端。

我们再通过 route 查看该容器的路由表:

我们可以看到这个eth0是这个容器的默认路由设备。我们也可以通过第二条路由规则,看到所有对 169.254.1.1/16 网段的请求都会交由eth0来处理。

而Veth Pair 设备的另一端,则在宿主机上,我们同样也可以通过查看宿主机的网络设备来查看它:

在宿主机上,容器对应的Veth Pair设备是一张虚拟网卡,我们再用 brctl show 命令查看网桥:

可以清楚的看到Veth Pair的一端 vethd08be47 就插在 docker0 上。

我现在执行docker run 启动两个容器,就会发现docker0上插入两个容器的 Veth Pair的一端。如果我们在一个容器内部互相ping另外一个容器的IP地址,是不是也能ping通?

容器1:

容器2:

从一个容器ping另外一个容器:

我们看到,在一个容器内部ping另外一个容器的ip,是可以ping通的。也就意味着,这两个容器是可以互相通信的。

我们不妨结合前文时所说的,理解下为什么一个容器能访问另一个容器?先简单看如一幅图:

当在容器1里访问容器2的地址,这个时候目的IP地址会匹配到容器1的第二条路由规则,这条路由规则的Gateway是0.0.0.0,意味着这是一条直连规则,也就是说凡是匹配到这个路由规则的请求,会直接通过eth0网卡,通过二层网络发往目的主机。而要通过二层网络到达容器2,就需要127.17.0.3对应的MAC地址。所以,容器1的网络协议栈就需要通过eth0网卡来发送一个ARP广播,通过IP找到MAC地址。

所谓ARP(Address Resolution Protocol),就是通过三层IP地址找到二层的MAC地址的协议。这里说到的eth0,就是Veth Pair的一端,另一端则插在了宿主机的docker0网桥上。eth0这样的虚拟网卡插在docker0上,也就意味着eth0变成docker0网桥的“从设备”。从设备会降级成docker0设备的端口,而调用网络协议栈处理数据包的资格全部交给docker0网桥。

所以,在收到ARP请求之后,docker0就会扮演二层交换机的角色,把ARP广播发给其它插在docker0网桥的虚拟网卡上,这样,127.17.0.3就会收到这个广播,并把其MAC地址返回给容器1。有了这个MAC地址,容器1的eth0的网卡就可以把数据包发送出去。这个数据包会经过Veth Pair在宿主机的另一端veth26cf2cc,直接交给docker0。

docker0转发的过程,就是继续扮演二层交换机,docker0根据数据包的目标MAC地址,在CAM表查到对应的端口为veth8762ad2,然后把数据包发往这个端口。而这个端口,就是容器2的Veth Pair在宿主机的另一端,这样,数据包就进入了容器2的Network Namespace,最终容器2将响应(Ping)返回给容器1。在真实的数据传递中,Linux内核Netfilter/Iptables也会参与其中,这里不再赘述。

CAM就是交换机通过MAC地址学习维护端口和MAC地址的对应表

这里介绍的容器间的通信方式就是docker中最常见的bridge模式,当然此外还有host模式、container模式、none模式等,对其它模式有兴趣的可以去阅读相关资料。

好了,这里不禁问个问题,到目前为止只是单主机内部的容器间通信,那跨主机网络呢?在Docker默认配置下,一台宿主机的docker0网桥是无法和其它宿主机连通的,它们之间没有任何关联,所以这些网桥上的容器,自然就没办法多主机之间互相通信。但是无论怎么变化,道理都是一样的,如果我们创建一个公共的网桥,是不是集群中所有容器都可以通过这个公共网桥去连接?

当然在正常的情况下,节点与节点的通信往往可以通过NAT的方式,但是,这个在互联网发展的今天,在容器化环境下未必适用。例如在向注册中心注册实例的时候,肯定会携带IP,在正常物理机内的应用当然没有问题,但是容器化环境却未必,容器内的IP很可能就是上文所说的172.17.0.2,多个节点都会存在这个IP,大概率这个IP是冲突的。

如果我们想避免这个问题,就会携带宿主机的IP和映射的端口去注册。但是这又带来一个问题,即容器内的应用去意识到这是一个容器,而非物理机,当在容器内,应用需要去拿容器所在的物理机的IP,当在容器外,应用需要去拿当前物理机的IP。显然,这并不是一个很好的设计,这需要应用去配合配置。所以,基于此,我们肯定要寻找其他的容器网络解决方案。

在上图这种容器网络中,我们需要在我们已有的主机网络上,通过软件构建一个覆盖在多个主机之上,且能把所有容器连通的虚拟网络。这种就是Overlay Network(覆盖网络)。

关于这些具体的网络解决方案,例如Flannel、Calico等,我会在后续篇幅继续陈述。

docker之容器网络篇

一、docker网络模式

Docker支持五种网络模式:
A、bridge
--net=bridge
默认网络,Docker启动后创建一个docker0网桥,默认创建的容器也是添加到这个网桥中。
B、host
--net=host
容器不会获得一个独立的network namespace,而是与宿主机共用一个。这就意味着容器不会有自己的网卡信息,而是使用宿主机的。容器除了网络,其他都是隔离的。
C、none
--net=none
获取独立的network namespace,但不为容器进行任何网络配置,需要我们手动配置。
D、container
--net=container:Name/ID
与指定的容器使用同一个network namespace,具有同样的网络配置信息,两个容器除了网络,其他都还是隔离的。
E、自定义网络
与默认的bridge原理一样,但自定义网络具备内部DNS发现,可以通过容器名或者主机名容器之间网络通信。

二、实例演示

自定义网络:

[[email protected] ~]# docker network create hello
c1ee291139357510355527cdbe44d625311d0ed4cb35828fb1d9d37fdfdca973

[[email protected] ~]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
881d091f073a        bridge              bridge              local
c1ee29113935        hello               bridge              local
8dcf04c946c5        host                host                local
d21578b3d24e        none                null                local
[[email protected] ~]# docker container run -itd --name=bs1 --net=hello busybox
Unable to find image ‘busybox:latest‘ locally
latest: Pulling from library/busybox
8c5a7da1afbc: Pull complete 
Digest: sha256:032ddd66f10483436e8a252e69fdfd20d0164e9953585c10d378183a0924db34
Status: Downloaded newer image for busybox:latest
a0d322ed58a7c0f0e38fa2405446bae4c0ac7208264a0f273f447ee0ec47c4cc
[[email protected] ~]#  docker container run -itd --name=bs2 --net=hello busybox 
ddfcb6a9f615c445a9fbd158dd339dd7ad1bc94e00197dd3224d45ab2bf0f04e
[[email protected] ~]# docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
ddfcb6a9f615        busybox             "sh"                     24 seconds ago      Up 23 seconds                            bs2
a0d322ed58a7        busybox             "sh"                     51 seconds ago      Up 50 seconds                            bs1
[[email protected] ~]# docker exec -it bs2 sh
~ # ping bs1
PING bs1 (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.191 ms
64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.120 ms
64 bytes from 172.18.0.2: seq=2 ttl=64 time=0.119 ms
64 bytes from 172.18.0.2: seq=3 ttl=64 time=0.122 ms
~ # ping bs2
PING bs2 (172.18.0.3): 56 data bytes
64 bytes from 172.18.0.3: seq=0 ttl=64 time=0.069 ms
64 bytes from 172.18.0.3: seq=1 ttl=64 time=0.086 ms
64 bytes from 172.18.0.3: seq=2 ttl=64 time=0.080 ms

创建的两个容器都加入了hello网络,并且可以通过主机名通信。
通过inspect查看hello网络详细信息:

[[email protected] ~]# docker network inspect hello
[
    {
        "Name": "hello",
        "Id": "c1ee291139357510355527cdbe44d625311d0ed4cb35828fb1d9d37fdfdca973",
        "Created": "2018-08-06T04:23:28.286872431+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": {
            "a0d322ed58a7c0f0e38fa2405446bae4c0ac7208264a0f273f447ee0ec47c4cc": {
                "Name": "bs1",
                "EndpointID": "6fd7afaa55e71edb72f4ed5c22b792cfdc5fcb2aee2acd683c178f93503fcb8d",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            },
            "ddfcb6a9f615c445a9fbd158dd339dd7ad1bc94e00197dd3224d45ab2bf0f04e": {
                "Name": "bs2",
                "EndpointID": "b2a0abdcc73e66b02b737c4960f1b85844472827c99a93be2e3a55dd9629da83",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

以上是关于Docker容器网络-实现篇的主要内容,如果未能解决你的问题,请参考以下文章

docker 容器网络篇

Linux运维容器篇 docker基础操作和网络模型

Linux运维容器篇 docker基础操作和网络模型

Linux运维容器篇 docker基础操作和网络模型

Docker容器高级篇

Docker 网络详解之使用自定义网络实现容器间通过容器名称互联