Pod 网卡热插拔
Pod 是 kubernetes 环境中最小的负载单元,设计之初应该从未想到现在对 pod 的要求越来越大,如现在的 kubevirt 等 vm in pod 的形式,需要在 pod 内部运行 libvirt 虚拟机;
Libvirt 虚拟机是支持网卡热插拔的,那么对 pod 提出的要求就是 pod 能够先进行网卡热插拔到 pod 内交付给 libvirt 进行使用。
如 dynamic-networks-controller项目(https://github.com/k8snetworkplumbingwg/multus-dynamic-networks-controller)就是为了实现 pod 网卡热插拔功能。
部署
当前该项目支持 crio 和 containerd 两种 runtime 实现;在部署时请选择对应的 yaml
manifests/dynamic-networks-controller.yaml (containerd)或 manifests/crio-dynamic-networks-controller.yaml (crio)
该项目依赖 multus cni
Multus cni 安装
git clone https://github.com/k8snetworkplumbingwg/multus-cni.git && cd multus-cni
cat ./deployments/multus-daemonset-thick.yml | kubectl apply -f -
创建 一个 nad
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: net1
namespace: default
spec:
config: '{
"cniVersion": "0.3.1",
"name": "kube-ovn",
"plugins":[
{
"type":"kube-ovn", // 调哪个 cni 去处理
"server_socket":"/run/openvswitch/kube-ovn-daemon.sock",
"provider": "net1.default.ovn"
},
{
"type":"portmap",
"capabilities":{
"portMappings":true
}
}
]
}'
dynamic-networks-controller 安装
git clone https://github.com/k8snetworkplumbingwg/multus-dynamic-networks-controller.git
cd multus-dynamic-networks-controller
kubectl apply -f manifests/dynamic-networks-controller.yaml
kubectl apply -f manifests/crio-dynamic-networks-controller.yaml (cri-o)
区别在于两者的配置不同,criSocketPath 分别指向宿主机的 /run/containerd/containerd.sock 或 /run/crio/crio.sock;
daemonset controller
kube-system dynamic-networks-controller-ds-2gtjm 1/1 Running 0 4h24m 10.16.0.2 node1 <none> <none>
kube-system dynamic-networks-controller-ds-hn44p 1/1 Running 0 4h24m 10.16.0.8 node2 <none> <none>
原理
通过 watch pod 变化,annotation 中 k8s.v1.cni.cncf.io/networks 和 k8s.v1.cni.cncf.io/network-status 的比较进行热插拔动作。
图片来自 kubevirt 社区
代码分析
- 初始化 controller 和 workqueue
podNetworksController, err := controller.NewPodNetworksController(
podInformerFactory, // pod Informer
nadInformerFactory, // network-attachment-definition Informer
eventBroadcaster,
newEventRecorder(eventBroadcaster),
k8sClient, // k8sClient
nadClientSet, // nadClient
containerRuntime, // runtime 根据选择 runtime 使用 socket 生成对应的 client
multuscni.NewClient(configuration.MultusSocketPath)) // multus client
workqueue: workqueue.NewNamedRateLimitingQueue(
workqueue.DefaultControllerRateLimiter(),
AdvertisedName),
- Controller 启动后,过滤一遍已有的 pod ,将非 hostnetwork pod 加到 workqueue 里
func (pnc *PodNetworksController) reconcileOnStartup() error {
pods, err := pnc.podsLister.List(labels.Everything())
if err != nil {
return fmt.Errorf("failed to list pods on current node: %v", err)
}
for _, pod := range pods {
if pnc.ignoreHostNetworkedPods(pod) {
continue
}
namespacedName := annotations.NamespacedName(pod.GetNamespace(), pod.GetName())
klog.V(logging.Debug).Infof("pod [%s] added to reconcile on startup", namespacedName)
pnc.workqueue.Add(&namespacedName)
}
return nil
}
- workQueue 处理初始化加到 queue 的 obj 和 watch 到的 obj
通过 annotation 获取 nad 信息。
indexedNetworkSelectionElements := annotations.IndexPodNetworkSelectionElements(pod)
indexedNetworkStatus := annotations.IndexNetworkStatusIgnoringDefaultNetwork(pod)
- indexedNetworkSelectionElements map[string]nadv1.NetworkSelectionElement,解析 k8s.v1.cni.cncf.io/networks 而来,key 是 “default/net1” 或者 “default/net1/eth0” value 是 nad 资源
- indexedNetworkStatus 则是通过 k8s.v1.cni.cncf.io/network-status 获取当前已绑定的 nad 信息;key 也是 “default/net1” 或者 “default/net1/eth0”,value 是网卡信息,包括 ip,mac,dns 等。
就是根据两者的比较来判断,indexedNetworkSelectionElements 多余 indexedNetworkStatus 的部分就是需要热插的,少于 indexedNetworkStatus 的部分就是热拔的。
- 获取 netns
func (pnc *PodNetworksController) netnsPath(pod *corev1.Pod) (string, error) {
if containerID := podContainerID(pod); containerID != "" {
netns, err := pnc.containerRuntime.NetNS(containerID)
if err != nil {
return "", fmt.Errorf("failed to get netns for container [%s]: %w", containerID, err)
}
return netns, nil
}
return "", nil
}
通过 pod 的 containerId 通过 runtime 去查询 netns
- 请求 add or del network
err = pnc.handleDynamicInterfaceRequest(
&DynamicAttachmentRequest{
Pod: pod,
Attachments: attachmentsToAdd,
Type: add,
PodNetNS: netnsPath,
})
- 调 multus 代理添加网卡
for i := range dynamicAttachmentRequest.Attachments {
netToAdd := dynamicAttachmentRequest.Attachments[i]
klog.Infof("network to add: %v", netToAdd)
netAttachDef, err := pnc.netAttachDefLister.NetworkAttachmentDefinitions(netToAdd.Namespace).Get(netToAdd.Name)
if err != nil {
klog.Errorf("failed to access the networkattachmentdefinition %s/%s: %v", netToAdd.Namespace, netToAdd.Name, err)
return err
}
netAttachDefWithDefaults, err := serializeNetAttachDefWithDefaults(netAttachDef)
if err != nil {
return err
}
response, err := pnc.multusClient.InvokeDelegate(
multusapi.CreateDelegateRequest(
multuscni.CmdAdd,
podContainerID(pod),
dynamicAttachmentRequest.PodNetNS,
netToAdd.InterfaceRequest,
pod.GetNamespace(),
pod.GetName(),
string(pod.UID),
netAttachDefWithDefaults,
interfaceAttributes(netToAdd),
))
- 先通过 nad 的 name namespace 拿到完整的 nad 信息
- 通过 multusClient 去使用 nad 里的 plugin 去创建和删除网络
- 完成后更新状态 k8s.v1.cni.cncf.io/network-status