赵占旭的博客

CNI插件之用户态网络插件

本篇主要是简单了解CNI的一个第三方插件,代码链接在此

概览

用户态CNI插件对接的是基于DPDK的应用程序(ovs-dpdk或者vpp),他会将接口memif/vhost-user添加到主机上,容器内的流量会bypass内核,所以对场景会有影响。

avatar

使用

建议使用go 1.11.10和ovs-dpdk 2.9.0-3或者VPP 19.04编译插件。

1
2
3
4
5
6
7
8
9
cat ~/.bashrc
export GOPATH=~/go
export CNI_PATH=$GOPATH/src/github.com/containernetworking/plugins/bin
cd $GOPATH/src/
go get github.com/intel/userspace-cni-network-plugin
cd github.com/intel/userspace-cni-network-plugin
make
cp userspace/userspace $CNI_PATH/.
make clean

更新vendor glide update --strip-vendor

配置解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
sudo cat > /etc/cni/net.d/90-userspace.conf <<EOF
{
"cniVersion": "0.3.1",
"type": "userspace",
"name": "memif-network",
"host": {
"engine": "vpp",
"iftype": "memif",
"netType": "bridge",
"memif": {
"role": "master",
"mode": "ethernet"
},
"bridge": {
"bridgeName": "4"
}
},
"container": {
"engine": "vpp",
"iftype": "memif",
"netType": "interface",
"memif": {
"role": "slave",
"mode": "ethernet"
}
},
"ipam": {
"type": "host-local",
"subnet": "10.56.217.0/24",
"rangeStart": "10.56.217.131",
"rangeEnd": "10.56.217.190",
"routes": [
{
"dst": "0.0.0.0/0"
}
],
"gateway": "10.56.217.1"
}
}
EOF
  • type (string, required): “userspace”
  • name (string, required): 名称
  • host (dictionary, required): 宿主机配置,包含usespace接口配置数据以及宿主机网络数据的userspace接口。
  • container (dictionary, optional): 容器配置,包含userspace接口配置数据以及容器网络数据的userspace接口。
  • ipam (dictionary, optional): 该网络的IPAM配置。

集成Multus插件

avatar

Multus插件支持将多个网络接口附加到Kubernetes中的pod。

avatar

这是Multus CNI提供的连接到Pod的网络接口的图示。该图显示了具有三个接口的容器:eth0,net0和net1。 eth0连接kubernetes集群网络以连接kubernetes服务器/服务(例如kubernetes api-server,kubelet等)。 net0和net1是其他网络附件,并通过使用其他CNI插件(例如vlan/vxlan/ptp)连接到其他网络。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# cat > /etc/cni/net.d/10-multus.conf <<EOF
{
"name": "multus-demo-network",
"type": "multus",
"delegates": [
{
"type": "sriov",
"if0": "ens786f1",
"if0name": "net0",
"dpdk": {
"kernel_driver": "ixgbevf",
"dpdk_driver": "igb_uio",
"dpdk_tool": "/path/to/dpdk/tools/dpdk-devbind.py"
}
},
{
"cniVersion": "0.3.1",
"type": "userspace",
"name": "memif-network",
"host": {
"engine": "vpp",
"iftype": "memif",
"netType": "bridge",
"memif": {
"role": "master",
"mode": "ethernet"
},
"bridge": {
"bridgeName": "4"
}
},
"container": {
"engine": "vpp",
"iftype": "memif",
"netType": "interface",
"memif": {
"role": "slave",
"mode": "ethernet"
}
},
"ipam": {
"type": "host-local",
"subnet": "10.56.217.0/24",
"rangeStart": "10.56.217.131",
"rangeEnd": "10.56.217.190",
"routes": [
{
"dst": "0.0.0.0/0"
}
],
"gateway": "10.56.217.1"
}
},
{
"type": "flannel",
"name": "control-network",
"masterplugin": true,
"delegate": {
"isDefaultGateway": true
}
}
]
}
EOF

代码学习

因为新版的CNI插件已经舍去cmdGet了,所以我们只看cmdAdd和cmdDel

cmdAdd操作

  • 调用loadNetConf–>json.Unmarshal解析配置
  • 调用ns.GetNS获取容器的namespace
  • 调用getPodAndSharedDir
    • 调用k8sclient.GetPod检索podSpec找到对应的pod
    • 调用annotations.GetPodVolumeMountHostSharedDir从podSpec中检索sharedDir,目录socket文件写入宿主机,并标记为found
    • 如果没有标记found,需要生成sharedDir
  • 如果是vpp,那么调用vpp.AddOnHost申请端口和网络
    • 调用vppinfra.VppOpenCh创建channel
      • govpp.Connect连接vpp
      • vppCh.conn.NewAPIChannel创建API channel
    • addLocalDeviceMemif创建本地端口
      • getMemifSocketfileName生成一个socketfile
      • 根据配置信息生成后续创建端口的参数
      • vppmemif.CreateMemifSocket创建memif socket
        • findMemifSocket循环浏览memif socket列表,并确定输入的socketfile是否存在,如果存在,则返回关联的socketid,如果没有,则返回下一个可用的socketid
        • filepath.Dir获取socketfile获取他的目录,判定是否存在,不存在则调用os.MkdirAll创建目录
        • 调用ch.SendRequest(req).ReceiveReply(reply)发送请求,并且接受reply
      • vppmemif.CreateMemifInterface创建端口,也是类似上面的channel发送请求
    • vppinterface.SetState将端口设置为up
    • 如果是bridge模式,表示L2网络,则调用vppbridge.AddBridgeInterface将端口添加到bridge
    • 如果是interface模式,表示L3网络,则调用vppinterface.AddDelIpAddress添加IP地址
    • SaveConfig存储数据,后续删除的时候会用到
  • 如果是ovs,调用ovs.AddOnHost申请端口和网络
    • 调用addLocalNetworkBridge创建bridge
      • findBridge调用ovs-vsctl --bare --columns=name find bridge name=<bridge_name>查看bridge名称
      • 如果没有发现,createBridge调用ovs-vsctl add-br <bridge_name> -- set bridge <bridge_name> datapath_type=netdev创建bridge
    • 如果端口类型是vhostuser,addLocalDeviceVhost创建端口
      • 判断sharedDir是否存在,不存在则创建
      • 判断是配置client模式还是server模式
      • createVhostPort创建vhost端口,ovs-vsctl add-port <bridge_name> <sock_name> -- set Interface <sock_name> type=<dpdkvhostuser|dpdkvhostuserclient>
      • getVhostPortMac获取mac地址,ovs-vsctl --bare --columns=mac find port name=<sock_name>
    • SaveVppConfig存储数据,后续删除的时候会用到
  • ipam.ExecAdd申请IP地址
  • 如果是vpp,则调用vpp.AddOnContainer给容器添加端口和网络,configdata.SaveRemoteConfig
    • 如果kubeClient存在,则调用annotations.WritePodAnnotation将容器内的配置发送给kubeClient
    • 如果不存在,则会通过ioutil.WriteFile给socketfile发送信息
  • 如果是ovs,则调用ovs.AddOnContainer给容器添加端口和网络,configdata.SaveRemoteConfig类似以上
  • 将结果返回

cmdDel操作

  • 调用loadNetConf–>json.Unmarshal解析配置
  • 调用getPodAndSharedDir
    • 调用k8sclient.GetPod检索podSpec找到对应的pod
    • 调用annotations.GetPodVolumeMountHostSharedDir从podSpec中检索sharedDir,目录socket文件写入宿主机,并标记为found
    • 如果没有标记found,需要生成sharedDir
  • 如果vpp的话,vpp.DelFromHost
    • 调用vppinfra.VppOpenCh创建channel
      • govpp.Connect连接vpp
      • vppCh.conn.NewAPIChannel创建API channel
    • LoadVppConfig从之前保存的数据
    • 如果是bridge的类型的话,vppbridge.RemoveBridgeInterface删除端口
      • 生成删除端口的请求,调用ch.SendRequest(req).ReceiveReply(reply)发送请求
      • DeleteBridge删除bridge,主要是先调用findBridge查找,然后生成删除bridge的请求,然后发送请求
      • delLocalDeviceMemif删除memif端口
        • getMemifSocketfileName获取memif对应的socketfile
        • vppmemif.DeleteMemifInterface删除端口
          • findMemifInterface删除memif
          • 生成请求,ch.SendRequest(req).ReceiveReply(reply)
          • 如果socketid不为0,则删除对应的socketfile,DeleteMemifSocket
  • 如果ovs的话,ovs.DelFromHost
    • LoadConfig加载之前保存的数据
    • 如果是vhostuser端口的话,调用delLocalDeviceVhost删除端口
      • 调用deleteVhostPort删除端口,执行命令ovs-vsctl del-port <bridge_name> <sock_name>
      • 如果成功了,删除对应的一些文件
    • delLocalNetworkBridge删除bridge,调用deleteBridge执行命令ovs-vsctl del-br <bridge_name>

注意:所有文章非特别说明皆为原创。为保证信息与源同步,转载时请务必注明文章出处!谢谢合作 :-)

原始链接:http://zhaozhanxu.com/2019/10/30/K8S/2019-10-30-CNI-Userspace/

许可协议: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。