前言:你是否学习使用k8s很久很久了可是对于网络这块仍旧似懂非懂呢? 您是否对网上一堆帖子有如下的抱怨:
- 打开多个博客,然后发现有区别么?
- 明显是直译过来的,越看越迷糊
- “因为xxx,所以yyy”,......怎么就从xxx到yyy了?
所以,就想用通俗的语言,用一般人类的思维历程将我学到的分享出来,在学习的过程中我我会将我读到了哪些文档一并贴出来,有理解偏差的还请指正,但是基本理论都是经过反复推敲和验证的,得为自己的言行负责呀~
大纲:
1.概述
1.1Linux的namespace+cgroup
1.2容器的网络
1.3pod的网络
插播1:Docker和容器的关系
2.Docker容器网络详解
2.1 CNM&libnetwork
2.2 单机网络---brige类型的网络
2.3 多机网络---overlay类型的网络
正文:
一,网络概述
1,Linux的namespace+cgroup
namespace和cgroup是Linux 内核的两大特性,namespace的诞生据说就是为了支持容器技术,那么这俩特性到底干了啥呢?
- namespace:linux支持多种类型的namespace,包括Network,IPC,PID, Mount, UTC, User。创建不同类型的namespace就相当于从不同资源维度在主机上作隔离。
- cgroup:为了不让某个进程一家独大,而其他进程饿死,所以它的作用就是控制各进程分配的CPU,Memory,IO等。
- namespace+cgroup也适用进程组,即多进程运行在一个单独的ns中,此时该ns下的进程就可以交互了。
参考:https://coolshell.cn/articles/17010.html
2,容器
容器实际上是结合了namespace 和 cgroup 的一般内核进程,注意,容器就是个进程
所以,当我们使用Docker起一个容器的时候,Docker会为每一个容器创建属于他自己的namespaces,即各个维度资源都专属这个容器了,此时的容器就是一个孤岛,也可以说是一个独立VM就诞生了。当然他不是VM,网上关于二者的区别和优劣有一对资料.
更进一步,也可以将多个容器共享一个namespace,比如如果容器共享的是network 类型的namespace,那么这些容器就可以通过 localhost:[端口号] 来通信了。因为此时的两个容器从网络的角度看,和宿主机上的两个内核进程没啥区别。
在下面的详解部分会有试验来验证这个理论
3,kubernetes的pod
根据前面的描述我们知道,多进程/多容器可以共享namespace,而k8s的pod里就是有多个容器,他的网络实现原理就是先创建一个共享namespace,然后将其他业务容器加入到该namespace中。
k8s会自动以"合适"的方式为他们创建这个共享namespace,这正是"pause"容器的诞生。
pause容器:创建的每一个pod都会随之为其创建一个所谓的"父容器"。其主要由两个功能:
- (主要)负责为pod创建容器共享命名空间,pod中的其他业务容器都将被加入到pause容器的namespace中
- (可选) 负责回收其他容器产生的僵尸进程,此时pause容器可以看做是PID为1的init进程,它是所有其他容器(进程)的父进程。但在k8s1.8及以后,该功能缺省是关闭的(可通过配置开启)
水鬼子:我猜这个功能是利用了共享PID类型的Namespace吧
插播1:Docker和容器的关系
容器:我们前面说了,就是一个具有独立资源空间的Linux进程
Docker:是一个容器引擎,用来创建运行管理容器,即由它负责去和linux 内核打交道,给我们创建出来一个容器来。
容器可以被任何人创建出来,但是现在流行由Docker创建出来的,所以我们总说Docker容器,甚至提到docker就意味着说的是容器TOT
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都有
[root@centos ~]# docker network inspect bridge
{
"Name": "bridge",
"Driver": "bridge",
"IPAM": { //负责给容器分配ip地址
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Containers": {
"9161f717c07ac32f96b1ede19d21a56a63f17fb69a63627f66704f5cec01ca27": {
"Name": "server.1.oeep0sn0121wrvrw3aunmf9ww",
"EndpointID": "5083992493b0a69fedb2adc02fe9c0aa61e59b068e16dd9371ec27e28d7d088c",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",""
},
"fb67b65aa43619779d0d4f9d2005815aea90586f0aba295436431f688239562b": {
"Name": "fervent_ritchie",
"EndpointID": "e402fa0f99f60199c8ba50263173ef3bc14ca75dbb597d2cbcd813dd4f8706f7",
"MacAddress": "02:42:ac:11:00:03",
"IPv4Address": "172.17.0.3/16",
}
},
插播: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将代表容器对外打交道,这里就不展开了,网上资料很多,上面贴出的链接中也有讲解。
==============================未完,正在写====================================================