k8s CNI#

CNI(container network interface)#

为了将网络功能插件化,k8s要1.5提出了CNI标准,现在我们常用的网络如:flannel、calico等都是基于CNI标准开发的。

kubelet配置CNI#

--network-plugin=cni
--cni-bin-dir=/opt/cni/bin
--cni-conf-dir=/etc/cni/net.d
  • /opt/cni/bin:CNI插件的存放目录
  • /etc/cni/net.d:插件的配置文件的存放目录

接口定义#

vendor/github.com/containernetworking/cni/libcni/api.go

type CNI interface {
	AddNetworkList(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
	DelNetworkList(net *NetworkConfigList, rt *RuntimeConf) error

	AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
	DelNetwork(net *NetworkConfig, rt *RuntimeConf) error
}

接口实现#

下面以calico-plugin为例说明CNI的实现,calico-plugin与calico容器网络之间没有直接关系,我们通常说的calico指的是这两个东西的组合,而calico容器网络是解决不同物理机上容器之间的通信,而calico-plugin是在k8s创建Pod时为Pod设置虚拟网卡,也就是容器中的eth0lo网卡,calico-plugin是由两个静态的二进制文件组成,由kubelet以命令行的形式调用,这两个二进制的作用如下:

  • calico-ipam:分配IP,维护IP池,需要依赖etcd。
  • calico:通过调用系统API来修改namespace中的网卡信息。

文件在物理机上的位置:

/opt/cni/
├── bin
│   ├── calico
│   ├── calico-ipam
└── net.d
    └── 10-calico.conf

calico-plugin在ETCD中维护的信息:

curl -s localhost:2379/v2/keys/calico/ipam/v2/assignment/ipv4/block | jq

calico插件配置

vim /opt/cni/net.d/10-calico.conf
{
    "name": "calico-k8s-network",
    "cniVersion": "0.1.0",
    "type": "calico",
    "etcd_endpoints": "http://127.0.0.1:2379",
    "log_level": "info",
    "ipam": {
        "type": "calico-ipam"
    },
    "kubernetes": {
        "kubeconfig": "/opt/rainbond/kubernetes/kubecfg/admin.kubeconfig"
    }
}

calico-plugin工作原理#

kubelet在创建一个Pod时,会先启动puase容器,然后为这个容器添加设置网络,也就是添加网卡,这里会通过CNI调起文件系统中的/opt/cni/bin/calico,并将Pod信息通过标准输入(stdin)传递给calico进程,calico通过修改系统中Namespace达到为容器添加网卡的目的。

calico-plugin源码分析#

/opt/cni/bin/calico的入口函数定义在calico.go文件中,这个文件中有三个主要函数:

  • main():入口函数,主要负责从进程的标准输入(stdin)中读取kubelet传进来的json数据,然后调用cmdAdd()或是cmdDel()
  • cmdAdd():为Pod添加网卡
  • cmdDel():为Pod删除网卡

我们看cmdAdd()函数的实现,cmdAdd()是个通用函数,它会根据wepIDs.Orchestrator字段的值调用calico针对k8s的实现:calico.go:190

if wepIDs.Orchestrator == api.OrchestratorKubernetes {
    if result, err = k8s.CmdAddK8s(ctx, args, conf, *wepIDs, calicoClient, endpoint); err != nil {
        return err
    }

其中k8s.CmdAddK8s()函数的主要工作如下:

  • 调用/opt/cni/bin/calico-ipam分配一个IP:k8s/k8s.go:171

    ipamResult, err := ipam.ExecAdd(conf.IPAM.Type, args.StdinData)
  • 创建endpoint:k8s/k8s.go:243

    endpoint.Name = epIDs.WEPName
    endpoint.Namespace = epIDs.Namespace
    endpoint.Labels = labels
    endpoint.GenerateName = generateName
    endpoint.Spec.Endpoint = epIDs.Endpoint
    endpoint.Spec.Node = epIDs.Node
    endpoint.Spec.Orchestrator = epIDs.Orchestrator
    endpoint.Spec.Pod = epIDs.Pod
    endpoint.Spec.Ports = ports
    endpoint.Spec.IPNetworks = []string{}
  • 创建一对veth设备,在Linux中,veth设备是两张虚拟网卡,一个在Linux的物机机上,一个在容器中,用于容器与物理机之间的通信:k8s/k8s.go:280

    _, contVethMac, err := utils.DoNetworking(args, conf, result, logger, hostVethName)
  • 将endpoint信息写入到Pod中:k8s/k8s.go:299

    if _, err := utils.CreateOrUpdate(ctx, calicoClient, endpoint); err != nil {
  • 将网卡信息写入到容器对应的Namespace中:utils/network.go:41

    err = ns.WithNetNSPath(args.Netns, func(hostNS ns.NetNS) error {
    veth := &netlink.Veth{
        LinkAttrs: netlink.LinkAttrs{
            Name:  contVethName,
            Flags: net.FlagUp,
            MTU:   conf.MTU,
        },
        PeerName: hostVethName,
    }
  • 将endpoint信息写入ETCD:k8s/k8s.go:229

    if _, err := utils.CreateOrUpdate(ctx, calicoClient, endpoint); err != nil {
        logger.WithError(err).Error("Error creating/updating endpoint in datastore.")
        releaseIPAM()
        return nil, err
    }
  • 写入ETCD的逻辑引用了另一个项目:github.com/projectcalico/libcalico-go

相关资料#




本站所有作品均采用知识共享署名-非商业性使用-禁止演绎 3.0 中国大陆许可协议进行许可。