kubernetes的CNI插件初始化与Pod网络设置

Tags: kubernetes 

目录

说明

CNI插件的加载是由kubelet完成的。

kubelet默认在/etc/cni/net.d目录寻找配置文件,在/opt/bin/目录中寻找二进制程序文件。

kubelet \
	...
	--network-plugin=cni 
	--cni-conf-dir=/etc/cni/net.d 
	--cni-bin-dir=/opt/cni/bin 
	...

通过--network-plugin指定要使用的网络插件类型。

kubelet在启动容器的时候调用CNI插件,完成容器网络的设置。

网络插件加载前

k8s.io/kubernetes/cmd/kubelet/kubelet.go:

if err := app.Run(s, nil); err != nil {

k8s.io/kubernetes/cmd/kubelet/app/server.go:

if err := run(s, kubeDeps); err != nil {

k8s.io/kubernetes/cmd/kubelet/app/server.go, run():

kubeDeps, err = UnsecuredKubeletDeps(s)

k8s.io/kubernetes/cmd/kubelet/app/server.go, UnsecuredKubeletDeps():

NetworkPlugins:     ProbeNetworkPlugins(s.NetworkPluginDir, s.CNIConfDir, s.CNIBinDir),

k8s.io/kubernetes/cmd/kubelet/app/plugins.go:

// ProbeNetworkPlugins collects all compiled-in plugins
func ProbeNetworkPlugins(pluginDir, cniConfDir, cniBinDir string) []network.NetworkPlugin {
    allPlugins := []network.NetworkPlugin{}

    // for backwards-compat, allow pluginDir as a source of CNI config files
    if cniConfDir == "" {
        cniConfDir = pluginDir
    }
    // for each existing plugin, add to the list
    allPlugins = append(allPlugins, cni.ProbeNetworkPlugins(cniConfDir, cniBinDir)...)
    allPlugins = append(allPlugins, kubenet.NewPlugin(pluginDir))

    return allPlugins
}

这里将所有的插件都存放在了NetworkPlugin中。

第一个插件是cni.ProbeNetworkPlugins()创建的cniNetworkPlugin,pkg/kubelet/network/cni/cni.go:

func probeNetworkPluginsWithVendorCNIDirPrefix(pluginDir, binDir, vendorCNIDirPrefix string) []network.NetworkPlugin {
	...
	plugin := &cniNetworkPlugin{
		defaultNetwork:     nil,
		loNetwork:          getLoNetwork(binDir, vendorCNIDirPrefix),
		execer:             utilexec.New(),
		pluginDir:          pluginDir,
		binDir:             binDir,
		vendorCNIDirPrefix: vendorCNIDirPrefix,
	}

	plugin.syncNetworkConfig()
	return []network.NetworkPlugin{plugin}
}

第二个是kubenet.NewPlugin()创建的kubenetNetworkPlugin,pkg/kubelet/network/kubenet/kubenet_unsupported.go:

func NewPlugin(networkPluginDir string) network.NetworkPlugin {
	return &kubenetNetworkPlugin{}
}

这里主要分析第一个插件,也就是CNI插件加载和使用,记住它的类型是cniNetworkPlugin

CNI网络插件的加载

k8s.io/kubernetes/pkg/kubelet/network/cni/cni.go:

func ProbeNetworkPlugins(pluginDir, binDir string) []network.NetworkPlugin {
	return probeNetworkPluginsWithVendorCNIDirPrefix(pluginDir, binDir, "")
}

k8s.io/kubernetes/pkg/kubelet/network/cni/cni.go,probeNetworkPluginsWithVendorCNIDirPrefix():

func probeNetworkPluginsWithVendorCNIDirPrefix(pluginDir, binDir, vendorCNIDirPrefix string) []network.NetworkPlugin {
	if binDir == "" {
		binDir = DefaultCNIDir
	}
	plugin := &cniNetworkPlugin{
		defaultNetwork:     nil,
		loNetwork:          getLoNetwork(binDir, vendorCNIDirPrefix),
		execer:             utilexec.New(),
		pluginDir:          pluginDir,
		binDir:             binDir,
		vendorCNIDirPrefix: vendorCNIDirPrefix,
	}
	// sync NetworkConfig in best effort during probing.
	plugin.syncNetworkConfig()
	return []network.NetworkPlugin{plugin}
}

加载CNI插件的配置

k8s.io/kubernetes/pkg/kubelet/network/cni/cni.go:

func (plugin *cniNetworkPlugin) syncNetworkConfig() {
	network, err := getDefaultCNINetwork(plugin.pluginDir, plugin.binDir, plugin.vendorCNIDirPrefix)
	if err != nil {
		glog.Warningf("Unable to update cni config: %s", err)
		return
	}
	plugin.setDefaultNetwork(network)
}

加载配置文件,k8s.io/kubernetes/pkg/kubelet/network/cni/cni.go:

func getDefaultCNINetwork(pluginDir, binDir, vendorCNIDirPrefix string) (*cniNetwork, error) {
	...
	//从pluginDir目录中读取所有.conf文件
	files, err := libcni.ConfFiles(pluginDir)
	...
	for _, confFile := range files {
		conf, err := libcni.ConfFromFile(confFile)
		if err != nil {
			glog.Warningf("Error loading CNI config file %s: %v", confFile, err)
			continue
		}
		vendorDir := vendorCNIDir(vendorCNIDirPrefix, conf.Network.Type)
		cninet := &libcni.CNIConfig{
			Path: []string{binDir, vendorDir},
		}
		network := &cniNetwork{name: conf.Network.Name, NetworkConfig: conf, CNIConfig: cninet}
		return network, nil
	}
	...

注意上面的for循环中,找到一个可用的插件即返回,vendorDir指定了插件程序的路径。

VendorCNIDirTemplate = "%s/opt/%s/bin"   //第一个%s是前缀,第二个%s是conf.Network.Type

得到的插件的类型是cniNetwork,pkg/kubelet/network/cni/cni.go

type cniNetwork struct {
	name          string
	NetworkConfig *libcni.NetworkConfig
	CNIConfig     libcni.CNI
}

CNI的配置文件

Bytes是配置文件的原始内容,Network是从配置文件中解读出的,vendor/github.com/containernetworking/cni/libcni/api.go:

type NetworkConfig struct {
	Network *types.NetConf
	Bytes   []byte
}

type NetConf struct {
	Name string `json:"name,omitempty"`
	Type string `json:"type,omitempty"`
	IPAM struct {
		Type string `json:"type,omitempty"`
	} `json:"ipam,omitempty"`
	DNS DNS `json:"dns"`
}

type DNS struct {
	Nameservers []string `json:"nameservers,omitempty"`
	Domain      string   `json:"domain,omitempty"`
	Search      []string `json:"search,omitempty"`
	Options     []string `json:"options,omitempty"`
}

这是在cni项目中定义的,配置文件的内容保存在Bytes中,因此具体的CNI插件可能会再次解读配置文件。

网络插件初始化

前面的过程结束后,kubeDeps.NetworkPlugins中就设置好了指定的插件。

在k8s.io/kubernetes/pkg/kubelet/kubelet.go,NewMainKubelet()中:

if plug, err := network.InitNetworkPlugin(kubeDeps.NetworkPlugins, 
        kubeCfg.NetworkPluginName, 
        &criNetworkHost{&networkHost{klet}, &network.NoopPortMappingGetter{}}, 
        klet.hairpinMode, 
        klet.nonMasqueradeCIDR, 
        int(kubeCfg.NetworkPluginMTU)); err != nil {

k8s.io/kubernetes/pkg/kubelet/network/plugins.go, InitNetworkPlugin()

func InitNetworkPlugin(plugins []NetworkPlugin, networkPluginName string, host Host, hairpinMode componentconfig.HairpinMode, nonMasqueradeCIDR string, mtu int) (NetworkPlugin, error) {
	...
	chosenPlugin := pluginMap[networkPluginName]
	if chosenPlugin != nil {
		err := chosenPlugin.Init(host, hairpinMode, nonMasqueradeCIDR, mtu)
	...

这里的networkPluginName,也就是kubeCfg.NetworkPluginName,是kubelet的配置参数中指定的,cniNetworkPlugin的name是cni,pkg/kubelet/network/cni/cni.go:

...
CNIPluginName        = "cni"
...

func (plugin *cniNetworkPlugin) Name() string {
	return CNIPluginName
}

这里只分析cni插件,chosenPlugin.Init()实际上是cniNetworkPlugin.Init(),pkg/kubelet/network/cni/cni.go:

func (plugin *cniNetworkPlugin) Init(host network.Host, hairpinMode componentconfig.HairpinMode, nonMasqueradeCIDR string, mtu int) error {
	var err error
	plugin.nsenterPath, err = plugin.execer.LookPath("nsenter")
	if err != nil {
		return err
	}
	plugin.host = host

	plugin.syncNetworkConfig()
	return nil
}

这里设置了命令nsenter的路径和host,最后执行的plugin.syncNetworkConfig()其实在前面创建插件的时候已经调用过一次,应当是可以去掉的。

CNI网络插件的使用

cniNetworkPlugin的定义:

--cniNetworkPlugin : struct
    [fields]
   -binDir : string
   -defaultNetwork : *cniNetwork
   -execer : utilexec.Interface
   -host : network.Host
   -loNetwork : *cniNetwork
   -nsenterPath : string        //二进制文件nsenter的路径
   -pluginDir : string
   -vendorCNIDirPrefix : string
    [embedded]
   +network.NoopNetworkPlugin : network.NoopNetworkPlugin
   +sync.RWMutex : sync.RWMutex
    [methods]
   +GetPodNetworkStatus(namespace string, name string, id kubecontainer.ContainerID) : *network.PodNetworkStatus, error
   +Init(host network.Host, hairpinMode componentconfig.HairpinMode, nonMasqueradeCIDR string, mtu int) : error
   +Name() : string
   +SetUpPod(namespace string, name string, id kubecontainer.ContainerID, annotations map[string]string) : error
   +Status() : error
   +TearDownPod(namespace string, name string, id kubecontainer.ContainerID) : error
   -checkInitialized() : error
   -getDefaultNetwork() : *cniNetwork
   -setDefaultNetwork(n *cniNetwork)
   -syncNetworkConfig()

Init()用于初始化,setUpPod用于设置容器的网络,重点关注setUpPod。

SetUpPod()

SetUpPod()的参数分别是namespace,容器的name, pause容器的ID,注解。

这里必须要说明一下,k8s中的pod至少是包含两个容器的,其中一个容器作为infra容器,同一个pod中的其它容器和infra容器共享一个网络ns。

创建pod的时候,kubelet首先创建infra容器,得到infra容器的ID,然后创建其它容器。

k8s.io/kubernetes/pkg/kubelet/dockertools/docker_manager.go:

// If we should create infra container then we do it first.
podInfraContainerID := containerChanges.InfraContainerId
if containerChanges.StartInfraContainer && (len(containerChanges.ContainersToStart) > 0) {
	...
	podInfraContainerID, err, msg = dm.createPodInfraContainer(pod)
	...
	if !kubecontainer.IsHostNetworkPod(pod) {
		if err := dm.network.SetUpPod(pod.Namespace, pod.Name, podInfraContainerID.ContainerID(), pod.Annotations); err != nil {
			setupNetworkResult.Fail(kubecontainer.ErrSetupNetwork, err.Error())
			glog.Error(err)

			// Delete infra container
...

在SetUpPod中为podInfraContainerID设置网络。

将容器加入指定网络

每个plugin都有一个类型为defaultNetwork的成员cniNetwork, pkg/kubelet/network/cni/cni.go:

defaultNetwork *cniNetwork

这个类型为cniNetworkdefaultNetwork,就是前面在加载CNI配置文件时候创建的。

容器网络的设置的工作,是由plugin.getDefaultNetwork()得到的defaultNetwork完成的,pkg/kubelet/network/cni/cni.go

func (plugin *cniNetworkPlugin) SetUpPod(namespace string, name string, id kubecontainer.ContainerID, annotations map[string]string) error {
	...
	netnsPath, err := plugin.host.GetNetNS(id.ID)
	...
	_, err = plugin.loNetwork.addToNetwork(name, namespace, id, netnsPath)
	...
	_, err = plugin.getDefaultNetwork().addToNetwork(name, namespace, id, netnsPath)
	...
	return err
}

pkg/kubelet/network/cni/cni.go,addToNetwork():

func (network *cniNetwork) addToNetwork(podName string, podNamespace string, \
podInfraContainerID kubecontainer.ContainerID, podNetnsPath string) (*cnitypes.Result, error) {
...
netconf, cninet := network.NetworkConfig, network.CNIConfig
glog.V(4).Infof("About to run with conf.Network.Type=%v", netconf.Network.Type)
res, err := cninet.AddNetwork(netconf, rt)
...

可以看到使用的是成员cniNetwork.CNIConfig.AddNetwork()

回顾一下前面cniNetwork的创建过程:

defaultNetwork在pkg/kubelet/network/cni/cni.go,getDefaultCNINetwork()中创建:

	vendorDir := vendorCNIDir(vendorCNIDirPrefix, conf.Network.Type)
	cninet := &libcni.CNIConfig{
		Path: []string{binDir, vendorDir},
	}
	network := &cniNetwork{name: conf.Network.Name, NetworkConfig: conf, CNIConfig: cninet}
	return network, nil

可以看到成员CNIConfig类型为libcni.CNIConfig,并且包括两个路径binDirvendorDir

在下面可以看到,Addnetwork()回到路径下寻找与网络类型同名的二进制程序,并用来完成最后的操作。

libcni.CNIConfig

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

func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (*types.Result, error) {
	pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
	...
	return invoke.ExecPluginWithResult(pluginPath, net.Bytes, c.args("ADD", rt))
}

invoke.FindInPath在c.Path目录下寻找名为net.Network.Type的二进制文件,返回文件的完整路径pluginPath。

最后,直接运行程序文件,运行时参数为ADD XXX

CNI支持的网络

cni中列出了支持的网络类型:

Project Calico - a layer 3 virtual network
Weave - a multi-host Docker network
Contiv Networking - policy networking for various use cases
SR-IOV
Cilium - BPF & XDP for containers
Infoblox - enterprise IP address management for containers
Multus - a Multi plugin
Romana - Layer 3 CNI plugin supporting network policy for kubernetes
CNI-Genie - generic CNI network plugin
Nuage CNI - Nuage Networks SDN plugin for network policy kubernetes support
Silk - a CNI plugin designed for Cloud Foundry
Linen - a CNI plugin designed for overlay networks with Open vSwitch and fit 
        in SDN/OpenFlow network environment

参考

  1. cni

kubernetes

  1. kubernetes 使用:多可用区、Pod 部署拓扑与 Topology Aware Routing
  2. kubernetes 扩展:Cloud Controller Manager
  3. kubernetes 准入:操作合法性检查(Admission Control)
  4. kubernetes 鉴权:用户操作权限鉴定(Authorization)
  5. kubernetes 认证:用户管理与身份认证(Authenticating)
  6. kubernetes 开发:代码生成工具
  7. kubernetes 扩展:operator 开发
  8. kubernetes 扩展:CRD 的使用方法
  9. kubernetes configmap 热加载,inotifywatch 监测文件触发热更新
  10. kubernetes 扩展:扩展点和方法(api/cr/plugin...)
  11. kubernetes 调度组件 kube-scheduler 1.16.3 源代码阅读指引
  12. kubernetes 代码中的 k8s.io 是怎么回事?
  13. 阅读笔记《不一样的 双11 技术,阿里巴巴经济体云原生实践》
  14. kubernetes ingress-nginx 启用 upstream 长连接,需要注意,否则容易 502
  15. ingress-nginx 的限速功能在 nginx.conf 中的对应配置
  16. kubernetes 中的容器设置透明代理,自动在 HTTP 请求头中注入 Pod 信息
  17. kubernetes ingress-nginx 的测试代码(单元测试+e2e测试)
  18. kubernetes ingress-nginx http 请求复制功能与 nginx mirror 的行为差异
  19. kubernetes 基于 openresty 的 ingress-nginx 的状态和配置查询
  20. kubernetes ingress-nginx 0.25 源代码走读笔记
  21. kubernetes ingress-nginx 的金丝雀(canary)/灰度发布功能的使用方法
  22. kubernetes 操作命令 kubectl 在 shell 中的自动补全配置
  23. kubernetes 组件 kube-proxy 的 IPVS 功能的使用
  24. kubernetes initializer 功能的使用方法: 在 Pod 等 Resource 落地前进行修改
  25. kubernetes 版本特性: 新特性支持版本和组件兼容版本
  26. kubernetes API 与 Operator: 不为人知的开发者战争(完整篇)
  27. kubernetes 1.12 从零开始(七): kubernetes开发资源
  28. kubernetes 1.12 从零开始(六): 从代码编译到自动部署
  29. kubernetes 网络方案 Flannel 的学习笔记
  30. kubernetes 1.12 从零开始(五): 自己动手部署 kubernetes
  31. kubernetes 1.12 从零开始(四): 必须先讲一下基本概念
  32. kubernetes 1.12 从零开始(三): 用 kubeadm 部署多节点集群
  33. kubernetes 1.12 从零开始(二): 用 minikube 部署开发测试环境
  34. kubernetes 1.12 从零开始(一): 部署环境准备
  35. kubernetes 1.12 从零开始(零): 遇到的问题与解决方法
  36. kubernetes 1.12 从零开始(初): 课程介绍与官方文档汇总
  37. kubernetes 集群状态监控:通过 grafana 和 prometheus
  38. 一些比较有意思的Kubernetes周边产品
  39. Borg论文阅读笔记
  40. kubelet下载pod镜像时,docker口令文件的查找顺序
  41. kubernetes 的 Client Libraries 的使用
  42. kubernetes的网络隔离networkpolicy
  43. kube-router的源码走读
  44. kubernetes 跨网段通信: 通过 calico 的 ipip 模式
  45. kubernetes的调试方法
  46. kubernetes 与 calico 的衔接过程
  47. 怎样理解 kubernetes 以及微服务?
  48. kubernetes中部署有状态的复杂分布式系统
  49. kubernetes的apiserver的启动过程
  50. kubernetes的api定义与装载
  51. kubernetes的federation部署,跨区Service
  52. kubernetes的编译、打包、发布
  53. kubernetes的第三方包的使用
  54. kubernetes的Storage的实现
  55. kubernetes 的 Apiserver 的 storage 使用
  56. kubernetes的Controller-manager的工作过程
  57. kubernetes的Client端Cache
  58. kubernetes 的 Apiserver 的工作过程
  59. kubernetes的CNI插件初始化与Pod网络设置
  60. kubernetes的Pod变更过程
  61. kubernetes的kubelet的工作过程
  62. kuberntes 的 Cmdline 实现
  63. kubernetes的Pod内挂载的Service Account的使用方法
  64. kubernetes的社区资源与项目参与方式
  65. kubernetes的Kube-proxy的转发规则分析
  66. kubernetes的基本操作
  67. kubernetes在CentOS上的集群部署
  68. kubernetes在CentOS上的All In One部署
  69. 怎样选择集群管理系统?

推荐阅读

Copyright @2011-2019 All rights reserved. 转载请添加原文连接,合作请加微信lijiaocn或者发送邮件: [email protected],备注网站合作

友情链接:  李佶澳的博客  系统软件  程序语言  运营经验  关注方向  水库文集  网文收藏  网络课程  发现知识星球  百度搜索 谷歌搜索