目前不管是我的仍是企业,在使用k8s时,都会采用CNI做为集群网络方案实现的规范。node
在早先的k8s版本中,kubelet代码里提供了networkPlugin,networkPlugin是一组接口,实现了pod的网络配置、解除、获取,当时kubelet的代码中有个一个docker_manager,负责容器的建立和销毁,亦会负责容器网络的操做。而现在咱们能够看到基本上kubelet的启动参数中,networkPlugin的值都会设置为cni。linux
使用CNI插件时,须要作三个配置:git
全部的cni组件都支持两个命令:add和del。即配置网络和解除网络配置。github
cni插件的配置文件是一个json文件,不一样版本的接口、以及不一样的cni组件,有着不一样的配置内容结构,目前比较通用的接口版本是0.3.1的版本。docker
在配置文件中咱们能够填入多个cni组件,当这些cni组件的配置以数组形式记录时,kubelet会对全部的组件进行按序链式调用,全部组件调用成功后,视为网络配置完成,过程当中任何一步出现error,都会进行回滚的del操做。以保证操做流上的原子性。数据库
cni插件按照代码中的存放目录能够分为三种:ipam、main、meta。json
因为官方提供的cni组件就有不少,这里咱们详细介绍一些使用率较高的组件。后端
ipam类型的cni插件,在执行add命令时会分配一个IP给调用者。执行del命令时会将调用者指定的ip放回ip池。社区开源的ipam有host-local、dhcp。api
咱们能够经过host-local的配置文件的数据结构来搞懂这个组件是如何管理ip的。数组
type IPAMConfig struct { *Range Name string Type string `json:"type"` Routes []*types.Route `json:"routes"`//交付的ip对应的路由 DataDir string `json:"dataDir"`//本地ip池的数据库目录 ResolvConf string `json:"resolvConf"`//交付的ip对应的dns Ranges []RangeSet `json:"ranges"`//交付的ip所属的网段,网关信息 IPArgs []net.IP `json:"-"` // Requested IPs from CNI_ARGS and args } #配置文件范例: { "cniVersion": "0.3.1", "name": "mynet", "type": "ipvlan", "master": "foo0", "ipam": { "type": "host-local", "resolvConf": "/home/here.resolv", "dataDir": "/home/cni/network", "ranges": [ [ { "subnet": "10.1.2.0/24", "rangeStart": "10.1.2.9", "rangeEnd": "10.1.2.20", "gateway": "10.1.2.30" }, { "subnet": "10.1.4.0/24" } ], [{ "subnet": "11.1.2.0/24", "rangeStart": "11.1.2.9", "rangeEnd": "11.1.2.20", "gateway": "11.1.2.30" }] ] } }
从上面的配置咱们能够清楚:
host-local的应用范围比较广,kubenet、bridge、ptp、ipvlan等cni network插件都被用来和host-local配合进行ip管理。
社区的cni组件中就包含了dhcp这个ipam,但并无提供一个能够参考的案例,翻看了相关的源码,大体逻辑是:
main类型的cni组件作的都是一些核心功能,好比配置网桥、配置各类虚拟化的网络接口(veth、macvlan、ipvlan等)。这里咱们着重讲使用率较高的bridge和ptp。
brige模式,即网桥模式。在node上建立一个linux bridge,并经过vethpair的方式在容器中设置网卡和IP。只要为容器配置一个二层可达的网关:好比给网桥配置IP,并设置为容器ip的网关。容器的网络就能创建起来。
以下是bridge的配置项数据结构:
type NetConf struct { types.NetConf BrName string `json:"bridge"` //网桥名 IsGW bool `json:"isGateway"` //是否将网桥配置为网关 IsDefaultGW bool `json:"isDefaultGateway"` // ForceAddress bool `json:"forceAddress"`//若是网桥已存在且已配置了其余IP,经过此参数决定是否将其余ip除去 IPMasq bool `json:"ipMasq"`//若是true,配置私有网段到外部网段的masquerade规则 MTU int `json:"mtu"` HairpinMode bool `json:"hairpinMode"` PromiscMode bool `json:"promiscMode"` }
咱们关注其中的一部分字段,结合代码能够大体整理出bridge组件的工做内容。首先是ADD命令:
其次是DEL命令:
ptp实际上是bridge的简化版。可是它作的网络配置其实看上去却是更复杂了点。而且有一些配置在自测过程当中发现并无太大用处。它只建立vethpair,可是会同时给容器端和node端都配置一个ip。容器端配置的是容器IP,node端配置的是容器IP的网关(/32),同时,容器里作了一些特殊配置的路由,以知足让容器发出的arp请求能被vethpair的node端响应。实现内外的二层连通。
ptp的网络配置步骤以下:
进入容器的网络namespace,配置容器端的网卡,修改网卡名,配置IP,并配置一些路由。假如容器ip是10.18.192.37/20,所属网段是10.18.192.0/20,网关是10.18.192.1,咱们这里将进行这样的配置:
10.18.192.0/20 dev eth0 scope link
,咱们将它删掉:ip r d ****
ip r a 10.18.192.0/20 via 10.18.192.1 dev eth0
10.18.192.1/32 dev eth0 scope link
与bridge不一样主要的不一样是:ptp不使用网桥,而是直接使用vethpair+路由配置,这个地方其实有不少其余的路由配置能够选择,同样能够实现网络的连通性,ptp配置的方式只是其中之一。万变不离其宗的是:
只要容器内网卡发出的arp请求,能被node回复或被node转发并由更上层的设备回复,造成一个二层网络,容器里的数据报文就能被发往node上;而后经过node上的路由,进行三层转发,将数据报文发到正确的地方,就能够实现网络的互联。
bridge和ptp实际上是用了不一样方式实现了这个原则中的“二层网络”:
ptp模式的路由还存在一个问题:没有配置default路由,所以容器不能访问外部网络,要实现也很简单,以上面的例子,在容器里增长一条路由:default via 10.18.192.1 dev eth0
相比前面两种cni main组件,host-device显得十分简单由于他就只会作两件事情:
细心的你确定会注意到,在bridge和ptp组件中,就已经有“将vethpair的一端移入到容器的网络namespace”的操做。那这个host-device不是画蛇添足吗?
并非。host-device组件有其特定的使用场景。假设集群中的每一个node上有多个网卡,其中一个网卡配置了node的IP。而其余网卡都是属于一个网络的,能够用来作容器的网络,咱们只须要使用host-device,将其余网卡中的某一个丢到容器里面就行。
host-device模式的使用场景并很少。它的好处是:bridge、ptp等方案中,node上全部容器的网络报文都是经过node上的一块网卡出入的,host-device方案中每一个容器独占一个网卡,网络流量不会通过node的网络协议栈,隔离性更强。缺点是:在node上配置数十个网卡,可能并很差管理;另外因为不通过node上的协议栈,因此kube-proxy直接废掉。k8s集群内的负载均衡只能另寻他法了。
有关macvlan的实践能够参考这篇文章。这里作一个简单的介绍:macvlan是linux kernal的特性,用于给一个物理网络接口(parent)配置虚拟化接口,虚拟化接口与parent网络接口拥有不一样的mac地址,但parent接口上收到发给其对应的虚拟化接口的mac的包时,会分发给对应的虚拟化接口,有点像是将虚拟化接口和parent接口进行了'桥接'。给虚拟化网络接口配置了IP和路由后就能互相访问。
macvlan省去了linux bridge,可是配置macvlan后,容器不能访问parent接口的IP。
ipvlan与macvlan有点相似,但对于内核要求更高(3.19),ipvlan也会从一个网络接口建立出多个虚拟网络接口,但他们的mac地址是同样的, 只是IP不同。经过路由能够实现不一样虚拟网络接口之间的互联。
使用ipvlan也不须要linux bridge,但容器同样不能访问parent接口的IP。
关于ipvlan的内容能够参考这篇文章
关于macvlan和ipvlan,还能够参考这篇文章
meta组件一般进行一些额外的网络配置(tuning),或者二次调用(flannel)。
用于进行内核网络参数的配置。并将调用者的数据和配置后的内核参数返回给调用者。
有时候咱们须要配置一些虚拟网络接口的内核参数,好比:网易云在早期经典网络方案中曾修改vethpair的proxy_arp参数(后面会介绍)。能够经过这个组件进行配置。
另一些可能会改动的网络参数好比:
能够在这里查看可配置的网络参数和释义。
用于在node上配置iptables规则,进行SNAT,DNAT和端口转发。
portmap组件一般在main组件执行完毕后执行,由于它的执行参数仰赖以前的组件提供
cni plugins中的flannel是开源网络方案flannel的“调用器”。这也是flannel网络方案适配CNI架构的一个产物。为了便于区分,如下咱们称cni plugins中的flannel 为flanenl cni
。
咱们知道flannel是一个容器的网络方案,一般使用flannel时,node上会运行一个daemon进程:flanneld,这个进程会返回该node上的flannel网络、subnet,MTU等信息。并保存到本地文件中。
若是对flannel网络方案有必定的了解,会知道他在作网络接口配置时,其实干的事情和bridge组件差很少。只不过flannel网络下的bridge会跟flannel0网卡互联,而flannel0网卡上的数据会被封包(udp、vxlan下)或直接转发(host-gw)。
而flannel cni
作的事情就是:
flannel cni
会从本地文件中读取到flanneld的配置。而后根据命令的参数和文件的配置,生成一个新的cni配置文件(保存在本地,文件名包含容器id以做区分)。新的cni配置文件中会使用其余cni组件,并注入相关的配置信息。以后,flannel cni
根据这个新的cni配置文件执行ADD命令。flannel cni
从本地根据容器id找到以前建立的cni配置文件,根据该配置文件执行DEL命令。也就是说flannel cni
此处是一个flannel网络模型的委托者,falnnel网络模型委托它去调用其余cni组件,进行网络配置。一般调用的是bridge和host-local。
上述全部的cni组件,能完成的事情就是创建容器到虚拟机上的网络。而要实现跨虚拟机的容器之间的网络,有几种可能的办法:
了解经常使用的网络方案前,咱们先了解一下kubenet,kubenet实际上是k8s代码中内置的一个cni组件。若是咱们要使用kubenet,就得在kubelet的启动参数中指定networkPlugin
值为kubenet
而不是cni
。
若是你阅读了kubernetes的源码,你就能够在一个名为kubenet_linux.go的文件中看到kubenet作了什么事情:
设计上其实挺蠢萌的。其实是为了省事。咱们能够看下自生成的配置文件:
{ "cniVersion": "0.1.0", "name": "kubenet", "type": "bridge", "bridge": "%s", //一般这里默认是“cbr0” "mtu": %d, //kubelet的启动参数中能够配置,默认使用机器上的最小mtu "addIf": "%s", //配置到容器中的网卡名字 "isGateway": true, "ipMasq": false, "hairpinMode": %t, "ipam": { "type": "host-local", "subnet": "%s", //node上容器ip所属子网,一般是kubelet的pod-cidr参数指定 "gateway": "%s", //经过subnet能够肯定gateway "routes": [ { "dst": "0.0.0.0/0" } ] } }
配置文件中明确了要使用的其余cni组件:bridge、host-local(这里代码中还会调用lo组件,一般lo组件会被k8s代码直接调用,因此不须要写到cni配置文件中)。以后的事情就是执行二进制而已。
为何咱们要学习kubenet?由于kubenet可让用户以最简单的成本(配置networkPlugin和pod-cidr两个启动kubelet启动参数),配置出一个简单的、虚拟机本地的容器网络。结合上面提到的几种“跨虚拟机的容器之间的网络方案”,就是一个完整的k8s集群网络方案了。
一般kubenet不适合用于overlay网络方案,由于overlay网络方案定制化要求会比较高。
许多企业使用vpc网络时,使用自定义路由实现不一样pod-cidr之间的路由,他们的网络方案里就会用到kubenet,好比azure AKS(基础网络)。
关于flannel,上面的文章也提到了一下。网上flannel的文章也是一搜一大把。这里简单介绍下flannel对k8s的支持,以及通用的几个flannel backend(后端网络配置方案)。
flannel在对kubernets进行支持时,flanneld启动参数中会增长--kube-subnet-mgr
参数,flanneld会初始化一个kubernetes client,获取本地node的pod-cidr,这个pod-cidr将会做为flannel为node本地容器规划的ip网段。记录到/run/flannel/subnet.env。(flannel_cni组件会读取这个文件并写入到net-conf.json中,供cni使用)。
flannel的overlay方案。每一个node节点上都有一个flanneld进程,和flannel0网桥,容器网络会与flannel0网桥互联,并经由flannel0发出,因此flanneld能够捕获到容器发出的报文,进行封装。udp方案下会给报文包装一个udp的头部,vxlan下会给报文包装一个vxlan协议的头部(配置了相同VNI的node,就能进行互联)。 目前flannel社区还提供了更多实验性的封装协议选择,好比ipip,但仍旧将vxlan做为默认的backend。
flannel的三层路由方案。每一个node节点上都会记录其余节点容器ip段的路由,经过路由,node A上的容器发给node B上的容器的数据,就能在node A上进行转发。
相似kubenet,只分配子网,不作其余任何事情。
flannel支持了aliVPC、gce、aws等云厂商的vpc网络。原理都是同样的,就是当flanneld在某云厂商的机器上运行时,根据机器自身的vpc网络IP,和flanneld分配在该机器上的subnet,调用云厂商的api建立对应的自定义路由。
calico是基于BGP路由实现的容器集群网络方案,对于使用者来讲,基础的calico使用体验可能和flannel host-gw是基本同样的:node节点上作好对容器arp的响应。而后经过node上的路由将容器发出的包转发到对端容器所在node的IP。对端节点上再将包转发给对端容器。
ipip模式则如同flannel ipip模式。对报文封装一个ipip头部,头部中使用node ip。发送到对端容器所在node的IP,对端的网络组件再解包,并转发给容器。
不一样之处在于flannel方案下路由都是经过代码逻辑进行配置。而calico则在每一个节点创建bgp peer,bgp peer彼此之间会进行路由的共享和学习,因此自动生成并维护了路由。
经过上文flannel aliVPC模式可见一斑。阿里云中kubernetes服务里,k8s集群一般使用自定义路由的方案+flannel_cni组件,这个方案易于部署和管理,同时将容器IP和nodeIP区分,用户能够自定义集群网络范围。
(比较奇怪的是这里flanenl的backend配置成alloc而非aliVPC,在集群中另外部署了一个controller进行自定义路由的配置)
自定义路由是vpc网络中的一个经常使用功能,在vpc范围内能够自定义某个网络接口做为一个任意网段的网关。在flannel host-gw模式中,咱们将这块的路由配置在node上,由内核执行,而自定义路由则是将相似的路由记录到vpc网络的数据库中,由vpc-router去执行。
azure最近开放了kubernetes服务AKS,AKS支持两种网络方案:基础和高级。
基础网络方案与阿里云的自定义路由方案一模一样。基础网络中k8s集群使用的网络组件是kubenet,简单的作了网络划分和本地的网络接口配置,自定义路由由其vpc实现。
高级网络方案中,node上的网络接口会建立并绑定多个(默认三十个)fixedIP,主FixedIP做为node IP,其他fixedIP则用于容器IP。
经过azure SDN的支持,不一样node之间的容器网络变成一个大二层,他们能够直接互联。高级网络方案中,k8s集群使用azure开源的cni组件:azure-container-networking。这个cni组件包括了ipam和main两部分
azure cni的ipam负责将本地网络接口上绑定着的空闲的fixedIP配置给容器使用。一旦空闲的fixedIP耗尽,除非手动给网卡建立新的fixedIP,不然容器没法建立成功。
azure cni的main组件在node上建立了一个bridge,将node的网卡链接到网桥上,并将node网卡IP设置到网桥上,容器网卡均由vethpair实现,vethpair的node端也是连在网桥上。由此构成node的网络:网桥上的IP做为容器网络的网关,容器网络经过网桥与其余节点造成一个大二层的网络。