美文网首页
k8s flannel cni plugin

k8s flannel cni plugin

作者: shoyu666 | 来源:发表于2022-03-17 15:41 被阅读0次

    k8s cni
    前面说到CRI负责 CNI的调用,以containerd为例子,sandbox_run.go 的 RunPodSandbox 函数会调用 setupPodNetwork 配置网络。
    本文主要结合 flannel -cni 介绍 setupPodNetwork 背后的cni逻辑。

    CNI

    //pkg/cri/server/sandbox_run.go
    func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandboxRequest) (_ *runtime.RunPodSandboxResponse, retErr error) {
    
    // Create initial internal sandbox object.
        sandbox := sandboxstore.NewSandbox(
            sandboxstore.Metadata{
                ID:             id,
                Name:           name,
                Config:         config,
                RuntimeHandler: r.GetRuntimeHandler(),
            },
            sandboxstore.Status{
                State: sandboxstore.StateUnknown,
            },
        )
    }
    sandbox.NetNS, err = netns.NewNetNS(netnsMountDir)
    if err := c.setupPodNetwork(ctx, &sandbox); err != nil
    

    先创建了一个sandbox(对应Pod),然后创建了一个 net namespace(linux namespaces隔离网络),调用 setupPodNetwork 设置网络

    //pkg/cri/server/sandbox_run.go
    func (c *criService) setupPodNetwork(ctx context.Context, sandbox *sandboxstore.Sandbox) error {
        var (
            id        = sandbox.ID
            config    = sandbox.Config
            path      = sandbox.NetNSPath
            netPlugin = c.getNetworkPlugin(sandbox.RuntimeHandler)
        )
        result, err := netPlugin.Setup(ctx, id, path, opts...)
    }
    

    获取CNI插件,然后调用插件设置。

    //pkg/cri/server/sandbox_run.go
    //获取插件
    func (c *criService) getNetworkPlugin(runtimeClass string) cni.CNI {
        i, ok := c.netPlugin[runtimeClass]
    }
    

    所有插件是保存在一个 map里,map是如何初始化的?

    //pkg/cri/server/service_linux.go
    func (c *criService) initPlatform() (err error) {
    i, err := cni.New(cni.WithMinNetworkCount(networkAttachCount),
                cni.WithPluginConfDir(dir),
                cni.WithPluginMaxConfNum(max),
                cni.WithPluginDir([]string{c.config.NetworkPluginBinDir}))
                c.netPlugin[name] = i
    }
    
    //containerd/go-cni/cni.go
    func New(config ...Opt) (CNI, error) {
        cni := defaultCNIConfig()
    
    func defaultCNIConfig() *libcni {
        return &libcni{
            config: config{
                pluginDirs:       []string{DefaultCNIDir},
                pluginConfDir:    DefaultNetDir,
                pluginMaxConfNum: DefaultMaxConfNum,
                prefix:           DefaultPrefix,
            },
            cniConfig: cnilibrary.NewCNIConfig(
                []string{
                    DefaultCNIDir,
                },
                &invoke.DefaultExec{
                    RawExec:       &invoke.RawExec{Stderr: os.Stderr},
                    PluginDecoder: version.PluginDecoder{},
                },
            ),
            networkCount: 1,
        }
    }
    

    Setup的调用

    ////containerd/go-cni/cni.go
    // Setup setups the network in the namespace and returns a Result
    func (c *libcni) Setup(ctx context.Context, id string, path string, opts ...NamespaceOpts) (*Result, error) {
        if err := c.Status(); err != nil {
            return nil, err
        }
            //创建 ns对象
        ns, err := newNamespace(id, path, opts...)
        if err != nil {
            return nil, err
        }
            //attachNetworks
        result, err := c.attachNetworks(ctx, ns)
        if err != nil {
            return nil, err
        }
        return c.createResult(result)
    }
    
    
    func (c *libcni) attachNetworks(ctx context.Context, ns *Namespace) ([]*types100.Result, error) {
        var wg sync.WaitGroup
        var firstError error
        results := make([]*types100.Result, len(c.Networks()))
        rc := make(chan asynchAttachResult)
     
           //遍历 NetWork
        for i, network := range c.Networks() {
            wg.Add(1)
                    //attach NetWork
            go asynchAttach(ctx, i, network, ns, &wg, rc)
        }
    }
    
    func asynchAttach(ctx context.Context, index int, n *Network, ns *Namespace, wg *sync.WaitGroup, rc chan asynchAttachResult) {
        defer wg.Done()
        r, err := n.Attach(ctx, ns)
        rc <- asynchAttachResult{index: index, res: r, err: err}
    }
    
    func (n *Network) Attach(ctx context.Context, ns *Namespace) (*types100.Result, error) {
        r, err := n.cni.AddNetworkList(ctx, n.config, ns.config(n.ifName))
        if err != nil {
            return nil, err
        }
        return types100.NewResultFromResult(r)
    }
    

    SetUp遍历 Networks ,add 每个Network

    github.com/containernetworking/cni/libcni/api.go// AddNetworkList executes a sequence of plugins with the ADD command
    func (c *CNIConfig) AddNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
        var err error
        var result types.Result
        for _, net := range list.Plugins {
            result, err = c.addNetwork(ctx, list.Name, list.CNIVersion, net, result, rt)
            if err != nil {
                return nil, fmt.Errorf("plugin %s failed (add): %w", pluginDescription(net.Network), err)
            }
        }
        return result, nil
    }
    
    
    func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) {
        return invoke.ExecPluginWithResult(ctx, pluginPath, newConf.Bytes, c.args("ADD", rt), c.exec)
    }
    

    addNetwork 实际就是调用 DefaultCNIDir = "/opt/cni/bin" 目录下的CNI插件实际实现(bridge,flannel等)
    参数 NetworkConfigList 到 NetworkConfig 的关系如下


    image.png

    上面还都是是containerd 中

    flannel-cni

    flannel 实现了 CNI 插件

    //flannel.go
    func main() {
        fullVer := fmt.Sprintf("CNI Plugin %s version %s (%s/%s) commit %s built on %s", Program, Version, runtime.GOOS, runtime.GOARCH, Commit, buildDate)
        skel.PluginMain(cmdAdd, cmdCheck, cmdDel, cni.All, fullVer)
    }
    

    可以看到CNI 的标准实现,cmdAdd,cmdCheck....,我们分析 cmdAdd

    func cmdAdd(args *skel.CmdArgs) error {
            //加载 NetworkConfig  配置,设置默认的 SubnetFile(/run/flannel/subnet.env)
        n, err := loadFlannelNetConf(args.StdinData)
            //加载 /run/flannel/subnet.env
            fenv, err := loadFlannelSubnetEnv(n.SubnetFile)
           return doCmdAdd(args, n, fenv)
    }
    
    //run/flannel/subnet.env 文件内容示例
    FLANNEL_NETWORK=10.244.0.0/16
    FLANNEL_SUBNET=10.244.0.1/24 
    FLANNEL_MTU=1450
    FLANNEL_IPMASQ=true
    

    /run/flannel/subnet.env 文件是flannel 启动时输出,用于后面的配置转换。

    //flannel_linux.go
    func doCmdAdd(args *skel.CmdArgs, n *NetConf, fenv *subnetEnv) error {
        return delegateAdd(args.ContainerID, n.DataDir, n.Delegate)
    }
    
    //flannel.go
    func delegateAdd(cid, dataDir string, netconf map[string]interface{}) error {
        if err = saveScratchNetConf(cid, dataDir, netconfBytes); err != nil {
            return err
        }
        result, err := invoke.DelegateAdd(context.TODO(), netconf["type"].(string), netconfBytes, nil)
    }
    

    flannel cni插件会做配置转换,并调用转换后的插件。

    //saveScratchNetConf + un/flannel/subnet.env 的内容
    {
        "name": "mynet",
        "type": "flannel"
    }
    
    FLANNEL_NETWORK=10.244.0.0/16
    FLANNEL_SUBNET=10.244.0.1/24 
    FLANNEL_MTU=1450
    FLANNEL_IPMASQ=true
    
    转换为
    {
        "name": "mynet",
        "type": "bridge",
        "mtu": 1472,
        "ipMasq": false,
        "isGateway": true,
        "ipam": {
            "type": "host-local",
            "subnet": "10.244.0.1/24"
        }
    }
    

    bridge 插件完成后,运行在host上的 flannel 接管了网络。

    上面 flannel cni插件会做配置转换 很重要,flannel cni插件通过重新调用转换后的配置文件的插件来配置网络(比如bridge)。

    相关文章

      网友评论

          本文标题:k8s flannel cni plugin

          本文链接:https://www.haomeiwen.com/subject/sxfpdrtx.html