LoginSignup
3
1

More than 5 years have passed since last update.

CNCF CNI プラグイン Goサンプルコード

Last updated at Posted at 2018-08-01

CNCF CNI プラグインでCNIのサンプルコードをBashで用意したが、実際にCNIプラグインを書き始めたいという人でbashスクリプトで書く人はいないと思うのでGoのサンプルコードも用意した。

公式のサンプルコード

CNI公式のリポジトリpluginsの中にsampleコードがある。

package main

import (
    "encoding/json"
    "fmt"
    "net"

    "github.com/containernetworking/cni/pkg/skel"
    "github.com/containernetworking/cni/pkg/types"
    "github.com/containernetworking/cni/pkg/types/current"
    "github.com/containernetworking/cni/pkg/version"
)

// PluginConf is whatever you expect your configuration json to be. This is whatever
// is passed in on stdin. Your plugin may wish to expose its functionality via
// runtime args, see CONVENTIONS.md in the CNI spec.
type PluginConf struct {
    types.NetConf // You may wish to not nest this type
    RuntimeConfig *struct {
        SampleConfig map[string]interface{} `json:"sample"`
    } `json:"runtimeConfig"`

    // This is the previous result, when called in the context of a chained
    // plugin. Because this plugin supports multiple versions, we'll have to
    // parse this in two passes. If your plugin is not chained, this can be
    // removed (though you may wish to error if a non-chainable plugin is
    // chained.
    // If you need to modify the result before returning it, you will need
    // to actually convert it to a concrete versioned struct.
    RawPrevResult *map[string]interface{} `json:"prevResult"`
    PrevResult    *current.Result         `json:"-"`

    // Add plugin-specifc flags here
    MyAwesomeFlag     bool   `json:"myAwesomeFlag"`
    AnotherAwesomeArg string `json:"anotherAwesomeArg"`
}

// parseConfig parses the supplied configuration (and prevResult) from stdin.
func parseConfig(stdin []byte) (*PluginConf, error) {
    conf := PluginConf{}

    if err := json.Unmarshal(stdin, &conf); err != nil {
        return nil, fmt.Errorf("failed to parse network configuration: %v", err)
    }

    // Parse previous result. Remove this if your plugin is not chained.
    if conf.RawPrevResult != nil {
        resultBytes, err := json.Marshal(conf.RawPrevResult)
        if err != nil {
            return nil, fmt.Errorf("could not serialize prevResult: %v", err)
        }
        res, err := version.NewResult(conf.CNIVersion, resultBytes)
        if err != nil {
            return nil, fmt.Errorf("could not parse prevResult: %v", err)
        }
        conf.RawPrevResult = nil
        conf.PrevResult, err = current.NewResultFromResult(res)
        if err != nil {
            return nil, fmt.Errorf("could not convert result to current version: %v", err)
        }
    }
    // End previous result parsing

    // Do any validation here
    if conf.AnotherAwesomeArg == "" {
        return nil, fmt.Errorf("anotherAwesomeArg must be specified")
    }

    return &conf, nil
}

// cmdAdd is called for ADD requests
func cmdAdd(args *skel.CmdArgs) error {
    conf, err := parseConfig(args.StdinData)
    if err != nil {
        return err
    }

    if conf.PrevResult == nil {
        return fmt.Errorf("must be called as chained plugin")
    }

    // This is some sample code to generate the list of container-side IPs.
    // We're casting the prevResult to a 0.3.0 response, which can also include
    // host-side IPs (but doesn't when converted from a 0.2.0 response).
    containerIPs := make([]net.IP, 0, len(conf.PrevResult.IPs))
    if conf.CNIVersion != "0.3.0" {
        for _, ip := range conf.PrevResult.IPs {
            containerIPs = append(containerIPs, ip.Address.IP)
        }
    } else {
        for _, ip := range conf.PrevResult.IPs {
            if ip.Interface == nil {
                continue
            }
            intIdx := *ip.Interface
            // Every IP is indexed in to the interfaces array, with "-1" standing
            // for an unknown interface (which we'll assume to be Container-side
            // Skip all IPs we know belong to an interface with the wrong name.
            if intIdx >= 0 && intIdx < len(conf.PrevResult.Interfaces) && conf.PrevResult.Interfaces[intIdx].Name != args.IfName {
                continue
            }
            containerIPs = append(containerIPs, ip.Address.IP)
        }
    }
    if len(containerIPs) == 0 {
        return fmt.Errorf("got no container IPs")
    }

    // Pass through the result for the next plugin
    return types.PrintResult(conf.PrevResult, conf.CNIVersion)
}

// cmdDel is called for DELETE requests
func cmdDel(args *skel.CmdArgs) error {
    conf, err := parseConfig(args.StdinData)
    if err != nil {
        return err
    }
    _ = conf

    // Do your delete here

    return nil
}

func main() {
    // TODO: implement plugin version
    skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
}

func cmdGet(args *skel.CmdArgs) error {
    // TODO: implement
    return fmt.Errorf("not implemented")
}

上記はシンプルだが、Chainプラグインの2番目以降のプラグインとして呼び出されることを前提としてコードになっている。prevResult関連の処理CNIバージョンに依存したコンテナIPの処理などが少し複雑。

Goで書いた最もシンプルなCNIサンプルコード

cnisampleリポジトリに実際に動くサンプルコードを置いた。


package main

import (
    "github.com/containernetworking/cni/pkg/skel"
    "github.com/containernetworking/cni/pkg/types"
    "github.com/containernetworking/cni/pkg/types/current"
    "github.com/containernetworking/cni/pkg/version"
)

// cmdAdd is called for ADD requests
func cmdAdd(args *skel.CmdArgs) error {
    return types.PrintResult(&current.Result{}, "0.4.0")
}

// cmdDel is called for DELETE requests
func cmdDel(args *skel.CmdArgs) error {
    return nil
}

func main() {
    skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "")
}

func cmdGet(args *skel.CmdArgs) error {
    return types.PrintResult(&current.Result{}, "0.4.0")
}

見てわかる通り特に何もしない。CNIオペレーションのためのコマンド(ADD, DEL, GET)しかCNI設定を活用していないし、ネットワーク設定も読み込むことすらしていない。ちゃんとしたものを書くなら前章の公式のコードを改良していくほうが良いかもしれない。上記のコード上でcniリポジトリのパッケージをいくつも利用しているが大したことはしてない、ただここから膨らませていくなら便利ではあるのでサンプルコードからそのまま流用した。cmdAddで処理を実装したらcurrent.Result{}に処理結果を入れてPrintResultに渡す。

$ go get github.com/containernetworking/cni/cnitool
$ sudo ip netns add testing
$ git clone https://github.com/hichihara/cnisample
$ cd cnisample/go/noop
$ mkdir bin
$ go build -o ./bin/noop
$ sudo CNI_PATH=./bin NETCONFPATH=./netconf cnitool add mysample /var/run/netns/testing
{
    "cniVersion": "0.4.0",
    "dns": {}
}

Cleanup

$ sudo ip netns del testing

次回は実際に利用価値のありそうなプラグインを試しに書いてみて記事にしたい(アイデアはもうある)。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1