Docker 的网络子系统是可插拔驱动式的,默认情况下存在或支持多种网络接口,如 bridge、host、overlay、macvlan 以及 none 类型的网络接口。
bridge 是 docker 默认的网络模式,如果在创建容器时不指定网络类型,则默认时 bridge 模式,会为每个容器分配一个 Network Namespace、IP 等,并将容器的网络连接到一个网桥(docker0)上。
这种模式的特点是:同一个网桥下的容器间可以相互通信,不同网桥间的容器网络会被隔离。
Linux 网桥
bridge 模式是基于 Linux 的网桥
(一同通过软件实现的虚拟设备)实现的,网桥
可以将 Linux 内部多个网络接口连接在一起,如下图:
通过网桥将网络接口连接起来可以实现:一个网络接口接收到网络数据包后,会复制到其他网络接口中,如下图所示:
如上图所示,当网络接口A收到数据包后,网桥
会将数据包复制发送给连接到网桥的其他接口(如上图中的网卡B和网卡C)。
docker0
Docker 通过 Linux 的网桥
实现了 bridge模式,原理如下图:
Docker 在启动时,会创建一个名为docker0
的网桥
,并把其IP地址设为172.17.0.1/16
(私有IP地址)。
$ brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242526953be no
注:brclt 一个用来管理网桥的命令行工具。详见
$ ip addr
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:9c:30:76 brd ff:ff:ff:ff:ff:ff
inet 192.168.59.128/24 brd 192.168.59.255 scope global noprefixroute dynamic ens33
valid_lft 1512sec preferred_lft 1512sec
inet6 fe80::b356:f4d:f6ea:25aa/64 scope link noprefixroute
valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:52:69:53:be 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
inet6 fe80::42:52ff:fe69:53be/64 scope link
valid_lft forever preferred_lft forever
然后 Docker 在创建容器时,会通过虚拟设备对veth-pair
来将容器与docker0
连接起来。对于172.17.0.0/16
网段的数据包, Docker 会定义一条iptables NAT
的规则来讲这些数据包的 IP 地址转换成公网 IP 地址,然后通过真实的网络接口(如上图 ens160 接口)发送出去。
启动一个容器,默认使用 bridge 模式
$ docker run -d --name redis -p6379:6379 redis:4
5bd019a047ce
查看网桥状态,已有 veth 连接
$ brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242526953be no vethed2a70d
查看容器的netns
当容器启动时,Docker 内部会自动为这个容器创建一个netns
用于网络隔离,但当我们使用ip netns list
查看时却看不到数据,是因为 Docker 把netns
创建到了其他地方,而ip netns list
命令只能读目录/var/run/netns
下面的数据。
我们可以通过以下命令来解决这个问题,方便我们学习docker网络。
# 得到容器对应的进程
pid=`docker inspect -f '{{.State.Pid}}' $container_id`
# 手动创建防止文件夹不存在
mkdir -p /var/run/netns/
# 建立软连接
ln -s /proc/$pid/ns/net /var/run/netns/$container_id
将上面的命令修改为和当前环境一致并验证netns中的网卡。
$ pid=`docker inspect -f '{{.State.Pid}}' 5bd019a047ce`
$ mkdir -p /var/run/netns/
$ ln -s /proc/$pid/ns/net /var/run/netns/5bd019a047ce
$ ip netns list
5bd019a047ce (id: 0)
$ ip netns exec 5bd019a047ce ip a
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
6: eth0@if7: <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 link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
可以看到我们成功输出了netns中的网卡信息,并且此信息和从docker容器中看到的一致。