cni 添加网络 流程分析
来源:互联网 发布:windows文件名最大长度 编辑:程序博客网 时间:2024/05/22 00:28
From http://www.cnblogs.com/YaoDD/p/6024535.html?utm_source=itdadao&utm_medium=referral
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
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
}
CNIConfig的数据结构如下所示:CNIconfig包含的是bridge,dhcp等等二进制文件的目录集
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类型:
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时 ------------------------------------------------------------------------
NetConf的数据结构如下所示:
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)、首先创建网桥的数据结构:
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 结构如下所示:
type
Result
struct
{
IP4 *IPConfig
IP6 *IPConfig
DNS DNS
}
IPConfig 的结构如下
type
IPConfig
struct
{
IP net.IPNet
Gateway net.IP
Routes []Route
}
- cni 添加网络 流程分析
- k8s网络插件cni
- CNI:容器网络接口
- CNI:容器网络接口详解
- Kubernetes基金会托管容器网络CNI
- Kubernetes网络插件CNI学习整理
- Kubernetes网络接口(CNI) midonet网络插件设计与实现
- VLC网络通信流程分析
- [网络编程]select流程分析
- VLC网络通信流程分析
- Memcached 源码分析--网络模型流程分析
- kubernetes容器网络接口(CNI) midonet网络插件的设计与实现
- kubernetes容器网络接口(CNI) midonet网络插件的设计与实现
- kubernetes容器网络接口(CNI) midonet网络插件的设计与实现
- Launcher3源码分析之Widget添加流程
- Fragment之添加显示流程源码分析
- FreeBSD 内核网络处理流程分析
- FreeBSD内核网络处理流程分析
- vector基本操作_迭代器遍历和种类
- Html5 Canvas 系列_绘图一
- Android下Pull生成xml与解析xml
- centos下安装visual studio code出现can't find libXss.so.1,出现这在类似怎么查找相关包
- Python中csv模块的用法
- cni 添加网络 流程分析
- [Mysql] Decimal用法
- 点滴故事-写在我个人网站的0岁生日
- Arrays:用于操作数组工具类,数组转集合,集合转数
- AIDL的理解与使用详解
- c++程序设计模式之factory模式
- 算法与游戏之AABB碰撞盒算法
- AndroidStudio 获取sha1
- Ubuntu Windows双系统切换技巧