# docker 中的网络

众所周知,Docker 使用了 Linux 的 Namespaces 技术来进行资源隔离,如 PID Namespace 隔离进程,Mount Namespace 隔离文件系统,Network Namespace 隔离网络等。

一个 Network Namespace 提供了一份独立的网络环境,包括网卡、路由、Iptable 规则等都与其他的 Network Namespace 隔离。一个 Docker 容器一般会分配一个独立的 Network Namespace

在 Docker 安装时,他会自动创建三个网络,bridge(类似桥接)、None(无网络)、host(类似 NAT)

image-20220321122134452

image-20220321132037445

# Bridge 模式

容器的默认网络模式,在 docker 安装的时候会自动创建一个名为 docker0 的 Linux Bridge 网络,在不指定网络模式从情况下,创建的容器都会默认挂到这个 docker0 网络上。

image-20220321132928453

# 容器间通信测试

在这种情况下,因为它们在同一个网络上,所以这些容器能够互相的访问。(很好理解,参考 vmware 的桥接模式)

image-20220321132956005

宿主机也是在这个网络内的,它默认的 ip 为 172.17.0.1

image-20220321133005961

# 容器向外通信测试

Q:前面讲了,这里的网卡是一张虚拟网卡,那么容器是如何向外界通信的呢?

A:这里为了实现跨网络的隔离和通信,docker 借助了 iptables 的机制。

为了实现内部容器访问外界的需求,docker 在 FORAWRD 链中加入了自己的规则,从而实现 bridge 网络之间的隔离和通信。

image-20220321133015682

image-20220321133020732

# bridge 模式做隔离

在这种模式下,我们改怎么做隔离呢?一种简单粗暴的办法就是

在 ExecStart 参数(在 docker.service 中修改)中设置–icc=false,但这种情况下会导致所有的容器间都无法互相访问,只有使用–link 才能使两个容器互相通信。(结果:容器之间隔离,能够访问宿主机和外网)

在宿主机中查看 docker 状态,找到配置文件的路径。

image-20220321133113953

image-20220321133117182

image-20220321133121273

此时发现无法访问其他容器(仍能访问宿主机)。但仍然能够正常访问外部网络。

image-20220321133130777

但如果我们只想要一部分的容器不能和另一部分的容器交换的话,用上述方法就不太现实了。因此另一种方法则是使用命名空间。虽然两个容器都使用了网桥模式,但只要它们的命名空间不相同就可以实现隔离。

(结果:不同命名空间的容器无法互相访问,其他没变化)

在这里,我们创建了两个 net 空间(就相当于创建了其他虚拟网卡)

然后只需要把容器给加进去就自然形成隔离了。

image-20220321133226886

在创建容器的时候选择网络即可。

image-20220321133235230

# Host 模式

image-20220321133246037

host 模式类似于 VMWARE 中的 NAT 模式,在这种模式下,容器没有自己的 IP,它会直接使用宿主机的网络命名空间。

在这种情况下,容器中和宿主机的所有网络相关的应用全都是共享的,就跟跑在宿主机上一样。但是文件系统和进程列表等还是隔离的,

这种模式下,就相当于容器和宿主机没有了隔离(仅限于网络),因此容器和宿主机会竞争网络的使用情况,比如,你宿主机占用了 80 端口,容器就不能占用。容器的崩溃也可能会导致宿主机的崩溃。

但它有有着相应的优势,从它的构造就可以看出,在整个网络请求过程中,无需进行 NAT 转换,而且也不需要通过 linux bridge 的转发。也就是说性能上比较占优。

# 网络测试

下图是在容器中执行的命令,我们可以看到在容器中是和宿主机公用一个网络空间,能够访问宿主机访问到的所有资源。

image-20220321133311885

这里是在宿主机上执行的操作,我们尝试在宿主机 kill 到容器的监听端口 80 服务,发现容器直接死了(容器依靠这个服务)

image-20220321133318314

# Container 模式

image-20220321133326456

前面讲了 host 模式是让当前创建的容器和宿主机共享一个网络命名空间。而这种情况下往往是不安全的,存在逃逸的风险。

而 Container 模式就是使用其他容器的命名空间,可以指定创建的容器和已经存在的一个容器共享网络命名空间。也就是说,两个容器共享 ip、端口范围、host 等等。

在这种情况下,在做跨容器的网络数据传输的时候,可以通过 localhost 来完成,一方面极大的加快了网络传输效率,另一方面也减少了网络链路的流量。

image-20220321133333709

# None 模式

这个模式下容器有独立的网络命名空间,但没有任何的网络配置,只有一张 loopback 环回网卡用于进程通信。

可以说是一张白纸,在这种情况下,我们可以为它进行任意配置,从而满足我们的网络拓扑要求。

可以看到,创建出来的容器没有任何网络。可以说它将网络创建的责任完全交给用户,因此可以实现更加灵活复杂的网络。

image-20220321133546105

# Overlay 模式(跨主机)

主机间的网络通信只能经由主机上可对外通信的网络接口进行,跨主机在数据链路层直接连接虚拟网桥的需求必然难以实现,也就是说上述的网络模式除了 host 模式(但是它不安全)外都做不到跨主机的通信。除非借助宿主机间的通信网络构建的通信 “隧道” 进行数据帧转发

Overlay 网络模式,在多个 docker daemon 主机之间穿件一个分布式的网络,该网络(overlay)位于 docker 主机层次之上,允许容器(同一集群服务的容器)之间加密通讯。也就是说 Overlay 模式是用来解决容器在跨主机的情况下的通信问题的。

使用 overlay 模式的网络会虚拟出一个网络比如 10.0.9.3 这个 ip 地址,在这个 overlay 网络模式里面,有一个类似于服务网关的地址,然后把这个包转发到物理服务器这个地址,最终通过路由和交换,到达另一个服务器的 ip 地址。

1.png

要实现 overlay 网络,我们会有一个服务发现。比如说 consul,会定义一个 ip 地址池,比如 10.0.2.0/24 之类的。上面会有容器,容器的 ip 地址会从上面去获取。获取完了后,会通过 ens33 来进行通信,这样就实现跨主机的通信。

2.png

使用 overlay 网络需要满足下面的这些条件:

  • 正常工作的 key-value 存储服务,比如 consul、etcd、zookeeper 等(服务注册发现)
  • 可以访问到 key-value 服务的主机集群
  • 集群中每台机器都安装并运行 docker daemon
  • 集群中每台机器的 hostname 都是唯一的,因为 key-value 服务是通过 hostname 标识每台主机的

# docker swarm Overlay 网络搭建

初始化 swarm 或将 Docker 主机加入现有 swarm 时,会在该 Docker 主机上创建两个新网络:

  • 一个名为 的覆盖网络 ingress,它处理与 swarm 服务相关的控制和数据流量。当您创建一个 swarm 服务并且不将其连接到用户定义的覆盖网络时,它会 ingress 默认连接到该网络。
  • 一个名为 的桥接网络 docker_gwbridge,它将单个 Docker 守护进程连接到参与 swarm 的其他守护进程。

下面我们利用 docker swarm 简单创建一个 overlay 网络,然后测试其连通性。

docker swarm init  #初始化docker集群

image-20220321133655921

image-20220321133659927

docker network create -d overlay --subnet=10.10.1.1/24  --attachable test_overlay  #创建一个 Overlay 网络。这里为了防止 ip 冲突,我给它设置了网段,如果不使用 --attachable 参数的话,这个网络只能被 swarmservice 使用(即只能 docker server 去使用),不能被独立的容器所使用的(不能被 docker run 使用)。

这个时候在其他节点上是看不到我们创建的这个 test_overlay 网络的,当服务部署在 work 节点上的时候,才可以看到这个网络。

image-20220321133720210

将不同节点的两个容器加入我们创建的 overlay 网络,测试联通性、

这里需要使用 docker server 才能指定节点创建我们的容器,首先给目标节点添加标签属性

docker node update --label-add role=work test2 #给 test2 这个节点添加 work 的标签
docker service create --constraint node.labels.role==work --network=test_overlay -d ruyueattention/php:v1

PS: 如果没成功可以看看哪里出了问题,docker service ls && docker service ps <id>

image-20220321133735679

随后在节点 test2 上可以成功看到创建了一个 overlay 网络,并且容器也跑了起来。

image-20220321133741394

成功访问跨主机的容器。

image-20220321133747490

# 非 swarm 中 Overlay 网络搭建

如果我们不想用 swarm 的话,怎么实现呢?

在这种情况下,想要使用 overlay 网络就得先提前准备好分布式键值数据库,比如 etcd、consul、zookeeper 等

我们需要借助服务注册发现的软件。这里用的是 consul

因为在 Linux 中,一切皆文件,因此这里原理其实就相当于把 docker 的网卡配置文件给弄到一个云上,大家共享这个信息。这样就实现了在 A 创建网卡,B 也会同步。(不确定)

# 部署运行 consul 服务

https://www.consul.io/downloads

image-20220321133842555

这里让 consul 服务运行在 0.0.0.0 上,以让多个 docker 能够成功访问到它。

consul agent -dev -client 0.0.0.0

随后我们访问目标的 8500 端口即可成功访问服务。

image-20220321133917024

# 将 consul 设置为 docker 集群存储

所有的宿主机都需要设置

添加 docker.service 中的 ExecStart 参数

-H tcp://0.0.0.0:2376 --cluster-store=consul://192.168.2.209:8500 --cluster-advertise=ens33:2376

--cluster-store=consul://192.168.2.209:8500:#docker daemon 存储地址指向的 key value service 地址

--cluster-advertise=ens33:2376:# 从本机的 ens33 网卡通过 2376 端口收集网络信息,存储在 consul 上。

systemctl daemon-reload
systemctl restart docker

image-20220321133949185

image-20220321133952276

# 创建 overlay 网络

现在我们只需要在任意一个宿主机上创建网络,网络就会同步到其他的容器上了。

这里,我们可以看到这个网卡的作用范围为 global

image-20220321134008224

image-20220321134017104

# 测试网络

可以看到,容器内也是存在三张网卡的。

image-20220321134037897

image-20220321134040979

更新于 阅读次数