cni 添加网络 流程分析

19 篇文章 3 订阅
17 篇文章 0 订阅

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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
multus-cni 是一个 Kubernetes 的多网络 CNI 插件,它是 Kubernetes 上负责多网络管理的代表性插件之一。它允许 Kubernetes 集群中的每个 Pod 拥有多个网络接口,并能针对每个网络接口对应的网络策略和路由进行不同的配置。 multus-cni 的源码主要分为三部分:CNI 插件相关代码、配置文件相关代码和网络资源相关代码。CNI 插件相关代码包括主程序 main.go、CNI 配置解析器 conf.go、IPAM 相关代码和网络审计相关代码。配置文件相关代码包括 multus.conf 和各种 JSON/YAML 配置文件的解析器。网络资源相关代码主要负责通过 Kubernetes API 获取和管理 Pod、NetworkAttachmentDefinition 和 Service 等网络资源信息。 multus-cni 的核心是 CNI 插件相关代码中的 main.go,它主要负责 CNI 插件的初始化和执行。CNI 插件的执行流程大概可以总结为如下三步:首先,multus-cni 解析 CNI 配置文件并获取 Pod 相关的网络资源信息;接着,multus-cni 调用下层 CNI 插件(比如 flannel、calico、ovs 等)完成网络接口的创建和配置;最后,multus-cni 继续执行其他 CNI 插件(比如 ipvlan、macvlan、bridge 等)完成其他网络接口的创建和配置。 此外,multus-cni 通过 Kubernetes API 获取和管理 NetworkAttachmentDefinition 和 Service 等网络资源信息。在 Kubernetes 中,NetworkAttachmentDefinition 用于定义和配置网络接口,而 Service 用于定义和管理 Kubernetes 集群中的服务。multus-cni 通过获取、解析和应用这些网络资源信息,实现了多网络的管理和配置。 总的来说,multus-cni 是一个非常优秀的多网络 CNI 插件,它利用 Kubernetes API 实现了多网络的管理和配置,并同时支持插件化扩展。它的源码比较清晰,适合对 Kubernetes 网络原理比较熟悉的开发者学习和探究。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值