Docker

背景知识

Docker 网络

在 Docker 中,网络是一个重要抽象。一个 Docker 可以有多个网络,每个容器可以连接到一个或多个中。 docker 安装完成后,会自动创建三个网络,分别是 bridge、host 和 none。通过 <font style="color:rgb(0, 0, 0);">docker network ls</font> 命令可以查看:
  1. NETWORK ID NAME DRIVER SCOPE
  2. 11da7fc827b4 bridge bridge local
  3. 4cd2eae9c4cd host host local
  4. 12730ca5beca none null local
其中名字为 bridge 的 bridge 类型网络,就是 docker 的默认网络(docker run 默认使用的网络)。 默认网络的实现是在宿主机环境创建一个名为 docker0 的 bridge 设备,并为其配置一个私有网段的网关 IP 地址。通过 <font style="color:rgb(0, 0, 0);">ip addr show docker0</font> 可以查看更该设备信息。
  1. 3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
  2. link/ether 02:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
  3. inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
  4. valid_lft forever preferred_lft forever
  5. inet6 fe80::xxxx:xx:xx:xxxx/64 scope link
  6. valid_lft forever preferred_lft forever
docker bridge 网络,在 IPv4 场景下拓扑如下所示(来自于:KVM + LinuxBridge 的网络虚拟化解决方案实践):
  1. +----------------------------------------------------------------+-----------------------------------------+-----------------------------------------+
  2. | Host | Container 1 | Container 2 |
  3. | | | |
  4. | +------------------------------------------------+ | +-------------------------+ | +-------------------------+ |
  5. | | Newwork Protocol Stack | | | Newwork Protocol Stack | | | Newwork Protocol Stack | |
  6. | +------------------------------------------------+ | +-------------------------+ | +-------------------------+ |
  7. | | | |
  8. |............|.............|.....................................|...................|.....................|....................|....................|
  9. | | | |
  10. | +------+ +--------+ | +-------+ | +-------+ |
  11. | |.3.101| | .9.1 | | | .9.2 | | | .9.3 | |
  12. | +------+ +--------+ +-------+ | +-------+ | +-------+ |
  13. | | eth0 | | br0 |<--->| veth | | | eth0 | | | eth0 | |
  14. | +------+ +--------+ +-------+ | +-------+ | +-------+ |
  15. | | | |
  16. | | | +-------------------------------------------+ | | |
  17. | | | | | |
  18. | | +-------+ | | | |
  19. | | | veth | | | | |
  20. | | +-------+ | | | |
  21. | | | | | |
  22. | | +-------------------------------------------------------------------------------|--------------------+ |
  23. | | | | |
  24. | | | | |
  25. | | | | |
  26. +------------|---------------------------------------------------+-----------------------------------------+-----------------------------------------+
  27. Physical Network (192.168.3.0/24)
通过 <font style="color:rgb(0, 0, 0);">docker network inspect bridge</font> 可以查看某该默认网络配置:
  1. [
  2. {
  3. "Name": "bridge",
  4. "Id": "11da7fc827b4dxxx",
  5. "Created": "2021-11-22T12:04:03.408536176+08:00",
  6. "Scope": "local",
  7. "Driver": "bridge",
  8. "EnableIPv6": false,
  9. "IPAM": {
  10. "Driver": "default",
  11. "Options": null,
  12. "Config": [
  13. {
  14. "Subnet": "172.17.0.0/16",
  15. "Gateway": "172.17.0.1"
  16. }
  17. ]
  18. },
  19. "Internal": false,
  20. "Attachable": false,
  21. "Ingress": false,
  22. "ConfigFrom": {
  23. "Network": ""
  24. },
  25. "ConfigOnly": false,
  26. "Containers": {
  27. "0d744147030829f0247xx": {
  28. "Name": "container1",
  29. "EndpointID": "6f539a054ae35cbxx",
  30. "MacAddress": "02:42:xx:xx:xx:xx",
  31. "IPv4Address": "172.17.0.14/16",
  32. "IPv6Address": ""
  33. },
  34. },
  35. "Options": {
  36. "com.docker.network.bridge.default_bridge": "true",
  37. "com.docker.network.bridge.enable_icc": "true",
  38. "com.docker.network.bridge.enable_ip_masquerade": "true",
  39. "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
  40. "com.docker.network.bridge.name": "docker0",
  41. "com.docker.network.driver.mtu": "1500"
  42. },
  43. "Labels": {}
  44. }
  45. ]
可以通过 <font style="color:rgb(0, 0, 0);">docker network create</font> 命令,创建一个自定义 bridge 网络。关于,默认网络和自定义 bridge,有如下不同:
  • 自定义 bridge 网络会使用 docker 内嵌的 dns server 服务,配置地址为 127.0.0.11,通过 iptables 转发到 43747 端口。因此可以直接通过 container name 访问同一个自定义网络下的其他容器网络。而默认网络则不支持。
  • 自定义 bridge 有更好的隔离性。
  • 一个容器可以在运行时动态的连接/断开一个自定义 bridge,默认网络只能重新创建。
  • 自定义 bridge 可以在创建的时候配置 Linux bridge,如果要修改默认网络的 bridge 则需要重启 docker daemon。因此,官方更推荐在生产环境使用自定义 bridge 而非默认网络。

默认网络支持 IPv6

本章节介绍的是如何配置默认的 bridge 网络支持 ipv6。(未经过测试,仅供参考) 前置条件:确保自己的设备被分配了一个 IPv6。通过 ip addr show 查看当前设备的 IPv6。其输出的物理网卡存在包含 inet6 和 scope global 的行时,表示该网卡支持 IPv6。需要注意的是:其 IPv6 地址的前缀不能是 /128,如果是 /128,建议通过 IPv6NAT 方式去支持 IPv6。
  1. 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
  2. link/ether fa:16:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
  3. inet 10.227.8.141/22 brd 10.227.11.255 scope global eth0
  4. valid_lft forever preferred_lft forever
  5. inet6 2xxx:xxxx::xxxx/64 scope global
  6. valid_lft forever preferred_lft forever
  7. inet6 fe80::xxxx:xxxx:xxxx:xxxx/64 scope link
  8. valid_lft forever preferred_lft forever
修改 /etc/docker/daemon.json,其中 fixed-cidr-v6 是上一步获取到的 IPv6 网段的子网(配置默认网络,前缀长度最大为 /80)。
  1. {
  2. "ipv6": true,
  3. "fixed-cidr-v6": "2xxx:xxxx::/80"
  4. }
reload 配置,docker daemon 将会使用 IPv6 网络。
  1. sudo systemctl reload docker
通过 <font style="color:rgb(0, 0, 0);">docker network inspect bridge</font> 命令检查是否生效。若生效,则 EnableIPv6 值为 true,<font style="color:rgb(0, 0, 0);">IPAM.Config[1].Subnet</font> 是上一步配置的 fixed-cidr-v6。 注意经测试,如下场景可能不会生效:
  • /etc/docker/daemon.json 存在 <font style="color:rgb(1, 1, 1);">"live-restore": true</font> 字段。
  • reload 时有容器仍然存在。
根据众多博客的说法,还需如下两步:
  • /etc/sysctl.conf 添加,并执行 sysctl -f,配置宿主机和 docker0 网卡支持 NDP proxy。
  1. # docker0 是 docker 默认的网桥 (bridge)
  2. net.ipv6.conf.docker0.proxy_ndp=1
  3. # eth0 表示物理网卡,注意替换为物理网卡
  4. net.ipv6.conf.eth0.proxy_ndp=1
  • 默认的 ndp 邻居发现配置仅允许单个 IP 配置。需要安装 ndppd 服务来转发邻居发现消息(这一步还有一个替代方案:手动为每一个容器配置如:ip -6 neigh add proxy 2xxx:xxxx::1 dev ens3,其中,2xxx:xxxx::1 为容器的分配的 IPv6,ens3 为宿主机绑定 IPv6 的网卡)。
  1. apt-get update -y
  2. apt-get install -y ndppd
  3. cp /usr/share/doc/ndppd/ndppd.conf-dist /etc/ndppd.conf
更改 proxy eth0 { 行到宿主机绑定 IPv6 的网卡,如:proxy ens3 {。更改 rule 1111:: { 行为需要暴露的网段 2xxx:xxxx::/80 {。最后执行 systemctl restart ndppd 注意:
  • 本方法仅针对新装 Docker 场景
  • 本章节 和 自定义网络支持 IPv6 配置的 IPv6 和 docker 默认 IPv4 是不同的。容器的 IPv6 用的不是私有网段,而是宿主机网络或者是宿主机网络的一个子网。因此,宿主机所在的网络的所有实例可以直接通过 IPv6 的地址。也就是说:容器的所有端口对于 IPv6 来说都是公开的,而无需 public。而容器的 IPv4 分配的是私有网段,因此,容器网段和宿主机网段是通过 NAT 转发数据的,因此宿主机所在网络的其他实例是无法直接访问容器。也就是说:容器的所有端口对于 IPv4 来说都是私有的,需 public 到 host 网络才能被外部访问到。

自定义网络支持 IPv6

本章节介绍的是如何创建一个支持 IPv6 的 bridge 网络。(未经过测试,仅供参考)
  • 前置条件:确保自己的设备被分配了一个 IPv6。
  • 创建一个支持 IPv6 的 bridge 网络。其中 —subnet 参数为上一步获取到的 IPv6 网段的子网(自定义 bridge 网络,前缀长度不限制,可以大于于 80)。
  1. docker network create my-net-ipv6 --ipv6 --subnet="2xxx:xxxx::/80"
  • 通过 <font style="color:black;">docker network inspect my-net-ipv6</font> 命令检查是否生效。若生效,则 EnableIPv6 值为 true,IPAM.Config[1].Subnet 是上一步配置的 fixed-cidr-v6。
  • 创建容器时,通过 <font style="color:black;">--network my-net-ipv6</font> 参数,给容器开启 IPv6 网络,如 <font style="color:black;">docker run --network my-net-ipv6 -it busybox ip addr show</font>,可以看到,网卡被分配了 IPv6 地址。

通过 IPv6NAT 方式支持 IPv6

测试可行,推荐使用该方式。 上文也提到,上文展示的方案,容器获得的 IPv6 IP 并不是私有网络 IP,是和外部网络直接连通,而不会经过 NAT。在如下场景下,以上方式可能不能满足要求:
  • 安全性,要求容器的网络是私有的,需要容器的网络行为和 Docker IPv4 的行为一致,只有特定端口才能访问。
  • 宿主机处于一个很小范围的网段(前缀大于 /80),如 xxx::xx/128,没有多余的 IPv6 可以分给容器。此时就需要,给容器配置一个私有 IPv6 网段,并启用 NAT。
但是 Docker 官方并没有内置 IPv6 的 NAT,如果想要使用 IPv6 NAT,需要安装外挂的 IPv6 启动,参见:https://github.com/robbertkl/docker-ipv6nat 有这些准备后,实施步骤如下所示:
  • 使用如下命令,后台启动 IPv6 NAT(通过 <font style="color:rgb(1, 1, 1);">--restart always</font> 配置了开机自启)。
  1. docker run -d --name ipv6nat --privileged --network host --restart always -v /var/run/docker.sock:/var/run/docker.sock:ro -v /lib/modules:/lib/modules:ro robbertkl/ipv6nat
  • 和 自定义网络支持 IPv6 类似,创建一个支持 IPv6 的 bridge 网络。其中 <font style="color:rgb(1, 1, 1);">--subnet</font> 参数为 fe80::/10 的一个子网。
  1. docker network create my-net-ipv6 --ipv6 --subnet="fd00:1::1/80" --gateway="fd00:1::1"
  • 通过 <font style="color:black;">docker network inspect my-net-ipv6</font> 命令检查是否生效。若生效,则 EnableIPv6 值为 true,<font style="color:black;">IPAM.Config[1].Subnet</font> 是上一步配置的 fixed-cidr-v6。
  • 创建容器时,通过 <font style="color:black;">--network my-net-ipv6</font> 参数,给容器开启 IPv6 网络,如 <font style="color:black;">docker run --network my-net-ipv6 -it busybox sh</font> - <font style="color:rgb(1, 1, 1);">ip addr show</font> ,可以看到,网卡被分配了 IPv6 地址。<font style="color:rgb(1, 1, 1);">wget https://ipv6.icanhazip.com -O /dev/stdout 2>/dev/null</font> 可以看到出网 IPv6 地址。