Docker容器网络详解
Docker容器网络详解
从范围上分:
单机网络:none,host, bridge
跨主机网络:overlay,macvlan,flannel等
从生成方式分:
原生网络:none,host, bridge
自定义网络:
使用docker原生实现的驱动自定义的网络:bridge(自定义),overlay,macvlan,
使用第三方驱动实现的自定义网络:flannel等
在学习网络的时候肯定遇到过关于CNM这个概念,所以首先,我们一起学习下CNM&libnetwork
CNM&libnetwork
libnetwork是Docker团队将Docker的网络功能从Docker的核心代码中分离出来形成的一个单独的库,libnetwork通过插件的形式为Docker提供网络功能。基于代码层面再升华一下,可以将docker的网络抽象出一个模型来,就叫CNM(Container Networking Model),该模型包含三大块:
- Sandbox:容器的网络栈,包含interface,路由表,DNS设置等,可以看做就是linux network类型的namespace本身,该有的网络方面的东西都要有,另外还包含一些用于连接各种网络的endpoint
- Endpoint : 用来将sandbox接入到network中。典型的实现是Veth pair技术(Veth pair是Linux固有的,是一个成对的接口,用来做连接用)
- Network : 具体的网络实现,比如是brige,VLAN等,同样它包含了很多endpoint(那一头)
一句话:sandbox代表容器,network代表容器外的网络驱动形成的网络,endpoint连接了二者
另外,CMN还提供了2个可插拔的接口,让用户可以自己实现驱动然后接入该接口,支持驱动有两类:网络驱动和IPAM驱动,看看这俩类驱动干什么的?
-
Network Drivers: 即真正的网络实现,可以为Docker Engine或其他类型的集群网络同时提供多种驱动,但是每一个具体的网络只能实例化一个网络驱动。细分为本地网络驱动和远端网络驱动:
- 本地网络驱动:对应前面说到的原生网络
- 远端网络驱动:对应前面说的自定义网络
- IPAM Drivers — 构建docker网络的时候,每个docker容器如果不手动指定的话是会被分配ip地址的,这个分配的任务就是由该驱动完成的,同样的,Docker Engine还是给我们提供了缺省的实现。
整个的原理模型图如下,参见官网:
参考:https://success.docker.com/article/networking
(一定要好好看看这篇文章,我英文不行看了整整2天,很有收获)
好了,收,开始真正进入docker网络的学习,我们挑2个代表性的网络一起研究下
单机网络---brige类型的网络
原理如下图(摘自https://success.docker.com/article/networking):
接下来听我慢慢道来,我们先按照步骤走一遍,然后再细抠里面的原理
【实操】:在主机上起两个docker容器,使用缺省网络即bridge网络,容器要使用有操作系统的镜像,要不不方便验证
1)进入任一个容器内
sh-4.2# ip addr
13: eth0@if14: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
inet 172.17.0.3/16 scope global eth0
附:容器中可能缺少诸多命令,可以在启动后安装如下工具:
yum install net-tools
yum install iputils
yum install iproute *
2)在宿主机上查看接口信息:
[root@centos network-scripts]# ip addr 4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default inet 172.17.0.1/16 scope global docker0 14: vetha470484@if13: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 16: veth25dfcae@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
3) 在host上查看docker缺省会创建的三个网络
[root@centos ~]# docker network ls NETWORK ID NAME DRIVER SCOPE 451a2ff68c71 bridge bridge local 7bd661f0c17f host host local 6c9bb2d42d95 none null local
4)再看下支撑网络背后的驱动,即这个叫“bridge”的bridge类型网络使用的驱动:一个名叫docker0的bridge(网桥)。网桥上挂两个interface:veth25dfcae, vetha470484
[root@centos network-scripts]# brctl show bridge name bridge id STP enabled interfaces docker0 8000.0242dee689ea no veth25dfcae vetha470484
【解析】:
看容器(Sandbox), 接口的number是13的那个,他名字是eth0, 然后他@if14,这个就是endpoint,那么这个if14是谁?
看主机,有个网桥叫做docker0,有两个interface 他们的master是docker0,并且这两个interface的number分别是14,16,并且分别@if13和@if15,是的,if13正是容器中的接口,同理if14也是另一个容器中的接口,也就是说在host上的veth接口(NO.14)和容器中的eth接口(NO.13)正是一对veth pair,至此Endpoint作为容器和nework的连接的任务达成了。而docker0正是名叫bridge的Network的驱动。
最后,看一下路由吧
容器1:
sh-4.2# ip route default via 172.17.0.1 dev eth0 172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.3
表示:目的是172.17的流量交给eth0出去,然后交给网关172.17.0.1,也就是docker0
宿主机:
[root@centos ~]# ip route default via 192.168.12.2 dev ens33 proto dhcp metric 100 172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 192.168.12.0/24 dev ens33 proto kernel scope link src 192.168.12.132 metric 100
表示:目的是172.17的流量从docker0出去,
缺省的交给ens33接口给网关192.168.12.2(因为我的宿主机是个虚拟机,所以还是个小网ip),也就是说如果访问的是同网段(如加入同一网络的其他容器)则交给网桥docker0内部转发,否则走向世界
另外:详细的可以 看一下bridge网络,可以网络中有两个容器,ip,mac都有
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
插播:bridge的原理
在容器技术中,bridge扮演了一个非常重要的角色,懂得bridge的原理可以很好的定位网络问题,这里就不展开讨论,在我的[爬坑]系列有讲述,你只需要记住:不要把bridge想复杂,bridge是一个桥作为master,可以往桥上挂很多类型及个数的interface接口,当桥上有一个接口接收到数据后,只要不是给桥所在的宿主机本身,则桥会内部转发,数据会从其余接口同步冒出来
【验证】:
1,容器连接外网--OK
sh-4.2# ping www.baidu.com PING www.a.shifen.com (115.239.210.27) 56(84) bytes of data. 64 bytes from 115.239.210.27 (115.239.210.27): icmp_seq=1 ttl=127 time=5.38 ms
注:这里面要说明一下,从容器发外网的egress流量之所以能顺利得到应答,是因为它在出去的时候经过了iptables的NAT表,就是这里:
# iptables -t nat -L ... Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all -- 172.17.0.0/16 anywhere MASQUERADE all -- 172.18.0.0/16 anywhere
这里如果对iptables的原理不是很清楚的,推荐这位大牛的博客:
http://www.zsythink.net/archives/1199/
2,容器连接另一个容器--OK
sh-4.2# ping 172.17.0.2 -c 2 PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data. 64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=95.9 ms
3,容器如果想要被外网可以访问的到,需要在起容器的时候指定--publish/-p,即在host上找一个port与容器映射,这个host将代表容器对外打交道,这里就不展开了,网上资料很多,上面贴出的链接中也有讲解。
单机网络---overlay类型的网络
上一个章节我们没有细说容器和linux的namespace之间的关系,因为在overlay类型的网络中,大量使用了linux的namespace技术,所以我们再展开讲。
overlay这个词不是docker家的,一直以来就有overlay类型的网络,意/译为"覆盖"型网络,其中VxLan技术就可以认为是一种overlay类型网络的实现,而在docker的overlay网络,使用的正是Vxlan技术。所以我们再跑偏一下,讲讲VxLAN
插播3:VxLAN
Virtual Extensible LAN,虚拟化可扩展局域网,基于IP网络,采用 MAC in UDP封装形式的二层VPN技术,一个简单拓扑和报文结构图来感性认识下
几个关键名词:
VNI:VXLAN Network Identifier,即虚拟出来的网络id
VTEP:VxLAN隧道的EndPoint,代表隧道的始端或者终端实体。负责对VxLAN报文进行封装/解封装,包括ARP请求报文和数据报文。VTEP可由硬件设备或软件去实现。
结合报文格式解读一下:学过TCP/IP的我们都知道,协议栈可以分成5层,从最上层的应用出来的数据会经过协议栈一层层封装:应用层--->传输层TCP/UDP头--->三层(网络层)IP头--->二层(链路层)MAC头-->物理层,从物理网卡出去。
VxLAN是其在封装到二层的时候,由VTEP进行再一次的封装叫做VxLAN封装,即又增加了UDP头->IP头->MAC头,所以叫"MAC in UDP"。这就非常适用于容器的应用场景,一方面满足了网络隔离的要求(比如多租户等),另一方面VNI是个24-bit的值,即支持2^24个vxlan网络,远高于vlan。这样的好处是用一张图就能看出来:
水鬼子:可以暂时这么理解,VTEP就是宿主机上的一个组件。它的ip就是宿主机的ip,负责和其他宿主机连通形成隧道。这样宿主机上的VM就可以利用该隧道和其他host上的VM沟通了,然后还并不感知该隧道的存在。也许这么说并不严谨,但是有助于对overlay网络的理解。
另外,为了学习vxlan网络以及容器对vxlan网络的应用,我做了大量的实验,遇到了很多很多的坑,需要的可以参考:.[爬坑系列]之VXLan网络实现
oK,再“收”,回到docker 网络
前情提要:
我们也可以直接使用docker 提供的overlay驱动创建overlay网络,然后创建容器加入到该网络,但如果是Docker Engine 1.12之前,还需要一个k-v类型的存储介质。Docker Engine 1.12之后的由于集成了一个叫做“网络控制平面(control plane)”的功能,则不需要额外的存储介质了。在这里为了更好的理解,我们选择前者,大致的步骤如下:
【操作】:
1,安装启动etcd,安装方法
2,在一个host上创建overlay类型的网络,并创建容器使用该网络
3,在另一个容器上也可以看到该网络,然后也加入这个网络
root@master ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
731d1b63b387 ov_net2 overlay global
分别在两个节点上创建两个docker 容器
master:
docker run -ti -d --network=ov_net2 --name=centos21 centos:wxy /bin/sh
minion:
docker run -d --name nginx --network=ov_net2 nginx22
注:更详细的步骤网上很多,另外想要看我自己搭建的过程中遇到的坑等,请参见【爬坑系列】之docker的overlay网络配置
这里我只想展示overlay网络的深层实现,包括和namespace,vxlan的关系等
【解析】:
1)看一下namespace以及配置
ln -s /var/run/docker/netns/ /var/run/netns
//一个容器创建完了,就多了两个namespace
[root@master ~]# ip netns
a740da7c2043 (id: 9)
1-731d1b63b3 (id: 8)
第一个namespace:其实就是docker容器本身,它有两个接口,都是veth pair类型,对应的兄弟接口分别位于另一个namespace和系统namespace
root@master ~]# ip netns exec a740da7c2043 ip addr 44: eth0@if45: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default link/ether 02:42:0a:00:01:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.0.1.2/24 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::42:aff:fe00:102/64 scope link valid_lft forever preferred_lft forever 46: eth1@if47: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:12:00:04 brd ff:ff:ff:ff:ff:ff link-netnsid 1 inet 172.18.0.4/16 scope global eth1 valid_lft forever preferred_lft forever inet6 fe80::42:acff:fe12:4/64 scope link valid_lft forever preferred_lft forever
第二个namespace:他的作用是专门用来做vxlan转发的,核心是一个bro桥,桥上有两个接口,一个与容器(第一个ns )相连,另一个vxlan1接口充当 vxlan网络的vtep
[root@master ~]# ip netns exec 1-731d1b63b3 ip addr 2: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default link/ether 12:54:57:62:92:74 brd ff:ff:ff:ff:ff:ff inet 10.0.1.1/24 scope global br0 valid_lft forever preferred_lft forever inet6 fe80::842a:a1ff:fec8:da3b/64 scope link valid_lft forever preferred_lft forever 43: vxlan1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master br0 state UNKNOWN group default link/ether 12:54:57:62:92:74 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet6 fe80::1054:57ff:fe62:9274/64 scope link valid_lft forever preferred_lft forever 45: veth2@if44: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master br0 state UP group default link/ether ae:a1:58:8c:c2:0a brd ff:ff:ff:ff:ff:ff link-netnsid 1 inet6 fe80::aca1:58ff:fe8c:c20a/64 scope link valid_lft forever preferred_lft forever
结论:docker的overlay网络 = 2namespace + vxlan
kubernetes之pod网络详解
一,kubernetes网络概述
我们说的k8s网络就是集群网络,因为k8s是用于集群的,单机网络没什么意义。一个集群的网络,主要涉及一下4个方面的通信
1,pod中的容器之间的通信:
这个在前面的章节我们已经说了,pod中容器都是共享网络空间的,所以就如一台机器上的应用一般,使用localhost:port就可以通信,也当然需要容器们自己保证大家别端口冲突了。
2,pod-to-pod的通信
首先k8s给每一个pod都分配ip了,具体他们之间怎么通信取决于具体的网络实施,这个不是k8s做的,但是k8s做了要求,也叫网络模型,具体要求如下:
1)一个节点上的pod可以和所有节点上的所有pod通信,且不需要NAT
2)节点上的agent(比如系统daemons:kubelet)可以和该节点上的所有pod通信
3)如果pod是运行与host的网络中,则要求pod同样可以和节点上的其他pod通信
3,pod和service的通信,这个详见service的定义
4,外网和serice的通信,同样相近service的定义
注:3,4的实现就是service的实现,建议仔仔细细研究官方文档就可以搞清楚了,有时间我会将这部分也整理下分享出来。那部分总结一句话就是:iptables的运用
另外,在进行k8s网络验证之前,需要进行k8s本地环境安装,为了更好的研究我走了一条非常复杂的安装之路:本地编译源码生成二进制,在打包镜像,最后通过三种途径安装了master节点和node节点:yum自动安装,二进制安装,镜像安装,想起来都是泪,想参考的见【爬坑系列】之kubernetes环境搭建:二进制安装与镜像安装
二,详解pod内的通信
1,一个例子模拟pod的通信
模拟k8的行为,首先创建pause容器,然后创建centos容器和nginx容器都加入到pause容器的网络中,然后在centos容器中访问localhost:80,于是访问到了nginx容器。具体如下:
docker run -d --name pause docker.io/ist0ne/pause-amd64
docker run -d --name nginx --net=container:pause --ipc=container:pause --pid=container:pause nginx
docker run -d -ti --name centos --net=container:pause --ipc=container:pause --pid=container:pause centos /bin/sh
sh-4.2# curl localhost:80 ---访问nginx
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
2,实际的k8s的:
创建一个有俩容器的pod,一个是centos操作系统,一个是nginx,在centos中访问localhost:80可以访问到nginx服务
//创建pod的yaml文件,该pod中有两个容器
# vi centos-nginx.yaml apiVersion: v1 kind: Pod metadata: name: myapp-2c labels: app: centos spec: containers: - name: centos-container image: centos:wxy command: ["/bin/sh"] args: ["-c", "while true; do echo hello; sleep 10; done"] - name: nginx-container image: nginx
//登录pod默认登录到centos-container这个容器,刚好
# kubectl exec -ti myapp-2c /bin/sh Defaulting container name to centos-container. Use 'kubectl describe pod/myapp-2c -n default' to see all of the containers in this pod. sh-4.2# curl localhost:80 <!DOCTYPE html> ... <h1>Welcome to nginx!</h1>
//看看容器的情况:一个pause容器领着领养俩业务容器
# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES edf722104aa6 nginx "nginx -g 'daemon ..." 27 minutes ago Up 27 minutes k8s_nginx-container.570a1191_myapp-2c_default_0461d9cd-8370-11e9-a770-5254006bdf04_702066eb 6e5e2c96bd90 centos:wxy "/bin/sh -c 'while..." 27 minutes ago Up 27 minutes k8s_centos-container.e8c7257b_myapp-2c_default_0461d9cd-8370-11e9-a770-5254006bdf04_d950aeda 044e4c09f0e9 docker.io/ist0ne/pause-amd64 "/pause" 27 minutes ago Up 27 minutes k8s_POD.68b7dd1e_myapp-2c_default_0461d9cd-8370-11e9-a770-5254006bdf04_00db96a2
二:详解pod之间的通信
这个是k8s集群网络的核心,前面说了,取决于具体的网络实施。
水鬼子:如果你刚接触k8s,不知道你是否会对这里感到迷惑,第三方实现是什么鬼?pod之间的通信不是k8s自己做的么?难道要额外安装啥软件?
是的需要额外安装,在这里我们就以etcd + flunnel为例来讲解。
概述:
集群中的各个node,首先一定是连通的,然后基于底层网络之上的flannel其实就是一个overlay网络。说到overlay,如果你看了前面Docker网络的章节,一定大概知道是怎么回事了。flannel网络有多种实现方式,目前支持的有:udp(默认),host-gw,VxLAN(是不是很熟悉?)。实际上pod之间的网络要比docker的还简单。
原理详解:
1,为什么需要etcd
etcd是一个key-value形式的存储系统,可以往里写各种类型的数据。我们需要做的就是首先约定好一个key,以这个key向etcd中写入网络配置参数;然后在安装flannel的时候配置FLANNEL_ETCD_PREFIX参数为此key。
k8s集群中的每个节点上都有安装flanneld作为网络代理,它通过读取etcd知道当前的网络配置信息,为pod分配ip后,再将当前的网络状况比如本节点分得的subnet和对应的public ip等信息再写入etcd中。这种网络管理模式就是所谓的network planes,用我的话说,大家也没什么上下级关系,都是处在同一平级,所以叫"网络平面"。
2,网络拓扑和转发原理
所谓flannel的安装,最主要是在每个节点上安装flanneld,这个"代理"一样的角色会做什么呢?
1)首先去etcd那里读取配置。根据配置为自己分配一个subnet,并将其与public ip(在安装flannel时指定的ip,如果没有指定则缺省是host上的eth0的地址)的对应关系再存入etcd中,这样其他节点上的flannel也就共享这些信息了。
2)创建虚拟网卡flannel0并分配一个该子网段ip,还会添加一条路由:所有到其他pod的数据都从flanel0出去;
3))将网段信息写入/run/flannel/subnet.env中,这是一个用于刷新Docker环境变量相关的的文件:/run/flannel/docker。所以这时候重启一下Docker,docker0就有了一个该子网段的ip。
[坑]这里要注意,我不知道别的版本,反正我目前安装的版本,如果flannel重启并重新给自己分配了subnet,那么此时一定记得重启Docker,否则docker0的ip和flannel0的ip不一致,进而导致数据无法正确转发。
最后,数据如何转发的呢?数据从容器出来,经过veth pair到达网桥docker0,根据路由再转发给flannel0,进而数据由flannel的backend(udp或者host-gw或者vxlan)转发给目的pod所在的backend上,最后由目的host上的backend上送至目的pod的容器中。具体如下:
//etcd中存放着flannel网络配置信息:地址空间,缺省backend类型
#etcdctl get /k8s/network/config
{"Network": "10.0.0.0/16"}
//flannel启动后,它会偷偷改变Docker的环境变量
#vi /usr/lib/systemd/system/flanneld.service
...
ExecStartPost=/usr/libexec/flannel/mk-docker-opts.sh -k DOCKER_NETWORK_OPTIONS -d /run/flannel/docker
...
//于是docker在启动的时候就有了约束,其中bip决定了网桥docker0的IP
# systemctl status docker.service
...
/usr/bin/dockerd-current --add-runtime docker-runc=/usr/libexec/docker/docker-runc-current ... --bip=10.0.42.1/24 --ip-masq=true --mtu=1472
...
//集群中两个节点,分别给自己分配了子网,也都存在了etcd中,包括到达子网络的public ip
# etcdctl ls /k8s/network/subnets /k8s/network/subnets/10.0.58.0-24 /k8s/network/subnets/10.0.42.0-24 # etcdctl get /k8s/network/subnets/10.0.42.0-24 {"PublicIP":"188.x.x.113"}
//veth pair把数据容器里导出来到达网桥docker0,再经过路由表到达flannel0,最后交给backend
# ip addr
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1472 qdisc noqueue state UP group default
link/ether 02:42:ad:d6:96:b7 brd ff:ff:ff:ff:ff:ff
inet 10.0.42.1/24 scope global docker0
valid_lft forever preferred_lft forever
...
36: flannel0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1472 qdisc pfifo_fast state UNKNOWN group default qlen 500
link/none
inet 10.0.42.0/16 scope global flannel0
valid_lft forever preferred_lft forever
40: veth9b5a76f@if48: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1472 qdisc noqueue master docker0 state UP group default
link/ether da:e4:3e:d3:cb:f9 brd ff:ff:ff:ff:ff:ff link-netnsid 3
# ip route
default via 172.21.0.1 dev eth0
10.0.0.0/16 dev flannel0 proto kernel scope link src 10.0.42.0
10.0.42.0/24 dev docker0 proto kernel scope link src 10.0.42.1
3,详解flannel的三种backend
参考博客:
http://www.360doc.com/content/16/0629/16/17572791_571683078.shtml
我觉得他讲的很明了了
实操
1,创建l两个pod,为了让他们能够分别调度到两个节点上,我还为node打了标签,并为pod配置了nodeSelector,例如
# cat centos.yaml apiVersion: v1 kind: Pod metadata: name: myapp-centos labels: app: centos spec: containers: - name: nginx-container image: centos:wxy command: ["/bin/sh"] args: ["-c", "while true; do echo hello; sleep 10; done"] nodeSelector: node: master
[root@master ~]# kubectl get pods NAME READY STATUS RESTARTS AGE myapp-centos 1/1 Running 0 16h myapp-nginx 1/1 Running 0 83s
2,验证连通性:在一个pod里访问另一个pod
1)看一下nginx的ip
# kubectl get pods myapp-nginx -oyaml |grep podIP
podIP: 10.0.58.3
2)在centos中访问一下nginx
[root@master ~]# kubectl exec -ti myapp-centos /bin/sh
sh-4.2# curl http://10.0.58.3:80
...
<h1>Welcome to nginx!</h1>