docker 网络

环境信息

  • Centos 7.9.2009
  • docker-ce-19.03.15

Docker 网络模式

Bridge 模式

bridge 模式是 docker 的默认网络模式,不使用 --network 参数,就是 bridge 模式。

当 Docker 进程启动时,会在主机上创建一个名为 docker0 的虚拟网桥,默认主机上启动的 Docker 容器会连接到这个虚拟网桥上。

容器启动时,docker 会从 docker0 网桥的子网中分配一个 IP 地址给容器中的网卡。大体流程为在主机上创建一个 `veth pair`,Docker 将 veth pair 的一端放在容器中,命名为 eth0 并配置 IP,网关,路由等信息,将 veth pair 的另一端加入 docker0 网桥。

通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。

Host 模式

如果启动容器的时候使用 host 模式,那么这个容器将不会获得一个独立的 Network Namespace,而是和宿主机一样在 Root Network Namespace,容器中看到的网络方面的信息和宿主机一样,容器使用的网络资源在整个 Root Network Namespace 不能出现冲突。容器将不会虚拟出自己的网卡,配置自己的 IP 等,而是使用宿主机的 IP 和端口,主机名也是使用宿主机的。但是,容器的其他方面,如文件系统、进程列表等还是和宿主机隔离的。

host 模式下的容器可以看到宿主机上的所有网卡信息,可以直接使用宿主机 IP 或主机名与外界通信,无需额外的 NAT,也无需通过 Linux bridge 进行转发或者数据包的封装,可以访问主机上的其他任一容器

使用如下命令参数启动 host 网络模式的容器

docker run --network host --name test1 -p 80:80 -d -it centos:centos7.9.2009

host 模式的容器,没有自己的 network namespace,在 root network namespace 中。进入测试容器 test1,查看网卡、 IP 信息及端口、主机名信息,会看到和宿主机一样的信息。

$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 00:0c:29:e7:c0:27 brd ff:ff:ff:ff:ff:ff
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default
link/ether 02:42:f2:1b:dc:ea brd ff:ff:ff:ff:ff:ff

$ ip add
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
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
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:0c:29:e7:c0:27 brd ff:ff:ff:ff:ff:ff
inet 192.168.142.10/24 brd 192.168.142.255 scope global noprefixroute ens33
valid_lft forever preferred_lft forever
inet6 fe80::20c:29ff:fee7:c027/64 scope link
valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:f2:1b:dc:ea brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever

$ [root@test1 /]# netstat -anutp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:81 0.0.0.0:* LISTEN 124/nginx: master p
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 36 192.168.142.10:22 192.168.142.1:61396 ESTABLISHED -
tcp6 0 0 :::80 :::* LISTEN -
tcp6 0 0 :::81 :::* LISTEN 124/nginx: master p
tcp6 0 0 :::22 :::* LISTEN -

host 模式的缺点

  • 容器没有自己的 network namespace ,网络和宿主机或其他使用 host 模式的容器未隔离,容易出现资源冲突,比如同一个宿主机上,使用 host 模式的容器中启动的端口不能相同。

None 模式

使用 none 模式,Docker 容器拥有自己的 Network Namespace,但是,系统并不为 Docker 容器进行任何网络配置。也就是说,这个 Docker 容器没有网卡(lo 回环网卡除外)、IP、路由等信息。需要我们自己为 Docker 容器添加网卡、配置 IP 等。

参考以下命令创建 none 模式的容器

docker run --network none --name test-none -p 82:80 -d -it centos7:my

容器创建后,进入容器中,查看网卡和 IP 等信息,容器中默认只存在 lo 网卡,不存在其他网卡

$ ip add
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
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

$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

以下操作演示手动为容器配置网络

  1. 创建 veth pair

    ip link add veth0 type veth peer name veth0_p
  2. veth pair 的一端 veth0 放入 docker 默认的网桥 docker0,另一端 veth0_p 放入容器中

    首先使用命令 docker inspect test-none | grep "Pid" 找到容器对应的 PID,此处为 84040,根据此 PID 将 veth 的一端放入容器的 network namespace 中

    ip link set dev veth0 master docker0

    ip link set dev veth0 up

    ip link set veth0_p netns 84040

    在宿主机上面检查 veth0,确定其已经加入网桥 docker0,并且 veth0_p 已不在 root network namespace 中

    $ ip link
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
    link/ether 00:0c:29:e7:c0:27 brd ff:ff:ff:ff:ff:ff
    3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default
    link/ether 02:42:f2:1b:dc:ea brd ff:ff:ff:ff:ff:ff
    12: veth0@if11: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue master docker0 state LOWERLAYERDOWN mode DEFAULT group default qlen 1000
    link/ether 16:7f:98:d8:9d:dc brd ff:ff:ff:ff:ff:ff link-netnsid 0

    重新进入容器,检查网卡信息,可以看到容器中已经有了网卡 veth0_p,状态为 DOWN

    $ ip -d link
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 promiscuity 0 addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
    11: veth0_p@if12: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether be:f1:94:9f:b8:c9 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0
    veth addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
  3. 为容器中的网卡配置 IP 及网关等信息

    为了能在宿主机对容器的 network namespace 进行操作,首先需要将容器的 network namespace 暴露出来,之后可以在宿主机通过 network namespace 名称(此处为 84040,可以自定义)操作 network namespace 。Linux network namespace 参考

    $ ln -s /proc/84040/ns/net /var/run/netns/84040
    $ ip netns ls
    84040 (id: 0)

    通过 network namespace 名称(此处为 84040)配置容器中网卡的 IP 地址信息

    ip netns exec 84040 ip link set dev veth0_p name eth0
    ip netns exec 84040 ip link set dev eth0 up

    ip netns exec 84040 ip add add 172.17.0.10/16 dev eth0

    ip netns exec 84040 ip route add default via 172.17.0.1

    进入容器检查网络信息

    $ ip add
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    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
    15: eth0@if16: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 7e:36:b3:20:a1:8c brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.10/16 scope global eth0
    valid_lft forever preferred_lft forever

    $ ip route show
    default via 172.17.0.1 dev eth0
    172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.10

    进入容器测试网络连接

    $ ping 8.8.8.8
    PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
    64 bytes from 8.8.8.8: icmp_seq=1 ttl=127 time=37.4 ms
    64 bytes from 8.8.8.8: icmp_seq=2 ttl=127 time=37.0 ms
    ^C
    --- 8.8.8.8 ping statistics ---
    2 packets transmitted, 2 received, 0% packet loss, time 1000ms
    rtt min/avg/max/mdev = 37.047/37.234/37.422/0.269 ms

Container 模式

在创建容器时通过参数 --network container:已运行的容器名称|ID 指定,处于这个模式下的 Docker 容器会共享一个网络栈,这样两个容器之间可以使用 localhost 高效快速通信。
Container 网络模式即新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围、主机名等。同样两个容器除了网络方面相同之外,其他的如文件系统、进程列表等还是隔离的。

Kubernetes 的 POD 网络采用的就是 Docker 的 container 模式网络。

Macvlan 模式

linux macvlan 网卡虚拟化技术

ipvlan 模式

IPVLAN/MACVLAN 实现 Docker 和物理局域网络真正互联互通

容器网络组网类型

常见的非主机网络(host network)的容器组网类型有 L2 overlay、L3 overlay、L2 underlay、L3 underlay。

overlay 网络

overlay 网络,也称为隧道网络或覆盖网络。

overlay 网络是在传统网络(数据链路层、网络层)上虚拟出一个虚拟网络,承载虚拟网络的底层传统网络不再需要进行任何适配和变更。在容器的世界了,底层物理网络只需要承载主机的网络通信,虚拟网络只承载容器网络通信。

overlay 网络的任何协议都要求发送方对报文进行封装(在虚拟网络报文的头部添加底层物理网络的地址信息,以将报文传输到容器所在的节点),接收方对报文进行解封装,使用 UDP 进行封装时,性能损失在 50% 以上,使用 VXLAN 也会有 20%-30% 的损耗。

overlay 网络最大的优点是适用于几乎所有的网络基础架构,唯一要求是主机 IP 互通,问题是随着规模的增长,复杂度会随之增加,封包和解封包性能损坏较大且难于定位问题。

L2 overlay

传统的 L2 网络,通信双方在同一个逻辑网段内,如 172.17.1.10/16 和 172.17.2.10/16

L2 overlay 是构建在底层物理网络之上的 L2 网络,相较于传统的 L2 网络,L2 overlay 是个 大二层(可以跨越多个数据中心,即可以跨 L3 underlay 进行 L2 通信)。

VXLAN 就是 L2 overlay 网络的典型实现,其通过在 UDP 包中封装原始的 L2 报文,实现了容器的跨主机通信。

L2 overlay 网络的容器可以在任意的宿主机间迁移而不改变其 IP 地址,这种特性使得构建在大二层 overlay 网络上的容器在动态迁移时具有很高的灵活性。

L3 overlay

L2 overlay 类似于 L2 overlay,但会在节点上增加个网关,每个节点上的容器都在同一个子网内,可以直接进行二层通信。跨接点间的通信只能通过 L3,都会经过网关转发,性能相比于 L2 overlay 弱。优点是跨节点通信的容器可以在不同的网段。

flannel 的 UDP 模式采用的就是 L3 overlay 模式。

underlay 网络

underlay 网络通常指底层网络,即传统的网络组网,主要要来区别于 overlay 网络。

L2 underlay

指传统的二层网络。 ipvlan 的 L2 模式属于 L2 underlay 类型的网络

L3 underlay

指传统的三层网络。

ipvlan 的 L3 模式、flannel 的 host-gw 模式和 Calico 的 BGP 组网都是 L3 underlay 类型的网络

容器互联

为了使容器互联,新版本建议将容器加入自定义的 Docker 网络 来连接多个容器,而不是使用 --link 参数。

从 Docker 1.10 版本开始,docker daemon 实现了一个内嵌的 DNS server,使容器可以直接通过容器名称通信。方法很简单,只要在创建容器时使用 --name 为容器命名即可。但是使用 Docker DNS 有个限制:只能在 user-defined 网络中使用。也就是说,默认的 bridge 网络是无法使用 DNS 的,所以我们就需要自定义网络。

先创建一个新的 Docker 网络

docker network create -d bridge my-net

-d 参数指定 Docker 网络类型,可选 bridge overlay

查看 Docker network

$ docker network ls
NETWORK ID NAME DRIVER SCOPE
1c751b3a52b9 bridge bridge local
f8d9861e5797 host host local
2e19366fb323 my-net bridge local
4c188c59ff20 none null local

新建容器并连接到刚刚新建的 Docker network

docker run -d -it --name c1 --network my-net centos
docker run -d -it --name c2 --network my-net centos

以上命令运行了 2 个 centos 最新版本的容器,并连接到了刚刚创建的网络: --network my-net
登录到容器 c1的终端,并 ping 容器 c2

$ docker exec -it c1 bash
[root@e520d1c8e30b /]# ping c2
PING c2 (172.18.0.3) 56(84) bytes of data.
64 bytes from c2.my-net (172.18.0.3): icmp_seq=1 ttl=64 time=0.115 ms
64 bytes from c2.my-net (172.18.0.3): icmp_seq=2 ttl=64 time=0.071 ms
^C
--- c2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 0.071/0.093/0.115/0.022 ms

可以看到,容器 c1 可以直接使用容器名 c2,来识别容器 c2,同理,容器 c2 也可以使用同样的方式识别到容器 c1,连接到同一个 Docker network 的容器 c1, c2 可以互相连接
使用以下命令,可以查看 Docker network 的详细信息,包括 ip 网段,ip 分配信息等

$ docker network inspect my-net
[
{
"Name": "my-net",
"Id": "2e19366fb32312235a3571192d1a19e6ebb7adc3e6e25c1f5e6007e8c3315f08",
"Created": "2022-08-30T15:44:54.241420767+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": {
"4b6b1159073d3b70312f2b7e5da8c4a36133a6c00c4746fe2c58d37e81a86b12": {
"Name": "c2",
"EndpointID": "0cbe15ff760040d20cd10a04ffed5f4398e1c3c2b597a86461758606ebdc919d",
"MacAddress": "02:42:ac:12:00:03",
"IPv4Address": "172.18.0.3/16",
"IPv6Address": ""
},
"e520d1c8e30bf97ece44fae53840d13e801648ca02899a17416a75563d3a64f6": {
"Name": "c1",
"EndpointID": "214c7596e2610208402cfa55a225fa67c0dd02d4b585c62b43a9a6c42f360147",
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]

如果容器启动时使用了自定义的 Docker network,并且启动过程中挂载了宿主机的 /etc/resolv.conf,则容器启动后无法再使用容器名和其他容器互联,比如使用以下命令启动容器 c3c3 无法使用容器名和 c1,c2 互联,但是c1,c2 可以使用容器名 c3 连接到容器 c3

docker run -d -ti --network my-net -v /etc/resolv.conf:/etc/resolv.conf --name c3 centos

以上示例中,如果登录 c1 终端后,修改 /etc/resolv.conf 文件,再次使用 c2 ,会无法连接 c2 容器

如果容器启动时,需要为容器指定域名 ip 映射关系,可以使用选项 --add-host host:ip

docker run --network my-net --add-host db:172.18.0.3 --name c3 centos

容器运行过程中,可以直接修改容器内的 /etc/hosts 文件

Docker 网络相关问题

定位 Docker 容器中的网卡和宿主机上面的 veth 的 pair 关系

在经典容器组网中,主要是使用 veth + bridge 的模式,容器中的 eth0 实际上和宿主机上面的某个 veth 是成对(pair)关系,要查看容器中的网卡和宿主机上面的 veth 网卡的成对关系,可以参考以下方法

方法 1

  1. 在目标容器中查看
    $ cat /sys/class/net/eth0/iflink 
    60

  2. 在宿主机上遍历 /sys/class/net/ 下面的全部目录,查看子目录中的 ifindex 文件的内容,找出和容器中 /sys/class/net/eth0/iflink 的值一样的 veth 的名称,这样就找到了容器和主机的 veth pair 的关系
    $ cat /sys/class/net/vethc41ba34/ifindex 
    60
    本示例中,宿主机上的 vethc41ba34 和容器中的网卡是 veth pair

方法 2

  1. 目标容器中查看
    $ ip link show eth0
    59: eth0@if60: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
    link/ether 02:42:ac:16:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    从上面的命令可以看到 59: eth0@if60,其中 59eth0 接口的 index,60 是和它成对的 veth 的 index。
  2. 在 host 上面执行下面的命令,可以看到对应 60veth 网卡是哪一个
    ip link show | grep 60
    60: vethc41ba34@if59: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-9d2a9fc0ff85 state UP mode DEFAULT group default

方法 3

通过 ethtool -S 命令列出 veth pair 对端的网卡 index

  1. 目标容器中执行
    $ ethtool -S eth0
    NIC statistics:
    peer_ifindex: 60
  2. 在宿主机上面查找 index 为 60 的 veth 网卡是哪一个
    ip link show | grep 60
    60: vethc41ba34@if59: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-9d2a9fc0ff85 state UP mode DEFAULT group default