当 docker 网络被创建(例如使用 docker network create 或间接通过 docker-compose)而没有明确指定子网范围时,dockerd 分配一个新的 /16 网络,从 172.N.0.0/16 开始,其中 N 是一个数字递增(例如 N=17、N=18、N=19、N=20、...)。如果范围内已存在 docker 网络(自定义网络或默认 docker 网桥),则跳过给定的 N。
在 CLI 上创建 docker 网桥(即排除网络中的主机 ips 的网桥)时,您可以明确指定一个安全的 IP 范围。但通常桥接网络是由 docker-compose 使用默认块自动创建的。要可靠地排除这些 IP,需要修改您遇到的每个 docker-compose.yaml 文件。在撰写文件中包含特定于主机的内容是一种不好的做法。
相反,您可以使用 docker 认为已分配的网络来强制 dockerd “跳过”子网。我在下面概述了三种方法:
方法 #0 -- 在 daemon config 中配置 ips 池
如果您的 docker 版本足够新(TODO 检查最低版本),并且您有权配置 docker 守护程序的命令行参数,则可以尝试将 --default-address-pool ARG 选项传递给 dockerd 命令。例如:
# allocate /24 subnets with the given CIDR prefix only.
# note that this prefix excludes 172.17.*
--default-address-pool base=172.24.0.0/13,size=24
您可以将此设置添加到 etc 文件之一:/etc/default/docker 或 /etc/sysconfig/docker,具体取决于您的发行版。还有一种方法可以在daemon.json中设置这个参数(see syntax)
方法#1——创建一个虚拟占位符网络
您可以通过在 172.17.0.0/16 内的任何位置创建一个非常小的 docker 网络来防止 dockerd(在未来的桥接网络中)使用整个 172.17.0.0/16。
在172.17.* 中找到 4 个您知道在您的主机网络中未使用的连续 IP,并将它们牺牲在“墓碑”docker 桥中。下面,我假设您的主机网络中未使用 ips 172.17.253.0、172.17.253.1、172.17.253.2、172.17.253.3(即172.17.253.0/30)。
docker network create --driver=bridge --subnet 172.17.253.0/30 tombstone
# created: c48327b0443dc67d1b727da3385e433fdfd8710ce1cc3afd44ed820d3ae009f5
注意这里的/30 后缀,它定义了一个包含 4 个不同 IP 的块。理论上,最小的有效网络子网应该是一个/31,它总共由2个IP(网络标识符+广播)组成。 Docker 要求/30 最小值,可能是为了说明网关主机和另一个容器。我随意选择了.253.0,你应该选择在你的环境中没有使用的东西。另请注意,标识符 tombstone 并没有什么特别之处,您可以将其重命名为任何可以帮助您在几个月后再次找到它时记住它的原因的名称。
Docker 将修改您的路由表,以使这 4 个 IP 的流量通过新的网桥而不是主机网络:
# output of route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.5.1 0.0.0.0 UG 0 0 0 eth1
172.17.253.0 0.0.0.0 255.255.255.252 U 0 0 0 br-c48327b0443d
172.20.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
192.168.5.0 0.0.0.0 255.255.255.0 U 0 0 0 eth1
注意:172.17.253.{0,1,2,3} 的流量通过刚刚创建的墓碑码头桥 (br-c4832...)。 172.17.* 中任何其他 IP 的流量将通过默认路由(主机网络)。我的 docker 桥 (docker0) 在 172.20.0.1 上,这可能看起来不寻常——我在 /etc/docker/daemon.json 中修改了 bip 来做到这一点。有关详细信息,请参阅this page。
转折点:如果存在甚至占据 /16 子部分的桥,则创建的新桥将跳过该范围。如果我们创建新的 docker 网络,我们可以看到 172.17.0.0/16 的其余部分被跳过,因为范围并不完全可用。
docker network create foo_test
# c9e1b01f70032b1eff08e48bac1d5e2039fdc009635bfe8ef1fd4ca60a6af143
docker network create bar_test
# 7ad5611bfa07bda462740c1dd00c5007a934b7fc77414b529d0ec2613924cc57
生成的路由表:
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.5.1 0.0.0.0 UG 0 0 0 eth1
172.17.253.0 0.0.0.0 255.255.255.252 U 0 0 0 br-c48327b0443d
172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 br-c9e1b01f7003
172.19.0.0 0.0.0.0 255.255.0.0 U 0 0 0 br-7ad5611bfa07
172.20.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
192.168.5.0 0.0.0.0 255.255.255.0 U 0 0 0 eth1
请注意,172.17.0.0/16 中的其余 IP 尚未使用。新网络保留.18. 和.19.。将流量发送到该墓碑网络之外的任何冲突 IP 将通过您的主机网络。
您必须在 docker 中保留该墓碑网络,但不要在您的容器中使用它。这是一个虚拟的占位符网络。
方法#2——关闭冲突的桥接网络
如果您希望暂时避免 IP 冲突,您可以使用 ip 关闭有冲突的 docker 网桥:ip link set dev br-xxxxxxx down(其中 xxxxxx 表示来自 route -n 或 ip link show 的网桥网络名称)。这将具有删除路由表中相应网桥路由条目的效果,而无需修改任何 docker 元数据。
这可能不如上面的方法好,因为您可能每次 dockerd 启动时都必须关闭接口,如果有任何容器使用该桥接器,它会干扰您的容器网络。
如果方法 1 在未来停止工作(例如,因为 docker 试图变得更聪明并重用 ip 块的未使用部分),您可以结合这两种方法:例如用整个/16创建一个大的tombstone网络,不要在任何容器中使用它,然后把它对应的br-x设备down掉。
方法 #3 -- 重新配置您的 docker 桥以占用冲突 /16 的子部分
作为上述的一个细微变化,您可以使默认的 docker 网桥与您的主机网络中未使用的 172.17.*.* 区域重叠。您可以通过更改/etc/docker/daemon.json 中的网桥 ip(即bip 键)来更改默认的 docker 网桥子网(有关详细信息,请参阅this page)。只需将其设为 /16 的子区域,例如在 /24 或更小。
我没有对此进行测试,但我认为任何新的 docker 网络都会跳过 172.17.0.0/16 的其余部分,并为每个新桥分配一个完全不同的 /16。