欢迎收藏作者个人网站
引言
什么是Docker?
Docker是应用容器引擎,它利用虚拟化技术打包应用及其依赖。
Docker解决什么问题?
Docker或者说虚拟化技术的出现,主要是为了解决应用能够快速构建、实施的问题。
- 一方面, Docker技术帮助应用程序在不同的基础环境下快速实施部署。 应用程序开发完成后,通常不会仅仅部署在一台特定的机器上,而是需要复制到不同环境、不同机器上实施,实施的基础环境很可能存在比较大的差异,如果在部署时才进行调试,部署效率低。Docker技术(或者说虚拟化技术)则可以用来帮助应用程序能够进行快速地实施、部署。
- 另一方面,Docker技术可以让不同的应用程序在同一台机器上良好的运行,互不影响。 比如我们想要在同一台机器上部署两个绑定了80端口的Web应用程序,如果没有Docker或虚拟化技术,就可能发生端口占用冲突。
说白了,Docker就是服务于应用的。
Docker的网络要解决什么问题?
Docker是服务于应用的,Docker网络就是解决容器中应用的网络通信问题,让容器中的应用就像在一台独立的主机上运行一般。
- 当应用是部署在物理主机上时: 应用发送的接收数据的流程如下图所示:
所有应用共享Linux系统网络协议栈, socket 中存储了特定的四元组: 源ip port,目的ip port;
- 当应用部署在虚拟机上时:
- 当应用部署在Docker容器时:
Docker容器的本质是 共享内核,资源隔离、资源限制、文件系统rootfs
同一台宿主机上的Docker容器共享内核中传输 层和网络层的处理流程以及设备驱动,共享硬件资源,但是socket套接字和虚拟网络设备相互独立,需要进行隔离和限制。
Docker网络的实现主要就是如何在共享内核的基础上,实现socket的隔离,虚拟设备的隔离和通信.、
Docker的网络实现是站在巨人的肩膀上的,Docker主要是利用的操作系统的虚拟化技术,来实现不同容器的网络隔离和通信。
虚拟网络接口
Linux网络协议栈中,IP层不与物理网络设备(网络驱动)直接通信,而是和抽象的网络接口(lo、eth0、eth1等)交互。这为Docker不同容器中能够模拟网络环境提供的基础。
namespace
Docker是轻量级的虚拟化,他和虚拟机的一个主要区别是不同的Docker容器共享Linux内核。 共享内核就会存在资源可见性的问题。为此,Linux支持为不同资源设置不同的namespace,不同namespace的资源相互隔离、相互不可见。
namespace其实不单单用于Linux的网络模块(network)。Linux 支持一下六种类型的命名空间:Cgroup、IPC、Network、Mount、PID、Time、User、UTS。 这六种类型都是Docker虚拟化实现资源隔离的基础。
有了namespace的概念,不同容器虚拟出独立的网络环境就变为可行。(比如我们可以在不同的容器中创建网络接口eth0.
veth
namespace用于实现网络资源的隔离,但是Docker容器与宿主机经常需要进行通信,这就需要Linux系统中veth-pair技术的支持。
veth是虚拟网络设备接口,它总是成对出现,用于连通两个namespace。从其中一个端口发出的数据包,可以直接出现在与它对应的另一个端口上。
bridge
veth-pair解决了不同命名空间两两通信的问题(容器与容器、容器与宿主机),但是一台宿主机上可以启动大量的容器,这些容器的数据包需要汇聚到同一个网络接口才能与宿主机以外的设备通信。如果仅仅是基于veth-pair来进行数据转发配置,就过于繁琐。
Linux网络内核引入网桥bridge来实现多个网络接口之间的通信,可以将一台机器上的若干接口连通起来。在OSI网络模型中,网桥属于数据链路层。
路由表
Linux网络内核通过路由表来指定数据包的转发路径。 一台Linux主机里可能有多个虚拟网络接口、多个物理网络设备,在TCP/IP协议中,IP数据包里包含了目的地址,但是并不知道如何到达目标地址。路由表则用于指示要抵达目的地址,数据包下一条应该先到哪里去。 路由表示例:
代码语言:javascript复制[root@VM_16_35_centos ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.16.16.1 0.0.0.0 UG 0 0 0 eth1
169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth1
172.16.16.0 0.0.0.0 255.255.240.0 U 0 0 0 eth1
Docker网络实现
对Linux的虚拟化支持有一定了解后,我们就可以来看下Docker的网络实现了。
Docker的几种网络模型
- bridge: Docker容器有自己的Network-Namesapce,通过veth-pair和Linux-Bridge技术实现容器与宿主机的网络通信。brige模式是Docker的默认网络模式。
- host: Docker容器与宿主机共享网络,容器不会有自己的Network-Namesapce,与宿主机不进行网络隔离。
- overlay: 将多个Docker Daemon连接到一起。
- IPvlan: 用户可以完全控制IPv4和IPv6寻址。支持对二层VLAN tag和三层网络路由的完全控制。
- macvlan: 支持为容器设置mac地址,让Docker daemon能够基于Mac地址路由流量。
- none: 这种模式下的容器禁用所有网络。
- Network plugins: 安装使用第三方的Docker网络插件。
这里我们主要介绍下bridge网络模式。
Docker网络实现
创建bridge
在Docker-Daemon启动是会在宿主机创建一个bridge:docker0.
代码语言:javascript复制[root@VM_52_29_centos ~]# ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT qlen 1000
link/ether 52:54:00:bb:18:70 brd ff:ff:ff:ff:ff:ff
3: br-703d0323a265: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT
link/ether 02:42:80:e4:57:84 brd ff:ff:ff:ff:ff:ff
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT
link/ether 02:42:40:84:e0:47 brd ff:ff:ff:ff:ff:ff
代码语言:javascript复制
[root@VM_52_29_centos ~]# ifconfig docker0
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.10.1 netmask 255.255.255.0 broadcast 192.168.10.255
ether 02:42:40:84:e0:47 txqueuelen 0 (Ethernet)
RX packets 12788 bytes 588170 (574.3 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 42200 bytes 59457226 (56.7 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
[root@VM_52_29_centos ~]#
容器网络
启动容器时,Docker会为容器新增一个network-namespace,并创建veth-pair与宿主机相连。
代码语言:javascript复制[root@VM_52_29_centos ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fea48be1f2d1 alpine:3.15.4 "/bin/sh" 9 minutes ago Up 9 minutes friendly_tereshkova
fd21e950935d mysql:5.7 "/bin/bash" 2 years ago Up 8 weeks tencenthealth
此处启动了两个容器。
查看当前的网络接口:
代码语言:javascript复制[root@VM_52_29_centos ~]# ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT qlen 1000
link/ether 52:54:00:bb:18:70 brd ff:ff:ff:ff:ff:ff
3: br-703d0323a265: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT
link/ether 02:42:80:e4:57:84 brd ff:ff:ff:ff:ff:ff
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT
link/ether 02:42:40:84:e0:47 brd ff:ff:ff:ff:ff:ff
6: vethf077bca: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT
link/ether a2:72:5a:9a:24:60 brd ff:ff:ff:ff:ff:ff
226: vethbf84100: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT
link/ether 36:bf:28:65:73:86 brd ff:ff:ff:ff:ff:ff
可以看到新增了两个veth接口vethf077bca
, vethbf84100
, 他们分别与两个容器中的eth0形成veth-pair。
再看网桥的连接
代码语言:javascript复制[root@VM_52_29_centos ~]# bridge link
6: vethf077bca state UP : <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master docker0 state forwarding priority 32 cost 2
226: vethbf84100 state UP : <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master docker0 state forwarding priority 32 cost 2
可以看到两个veth-pair在宿主机这一端都和docker0关联。
路由表与流量转发
bridge、namespace、虚拟网络接口、veth-pair都准备好后,流量如何转发呢,这就需要用到路由表了。
在分析路由表前,我们需要先看下各个虚拟网络接口关联的IP。 宿主机上的网络接口:
代码语言:javascript复制[root@VM_52_29_centos ~]# ifconfig
br-703d0323a265: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 172.25.0.1 netmask 255.255.0.0 broadcast 172.25.255.255
ether 02:42:80:e4:57:84 txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.10.1 netmask 255.255.255.0 broadcast 192.168.10.255
ether 02:42:40:84:e0:47 txqueuelen 0 (Ethernet)
RX packets 12788 bytes 588170 (574.3 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 42200 bytes 59457226 (56.7 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 9.134.52.29 netmask 255.255.252.0 broadcast 9.134.55.255
ether 52:54:00:bb:18:70 txqueuelen 1000 (Ethernet)
RX packets 22843799 bytes 4344974378 (4.0 GiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 23575652 bytes 5537180006 (5.1 GiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 0 (Local Loopback)
RX packets 4483879 bytes 3456236895 (3.2 GiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 4483879 bytes 3456236895 (3.2 GiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
vethbf84100: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
ether 36:bf:28:65:73:86 txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
vethf077bca: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
ether a2:72:5a:9a:24:60 txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 92 bytes 4094 (3.9 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
可以看到,宿主机自身的网络接口lo、eth0分别绑定了IP:127.0.0.1
, 9.134.52.29
.
网桥Docker0也绑定了IP: 192.168.10.1。
而veth设备vethbf84100、vethf077bca并没有IP,因为他们并不与外界直接通信,而是通过Docker0来转发流量。Docker0与vethbf84100、vethf077bca流量互通。
容器中的网络接口:
代码语言:javascript复制[root@VM_52_29_centos ~]# docker exec -it fd21e950935d /bin/bash
eth1: error fetching interface information: Device not found
[root@fd21e950935d /]# ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:C0:A8:0A:02
inet addr:192.168.10.2 Bcast:192.168.10.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:92 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:4094 (3.9 KiB) TX bytes:0 (0.0 b)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
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)
[root@fd21e950935d /]#
可以看到容器中的eth0与宿主机的docker0在同一个网段。
接下来看下路由表 宿主机上的路由表:
代码语言:javascript复制[root@VM_52_29_centos ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 9.134.52.1 0.0.0.0 UG 0 0 0 eth1
9.0.0.0 9.134.52.1 255.0.0.0 UG 0 0 0 eth1
9.134.52.0 0.0.0.0 255.255.252.0 U 0 0 0 eth1
10.0.0.0 9.134.52.1 255.0.0.0 UG 0 0 0 eth1
100.64.0.0 9.134.52.1 255.192.0.0 UG 0 0 0 eth1
169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth1
172.16.0.0 9.134.52.1 255.240.0.0 UG 0 0 0 eth1
172.25.0.0 0.0.0.0 255.255.0.0 U 0 0 0 br-703d0323a265
192.168.0.0 9.134.52.1 255.255.0.0 UG 0 0 0 eth1
192.168.10.0 0.0.0.0 255.255.255.0 U 0 0 0 docker0
[root@VM_52_29_centos ~]#
当docker0上的包,目的地址不是192.168.10.0/24时,表示目的地址在相同网段,不需要经过网关,Flags=U(不含G)。 其他网络接口上的包按普通流程处理。
在看下容器内的路由表:
代码语言:javascript复制[root@fd21e950935d /]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.10.1 0.0.0.0 UG 0 0 0 eth0
192.168.10.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
[root@fd21e950935d /]#
当目的地址为192.168.10.0/24时,目的地址在相同网段,不需要经过网关。Flags=U(不含G) 当目的地址为其他地址时,流量转发需要经过网关,Flags=UG。
参考文章
Networking overview
理解 Linux 网络栈(1):Linux 网络协议栈简单总结
linux 路由表设置 之 route 指令详解