cni 添加网络 流程分析

Posted Monster-Z

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了cni 添加网络 流程分析相关的知识,希望对你有一定的参考价值。

cnitool的使用方式如下:其中<net>是配置文件所在目录,一般为/etc/cni/net.d/*.conf文件,<netns>为network namespace的目录文件,一般为/var/run/netns/NS-ID

cnitool: Add or remove network interfaces from a network namespace

  cnitool  add  <net>  <netns>
  cnitool  del   <net>  <netns>

  

 

1、cni/cnitool/cni.go

main函数:

(1)、首先从环境变量NETCONFPATH中获取netdir,若不存在则设置为默认值"/etc/cni/net.d",之后调用netconf, err := libcni.LoadConf(netdir, os.Args[2])加载配置变量,netns赋值为os.Args[3]

(2)、调用获得CNIConfig和RuntimeConf

cninet := &libcni.CNIConfig{

  Path: strings.Split(os.Getenv(EnvCNIPath), ":")

}

rt := &libcni.RuntimeConf{

  ContainerID:   "cni",

  NetNS:     netns,
  IfName:      "eth0"
}

(3)、os.Args[1]为add时,调用_, err := cninet.AddNetwork(netconf, rt)添加网络

 

NetworkConfig的数据结构如下所示:

type NetworkConfig struct {

  Network  *types.NetConf
  Bytes   []byte
}

  

type NetConf struct {

  CNIVersion    string
  Name       string
  Type       string
  IPAM    struct {
      Type    string
  }

  DNS    DNS
}

  

Runtime的数据结构如下所示:

type RuntimeConf struct {

  ContainerID    string
  NetNS       string
  IfName      string
  Args       [][2]string
}

  

 

2、cni/libcni/api.go

// AddNetwork executes the plugin with ADD command

func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (*types.Result, error)

(1)、首先调用pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path),该函数用于在c.Path中寻找net.Network.Type,然后返回全路径

(2)、调用 return invoke.ExecPluginWithResult(pluginPath, net.Bytes, c.args("ADD", rt)),net.Bytes是配置文件的序列化二进制码,其中c.args函数主要的作用是填充并返回一个*invoke.Args类型:

return &invoke.Args {

  Command:       action,

  ContainerID:     rt.ContainerID,

  NetNS:         rt.NetNS,

  PluginArgs:      rt.Args,

  IfName:        rt.IfName,

  Path:         strings.Join(c.Path, ":"),

}

  

3、cni/pkg/invoke/exec.go

func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs) (*types.Result, error)

该函数只是简单地返回 return defaultPluginExec.WithResult(pluginPath, netconf, args)

其中defaultPluginExec是一个*PluginExec的类型变量,赋值过程如下所示:

var defaultPluginExec = &PluginExec{

  RawExec:      &RawExec{Stderr: os.Stderr},    --->RawExec又是在raw_exec.go中定义的一个结构类型,其中只有一个Stderr的io.Writer类型
  VersionDecoder:   &version.PluginDecoder{},
}

// 其中PluginExec的定义如下所示:

type PluginExec struct {

  RawExec   interface {
    ExecPlugin(pluginPath string, stdinData []byte, environ []string)
  }

  VersionDecoder interface {

    Decode(jsonBytes []byte) (version.PluginInfo, error)
  }

}

  

4、cni/pkg/invoke/exec.go

func (e *PluginExec) WithResult(pluginPath string, netconf []byte, args CNIArgs) (*types.Result, error)

(1)、调用stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, netconf, args.AsEnv()),args.AsEnv()将args里的内容转变为环境变量返回,例如CNI_COMMAND=ADD等等

(2)、res := &types.Result{},再调用json.Unmarshal(stdoutBytes, res)解析出Result并返回

 

5、cni/pkg/invoke/raw_exec.go

func (e *RawExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error)

(1)、首先获得stdout := bytes.Buffer{}作为输出缓冲器

(2)、创建执行命令并调用c.Run()运行

c := exec.Cmd {

  Env:    environ,
  Path:    pluginPath,
  Args:    []string{pluginPath},
  Stdin:    bytes.NewBuffer(stdinData),
  Stdout:    stdout,
  Stderr:    e.Stderr,
}

(3)、最后返回stdout.Bytes()

 

----------------------------------------------------------------------------------- plugin的执行框架 -----------------------------------------------------------------------

每个插件的main函数都调用了skel.PluginMain(cmdAdd, cmdDel, version.Legacy),其中skel包是一个框架库,所有新添加的插件只要调用skel.PluginMain传入差价的cmdAdd和cmdDel方法就可以了。

1、cni/pkg/skel/skel.go

// PluginMain is the "main" for a plugin. It accepts two callbacks functions for add and del commands

func PluginMain(cmdAdd, cmdDel)

(1)、首先构造一个一个dispatcher类型的caller:

caller := dispatcher {

  Getenv:    os.Getenv,

  Stdin:     os.Stdin,

  Stdout:      os.Stdout,

  Stderr:       os.Stderrr,

}

  

之后再简单地调用 err := caller.pluginMain(cmdAdd, cmdDel, versionInfo)

 

2、cni/pkg/skel/skel.go

func (t *dispatcher) pluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) *types.Error

(1)、首先调用cmd, cmdArgs, err := t.getCmdArgsFromEnv(),该函数从之前传入给本进程的环境变量中解析出很多例如CNI_COMMAND,CNI_CONTAINERID之类的信息,填充获得cmdArgs,如下所示:

cmdArgs := &CmdArgs {

  ContainerID:      contID,
  Netns:         netns,
  IfName:         ifName,
  Args:           args,
  Path:           path,
  StdinData:        stdinData,
}

  

(2)、根据cmd调用相应的处理函数进行处理,例如我们对add进行分析,调用err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdAdd)

 

3、cni/pkg/skel/skel.go

func (t *dispatcher) checkVersionAndCall(cmdArgs *CmdArgs, pluginVersionInfo version.PluginInfo, toCall func (*CmdArgs) error)

(1)、首先调用configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData)和verErr := t.VersionReconciler.Check(configVersion, pluginVersionInfo)对version进行检查

(2)、最后调用return toCall(cmdArgs)函数,进入具体的插件执行网络的add或者delete操作

 

------------------------------------------------------------------------------------ 当type为bridge时 ------------------------------------------------------------------------

1、cni/plugins/main/bridge/bridge.go

 

以上是关于cni 添加网络 流程分析的主要内容,如果未能解决你的问题,请参考以下文章

k8s cni bridge

Kubernetes — Flannel CNI

非全研究生计算机网络-k8s网络插件(CNI)性能分析

非全研究生计算机网络-k8s网络插件(CNI)性能分析

非全研究生计算机网络-k8s网络插件(CNI)性能分析

非全研究生计算机网络-k8s网络插件(CNI)性能分析