概述
Docker 原生网络是基于 Linux 的 网络命名空间(net namespace) 和 虚拟网络设备(veth pair)实现的。当 Docker 进程启动时,会在宿主机上创建一个名称为 docker0 的 虚拟网桥,在该宿主机上启动的 Docker 容器会连接到这个虚拟网桥上。
虚拟网桥的工作方式和物理交换机类似,宿主机上所有的容器通过虚拟网桥连接在一个二层网络中。
从 docker0 子网中分配一个 IP 给容器使用,并设置 docker0 的 IP 地址为容器的默认网关。在宿主机上创建一对虚拟网卡 veth pair 设备, Docker 将 veth pair 设备的一端放在新创建的容器中,并命名为 eth0(容器的网卡), 另一端放在宿主机中,以 vethxxx 类似的名字命名, 并将这个网络设备连接到 docker0 网桥中。
Docker 会自动配置 iptables 规则和配置 NAT,便于连通宿主机上的 docker0 网桥,完成这些操作之后,容器就可以使用它的 eth0 虚拟网卡,来连接其他容器和访问外部网络。
Docker 中的网络接口默认都是虚拟的接口,Linux 在内核中通过 数据复制 实现接口之间的数据传输,可以充分发挥数据在不同 Docker 容器或容器与宿主机之间的转发效率, 发送接口发送缓存中的数据包,将直接复制到接收接口的缓存中,无需通过物理网络设备进行交换。
图片来源: https://www.suse.com/c/rancher_blog/introduction-to-container-networking/
虚拟网桥 docker0 通过 iptables 配置与宿主机器上的网卡相连,符合条件的请求都会通过 iptables 转发到 docker0, 然后分发给对应的容器。
网络驱动
Docker 的网络子系统支持插拔式的驱动程序,默认存在多个驱动程序,并提供核心网络功能。
名称 | 描述 |
bridge | 默认的网络设备,当应用程序所在的容器需要通信时使用 |
host | 移除容器与宿主机之间的网络隔离,直接使用宿主机的网络 |
overlay | 将多个容器连接,并使集群服务能够相互通信 |
ipvlan | 使用户可以完全控制 IPv4 和 IPv6 寻址 |
macvlan | 可以为容器分配 MAC 地址 |
none | 禁用所有网络 |
Network plugins | 通过 Docker 安装和使用第三方网络插件 |
图片来源: Docker——容器与容器云
Docker daemon 通过调用 libnetwork 提供的 API 完成网络的创建和管理等功能。libnetwork 中使用了 CNM 来完成网络功能, CNM 中主要有沙盒(sandbox)、端点(endpoint)和网络(network)3 种组件。
• 沙盒:一个沙盒包含了一个容器网络栈的信息。一个沙盒可以有多个端点和多个网络,沙盒可以对容器的接口、路由和 DNS 设置等进行管理,沙盒的实现可以是 Linux network namespace、FreeBSD Jail或者类似的机制
• 端点:一个端点可以加入一个沙盒和一个网络。一个端点只属于一个网络和一个沙盒,端点的实现可以是 veth pair、Open vSwitch 内部端口或者相似的设备
• 网络:一个网络是一组可以直接互相联通的端点。一个网络可以包含多个端点,网络的实现可以是Linux bridge、VLAN 等
bridge 模式
bridge 是默认的网络模式,为容器创建独立的网络命名空间,容器具有独立的网卡等所有的网络栈。使用该模式的所有容器都是连接到 docker0 这个网桥, 作为 虚拟交换机 使容器可以相互通信,但是由于宿主机的 IP 地址与容器 veth pair 的 IP 地址不在同一个网段,所以为了和宿主机以外的网络通信, Docker 采用了端口绑定的方式,也就是通过 iptables 的 NAT,将宿主机上的端口流量转发到容器。
bridge 模式已经可以满足 Docker 容器最基本的使用需求了,但是其与外界通信时使用 NAT,增加了通信的复杂性,在复杂场景下使用会有限制。
通过上面的输出可以看到,虚拟网桥 的 IP 地址就是 bridge 网络类型的网关地址。
我们可以从输出的 Containers 容器列表中找一个容器,查看其网络类型和配置:
通过上面的输出可以看到,虚拟网桥 的 IP 地址就是 bridge 网络类型的容器的网关地址。
实现机制
在 iptables 做了 DNAT 规则,实现端口转发功能:
当容器需要将端口映射到宿主机时,Docker 会自动为该容器分配一个 IP 地址,同时新增一个 iptables 规则。
host 模式
容器不会获得一个独立的网络命名空间,而是和宿主机共用一个。容器不会虚拟出自己的网卡,配置自己的IP等,而是直接使用宿宿主机的。但是容器的其他方面,如文件系统、进程列表等还是和宿宿主机隔离的,容器对外界是完全开放的,能够访问到宿主机,就能访问到容器。
host 模式降低了容器与容器之间、容器与宿主机之间网络层面的隔离性,虽然有性能上的优势,但是引发了网络资源的竞争与冲突,因此适用于容器集群规模较小的场景。
启动一个网络类型为 host 的 Nginx 容器:
查看网络类型为 host 的容器列表:
查看 Nginx 容器网络类型和配置:
通过上面的输出可以看到,Nginx 容器使用的网络类型是 host,没有独立的 IP。
查看 Nginx 容器 IP 地址:
通过上面的输出可以看到,Nginx 容器内部并没有独立的 IP,而是使用了宿主机的 IP。
查看宿主机的端口监听状态:
通过上面的输出可以看到,监听 80 端口的进程为 nginx, 而不是 docker-proxy。
none 模式
容器拥有自己的 Network Namespace,但是并不进行任何网络配置。也就意味着该容器没有网卡、IP、路由等信息,需要手动为容器添加网卡、配置 IP 等, none 模式下的容器会完全隔离,容器中只有 lo 这个 loopback(回环网络)网卡用于进程通信。
none 模式为容器做了最少的网络设置,在没有网络配置的情况下,通过自定义配置容器的网络,提供了最高的灵活性。
启动一个网络类型为 host 的 Nginx 容器:
查看网络类型为 none 的容器列表:
查看 Nginx 容器网络类型和配置:
通过上面的输出可以看到,Nginx 容器使用的网络类型是 none,没有独立的 IP。
查看 Nginx 容器 IP 地址:
查看宿主机的端口监听状态:
container 模式
与 host 模式类似,容器与指定的容器共享网络命名空间。这两个容器之间不存在网络隔离,但它们与宿主机以及其他的容器存在网络隔离。该模式下的容器可以通过 localhost 来访问同一网络命名空间下的其他容器,传输效率较高,且节约了一定的网络资源。在一些特殊的场景中非常有用,例如 k8s 的 Pod。
其他模式
出于篇幅考虑,这里不再赘述其他网络模式,感兴趣的读者可以根据文章末尾的引用连接自行阅读。
网络驱动概述
- • 当需要多个容器在同一台宿主机上进行通信时,使用 bridge
- • 当网络栈不应该与宿主机隔离,但是希望容器的其他方面被隔离时,使用 host
- • 当需要在不同宿主机上运行的容器进行通信时,使用 overlay
- • 当从虚拟机迁移或需要使容器看起来像物理宿主机时,使用 Macvlan, 每个容器都有一个唯一的 MAC 地址
- • 当需要将 Docker 与专门的网络栈集成,使用 Third-party
Docker 和 iptables
如果在公网可以访问的服务器运行 Docker,需要对应的 iptables 规则来限制访问主机上的容器或其他服务。
在 Docker 规则之前添加 iptables 规则
Docker 安装了两个名为 DOCKER-USER 和 DOCKER 的自定义 iptables 链,确保传入的数据包始终先由这两个链进行检查。
Docker 的所有 iptables 规则都被添加到 Docker 链中,不要手动修改此链 (可能会引发问题)。如果需要添加在一些在 Docker 之前加载的规则,将它们添加到 DOCKER-USER 链中,这些规则应用于 Docker 自动创建的所有规则之前。
添加到 FORWARD 链中的规则在这些链之后进行检测,这意味着如果通过 Docker 公开一个端口,那么无论防火墙配置了什么规则,该端口都会被公开。如果想让这些规则在通过 Docker 暴露端口时仍然适用,必须将这些规则添加到 DOCKER-USER 链中。
限制到 Docker 主机的连接
默认情况下,允许所有 外部 IP 连接 Docker 主机,为了只允许特定的 IP 或网络访问容器,在 DOCKER-USER 过滤器链的顶部插入一个规则。
例如,只允许 192.168.1.1 访问:
也可以允许来自源子网的连接,例如,允许 192.168.1.0/24 子网的用户访问:
阻止 Docker 操作 iptables
在 Docker 引擎的配置文件 /etc/docker/daemon.json 设置 iptables 的值为 false,但是最好不要修改,因为这很可能破坏 Docker 引擎的容器网络。
为容器设置默认绑定地址
默认情况下,Docker 守护进程将公开 0.0.0.0 地址上的端口,即主机上的任何地址。如果希望将该行为更改为仅公开内部 IP 地址上的端口,则可以使用 --ip 选项指定不同的IP地址。
集成到防火墙
如果运行的是 Docker 20.10.0 或更高版本,在系统上启用了 iptables, Docker 会自动创建一个名为 docker 的防火墙区域, 并将它创建的所有网络接口 (例如 docker0 ) 加入到 docker 区域,以允许无缝组网。
运行命令将 docker 接口从防火墙区域中移除:
Reference
• Networking overview[1]
• Networking tutorials[2]
• Docker and iptables[3]
• 容器Docker详解[4]
• Introduction to Container Networking[5]
• docker 容器网络方案:calico 网络模型[6]
• Docker——容器与容器云[7]
引用链接
[1] Networking overview: https://docs.docker.com/network/[2] Networking tutorials: https://docs.docker.com/network/network-tutorial-standalone/[3] Docker and iptables: https://docs.docker.com/network/iptables/
[4] 容器Docker详解: https://juejin.cn/post/6844903766601236487
[5] Introduction to Container Networking: https://www.suse.com/c/rancher_blog/introduction-to-container-networking/
[6] docker 容器网络方案:calico 网络模型: https://cizixs.com/2017/10/19/docker-calico-network/
[7] Docker——容器与容器云: https://book.douban.com/subject/26894736/