1.单主机网络
1.1 网络类型
· none网络
none 网络:是指挂在这个网络下的容器除了lo(内部环回网络)之外, 没有其他任何网卡;容器创建时,可以通过 --network=none 指定使用 none 网络;
一般应用于对安全性要求高并且不需要联网的封闭环境下的场景,比如某个容器的唯 用途是生成随机密码,就可以放到 none 网络中避免密码被窃取。
[root@master ~]# docker run -it --network=none busybox
...
Pull complete
/ # ifconfig
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
...
/ #
· host网络
连接到 host 网络的容器共享 Docker host 的网络栈,容器的网络配置与 host 完全一样,最大的优势在于网络性能好,但是缺乏灵活性;在可以通过 --network=host 指定使用 host 网络。
· bridge网络
Docker 安装时会创建一个命名为 docker0的 Linux bridge ,默认的subnet网段是172.17.0.0/16,docker0就是网关所在;如果不指定--network, 新创建的容器默认都会挂到 docker0上;
每运行一个容器,就会在docker0网桥上自动创建一个veth接口,和新建容器的虚拟网卡共同构成一个veth-pair(成对出现的特殊网卡设备),这样就将容器桥接到了docker0上。
注:在桥接模式下, Docker 容器 Internet 的通信,以及不同容器之间的通信,都是通过 iptables 规则控制的;可以通过使用 iptables -vnL -t nat 命令来查看 iptables nat 表内容。
注:docker0只为运行中的容器分配veth接口,如果容器停止运行接口就会被回收;如果容器重新运行,docker0会分配新的接口给该容器
#在没有运行容器的情况下,docker0没有生成新的接口
[root@master ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242b6d3f44e no
virbr0 8000.52540036ad48 yes virbr0-nic
#当运行两个容器之后,docker0会产生两个接口
[root@master ~]# docker run -d httpd:2.4
[root@master ~]# docker run -d httpd
[root@master ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242b6d3f44e no veth256e773
veth3263584
virbr0 8000.52540036ad48 yes virbr0-nic
· user-defined网络
Docker 提供三种 user-defined 网络驱动: bridge、overlay和macvlan;其中overlay和macvlan类型是跨主机网络(在跨主机网络中详解)。
可以使用docker network命令来创建自定义网络,同时可以指定驱动类型、子网和网关,默认使用的子网网段是172.18.0.0/16;如果容器要使用这个网络,就需要在启动时通过 --network 指定,默认情况下是由网桥自动从subnet中分配一个地址,也可以指定一个静态IP(但是地址必须在子网的网段内)
注:①、只有使用 --subnet 创建的网络才能指定静态 IP;
②、同一网络中的容器、网关之间都是可以通信的;默认情况下docker0和net1网络是不通的,可以通过docker connect命令连接到net1下,相当于给docker0下的容器添加一块网卡,网卡连接到net1的子网上。
[root@master ~]# docker network create --driver bridge \
--subnet 10.1.1.0/24 --gateway 10.1.1.1 net1
...
[root@master ~]# docker network inspect net1
...
"Driver": "bridge", #网络驱动类型是bridge
...
"Subnet": "10.1.1.0/24", "Gateway": "10.1.1.1"
[root@master ~]# docker run -it --network=net1 busybox:latest
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:0A:01:01:02
inet addr:10.1.1.2 Bcast:10.1.1.255 Mask:255.255.255.0
...
[root@master ~]# docker run -it --network=net1 --ip=10.1.1.10 busybox
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:0A:01:01:0A
inet addr:10.1.1.10 Bcast:10.1.1.255 Mask:255.255.255.0 ...
1.2 容器间通信
容器之间可通过 IP、Docker DNS Server和 Joined 容器三种方式实现通信;这里所说的容器间通信有个前提:需要通信的容器必须处于同一个network,不同network之间的容器是不互通的。
· IP
两个容器要能通信,必须要有属于同一个网络的网卡(情况同docker0和net1中容器互通)。
· Docker DNS Server
docker daemon 实现了一个内嵌的 DNS server,使容器可以直接通过容器名进行通信,解决了IP地址不够灵活的的不足;注意这里只是修改了容器名,容器里的hostname并没有修改,还是容器ID;由于容器名是唯一的,所以DNS server不能用于容器的负载均衡。
注:只能在自定义bridge网络中使用DNS server,在docker0网桥中不能使用。
· joined容器
joined 容器可以使两个或多个容器共享一个网络栈,共享网卡和配置信息,使 joined 容器之间可以通过 127.0.0.1 (本地环回地址)直接通信;也就是指定某个容器为 joined容器,其他容器共享这个容器的网卡;joined 容器非常适合以下场景:
①、不同容器中的程序需要通过 loopback进行高效快速地通信,比如Web Server和AppServer;
②、需要监控某个容器的网络流量,比如运行在独立容器中的网络监控程序要监控Web Server容器的流量,就可以将Web Server容器设置为joined容器,然后将监控容器连接到 joined容器上。
[root@master ~]# docker run -d --name webserver httpd:2.4
fe56671d8b12855b747726fccd068c6e246f1be854babddc0b7a3129401f70da
#指定webserver这个容器的网络作为busybox的网络
[root@master ~]# docker run -d -it --network=container:webserver busybox
58b951da6cc7bf227826736081a4637f72d504bd60bfe8ac95f8732d1b9ddc82
[root@master ~]# docker exec -it 58b951da6cc7 sh
#通过环回地址就可以访问webserver
/ # wget 127.0.0.1
Connecting to 127.0.0.1 (127.0.0.1:80) index.html 100% |*********| 45 0:00:00 ETA
/ # cat index.html
It works!
1.3 容器与外界通信
· 容器访问外界
容器默认就能访问外网;这里外网指的是容器网络以外的网络环境,并非特指 Internet。当容器通过bridge访问外界时,host的iptables会生成一条NAT转发规则,将容器的IP地址转换成host地址转发出去(转发动作:MASQUERADE)。
容器访问外界(www.baidu.com)流程:
①、busybox 发送 ping 包: 10.1.1.10到 www.baidu.com;
②、net1 收到包发现是发送到外网的(而不是在本网桥内转发的),根据规则交给 NAT 处理;
③、NAT 将源地址换成 enp33的 IP: 192.168.154.10到 www.baidu.com;
④、ping 包从 enp33网卡发送出去,到达 www.baidu.com,从而实现了容器对外网的访问。
注:MASQUERADE 的处理方式是将包的源地址替换成 host 的地址发送出去,即做了一次网络地址转换 (NAT)。
[root@master ~]# iptables -t nat -S
#源地址在这个网段上的、不是发往docker0和net1的数据包,执行MASQUERADE
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 10.1.1.0/24 ! -o br-85b3aba32ee9 -j MASQUERADE
#容器中ping baidu.com,在host网卡上抓取数据可以看到地址已经转换成host网卡地址了
[root@master ~]# tcpdump -i ens33 -n icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
... IP 192.168.154.10 > 220.181.38.148: ICMP echo request...
... IP 220.181.38.148 > 192.168.154.10: ICMP echo reply...
... IP 192.168.154.10 > 220.181.38.148: ICMP echo request...
· 外界访问容器
docker 可将容器对外提供服务的端口映射到 host 的某个端口,外网通过该端口访问容器;当每映射一个端口时,docker就会在host上启动一个docker-proxy进程,用来处理访问容器的流量。
外界访问容器流程:
①、docker-proxy 监听 host 8080 端口;
②、当外界访问 192.168.154.10:8080 时, docker-proxy 转发到容器httpd的 10.1.1.2:80;
③、httpd 容器响应请求并返回结果。
注:-p 参数格式为:
# -p 指定将docker host的端口映射到容器的端口;-P 表示随机映射端口
[root@master ~]# docker run -d --network=net1 -p 8080:80 httpd:2.4
[root@master ~]# curl 192.168.154.10:8080
It works!
[root@master ~]# ps -aef | grep docker-proxy
...
/usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8080 -container-ip 10.1.1.2 -container-port 80
2.跨主机网络
跨主机网络方案包括:①、docker 原生的 overlay和macvlan;②、第三方解决方案:常用的包括 flannel、OpenVSwitch、weave和calico(以下将以flannel为例)。
1.1 容器网络模型
Libnetwork是 docker 容器网络库,最核心的内容是其定义的 容器网络模型CNM(Container Network Model),通过3个组件对容器网络进行抽象,按照该模型开发出的 driver 就能与docker daemon 协同工作,实现容器网络。
· Sanbox
Sandbox 是容器的网络栈,包含容器的 interface 、路由 DNS 设置;Linux Network Namespace是Sandbox 的标准实现;可以包含来自不同 Network的 Endpoint。
· Endpoint
Endpoint 的作用是将 Sandbox 接入 Network;Endpoint 的典型实现是 veth pair,一端挂在Network(网桥)上、一端挂在Sandbox上,相当于网卡的两端;一个Endpoint 只能属于一个网络,也只能属于一个 Sandbox;通过将沙盒增加多个Endpoints来接入到多个网络中。
· Network
Network 包含一组 Endpoint,同一 Network的 Endpoint 可以直接通信;Network 的实现可以是 Linux Bridge、VLAN 等。
1.2 overlay网络
Docker 提供了 overlay driver(docker自带),可以创建基于 VxLAN 的 overlay 网络;Docerk overlay 网络需要 key-value 数据库用于保存网络状态信息,包括 Network、Endpoint和IP等,Consul、Etcd、ZooKeeper 都是 Docker 支待、高可用的分布式数据库软件。
注:VxLAN 全称 Virtual eXtensible Local Area Network,VxLAN 是基于隧道技术实现的,通过将二层数据封装到 UDP报文中来进行传输,提供与VLAN 相同的以太网二层服务,但是拥有更强的扩展性和灵活性。
实现原理:
①、首先需要部署分布式数据库节点(如consul),然后修改集群中docker host上 /etc/systemd/system/multi-user.target.wants/docker.service配置文件, host会自动注册到数据库中;
②、然后在集群中的任意节点上创建overlay网络,创建的overlay的网络信息会存入到consul数据库中,其他host从consul 中读取到新网络的数据;之后该overlay网络的任何变化都会同步到集群中的所有host;
注:docker 会为每个 overlay 网络创建一个独立的 network namespace,所以不同的overlay网络之间是隔离的;连接在同一个overlay网络上的不同容器之间可以直接实现跨主机互通。
③、当运行容器之后,docker 会创建一个 bridge 网络 "docker_gwbridge" , 为所有连接到overlay 网络的容器提供访问外网的能力(通过这个网桥转发到物理网卡做NAT转换);overlay网络会创建一个br0网桥作为当前节点上所有endpoint的网关;除了连接所有的 endpoint,还会连接一个 vxlan 设备,用于与其他 host 建立vxlan tunnel ,通过这个tunnel来实现容器跨主机通信。
注:容器会生成2块网卡,一块挂在docker_gwbridge网桥上用于访问外网;一块挂在overlay上用于容器跨主机通信。
# consul参数项详见:https://blog.csdn.net/zl1zl2zl3/article/details/79622476
[root@master ~]# docker run -d -p 8500:8500 -h consul --name consul \
progrium/consul -server -bootstrap
#定义master节点为consul服务端且为单节点
#修改node1和node2中docker配置文件,将节点注册到数据库中(可通过网页验证)
[root@node2 ~]# vim /etc/systemd/system/multi-user.target.wants/docker.service
...
[Service] ExecStart=...
#--cluster-store 指定consul节点地址及端口
--cluster-store=consul://192.168.154.10:8500 \
#--cluster-advertise 告知consul本节点的连接网卡及端口
--cluster-advertise=ens33:2376
[root@node2 ~]# systemctl daemon-reload
[root@node2 ~]# systemctl restart docker.service
#Centos7默认内核版本3.10不支持vxlan,需要先升级内核版本(Centos8没有这个问题)
#--subnet 用来指定overlay网络的子网,由它给各个容器分配IP地址
[root@node1 ~]# docker network create -d overlay \
--subnet 192.168.10.0/24 ov-net1
d472fbb62d25cf19d8983fdf0936f04616349465d261ca926b495834aaa1730e
#运行一个容器,并连接到overlay网络上
[root@node1 ~]# docker run -it --network ov-net1 --name box1 busybox
/ # ifconfig
#挂在overlay网络上,由overlay的子网分配IP地址
eth0 Link encap:Ethernet HWaddr 02:42:C0:A8:0A:02
inet addr:192.168.10.2 Bcast:192.168.10.255 Mask:255.255.255.0
...
#挂在docker_gwbridge网桥上,由docker_gwbridge的子网分配IP地址
eth1 Link encap:Ethernet HWaddr 02:42:AC:13:00:02
inet addr:172.19.0.2 Bcast:172.19.255.255 Mask:255.255.0.0
...
[root@node1 ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE ...
9836ff4dc48b docker_gwbridge bridge local
d472fbb62d25 ov-net1 overlay global
#在node2上直接运行容器,并连接到overlay网络上,两个容器可以直接互通 [root@node2 ~]# docker run -it --network ov-net1 --name box2 busybox
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:C0:A8:0A:03
inet addr:192.168.10.3 Bcast:192.168.10.255 Mask:255.255.255.0 ...
eth1 Link encap:Ethernet HWaddr 02:42:AC:12:00:02
inet addr:172.18.0.2 Bcast:172.18.255.255 Mask:255.255.0.0 ...
/ # ping 192.168.10.2
PING 192.168.10.2 (192.168.10.2): 56 data bytes
64 bytes from 192.168.10.2: seq=0 ttl=64 time=0.569 ms
64 bytes from 192.168.10.2: seq=1 ttl=64 time=1.616 ms
#将docker的netns链接到host的netns
[root@node1 ~]# ln -s /var/run/docker/netns /var/run/netns
[root@node1 ~]# ip netns list
#查看当前多了一个network namespace
...
1-d472fbb62d (id: 0)
#br0有两个接口,一个是与容器相连的veth-pair;另一个vxlan0 设备
[root@node1 ~]# ip netns exec 1-d472fbb62d brctl show
bridge name bridge id STP enabled interfaces
br0 8000.0e8da6cbb5a7 no veth0
vxlan0
#查看vxlan0设备的具体配置信息可知此 overlay 使用的 VNI (VxLAN ID) 257
[root@node1 ~]# ip netns exec 1-d472fbb62d ip -d link show vxlan0
20: vxlan0@if20:
... vxlan id 257 ...
1.3 flannel网络
flannel是CoreOS开发的容器网络解决方案;为了在各个主机间共享信息,flannel 用etcd(与consul类似的key-value分布式数据库)存放网络配置、已分配的subnet、host的IP等信息;数据包如何在主机间转发是由backend实现的,最常用的有vxlan和 host-gw。
· flannel-vxlan网络原理
①、首先需要部署分布式数据库节点(如etcd),然后创建一个flannel-config.json,里边配置了网络配置、子网信息和backend后端类型;使用etcdctl命令创建一个key-value键值对,key任意指定,value在 flannel-config.json文件中,这个 key-value将作为 flanneld 的一个启动参数;
②、flannel 为加入集群的每个host分配 一个subnet(在flannel-config.json文件中指定),每个subnet都是从一个更大的IP 池中划分的,flannel会在每个主机上运行一个叫 flanneld的 agent,其职责就是从池子中分配 subnet ;
③、容器从此subnet中分配 IP,这些IP可以在host间路由, 容器间无须NAT和portmapping就可以跨主机通信。
数据流分析:
?、数据包从box3发往网关(node1上的docker0网桥);
?、根据 node1上 的路由表,数据包会发给 flannel.1设备,由它将数据包封装成 VxLAN,通过 ens33 网卡发送给 node2;
?、node2的网卡ens33收到数据包后,进行解封装发现数据包目的地址为 10.142.7.2,,根据路由表将数据包发送给flannel.1设备,由flannel.1通过 docker0 发送到box4。
注:flannel 没有创建新的 docker 网络,而是直接使用默认的 bridge 网络(docker0);同主机的容器(或者容器访问外网)通过 docker0 连接,跨主机流量通过 flannel.1设备转发。
· 验证
①、在master节点上安装 etcd数据库,并启用etcd服务;修改etcd配置文件,将master节点作为服务端;创建一个key-value键值对,key任意指定,value在 flannel-config.json文件中,这个 key-value将作为 flanneld 的一个启动参数;
#在master节点上安装 etcd数据库,并启用etcd服务
[root@master ~]# yum install etcd.x86_64 -y
#修改etcd配置文件,将master节点作为服务端
[root@master ~]# vim /etc/etcd/etcd.conf
[Member]
ETCD_LISTEN_CLIENT_URLS="http://192.168.154.10:2379"
[Clustering]
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.154.10:2379"
...
#创建一个flannel-config.json文件作为value值
[root@master ~]# vim flannel-config.json
{ "Network":"10.142.0.0/16","SubnetLen":24,"Backend":{ "Type":"vxlan" }
#在etcd数据库中创建一条key-value
[root@master ~]# etcdctl --endpoints=http://192.168.154.10:2379 \
set /docker/network/config < flannel-config.json
{ "Network":"10.142.0.0/16","SubnetLen":24,"Backend":{ "Type":"vxlan" } }
②、分别在node1和node2上完成配置:
安装flannel工具包,然后为各个host启动 flannel 网络;修改flannel的配置文件;在host上会生成一个flannel网卡,并且添加一条路由条目:目的地址为 flannel 网络 10.142.0.0/16 的数据包都由 flannel.1转发;
[root@node1 ~]# yum install flannel.x86_64 -y
[root@node1 ~]# vim /etc/sysconfig/flanneld
#添加etcd服务器的地址 FLANNEL_ETCD_ENDPOINTS="http://192.168.154.10:2379"
#添加创建的key-value键值对的key的目录,flannel会根据value来为host分配地址池
FLANNEL_ETCD_PREFIX="/docker/network/"
#指定host的用于对外通信的物理网卡
FLANNEL_OPTIONS="--iface=ens33"
#修改配置文件之后重启flanneld服务
[root@node1 ~]# systemctl restart flanneld.service
[root@node1 ~]# ifconfig
...
flannel.1: flags=4163 mtu 1450
inet 10.142.35.0 netmask 255.255.255.255 broadcast 0.0.0.0
[root@node1 ~]# ip route
10.142.0.0/16 dev flannel.1
③、分别在node1和node2上完成配置:
修改 /etc/systemd/system/multi-user.target.wants/docker.service配置文件,保证 --bip 和--mtu参数和flannel设置的环境变量一致(/run/flannel/subnet.env中设定),然后重新加载配置文件并重启服务;Docker会将子网地址配到docker0网桥上,并添加一条路由条目:目的地址为 flannel 网络的子网 10.142.35.0/16 的数据包都由 docker0 转发;
[root@node1 ~]# vim /run/flannel/subnet.env FLANNEL_NETWORK=10.142.0.0/16
FLANNEL_SUBNET=10.142.35.1/24 #对应docker.service文件中的 --bip
FLANNEL_MTU=1450 #对应docker.service文件中的 --mtu
FLANNEL_IPMASQ=false
[root@node1 ~]# vim /etc/systemd/system/multi-user.target.wants/docker.service
[Service]
...
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --bip=10.142.35.1/24 --mtu=1450
[root@node1 ~]# systemctl daemon-reload
[root@node1 ~]# systemctl restart docker
[root@node1 ~]# ip route
default via 192.168.154.2 dev ens33 proto static metric 100
10.142.0.0/16 dev flannel.1
10.142.35.0/24 dev docker0 proto kernel scope link src 10.142.35.1
④、分别在node1和node2上启动容器(不用指定网络,会默认使用docker0网桥),地址是由flannel的子网分配的;
[root@node1 ~]# docker run -it --name box3 busybox
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:0A:8E:23:02
inet addr:10.142.35.2 Bcast:10.142.35.255 Mask:255.255.255.0
...
/ # ping 10.142.7.2 #该地址为node2上启动的容器box4获得的地址
PING 10.142.7.2 (10.142.7.2): 56 data bytes
64 bytes from 10.142.7.2: seq=0 ttl=62 time=1.724 ms
64 bytes from 10.142.7.2: seq=1 ttl=62 time=4.504 ms
#流量从box3 → node1的docker0网桥 → node2上flannel.1设备 → box4
/ # traceroute 10.142.7.2
traceroute to 10.142.7.2 (10.142.7.2), 30 hops max, 46 byte packets
1 10.142.35.1 (10.142.35.1) 0.006 ms 0.022 ms 0.014 ms
2 10.142.7.0 (10.142.7.0) 0.933 ms 3.510 ms 2.745 ms
3 10.142.7.2 (10.142.7.2) 3.462 ms 1.960 ms 1.413 ms
· host-gw网络原理
host-gw 不会封装数据包,而是在主机的路由表中创建到其他主机 subnet 的路由条目,从而实现容器跨主机通信;其他配置都一样,只是在flannel-config.json文件中"Type"要指定为"host-gw";配置完成之后,node1上会增加一条去往node2上子网 10.142.7.0/24 的路由条目,网关是node2的ens33网卡的地址(node1和node2物理网卡在同一网段上或者三层互通)。
所以,相对于VxLAN网络来说,host-gw没有封装和解封装的过程,性能相对更好。