原理
1. docker0 网桥(Bridge)
通过输入命令
- windows:ipconfig
- linux:ifconfig
会发现多了一个叫 docker0 的网卡,这个就是 docker0 网桥。
网桥,简而言之,就是早期的两端口二层网络设备,用来连接不同的局域网,对数据包进行存储、转发操作。这里的一个关键点就是两端口,docker0 网桥连接的就是容器网段和宿主机网段。
docker0 网桥是在 Docker Daemon 启动的时候自动创建的,从我们上面的结果 (inet 和 netmask) 可以看出来 docker0 的 IP 为 172.17.0.1/16。之后使用 bridge 模式(默认)创建出来的 Docker 容器都将在 docker0 子网的范围内选取一个未被占用的 IP 使用,并连接到 docker0 网桥上。
docker0 网桥的 IP 地址和子网范围是可以通过参数修改的,使用 CIDR 的格式。
在 Linux 系统中,我们可以通过 brctl 命令来查看网桥的信息(如果提示找不到命令,需要先安装 bridge-utils 软件包)。下面是我的dev机器的一个结果
我们从 brctl 的结果中可以看到网桥上面连接了很多了 veth 设备,同时 veth 设备总是成对出现的,那么也就意味着 veth 的另一端连接的是容器的 eth0,正如上面那幅图所示。
2. iptables
iptables 可以简单理解为是一个命令行防火墙(firewall)工具,我们可以设置一些 iptables 规则来达到流量控制。Docker 会在宿主机系统上增加一些 iptables 规则,以用来管理 Docker 容器和容器之间以及和外界的通信。
下面我们通过命令 iptables-save 命令来查看一下我的这台虚拟机(运行着多个 Docker 容器)上面的 iptable 规则情况,下面是全部命令输出,我们下面就看看 Docker 的数据转发是怎么做的
# Generated by iptables-save v1.4.21 on Wed Jun 2 16:53:06 2021*filter:INPUT ACCEPT [1809099:252349089]:FORWARD DROP [0:0]:OUTPUT ACCEPT [1993484:1270123964]:DOCKER - [0:0]:DOCKER-ISOLATION-STAGE-1 - [0:0]:DOCKER-ISOLATION-STAGE-2 - [0:0]:DOCKER-USER - [0:0]-A FORWARD -j DOCKER-USER-A FORWARD -j DOCKER-ISOLATION-STAGE-1-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT-A FORWARD -o docker0 -j DOCKER-A FORWARD -i docker0 ! -o docker0 -j ACCEPT-A FORWARD -i docker0 -o docker0 -j ACCEPT-A DOCKER -d 172.17.0.3/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 6379 -j ACCEPT-A DOCKER -d 172.17.0.4/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 9000 -j ACCEPT-A DOCKER -d 172.17.0.6/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 8081 -j ACCEPT-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 7101 -j ACCEPT-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 7100 -j ACCEPT-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 443 -j ACCEPT-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT-A DOCKER -d 172.17.0.5/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 15672 -j ACCEPT-A DOCKER -d 172.17.0.5/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 5672 -j ACCEPT-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2-A DOCKER-ISOLATION-STAGE-1 -j RETURN-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP-A DOCKER-ISOLATION-STAGE-2 -j RETURN-A DOCKER-USER -j RETURNCOMMIT# Completed on Wed Jun 2 16:53:06 2021# Generated by iptables-save v1.4.21 on Wed Jun 2 16:53:06 2021*nat:PREROUTING ACCEPT [6467:1150885]:INPUT ACCEPT [801:48308]:OUTPUT ACCEPT [709:52869]:POSTROUTING ACCEPT [1765:107826]:DOCKER - [0:0]-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE-A POSTROUTING -s 172.17.0.3/32 -d 172.17.0.3/32 -p tcp -m tcp --dport 6379 -j MASQUERADE-A POSTROUTING -s 172.17.0.4/32 -d 172.17.0.4/32 -p tcp -m tcp --dport 9000 -j MASQUERADE-A POSTROUTING -s 172.17.0.6/32 -d 172.17.0.6/32 -p tcp -m tcp --dport 8081 -j MASQUERADE-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 7101 -j MASQUERADE-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 7100 -j MASQUERADE-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 443 -j MASQUERADE-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 80 -j MASQUERADE-A POSTROUTING -s 172.17.0.5/32 -d 172.17.0.5/32 -p tcp -m tcp --dport 15672 -j MASQUERADE-A POSTROUTING -s 172.17.0.5/32 -d 172.17.0.5/32 -p tcp -m tcp --dport 5672 -j MASQUERADE-A DOCKER -i docker0 -j RETURN-A DOCKER ! -i docker0 -p tcp -m tcp --dport 6379 -j DNAT --to-destination 172.17.0.3:6379-A DOCKER ! -i docker0 -p tcp -m tcp --dport 9000 -j DNAT --to-destination 172.17.0.4:9000-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8081 -j DNAT --to-destination 172.17.0.6:8081-A DOCKER ! -i docker0 -p tcp -m tcp --dport 7101 -j DNAT --to-destination 172.17.0.2:7101-A DOCKER ! -i docker0 -p tcp -m tcp --dport 7100 -j DNAT --to-destination 172.17.0.2:7100-A DOCKER ! -i docker0 -p tcp -m tcp --dport 443 -j DNAT --to-destination 172.17.0.2:443-A DOCKER ! -i docker0 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.17.0.2:80-A DOCKER ! -i docker0 -p tcp -m tcp --dport 15672 -j DNAT --to-destination 172.17.0.5:15672-A DOCKER ! -i docker0 -p tcp -m tcp --dport 5672 -j DNAT --to-destination 172.17.0.5:5672
iptables 默认有 4 个表:
- nat:地址转换表;
- filter:数据过滤表;
- raw:状态跟踪表;
- mangle:包标记表。
外界访问 Docker 容器是通过 iptables 做 DNAT 实现的。DNAT 将 SNAT 中的 Source 换成 Destination,表示目的地址转换。
*nat...-A DOCKER ! -i docker0 -p tcp -m tcp --dport 7101 -j DNAT --to-destination 172.17.0.2:7101...*filter...-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 7101 -j ACCEPT...
filter 表中的规则用来对流量做限制,这里的这条规则表示允许所有的外部 IP 访问容器,可以通过在 filter 的 Docker 链上添加规则来对外部的 IP 访问做出限制,这里就不再演示了。
不光是与外界通信,Docker 容器之间通信也受到 iptables 规则限制。我们前面也了解到宿主机上面的所有 Docker 容器都位于 docker0 网桥的子网内。同时我们从 iptables 中的输出看到一条 filter 规则。-A FORWARD -i docker0 -o docker0 -j ACCEPT
这条规则保证容器之间可以互相通信,如果将 Docker Server 启动参数 —icc 设置为 false,则这条规则会被设置为 DROP,容器之间的相互通信就会被禁止。
3. IP-Forward
在 Docker 容器网络通信的过程中,还涉及到数据包在多个网卡间的转发,这需要将内核参数 ip-forward 打开,参数位于 /proc/sys/net/ipv4/ip_forward。
[root@docker ~]# cat /proc/sys/net/ipv4/ip_forward 1
通常这一步不需要我们手动来设置,Docker server 启动的时候默认会将 ip-forward 设置为 1。
4. DNS 和主机名
容器的主机名以及 DNS 是设置在文件 /etc/hostname、/etc/hosts、/etc/resolv.conf 中的,对于容器来说,在容器启动后会覆盖这些文件从而达到修改属性的目的。下面是我的机器上面的示例。
[root@docker ~]# docker exec -ti 4be4cca01392 sh/ # mount.../dev/vda1 on /etc/hostname type ext4 (rw,relatime,data=ordered)/dev/vda1 on /etc/hosts type ext4 (rw,relatime,data=ordered)/dev/vda1 on /etc/resolv.conf type ext4 (rw,relatime,data=ordered)...
同时我们也可以通过参数 -h HOSTNAME 和 —dns=IP_ADDRESS… 来对 hostname 和 DNS 进行设置。
Docker网络模式
1. bridge 模式
Docker 的默认网络模式。这种模式会将创建出来的所有 Docker 容器链接到 docker0 网桥或者自定义网桥上,所有的 Docker 容器处于同一个子网。
使用 bridge 模式的 Docker 容器默认使用 docker0 网桥,除此之外,你也可以使用自定义网桥(User-defined bridge network)。自定义网桥和默认 docker0 网桥的区别在于:
- 自定义网桥提供容器间的自定义 DNS 解析。默认网桥网络下的 Docker 容器只能通过 IP 地址交互,除非使用 —link 参数将多个 Docker 容器连接起来。
- 自定义网桥具有更好的隔离性。默认创建的 Docker 容器如果没有指定 —network 参数,都会连接到默认的 docker0 网桥上,这样相当于将所有不不相干的容器都置于一个同一个网络环境中,可能存在风险。自定义网桥相当于将 docker0 网桥按我们需要分隔成多个自定义网桥,毫无疑问,这样隔离性更好。
- 容器可以在运行时和自定义网桥进行绑定或者解绑。这个默认 docker0 网桥是不行的,需要停止容器。
- 每个自定义网桥可以自定义自己的配置,比如 MTU 和 iptables 规则等。但是如果使用默认 docker0 网桥,相当于共享配置。
- 通过默认网桥 Link 的 Docker 容器可以共享环境变量。所谓 Link 是指 docker run 的时候指定 —link 参数。这个在自定义网桥中是不行的,但是可以通过其他方式来实现,比如:
Host 模式可以通过参数 —network host 指定,比如我们使用 host 模式启动一个 nginx 容器。
[root@docker ~]# docker run --rm -d --network host --name host_nginx nginx:1.19
Host 模式的优缺点都很明显。
- 缺点:没有和宿主机的 network namespace 进行隔离。可能会存在端口冲突的情况,比如 nginx 镜像的 Docker 容器会使用 80 端口,那么我们就不能以 host 模式启动两个容器,不然会冲突。
- 优点:共用同一个 network namespace 也就意味没有个多个 network namespace 之间的数据转发,性能更好。
3. overlay 模式
这种模式在多个 Docker daemon 主机之间创建一个分布式网络,该网络位于 Docker 主机层次之上,允许容器之间加密通讯,需要处理容器之间和主机之间的网络包。4. macvlan 模式
macvlan 是 Linux 的一个内核模块,算是一个比较新的特性。本质上是一种网卡虚拟化技术,通过 macvlan 可以在同一个物理网卡上虚拟出多个网卡,通过不同的 Mac 地址在数据链路层进行网络数据的转发,一块网卡上配置多个 Mac 地址。Docker 的 macvlan 网络实际上就是使用 Linux 提供的 macvlan 驱动。5. none 模式
这种模式下 Docker 容器拥有自己的 network namespace,但是并不会做任何网络配置。换句话说,这个 Docker 容器除了 network namespace 自带的 lo 网卡(loopback,127.0.0.1)外没有其他任何网卡、IP 等信息。这种模式如果不做额外配置是无法使用的,要使用需要自己添加网卡等,也就是它给了用户最大的自由度。
Dokcer Link
Link 是在 Docker 容器创建的过程中通过 —link 参数将新创建出来的 Docker 容器和已有的容器之间串讲一个安全通道用来做数据交互。
Link 的使用场景还是很常见的,比如我们线上应用有一个 web 应用以 Docker 容器运行,有一个数据库(MySQL)也以 Docker 容器运行,由于 web 应用需要访问数据库的数据,那么我们就可以在这两个容器之间使用 Link 连接起来。
使用
Link 的使用比较简单,我们这里演示一下。首先运行一个 MySQL 的 Docker 容器。
[root@docker1 ~]# docker run -d -e MYSQL_ROOT_PASSWORD=123456 -p 3307:3306 --name mysql mysql:latest
然后我们创建一个 busybox 的 Docker 容器,并通过 telnet 连接 MySQL 的 Docker 容器。
[root@docker1 ~]# docker run -ti --name busybox --link mysql:mysql busybox:latest sh/ # telnet mysqltelnet: can't connect to remote host (172.17.0.2): Connection refused/ # telnet mysql 3306Connected to mysqlJ�1.1jJXq/%p@R|Iccaching_sha2_password
其中 busybox 容器的启动参数里面的 —link mysql:mysql 就是将我们新建出来的 busybox 容器和名字叫 mysql 的 Docker 容器建立一个 link 通道。—link 的参数格式为 —link
link原理主要就是修改 /etc/hosts 文件。现在已经不推荐了
警告:—link 参数是 Docker 早期的遗留特性,可能最终会被移除掉。除非你一定要使用它,否则我们建议你使用自定义网络的方式来实现多个 container 之间的网络通信。自定义网络相比 —link 的一个弊端是无法共享环境变量,但是你可以通过类似在多个容器中挂载同一个 volume 的方式来实现这个需求。
最佳实践
官方给了一个针对各个网络模式的选择使用建议:
- User-defined bridge network 适用于同一个宿主机上多个 Docker 容器进行通信。这里的 user-defined 可以理解为自定义网桥,不适用 docker0 网桥,这样可以更灵活地设置子网和 iptables。
- Host networks 适用于 Docker 容器的网络不需要和宿主机进行隔离的场景,比如对于网络性能比较敏感的场景。
- Overlay networks 适用于运行在多个宿主机上 Docker 容器之间的通信情况。
- Macvlan networks 适用于 VM 迁移的场景,这样每个 Docker 容器看起来和物理主机一样。
- Third-party network plugins 适用于将 Docker 和特定网络协议栈整合的场景。
