零基础学习 Docker(四)| 关于网络通信

作者: 王炳明 分类: Docker 发布时间: 2020-12-25 14:30 热度:1,990

0. 系列导读

本系列共六篇:

1. 三种local网络

在docker安装的时候会自动创建三种网络可供使用。

$ docker network ls

1.【none】 :没有网络,与外界完全隔离。这个只能用在容器业务不需要联网,不需要集群,而只在本机运行业务时使用。目的就是隔绝网络。

--> 指定: $ docker run -it --network=none busybox



2. 【host】 :使用 host 网络,容器的网络配置和 host 完全一样。
--> 指定: $ docker run -it --network=host busybox



3. 【bridge】: docker安装的时候默认会创建一个 “docker0” 的linux bridge,若不指定网络,就会都挂在这个网桥下。若宿主机可上网,用这个网络创建的容器也可以上网。

--> 1. 无需指定,就使用这个网络
    2. $ docker run -it --network=bridge busybox

查看网桥的信息,,可以看到有哪些容器挂在这个网桥下面

$ docker network inspect bridge

说明一下,host 网络:
直接使用 Docker host 的网络最大的好处就是性能,如果容器对网络传输效率有较高要求,则可以选择 host 网络。当然不便之处就是牺牲一些灵活性,比如要考虑端口冲突问题,Docker host 上已经使用的端口就不能再用了。

2. 自定义网络

2.1 跨主机网络

上面几种网络都是自带的,同时我们也可以自定义网络(通常是网桥)。

# 指定driver=bridge 
docker network create --driver bridge my_net1  


# 如果不指定网络段,会自己生成 172.x.0.1/16 ,x 从17开始,每创建一个递增。 docker network create --driver bridge --subnet 192.168.7.0/24 --gatewat 192.168.7.1 my_net2
  1. 同一网络(网桥)下的容器,可以通信
  2. 不同网络(网桥)下的容器,docker 默认不允许通信(当然可以通过路由转发来实现),可以用命令将容器挂到另一网桥上

特别说明:

不指定subnet创建的网络,在创建容器时无法指定ip创建,会报如下错误

docker: Error response from daemon: user specified IP address is supported only when connecting to networks with user configured subnets.

命令:docker network connect my_net2

上面 7.5.1节 里的三种网络,不管是 none , host, bridge 都是 local 类型的网络,它们仅能在本机上进行通信。

而如果如实现集群通信,这三种基础的local网络是不能满足要求的。这就需要我们使用自定义网络。自定义网络,同样也分为三种(主要是根据 driver 不同):

  • bridge
  • overlay
  • macvlan

2.2 bridge

先来看看 bridge的。

# 创建网络:bridge
docker network create --driver bridge my_net

docker network create --driver bridge --subnet 192.168.7.0/24 --gatewat 192.168.7.1 my_net2


# 该网络没有指定子网,使用的是dhcp分配ip
docker run -it --network my_net1 <image> 


# 该网络创建时有指定子网,所以可以指定ip创建
docker run -it --network my_net2 --ip 192.168.7.10 <image>

2.3 overlay

为支持容器跨主机通信,Docker 提供了 overlay driver,使用户可以创建基于 VxLAN 的 overlay 网络。

Docerk overlay 网络需要一个 key-value 数据库用于保存网络状态信息,包括 Network、Endpoint、IP 等。Consul、Etcd 和 ZooKeeper 都是 Docker 支持的 key-vlaue 软件,我们这里使用 Consul。

注:Consul 模式,不能与 swarm 模式共存,只能选取一个。

先确保,当前集群没有处在swarm模式下,如果有要先清除。

# worker 节点
docker swarm leave

# manager 节点
docker swarm leave --force

# 如果 docker node ls 还有节点存在,就删除
docker node rm [node_name]

然后在管理节点上创建 consul 存放网络数据,这样在一台上创建的网络都能同步到其他机器上。

# 在 docker 管理机器上运行
docker run -d -p 8500:8500 -h consul --name consul progrium/consul -server -bootstrap

然后到其他两台节点上,修改配置,路径为:/etc/systemd/system/docker.service.d/10-machine.conf

# 这里的 eth1 是可以和 192.168.2.55 通信的网卡
--cluster-store=consul://192.168.2.55:8500 --cluster-advertise=eth1:2376

零基础学习 Docker(四)|  关于网络通信

然后重启

systemctl daemon-reload               # 刷新配置,不然修改的配置不会生效
systemctl restart docker.service

在浏览器上输入地址
零基础学习 Docker(四)|  关于网络通信

至此,Consul 安装成功。

然后就可以创建 overlay 网络了,这个网络,在没有配好 Consul 的情况下,或者没有 Swarm 的情况下是无法创建的。所以我们加入了Consul的节点上创建。

我们在 bm-docker-01 上创建

docker network create --driver overlay ov_net1

然后在 bm-docker-02 上也可以看到这个网络,原因是这些数据已经经过 Consul 进行同步了。

这个时候,我们基于这个 overlay 网络分别在两台节点上创建容器。

# bm-docker-01
docker run -itd --name bbox1 --network ov_net1 busybox

# bm-docker-02
docker run -itd --name bbox2 --network ov_net1 busybox

试着ping一下在两台 host 上的网络是否可通
零基础学习 Docker(四)|  关于网络通信

查看一下ip网卡信息。

docker exec bbox2 ip r

零基础学习 Docker(四)|  关于网络通信

会发现使用 overlay 网络会有两张网卡:

  • eth0:负责东西向流量(跨宿主机的容器访问),主要原理是在UDP(负责发送到对端宿主机)包里封装原始的二层以太网包(负责转发到对应的容器中)通过隧道(VxLAN Tunel Endpoint, 也称 VTEP)的进行传输。
  • eth1:负责南北向流量(容器访问公网),主要原理是通过 bridge NAT 出去。

零基础学习 Docker(四)|  关于网络通信

在使用 vxlan 进行隧道传输中,有两个细节:

  • 容器 mac 与 容器ip 映射表:跨主机的两个容器有可能是在一个二层里,也有可能不在。但不管在不在,容器A 都可以通过隧道广播去寻得 容器B 的 mac 地址,这本身没有问题,但问题在于这样的成本会比较大。因此更好的做法是在集群里的各个主机上都维护着同一张 容器mac 与 ip 的映射表(因此需要有一个数据库,比如etcd),这样就可以避免大量 arp 隧道广播。
  • 容器 mac 与主机ip 映射表:上面封装好包含对方容器 mac 的二层包会放到 UDP 的数据里,接下来要把这个包发送到该容器的宿主机上,那如何知道该容器在哪个宿主机上?因此还需要在每个主机上给你说的一张容器 mac 与主机ip 映射表。

使用 overlay 实现的跨主机通信,实际上还有其他的网络实现,上面讲的是 docker 原生的 overlay (基于 vxlan 隧道),除此之外 还有 host-gw (基于路由)等等

目前主流的网络插件之一 flannel ,就同时支持两种模式。

补充阅读:

3. 容器之间通信

  1. 同一网络(网桥)下的容器,可以通信
  2. 不同网络(网桥)下的容器,docker 默认不允许通信(当然可以通过路由转发来实现),可以通过在容器挂一个和另一容器在同一网桥下的网卡
    命令:docker network connect my_net2 <container id>
  3. 共享网络
    两个容器是可以共享一个网络的。共享网卡和配置信息。
    命令:docker run -it --network:container:<container name> busy

检测容器间的通信,都是用 ping ip 来判断的。
在我们不知道其他容器的ip时,其实是可以通过 ping 容器名,前提是这些容器都处于自定义的网络中,必须是自定义的。

如:现有两个容器,bbox1和bbox2,都用的my_net2,在bbox1里可以通过 ping bbox2来与bbox2通信。
但若现有bbox3,是使用bridge的网络,则无法使用这样的方式

4. 容器与外部通信

包括 容器访问外部外部访问容器

  1. 容器访问外部
    是通过 NAT 网络地址转换,来使用host的ip给外部发送数据包。
    这个只要配置下iptales就可以。

  2. 外部访问容器
    容器对外暴露一个端口,这个port和host的port,要映射起来,这个是由docker proxy来做的。docker proxy会时时监控host的端口,若有请求访问这个host port就重定向给容器的port

命令:docker run -it -p 8080:80 httpd

是将 host 的8080映射给httpd容器的80端口。

5. 容器的通信原理

Docker 的网络是基于 namespacecgroup 实现的。

这里来讲讲的 namespace。

在Linux下,我们一般用ip命令创建 Network Namespace,而在Docker的源码中,它并没有用ip命令,而是自己实现了ip命令内的一些功能——是用了Raw Socket发些“奇怪”的数据。

这里,我通过 ip命令的演示来重现这个过程。

## 首先,我们先增加一个网桥lxcbr0,模仿docker0brctl addbr lxcbr0brctl stp lxcbr0 offifconfig lxcbr0 192.168.10.1/24 up #为网桥设置IP地址 ## 接下来,我们要创建一个network namespace - ns1 # 增加一个namesapce 命令为 ns1 (使用ip netns add命令)ip netns add ns1  # 激活namespace中的loopback,即127.0.0.1(使用ip netns exec ns1来操作ns1中的命令)ip netns exec ns1   ip link set dev lo up  ## 然后,我们需要增加一对虚拟网卡 # 增加一个pair虚拟网卡,注意其中的veth类型,其中一个网卡要按进容器中ip link add veth-ns1 type veth peer name lxcbr0.1 # 把 veth-ns1 按到namespace ns1中,这样容器中就会有一个新的网卡了ip link set veth-ns1 netns ns1 # 把容器里的 veth-ns1改名为 eth0 (容器外会冲突,容器内就不会了)ip netns exec ns1  ip link set dev veth-ns1 name eth0  # 为容器中的网卡分配一个IP地址,并激活它ip netns exec ns1 ifconfig eth0 192.168.10.11/24 up  # 上面我们把veth-ns1这个网卡按到了容器中,然后我们要把lxcbr0.1添加上网桥上brctl addif lxcbr0 lxcbr0.1 # 为容器增加一个路由规则,让容器可以访问外面的网络ip netns exec ns1     ip route add default via 192.168.10.1 # 在/etc/netns下创建network namespce名称为ns1的目录,# 然后为这个namespace设置resolv.conf,这样,容器内就可以访问域名了mkdir -p /etc/netns/ns1echo "nameserver 8.8.8.8" > /etc/netns/ns1/resolv.conf

上面基本上就是docker网络的原理了,只不过,

了解了这些后,你甚至可以为正在运行的docker容器增加一个新的网卡:

`ip link add peerA ``type` `veth peer name peerB ``brctl addif docker0 peerA ``ip link ``set` `peerA up ``ip link ``set` `peerB netns {container-pid} ``ip netns ``exec` `{container-pid} ip link ``set` `dev peerB name eth1 ``ip netns ``exec` `{container-pid} ip link ``set` `eth1 up ; ``ip netns ``exec` `{container-pid} ip addr add ${ROUTEABLE_IP} dev eth1 ;`

上面的示例是我们为正在运行的docker容器,增加一个eth1的网卡,并给了一个静态的可被外部访问到的IP地址。

这个需要把外部的“物理网卡”配置成混杂模式,这样这个eth1网卡就会向外通过ARP协议发送自己的Mac地址,然后外部的交换机就会把到这个IP地址的包转到“物理网卡”上,因为是混杂模式,所以eth1就能收到相关的数据,一看,是自己的,那么就收到。这样,Docker容器的网络就和外部通了。

当然,无论是Docker的NAT方式,还是混杂模式都会有性能上的问题,NAT不用说了,存在一个转发的开销,混杂模式呢,网卡上收到的负载都会完全交给所有的虚拟网卡上,于是就算一个网卡上没有数据,但也会被其它网卡上的数据所影响。

这两种方式都不够完美,我们知道,真正解决这种网络问题需要使用VLAN技术,于是Google的同学们为Linux内核实现了一个IPVLAN的驱动,这基本上就是为Docker量身定制的。

参考文章

文章有帮助,请作者喝杯咖啡?

发表评论