Kubernetes CNI网络插件

CNI

容器网络接口,就是在网络解决方案由网络插件提供,这些插件配置容器网络则经过CNI定义的接口来完成,也就是CNI定义的是容器运行环境与网络插件之间的接口规范。这个接口只关心容器的网络链接,在建立容器是分配网络,删除容器是移除网络。插件就是对CNI的规范的具体实现。git

Network Namespace提供的是什么

这里咱们简要回顾一下,容器具备本身的网络协议栈并且被隔离在它本身的网络名称空间内,在这个隔离的网络空间里会为容器提供网卡、回环设备、IP地址、路由表、防火墙规则等这些基本的网络环境。github

为何POD中的容器共享同一网络名称空间

每个POD中都有一个特殊的容器且永远是POD中建立的第一个容器,只是咱们查看的时候看不到,由于它完成做用就暂停了。这个容器就是Infra容器,使用汇编语言编写。当建立POD的时kubernetes先建立名称空间,而后把其中的网络名称空间和这个Infra关联,后面属于你的建立的容器都是经过Join的方式与这个Infra容器关联在一块儿,这样这些容器和Infra都属于一个网络名称空间,这也就是为何POD中多个容器可使用本地通讯的缘由,因此严格意义来讲网络名称空间属于Infra。咱们看下面的演示docker

咱们启动PODjson

登录Srv03api

查看这个进程所属的名称空间,以下图:bash

上图说容器名称空间,实际上是那个进程的名称空间,名称空间名称-->该名称空间的文件描述符。服务器

就拿net来讲,它为进程提供了一个彻底独立的网络协议栈视图,包括网络接口、IPV4/6协议栈、IP路由表、防火墙规则等,也就是为进程提供一个独立的网络环境。网络

进入POD中的容器,查看这个名称空间app

能够看到连接的文件的指向的都是同样的,我这里的POD只有一个容器,若是设置2个你就能够分别进入查看,指向都是同样的。工具

上面的结果其实为进程建立的名称空间,Infra容器抓住网络名称空间,而后POD中的容器JOIN到Infra中,这样POD中的容器就能使用这个网络名称空间。因此Kubernetes的网络插件考虑的不是POD中容器的网络配置,而是配置这个POD的Network Namespace。

Infra是为了给容器共享网络名称空间,固然也能够共享volume。

理解了为何POD里面的容器能够进行本地通讯的缘由,咱们就要向下看一层,是谁给这个Infra使用的网络名称空间提供的各类网络栈配置呢?

谁为容器提供的网络配置

咱们这里以网桥模式为例来讲明。容器就是进程,容器拥有隔离的网络名称空间,就意味着进程所在的网络名称空间也是隔离的且和外界不能联系,那么如何让实现和其余容器交互呢?你就理解为2台独立的主机要想通讯就把它们链接到一个交换机上,经过一个中间设备来链接两个独立的网络。这个就是能起到交换机做用的网桥,Linux中就有这样一个虚拟设备。有了这样一个设备那么就要解决谁来建立、谁来链接两个容器的网络名称空间的问题。

Docker项目

在Dcoker项目中,这个网桥是由docker来建立的且安装完以后就会默认建立一个这样的设备,默认叫作docker0,这种网桥并非docker的专利,是属于Linux内核就支持的功能,docker只是经过某些接口调用建立这样一个网桥而且命名为docker0。

网桥是一个二层设备,传统网桥在处理报文时只有2个操做,一个是转发;一个是丢弃。固然网桥也会作MAC地址学习就像交换机同样。可是Linux中的虚拟网桥除了具备传统网桥的功能外还有特殊的地方,由于运行虚拟网桥的是一个运行着Linux内核的物理主机,有可能网桥收到的报文的目的地址就是这个物理主机自己,因此这时候处理转发和丢弃以外,还须要一种处理方式就是交给网络协议栈的上层也就是网络层,从而被主机自己消化,因此Linux的虚拟网桥能够说是一个二层设备也能够说是一个三层设备。另外还有一个不一样的地方是虚拟网桥能够有IP地址。

充当链接容器到网桥的虚拟介质叫作Veth Pair。这个东西一头链接在容器的eth0上,一头链接在网桥上,并且链接在网桥上的这一端没有网络协议栈功能能够理解为就是一个普通交换机的端口,只有一个MAC地址;而链接在容器eth0上的一端则具备网站的网络协议栈功能。

完成上述功能就是经过Libnetwork来实现的:

Libnetwork是CNM原生实现的,它为Docker daemon和网络驱动之间提供了接口,网络控制器负责将驱动和一个网络对接,每一个驱动程序负责管理它拥有的网络以及为网络提供各类负责,好比IPAM,这个IPAM就是IP地址管理。

Docker daemon启动也就是dockerd这个进程,它会建立docker0,而docker0默认用的驱动就是bridge,也就是建立一个虚拟网桥设备,且具备IP地址,因此dockerd启动后你经过ifconfig 或者 ip addr能够看到docker0的IP地址。

接下来就能够建立容器,咱们使用docker这个客户端工具来运行容器,容器的网络栈会在容器启动前建立完成,这一系列的工做都由Libnetwork的某些接口(底层仍是Linux系统调用)来实现的,好比建立虚拟网卡对(Veth pair),一端链接容器、一端链接网桥、建立网络名称空间、关联容器中的进程到其本身的网络名称空间中、设置IP地址、路由表、iptables等工做。

在dockerd这个程序中有一个参数叫作--bridige和--bip,它就是设置使用哪一个网桥以及网桥IP,以下:

dockerd --bridge=docker0 --bip=X.X.X.X/XX

经过上面的描述以及这样的设置,启动的容器就会链接到docker0网桥,不写--bridge默认也是使用docker0,--bip则是网桥的IP地址,而容器就会得到和网桥IP同网段的IP地址。

上述关于网络的东西也没有什么神秘的,其实经过Linux命令均可以实现,以下就是在主机上建立网桥和名称空间,而后进入名称空间配置该空间的网卡对、IP等,最后退出空间,将虚拟网卡对的一端链接在网桥上。

# 建立网桥
brctl addbr 
# 建立网络名称空间
ip netns add 
# 进入名称空间
ip netns exec  bash
# 启动lo
ip link set lo up
# 在建立的名称空间中创建veth pair
ip link add eth0 type veth peer name veth00001
# 为eth0设置IP地址
ip addr add x.x.x.x/xx dev eth0
# 启动eth0
ip link set eth0 up
# 把网络名称空间的veth pair的一端放到主机名称空间中
ip link set veth00001 netns 
# 退出名称空间
exit
# 把主机名称空间的虚拟网卡链接到网桥上
brctl addif  veth0001
# 在主机上ping网络名称空间的IP地址
ping x.x.x.x

Kubernetes项目

可是对于Kubernetes项目稍微有点不一样,它本身不提供网络功能(早期版本有,后来取消了)。因此就致使了有些人的迷惑尤为是初学者,本身安装的Kubernetes集群使用的cni0这个网桥,有些人则使用docker0这个网桥。其实这就是由于Kubernetes项目自己不负责网络管理也不为容器提供具体的网络设置。而网络插件的目的就是为了给容器设置网络环境。

这就意味着在Kubernetes中我可使用docker做为容器引擎,而后也可使用docker的网络驱动来为容器提供网络设置。因此也就是说当你的POD启动时,因为infra是第一个POD中的容器,那么该容器的网络栈设置是由Docker daemon调用Libnetwork接口来作的。那么我也可使用其余的可以完成为infra设置网络的其余程序来执行这个操做。因此CNI Plugin它的目的就是一个可执行程序,在容器须要为容器建立或者删除网络是进行调用来完成具体的操做。

因此这就是为何以前要说一下POD中多容器是如何进行本地通讯的缘由,你设置POD的网络其实就是设置Infra这个容器的Network Namespace的网络栈。

在Kubernetes中,kubelet主要负责和容器打交道,而kubelet并非直接去操做容器,它和容器引擎交互使用的是CRI(Container Runtime Interface,它规定了运行一个容器所必须的参数和标准)接口,因此只要容器引擎提供了符合CRI标准的接口那么能够被Kubernetes使用。那么容器引擎会把接口传递过来的数据进行翻译,翻译成对Linux的系统调用操做,好比建立各类名称空间、Cgroups等。而kubelet默认使用的容器运行时是经过kubelet命令中的--container-runtime=来设置的,默认就是docker。

若是咱们不使用docker为容器设置网络栈的话还能够怎么作呢?这就是CNI,kubelet使用CNI规范来配置容器网络,那么同理使用CNI这种规范也是经过设置Infra容器的网络栈实现POD内容器共享网络的方式。那么具体怎么作呢?在kubelet启动的命令中有以下参数:

  • --cni-bin-dir=STRING这个是用于搜索CNI插件目录,默认/opt/cni/bin

  • --cni-conf-dir=STRING这个是用于搜索CNI插件配置文件路径,默认是/opt/cni/net.d

  • --network-plugin=STRING这个是要使用的CNI插件名,它就是去--cni-bin-dir目录去搜索

咱们先看看CNI插件有哪些,官网分了三大类,以下图:

  • Main插件:这就是具体建立网络设备的二进制程序文件,好比birdge就是建立Linux网桥的程序、ptp就是建立Veth Pair设备的程序、loopback就是建立lo设备的程序,等等。

  • IPAM插件:负责分配IP地址的程序文件,好比dhcp就是会向DHCP服务器发起地址申请、host-local就是会使用预先设置的IP地址段来进行分配,就像在dockerd中设置--bip同样。

  • Meta其余插件:这个是由CNI社区维护的,好比flannel就是为Flannel项目提供的CNI插件,不过这种插件不能独立使用,必须调用Main插件使用。

因此要用这些东西就要解决2个问题,咱们以flannel为例,一个是网络方案自己如何解决跨主机通讯;另一个就是如何配置Infra网络栈并链接到CNI网桥上。

下面咱们以flannel为例的网桥模式为例说一下流程

结合使用docker容器引擎以及flannel插件来讲就是,kubelet经过CRI接口建立POD,它会第一个建立Infra容器,这一步是调用Dokcer对CRI的实现(dockershim),dockershim调用docker api来完成,而后就会设置网络,首选须要准备CNI插件参数、其次就是传递参数并调用这个CNI插件去设置Infra网络。对于flannel插件所须要的参数包含2个部分:

  1. kubelet经过CRI调用dockershim时候传递的一组信息,也就是具体动做好比ADD或者DEL以及这些动做所须要的参数,若是是ADD的话则包括容器网卡名称、POD的Network namespace路径、容器的ID等。

  2. dockershim从CNI配置文件中加载到的(在CNI中叫作Network Configuration),也就是从--cni-conf-dir目录中加载的默认配置信息。

有了上面2部分信息以后,dockershim就会把这个信息给flannel插件,插件会对第二部分信息作补充,而后这个插件来作ADD操做,而这个操做是由CNI bridge这个插件来完成的。这个bridge使用所有的参数信息来执行把容器加入到CNI网络的操做了。它会作以下内容:

  1. 检查是否有CNI网桥,若是没有就建立并UP这个网桥,至关于在宿主机上执行ip link add cni0 type bridgeip link set cni0 up命令

  2. 接下来bridge会经过传递过来的Infra容器的网络名称空间文件进入这个网络名称空间中,而后建立Veth Pair设备,而后UP容器的这一端eth0,而后将另一端放到宿主机名称空间里并UP这个设备。

  3. bridge把宿主机的一端链接到网桥cni0上

  4. bridge会调用IPAM插件为容器的eht0分配IP地址,并设置默认路由。

  5. bridge会为CNI网桥添加IP地址,若是是第一次的话。

  6. 最后CNI插件会把容器的IP地址等信息返回给dockershim,而后被kubelet添加到POD的status字段中。

如今咱们再来回顾一下以前那个问题,个人环境是kubernetes、flannel、docker都是二进制程序安装经过系统服务形式启动。我没有/opt/cni/bin目录,也就是没有任何CNI插件,因此也就没有设置--network-plugin。那经过kubernetes部署的POD是如何被设置网络的呢?

经过以前的介绍咱们知道了kubelet经过CRI与容器引擎打交道来操做容器,那么kubelet默认是用容器运行时就是docker,由于kubelet启动参数--container-runtime就是docker,而docker又提供了符合CRI规范的接口那么kubelet就天然可让容器运行在docker引擎上。接下来讲网络,--network-plugin没有设置,那kubelet到底用的什么网络插件呢?而CNI插件里面也没有docker插件这个东西。咱们看一下这个参数说明以下:

The name of the network plugin to be invoked for various events in kubelet/pod lifecycle. This docker-specific flag only works when container-runtime is set to docker.

前半句意思是说这个参数定义POD使用的网络插件名称,可是后面半句的意思是若是使用的容器运行时是docker,那么默认网络插件就是docker。

二进制安装环境下如何使用CNI

个人环境全部组件都是二进制安装,以系统服务形式运行。最初是使用docker0网桥。这里说一下过程:

  1. 你须要向etcd写入子网信息{"Network":"'${CLUSTER_CIDR}'", "SubnetLen": 24, "Backend": {"Type": "vxlan"}}

  2. flanneld服务启动后会去etcd中申请本机使用的子网网段,而后就会写入到/run/flannel/subnet.env文件中

  3. 而后flanneld的service文件中有一条ExecStartPost=指令,就是在flanneld启动后执行的,做用就是调用一个脚本去修改dockerd的--bip参数把获取的子网信息写入

  4. 启动docker服务,而后dockerd会根据--bip的信息设置docker0网桥

在上面这个过程当中部署的POD容器的网络协议栈都是docker来设置的,固然是kubelet经过调用CRI接口来去和docker交互的,跨主机通讯是由flannel来完成的。

如今咱们要在某一台主机上切换成CNI,须要作以下修改:

  1. 创建目录mkdir -p /opt/cni/{bin,net.d}

  2. 下载CNI插件到上面的bin目录中,而后解压。

  3. 在上面的net.d目录中创建10-flannel.conflist文件,内容后面再说。

  4. 中止该主机的docker、kubelet服务,这样该主机的容器就会被master调度到其余可用主机,不过DaemonSet类型的则不行。

  5. 修改docker服务文件的dockerd的启动参数,去掉那个环境变量,增长--bip=10.0.0.1/16,这个IP随便,主要是为了避免占用etcd中要使用的地址段,由于最初它用就是etcd中分配的。

  6. 修改kubelet服务文件,增长3个参数--cni-bin-dir=/opt/cni/bin --cni-conf-dir=/opt/cni/net.d --network-plugin=cni,最后这个必定要写cni,不要写flannel,由于这里咱们要用的就是cni,而至于cni会使用什么插件,这个在配置文件中写。由于在kubernetes中只有2类插件,一个是cni一个是kubenet,这里设置的是用哪一类插件。

  7. 而后启动以前中止的服务

10-flannel.conflist文件内容

{
  "name": "cni0",
  "cniVersion": "0.4.0",
  "plugins": [
    {
      "type": "flannel",
      "delegate": {
        "hairpinMode": true,
        "isDefaultGateway": true
      }
    },
    {
      "type": "portmap",
      "capabilities": {
        "portMappings": true
      }
    }
  ]
}

还能够简化为这样

{
  "name": "cni0",
  "type": "flannel",
  "subnetFile": "/run/flannel/subnet.env",
  "delegate": {
    "hairpinMode": true,
    "isDefaultGateway": true
  }
}

name: 网络名称而不是网桥的名字

cniVersion: 插件版本,能够不写。

subnetFile: 本机flanneld从etcd中申请的本机网段,这个信息存放在哪一个文件中,默认就是/run/flannel/subnet.env,我没有改位置,因此能够省略。

plugins:表示使用什么插件

type: 插件类型,这里是flannel

delegate:这个CNI插件并非本身完成,而是须要调用某中内置的CNI插件完成,对于flannel来讲,就是调用bridge。flannel只是实现了网络方案,而网络方案对应的CNI插件是bridge,咱们下载的插件里面就包括了bridge,全部flannel对应的CNI插件就已经被内置了,由于flannel方案自己就是使用网桥来作的。

执行过程以下:

kubelet调用CRI接口也就是dockershim,它调用CNI插件,也就是/opt/cni/bin中的flannel程序,这个程序须要2部分参数,一部分是dockershim的CNI环境变量也就是具体动做ADD或者DEL,若是是ADD则是把容器添加到CNI网络里,ADD会有一些参数;另一部分就是dockershim加载的JSON文件,而后/opt/cni/bin/flannel程序会对2部分参数作整合以及填充其余信息好比ipam信息也就是具体的子网信息,ipMasp信息,mtu信息,这些都是从/run/flannel/subnet.env文件中读取的,而后flannel会调用CNI的birdge插件,也就是执行/opt/cni/bin/bridge这个命令,由它来完成将容器加入CNI网络的具体操做。

CNI网络插件

CNI操做参数以及配置文件格式

Flannel插件

CNI接口参数

相关文章
相关标签/搜索