docker 操纵网络是无形的,它通过修改路由规则来让某些包在特定的 network 里面流动。在 Linux 下,它是通过操纵 iptables 来做到这件事的(windows下通过其他机制)。

docker 驱动

docker 的网络是可插拔的,因为使用了驱动。默认就自动携带的驱动是:

bridge

默认的网络驱动。
当所有的容器都在一个宿主机的时候,应该使用这个驱动。

在计算机网络的范畴里,一个桥接网络是一个链路层设备,转发网络片段。一个桥可以一个硬件设备或者宿主机内核里的软件设备。
在 Docker的范畴里,桥接网络使用一个软件桥来让容器连在同一座桥上,通过桥通信。同一个宿主机里,不同桥网络是不能相互通信的(实际上不同的网络就不应该彼此通信)。

启动 docker 的时候,一个默认的桥接网络就被创建了。如果没有其他网络被创建,则默认大家都使用这个网络。所有新创建的容器,都会自动在这个名称为 bridge 网络里互连。用户也可以创建自定义的桥接网络,自定义的桥接网络拥有更高的优先级。

自定义网络的优势

  • 用户自定义网络在容器化应用程序里,提供了更好的隔离和互操作性。在同一个桥接网络里的容器,默认向彼此打开了所有端口,但却不向网络外开放端口。如果使用自定义的桥接网络,则可以指定开放哪些端口给网络外部,如果使用缺省桥接网络;则开放时必须默认全部开放端口(可以有其他迂回策略做到部分开放)。细想一下,这种网络隔离免去了网络内部容器之间鉴权的必要。
  • 用户自定义的桥接网络提供网络内容器间自动的 DNS 解析。这让我们可以免于使用 —link 这样的遗留选项(这个选项本质上 必须和缺省桥接网络共生),使我们可以用容器名或者别名直接访问网络。我们当然也可以采用 hack 容器内的 /etc/hosts 的方法来解决这个问题。然而,这产生了难以 debug 的问题,特别是容器每次重建这个文件都会被刷新。在隔离得不好的桥接网络时代,—link 会导致容器之间互联,默认使用 docker0 网桥。docker-compose.yml 中的 links 也有类似的问题。
  • 可以动态对用户自定义桥接网络添加容器和解绑,而对默认桥接网络这么做,就需要停下整个网络。
  • 可以动态修改用户自定义桥接网络的配置,但缺省网络配置就难以修改,因为又涉及到重启。
  • 使用缺省网络,所有容器共享环境变量。

操纵自定义网络

基础的 CRUD:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 缺省的 docker network create 命令创造出的就是桥接网络
docker network create my-net
docker network rm my-net

# 以一个来自于 nginx 镜像的名为 my-nginx 的容器建立一个名为 my-net 的网络,并开放80端口。
docker create --name my-nginx \
--network my-net \
--publish 8080:80 \
nginx:latest

# 把一个已经运行的容器,加入到一个已经存在的网络里
docker network connect my-net my-nginx
# 切断连接
docker network disconnect my-net my-nginx

允许 docker 容器内的流量转发到外部世界:

1
2
3
4
5
# 相当于本来内网也是不能访问外网的

sysctl net.ipv4.conf.all.forwarding=1

sudo iptables -P FORWARD ACCEPT

配置缺省桥接网络,要求 docker 守护进程重启:

1
2
3
4
5
6
7
8
9
{
"bip": "192.168.1.5/24",
"fixed-cidr": "192.168.1.5/25",
"fixed-cidr-v6": "2001:db8::/64",
"mtu": 1500,
"default-gateway": "10.20.1.1",
"default-gateway-v6": "2001:db8:abcd::89",
"dns": ["10.20.1.2","10.20.1.3"]
}

容器加入自定义网络:

1
2
3
4
5
6
7
8
9
10
11
12
docker run -dit --name alpine1 --network alpine-net alpine ash

docker run -dit --name alpine2 --network alpine-net alpine ash

docker run -dit --name alpine3 alpine ash

docker run -dit --name alpine4 --network alpine-net alpine ash

docker network connect bridge alpine4

# 使用 attach 而不是 exec 来进入容器内部
docker container attach alpine1

使用 IP V6

需要单独配置。参考《Use IPv6》

host

移除单个容器和宿主机之间的网络间隔,并直接使用宿主机网络。这个驱动只在高于等于版本 17.06 的 swarm 服务上可用。

当网络栈部分不与宿主机隔离,而容器的其他部分需要与宿主机隔离的时候,应该使用这个驱动。

这个驱动只真对 Linux。

使用这个驱动,容器开放的端口和宿主机开放的端口号一致。比如容器内应用绑定了80端口,在宿主机上可以看到是 docker 进程占用了该端口:

1
sudo netstat -tulpn | grep :80

创建网络:

1
docker container create --network host

overlay

层叠网络虽然最被我们所熟悉,但却不是缺省网络。

层叠网络可以让多个 docker 守护进程(注意,每个 宿主机上实际上只有一个 docker 守护进程)连接到一起,也让 swarm 服务与他们一一通信。

层叠网络可以让 swarm 服务和单独的容器一起通信。

层叠网络可以让多个 docker 守护进程里的单一容器通信。

层叠网络消除了 OS 级别的容器间路由。

当多个宿主机上的容器需要通信,或者需要使用 swarm 服务让多重应用一起工作时,应该使用这个驱动。

层叠网络创建的是一个分布式网络,构建在多个宿主专属网络上。

两个内部网络(这个主题似乎和层叠网络不是很有关系,而和 swarm 本身有关系)

当我们初始化一个 swarm,或者让一个已经存在的容器加入一个已经存在的 swarm 服务时,每一个节点(manager 或者 worker 都有)产生了两个内部网络:

  • ingress 负责处理 swarm 相关的流量
  • docker_gwbridge 这是一个桥接网络,连接不同的 docker 守护进程。

容器和服务可以连接多个网络,但每次只能在一个它们连上的网络里相互通信。

层叠网络操作

创建层叠网络

先决条件:

  • 打开以下端口
    • TCP port 2377 for cluster management communications
    • TCP and UDP port 7946 for communication among nodes
    • UDP port 4789 for overlay network traffic
  • 成为 swarm manager 或者加入一个 swarm 集群。这样才能初始化 ingress 网络。
1
2
3
4
# 给 swarm 用
docker network create -d overlay my-overlay
# 给 swarm 或者单独的容器通信用
docker network create -d overlay --attachable my-attachable-overlay

实际上这些操作,还是在 swarm manager 节点上执行最好,详情见《Networking with overlay networks》

层叠网络里的加密

swarm 的管理流量默认就是被 AES 算法 GCM 模式加密过的。

创建层叠网络时使用 —opt encrypted 会让普通的应用流量也加密。

1
docker network create --opt encrypted --driver overlay --attachable my-attachable-multi-host-network

不要让 windows 节点加入加密的 swarm 网络,有错也检验不出来。

定制缺省的 ingress 网络

ingress 是边界流量网络,理论上建立了 Swarm 的宿主机上都会有。

修改有服务存在的 ingress 网络是很麻烦的:

  1. docker network inspect ingress,然后移除网络中的服务。
  2. 去除现存的 ingress 网络,docker network rm ingress
  3. 使用新的 opption 来创建新的 ingress 网络:
    1
    2
    3
    4
    5
    6
    7
    docker network create \
    --driver overlay \
    --ingress \
    --subnet=10.11.0.0/16 \
    --gateway=10.11.0.2 \
    --opt com.docker.network.driver.mtu=1200 \
    my-ingress

    定制 docker_gwbridge

也是类似的操作。

绕过路由网格(routing mesh)

swarm 默认会帮我们做服务级的负载均衡,这样应用程序就获得了 VIP。

我们可以关闭这种模式,通过

To bypass the routing mesh, you can start a service using DNS Round
Robin (DNSRR) mode, by setting the —endpoint-mode flag to dnsrr.

让 docker 宿主机成为一个 DNS 服务器,用自己的负载均衡来查询这个 IP 列表。docker 守护进程本身也可以像一个 DHCP server 一样工作。

macvlan

这个驱动让容器可以得到一个虚拟的 MAC 地址,让它表现得好像一个物理设备一样。而 docker 守护进程按照 Mac 地址往 docker 容器发流量。

这个驱动是面对要直连物理设备的遗留程序(比如一些网络监控程序)的最好选择。

详情见《Use Macvlan networks》

none

这个驱动必须可以第三方驱动配合使用。

也可以用来完全禁止一个容器的网络栈部分:

1
2
3
4
5
docker run --rm -dit \
--network none \
--name no-net-alpine \
alpine:latest \
ash

其他第三方驱动

这些驱动可以在 docker 商店里找到。

创建容器的时候可以指定 ip 地址

可以指定网络中的 ip。具体可以看文档:

When you connect an existing container to a different network using
docker network connect, you can use the —ip or —ip6 flags on that
command to specify the container’s IP address on the additional
network.

DNS

容器有很多很多种方式来配置 DNS

最好的方法还是用各种方式来容器内部的 /etc/hosts

启动的命令行参数也一样可以改容器的 DNS。