はじめに
「体験しながら学ぶネットワーク管理技術入門」という本
のテストネットワークは、tinet
で試すようになっています。
これをContainerlab
で試すための変換ツールを開発しました。
そのツールをここで紹介します。
tinetとcontainerlabのネットワーク定義
tinetのネットワーク定義は、
spec.yaml
---
nodes:
- name: R1
image: tinynetwork/pmacctd:develop
interfaces:
- { name: net0, type: direct, args: R2#net0 }
- name: R2
image: tinynetwork/pmacctd:develop
interfaces:
- { name: net0, type: direct, args: R1#net0 }
node_configs:
- name: R1
cmds:
- cmd: ip addr add 10.2.0.1/24 dev net0
- name: R2
cmds:
- cmd: ip addr add 10.2.0.2/24 dev net0
- cmd: ip route add default via 10.2.0.1
のようにnodeとnodeで実行する設定コマンドで構成されます。
この定義をcontainerlabの
topology.yaml
name: test
topology:
nodes:
R1:
kind: linux
image: tinynetwork/pmacctd:develop
network-mode: none
exec:
- ip addr add 10.2.0.1/24 dev net0
R2:
kind: linux
image: tinynetwork/pmacctd:develop
network-mode: none
exec:
- ip addr add 10.2.0.2/24 dev net0
- ip route add default via 10.2.0.1
links:
- endpoints: ["R1:net0","R2:net0"]
のように変更します。
変換のポイント
変換処理のポイントは
- nameにネットワーク名を設定
- topologyの配下にnodesとlinksを配置
- nodesは名前をキーに設定
- kindをlinuxに設定
- imageは同じ値を設定
- network-modeをnoneに設定
- node_configsの設定コマンドはノード単位のexecに設定
- nodeのinterfacesから接続関係のlinksを作成
- mountはbindに変更
- dnsの設定も以降
です。
変換ツール
変換処理のポイントを元に変換ツールをGo言語で作成しました。
$go build -o tinet2clab .
$cat spec.yaml | tinet2clab > topology.yaml
のように変換します。
「体験しながら学ぶネットワーク管理技術入門」のspec_01.yamlも変換できました。
変換ツールのソースコードは、以下の通りです。
main.go
package main
import (
"fmt"
"log"
"os"
"strings"
"github.com/spf13/viper"
)
func main() {
c, err := loadTinetSpec()
if err != nil {
log.Fatalln(err)
}
err = saveClabTopology(c)
if err != nil {
log.Fatalln(err)
}
}
type Tn struct {
PreCmd []PreCmd `yaml:"precmd"`
PreInit []PreInit `yaml:"preinit"`
PreConf []PreConf `yaml:"preconf"`
PostInit []PostInit `yaml:"postinit"`
PostFini []PostFini `yaml:"postfini"`
Nodes []Node `yaml:"nodes" mapstructure:"nodes"`
Switches []Switch `yaml:"switches" mapstructure:"switches"`
NodeConfigs []NodeConfig `yaml:"node_configs" mapstructure:"node_configs"`
Test []Test `yaml:"test"`
}
// PreCmd
type PreCmd struct {
// Cmds []Cmd `yaml:"cmds"`
Cmds []Cmd `yaml:"cmds" mapstructure:"cmds"`
}
// PreInit
type PreInit struct {
Cmds []Cmd `yaml:"cmds" mapstructure:"cmds"`
}
// PreConf
type PreConf struct {
Cmds []Cmd `yaml:"cmds" mapstructure:"cmds"`
}
// PostInit
type PostInit struct {
Cmds []Cmd `yaml:"cmds" mapstructure:"cmds"`
}
// PostFini
type PostFini struct {
Cmds []Cmd `yaml:"cmds" mapstructure:"cmds"`
}
// Node
type Node struct {
Name string `yaml:"name" mapstructure:"name"`
Type string `yaml:"type" mapstructure:"type"`
NetBase string `yaml:"net_base" mapstructure:"net_base"`
VolumeBase string `yaml:"volume" mapstructure:"volume"`
Image string `yaml:"image" mapstructure:"image"`
BuildFile string `yaml:"buildfile" mapstructure:"buildfile"`
BuildContext string `yaml:"buildcontext" mapstructure:"buildcontext"`
Interfaces []Interface `yaml:"interfaces" mapstructure:"interfaces"`
Sysctls []Sysctl `yaml:"sysctls" mapstructure:"sysctls"`
Mounts []string `yaml:"mounts,flow" mapstructure:"mounts,flow"`
DNS []string `yaml:"dns,flow" mapstructure:"dns,flow"`
DNSSearches []string `yaml:"dns_search,flow" mapstructure:"dns_search,flow"`
HostNameIgnore bool `yaml:"hostname_ignore" mapstructure:"hostname_ignore"`
EntryPoint string `yaml:"entrypoint" mapstructure:"entrypoint"`
ExtraArgs string `yaml:"docker_run_extra_args" mapstructure:"docker_run_extra_args"`
Vars map[string]interface{} `yaml:"vars" mapstructure:"vars"`
Templates []Template `yaml:"templates" mapstructure:"templates"`
}
// Interface
type Interface struct {
Name string `yaml:"name"`
Type string `yaml:"type"`
Args string `yaml:"args"`
Addr string `yaml:"addr"`
Label string `yaml:"label"`
}
// Sysctl
type Sysctl struct {
Sysctl string `yaml:"string"`
}
type Template struct {
Src string `yaml:"src"`
Dst string `yaml:"dst"`
Content string `yaml:"content"`
}
// Switch
type Switch struct {
Name string `yaml:"name"`
Interfaces []Interface `yaml:"interfaces" mapstructure:"interfaces"`
}
// NodeConfig
type NodeConfig struct {
Name string `yaml:"name"`
Cmds []Cmd `yaml:"cmds" mapstructure:"cmds"`
}
// Cmd
type Cmd struct {
Cmd string `yaml:"cmd"`
}
// Test
type Test struct {
Name string
Cmds []Cmd `yaml:"cmds" mapstructure:"cmds"`
}
func loadTinetSpec() (Tn, error) {
var r Tn
var err error
viper.SetConfigType("yaml")
if err = viper.ReadConfig(os.Stdin); err != nil {
return r, err
}
err = viper.Unmarshal(&r)
return r, err
}
func saveClabTopology(c Tn) error {
name := "test"
if len(os.Args) > 1 {
name = os.Args[1]
}
fmt.Printf("name: %s\n", name)
fmt.Printf("topology:\n")
fmt.Printf(" nodes:\n")
for _, n := range c.Nodes {
fmt.Printf(" %s:\n", n.Name)
fmt.Printf(" kind: linux\n")
fmt.Printf(" image: %s\n", n.Image)
if n.NetBase == "" {
n.NetBase = "none"
}
fmt.Printf(" network-mode: %s\n", n.NetBase)
if len(n.DNS) > 0 {
fmt.Printf(" dns:\n")
fmt.Printf(" servers:\n")
for _, d := range n.DNS {
fmt.Printf(" - %s\n", d)
}
if len(n.DNSSearches) > 0 {
fmt.Printf(" search:\n")
for _, d := range n.DNSSearches {
fmt.Printf(" - %s\n", d)
}
}
}
if len(n.Mounts) > 0 {
fmt.Printf(" binds:\n")
for _, m := range n.Mounts {
fmt.Printf(" - %s\n", m)
}
}
for _, nc := range c.NodeConfigs {
if nc.Name == n.Name && len(nc.Cmds) > 0 {
fmt.Printf(" exec:\n")
for _, cmd := range nc.Cmds {
fmt.Printf(" - %s\n", strings.ReplaceAll(cmd.Cmd, "\\$", "$$"))
}
break
}
}
if n.BuildFile != "" {
buildContext := "."
if n.BuildContext != "" {
buildContext = n.BuildContext
}
fmt.Printf("#docker build -t %s -f %s %s\n", n.Image, n.BuildFile, buildContext)
}
if n.ExtraArgs != "" {
a := strings.Fields(n.ExtraArgs)
ports := []string{}
for i, p := range a {
if p == "-p" && i+1 < len(a) {
ports = append(ports, a[i+1])
}
}
if len(ports) > 0 {
fmt.Printf(" ports:\n")
for _, e := range ports {
fmt.Printf(" - %s\n", e)
}
}
}
}
linkMap := make(map[string]bool)
fmt.Printf(" links:\n")
for _, n := range c.Nodes {
for _, i := range n.Interfaces {
if i.Type == "direct" {
a := strings.SplitN(i.Args, "#", 2)
if len(a) == 2 {
k := fmt.Sprintf("%s:%s:%s:%s", a[0], a[1], n.Name, i.Name)
if _, ok := linkMap[k]; !ok {
fmt.Printf(" - endpoints: [\"%s:%s\",\"%s:%s\"]\n", n.Name, i.Name, a[0], a[1])
linkMap[k] = true
linkMap[fmt.Sprintf("%s:%s:%s:%s", n.Name, i.Name, a[0], a[1])] = true
}
}
}
}
}
return nil
}