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设置虚拟网卡,也就是容器中的eth0
和lo
网卡,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:171ipamResult, 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