cni 添加网络 流程分析

来源:互联网 发布:windows文件名最大长度 编辑:程序博客网 时间:2024/05/22 00:28

From http://www.cnblogs.com/YaoDD/p/6024535.html?utm_source=itdadao&utm_medium=referral

1
2
3
4
cnitool: Add or remove network interfaces from a network namespace
 
  cnitool  add  <net>  <netns>
  cnitool  del  <net>  <netns>

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

 

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

1
2
3
4
5
6
7
8
9
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的数据结构如下所示:

1
2
3
4
type NetworkConfig struct {
  Network  *types.NetConf
  Bytes   []byte
}

  

1
2
3
4
5
6
7
8
9
type NetConf struct {
  CNIVersion    string
  Name       string
  Type       string
  IPAM struct {
      Type   string
  }
  DNS    DNS
}

  

Runtime的数据结构如下所示:

1
2
3
4
5
6
type RuntimeConf struct {
  ContainerID   string
  NetNS       string
  IfName      string
  Args       [][2]string
}

 

CNIConfig的数据结构如下所示:CNIconfig包含的是bridge,dhcp等等二进制文件的目录集

1
2
3
type CNIConfig struct {
  Path    []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类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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的类型变量,赋值过程如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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()运行

1
2
3
4
5
6
7
8
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:

1
2
3
4
5
6
7
8
9
10
11
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,如下所示:

1
2
3
4
5
6
7
8
9
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时 ------------------------------------------------------------------------

NetConf的数据结构如下所示:

1
2
3
4
5
6
7
8
9
10
11
type NetConf struct {
 
  types.NetConf      // 各个plugin的公共部分
  BrName    string
  IsGW      bool
  IsDefaultGW  bool
  ForceAddress   bool
  IPMasq     bool
  MTU       int
  HairpinMode  bool
}

  

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

(1)、首先调用n, err := loadNetConf(args.StdinData),加载获得配置文件

(2)、调用br, err := setupBridge(n)建立网桥

(3)、调用netns, err := ns.GetNS(args.Netns), 返回一个接口NetNS,其中最核心的内容就是打开的net ns的*os.File

(4)、调用setupVeth(netns, br, args.IfName, n.MUT, n.HairpinMode)

(5)、调用result, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData),运行IPAM插件,返回配置

(6)、当result.IP4.Gateway == nil 并且n.IsGW为true时,调用result.IP4.Gateway = calcGatewayIP(&result.IP4.IP)

(7)、调用netns.Do(),其中填充函数,该函数的执行流程如下:

    一、当n.IsDefaultGW为true时,首先调用_, defaultNet, err := net.parseCIDR("0.0.0.0/0"),之后在遍历result.IP4.Routes,判断是否同时设置了isDefaultGateway为true以及IPAM设置了默认的路由,最后扩展result.IP4.Routes

    二、调用ipam.ConfigureIface(args.IfName, result)和ip.SetHWAddrByIP(args.IfName, result.IP4.IP.IP, nil)

(8)、当n.IsGW为true,设置变量gwn := &net.IPNet{ IP: result.IP4.Gateway, Mask: result.IP4.IP.Mask},接着再依次调用ensureBridgeAddr(br, gwn, n.ForceAddress),ip.SetHWAddrByIP(n.BrName, gwn.IP, nil)和ip.EnableIP4Forward()

(9)、当n.IPMasq为true时,调用chain := utils.FormatChainName(n.Name, args.ContainerID),comment := utils.FormatComment(n.Name, args.ContainerID),最后再调用ip.SetupIPMasq(ip.Network(&result.IP4.IP), chain, comment)

(10)、最后设置result.DNS = n.DNS并返回return result.Print()

 

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

func setupBridge(n *NetConf) (*netlink.Bridge, error)

该函数的作用仅仅是调用br, err := ensureBridge(n.BrName, n.MTU),并返回return br, nil

 

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

func ensureBridge(brName, mtu int) (*netlink.Bridge, error)

(1)、首先创建网桥的数据结构:

1
2
3
4
5
6
7
8
9
10
br := &netlink.Bridge{
 
  LinkAttrs : netlink.LinkAttrs{
 
    Name:  brName,
    MTU:    mtu,
    TxQlen:   -1, // means use TX queue length as the default packet limit
  },
 
}

  

(2)、调用netlink.LinkAdd(br),添加网桥,如果报错,且错误不是设备已经存在了,那么返回错误。如果问题是设备已经存在,并且配置是相同的,那么没有问题。

(3)、调用netlink.LinkSetUp(br),启动网桥,并返回return br, nil

 

------------------------------------------------------------------------ veth 的配置 ------------------------------------------------------------------------------ 

 

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

func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool) error

(1)、首先调用函数netns.Do(),其中封装的函数的作用为创建一个veth对,并且将host端放入host的namespace,其中主要调用了hostVeth, _, err := ip.SetupVeth(ifName, mtu, hostNS) 和 hostVethName = hostVeth.Attrs().Name

(2)、随着namespace的移动,hostVeth的索引已经移动了,因此需要调用hostVeth, err := netlink.LinkByName(hostVethName)找回hostVeth

(3)、调用netlink.LinkSetMaster(hostVeth, br)将host端的veth和bridge相连

(4)、调用netlink.LinkSetHairpin(hostVeth, hairpinMode)来设置hairpin mode

 

5、cni/pkg/link.go

// SetupVeth sets up a virtual ethernet link.

// Should be in container netns, and will switch back to hostNS to set the host veth end

func SetupVeth(convVethName string, mtu int, hostNS net.NetNS) (hostVeth, contVeth netlink.Link, err error)

(1) 、首先调用hostVethName, contVeth, err = makeVeth(contVethName, mtu)创建veth对

(2)、调用netlink.LinkSetUp(contVeth)

(3)、调用hostVeth, err = netlink.LinkByName(hostVethName)

(4)、调用netlink.LinkSetNsFd(hostVeth, int(hostNS.Fd())) --> move veth to host netns

(5)、最后调用hostNS.Do(func() error{...}),函数中调用hostVeth, err := netlink.LinkByName(hostVethName)和netlink.LinkSetUp(hostVeth),激活host端的veth

 

5、cni/pkg/ipam/ipam.go

func ExecAdd(plugin, netconf []byte) (*types.Result, error)

该函数仅仅调用 return invoke.DelegateAdd(plugin, netconf)

Result 结构如下所示:

1
2
3
4
5
6
type Result struct {
 
  IP4  *IPConfig
  IP6  *IPConfig
  DNS   DNS
}

  

IPConfig 的结构如下

1
2
3
4
5
6
type IPConfig struct {
 
  IP    net.IPNet
  Gateway net.IP
  Routes  []Route
}

  


0 0
原创粉丝点击