ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

CNI开发的基本套路

2021-05-17 17:55:52  阅读:203  来源: 互联网

标签:string err 套路 IP json 开发 IPAM CNI


背景

CNI 这东西搞容器的运维想必都不陌生,要知道,kubernetes的设计之初是不包含网络的,网络这玩意每家公司有每家公司自己的玩法,对各个公司来说,没有那种大一统的完美方案,只有最适合自己的方案,所以,kubernetes在设计的时候,没有设计统一的网络方案,只提供了统一的容器网络接口,Container Network Interface,也就是所谓的CNI。

CNI 和 IPAM

一般说的CNI都是包含IPAM的,但其实2个功能是分开实现的。

CNI用于实现网络构建(network部分,网络接口创建,VLAN划分,端口打开关闭等等)。

IPAM用于实现IP地址,DNS,网关信息的分配。

这两者是可以自由组合的,比如你可以用flannel的CNI,IPAM却用DHCP或者HOSTLOCAL,甚至如果不想用IPAM的接口,自己在CNI里也可以实现对于的代码逻辑。


CNI创建流程

创建流程可以参考官方的源码,以官方的bridge的CNI代码进行分析,在一个POD的生命周期中,CNI主要有3个方法进行调用,分别是:

  • cmdadd
  • cmdcheck
  • cmddel
func main() {

    skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("bridge"))

}

看方法名字大概能猜到干了啥吧,创建容器调用cmdadd, 销毁cmddel, cmdcheck是0.4.0之后新加的,比如粗暴的del,所以在del前会做一些check,check内容可以根据需要自己定义.

先画一下基本的创建流程

graph TD;
 kubelet调用CNI-->获取创建基本参数;
 获取创建基本参数-->解析参数;
 解析参数-->获取ns,podname;
 获取ns,podname-->解析net.d下配置文件;
 解析net.d下配置文件-->netlink配置端口;
 netlink配置端口-->调用ipam;
 调用ipam-->分配IP,DNS,GW;
 分配IP,DNS,GW-->传值CNI;
  传值CNI-->配置IP;
  配置IP-->返回kubelet;

大致流程如上,每个CNI插件基本都会遵循以上流程进行网络的创建,只是不同插件会在上述流程上添加不同的动作,比如bridge会基于netlink创建网桥,vlan会分vlanID等等

代码分析(以bridge为例):

解析参数:cni创建网络一般会从2个渠道获取参数,一个是创建时master传过来的args.Args,是个很长的字符串,一般会包含namespace信息,podname信息等,一个是配置文件传入的args.StdinData,基于配置文件,结构体如下:

type NetConf struct {
    types.NetConf
    BrName       string `json:"bridge"`
    IsGW         bool   `json:"isGateway"`
    IsDefaultGW  bool   `json:"isDefaultGateway"`
    ForceAddress bool   `json:"forceAddress"`
    IPMasq       bool   `json:"ipMasq"`
    MTU          int    `json:"mtu"`
    HairpinMode  bool   `json:"hairpinMode"`
    PromiscMode  bool   `json:"promiscMode"`
    Vlan         int    `json:"vlan"`
}

对比配置文件:

{
    "cniVersion": "0.3.1",    "name": "mynet",    "type": "bridge",    "bridge": "mynet0",    "isDefaultGateway": true,    "forceAddress": false,    "ipMasq": true,    "hairpinMode": true,    "ipam": {
        "type": "host-local",        "subnet": "10.10.0.0/16"
    }}

再看一下types.NetConf:

// NetConf describes a network.
type NetConf struct {
    CNIVersion string `json:"cniVersion,omitempty"`
    Name         string          `json:"name,omitempty"`
    Type         string          `json:"type,omitempty"`
    Capabilities map[string]bool `json:"capabilities,omitempty"`
    IPAM         IPAM            `json:"ipam,omitempty"`
    DNS          DNS             `json:"dns"`
    RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
    PrevResult    Result                 `json:"-"`
}

type IPAM struct {
    Type string `json:"type,omitempty"`
}

// DNS contains values interesting for DNS resolvers
type DNS struct {
    Nameservers []string `json:"nameservers,omitempty"`
    Domain      string   `json:"domain,omitempty"`
    Search      []string `json:"search,omitempty"`
    Options     []string `json:"options,omitempty"`
}

// Result is an interface that provides the result of plugin execution
type Result interface {
    // The highest CNI specification result version the result supports
    // without having to convert
    Version() string
    // Returns the result converted into the requested CNI specification
    // result version, or an error if conversion failed
    GetAsVersion(version string) (Result, error)
    // Prints the result in JSON format to stdout
    Print() error
    // Prints the result in JSON format to provided writer
    PrintTo(writer io.Writer) error
}

其中类似IsGW,IPMasq之类的均是自定义的变量,暂时可以忽略,主要需要注意的是cniVersion,name,IPAM之类的通用变量.

创建网络:以bridge为例,基本都是调用netlink 进行创建

func setupBridge(n *NetConf) (*netlink.Bridge, *current.Interface, error) {
    vlanFiltering := false
    if n.Vlan != 0 {
        vlanFiltering = true
    }
    // create bridge if necessary
    br, err := ensureBridge(n.BrName, n.MTU, n.PromiscMode, vlanFiltering)
    if err != nil {
        return nil, nil, fmt.Errorf("failed to create bridge %q: %v", n.BrName, err)
    }
    return br, &current.Interface{
        Name: br.Attrs().Name,
        Mac:  br.Attrs().HardwareAddr.String(),
    }, nil
}

IP分配: ip分配基于ipam进行分配,和cni 一样,同样有cmdadd, cmddel, cmdcheck等方法,ipam之后再分析,这边只要知道ipam分配出IP即可。

// run the IPAM plugin and get back the config to apply
        r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
        if err != nil {
            return err
        }
        // release IP in case of failure
        defer func() {
            if !success {
                ipam.ExecDel(n.IPAM.Type, args.StdinData)
            }
        }()

IP配置: CNI的工作除了在Node节点上创建适配的网络以外,主要还要在pod所在的pasue container上进行IP的分配,DNS以及路由的配置,由于pause container 享有独立的namespace,所以需要需要在指定的ns下进行IP配置, 官方提供了netns .Do 可以帮助进行操作

pod 由于需要经常销毁,创建,对于网络来说arp刷新很频繁,一般交换机都有mac 表的老化时间,所以在配置IP后,使用arping 手动刷新arp 表。

// Send a gratuitous arp
        if err := netns.Do(func(_ ns.NetNS) error {
            contVeth, err := net.InterfaceByName(args.IfName)
            if err != nil {
                return err
            }
            for _, ipc := range result.IPs {
                if ipc.Version == "4" {
                    _ = arping.GratuitousArpOverIface(ipc.Address.IP, *contVeth)
                }
            }
            return nil
        }); err != nil {
            return err
        }

所以编写一个自定义的CNI并不困难,遵循基本的套路,然后再套路里加上自己的业务逻辑,就可以实现符合自己业务需求的K8S网络模型。


标签:string,err,套路,IP,json,开发,IPAM,CNI
来源: https://blog.51cto.com/u_2010293/2781931

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有