自己动手写Docker系列 — 6.2创建网络

自己动手写Docker系列 — 6.2创建网络在前面的文章中,我们完成了一个IP管理和分配的工具类,能自动分配和回收子网的IP,本篇将继续上篇,开始网络的创建部分

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第16天,点击查看活动详情

简介

在前面的文章中,我们完成了一个IP管理和分配的工具类,能自动分配和回收子网的IP,本篇将继续上篇,开始网络的创建部分

源码说明

同时放到了Gitee和Github上,都可进行获取

本章节对应的版本标签是:6.2,防止后面代码过多,不好查看,可切换到标签版本进行查看

代码实现

主要是实现思路如下:

  • 创建网络:读取用户输入的子网,如何取第一个作为网关ip,创建虚拟网络(或者网卡);创建成功后,将网络信息序列化成JSON保存到本地,便于后序读取
  • 删除网络:根据输入的虚拟网络名,删除对应的网络

下面是具体的实现:

network命令新增

首先增加相关的命令操作:

新增network命令操作

func main() {
	......
	
	app.Commands = []cli.Command{
		command.InitCommand,
		command.RunCommand,
		command.CommitCommand,
		command.ListCommand,
		command.LogCommand,
		command.ExecCommand,
		command.StopCommand,
		command.RemoveCommand,
		command.NetworkCommand,
	}
	......
}

network 相关详细命令


var NetworkCommand = cli.Command{
	Name:  "network",
	Usage: "container network commands",
	Subcommands: []cli.Command{
		{
			Name:  "create",
			Usage: "create a container network",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "driver",
					Usage: "network driver",
				},
				cli.StringFlag{
					Name:  "subnet",
					Usage: "subnet cidr",
				},
			},
			Action: func(context *cli.Context) error {
				if len(context.Args()) < 1 {
					return fmt.Errorf("missing network name")
				}

				err := network.Init()
				if err != nil {
					return err
				}

				driver := context.String("driver")
				subnet := context.String("subnet")
				name := context.Args()[0]
				if err := network.CreateNetwork(driver, subnet, name); err != nil {
					return err
				}
				return nil
			},
		},

		{
			Name:  "list",
			Usage: "list container network",
			Action: func(context *cli.Context) error {
				if err := network.Init(); err != nil {
					return err
				}
				return network.ListNetwork()
			},
		},

		{
			Name:  "remove",
			Usage: "remove container network",
			Action: func(context *cli.Context) error {
				if len(context.Args()) < 1 {
					return fmt.Errorf("missing network name")
				}
				if err := network.Init(); err != nil {
					return err
				}
				if err := network.DeleteNetwork(context.Args()[0]); err != nil {
					return err
				}
				return nil
			},
		},
	},
}

如上,新增了创建新的网络、查看网络列表和删除网络的操作

创建新网络

在命令启动的时候,传入子网和虚拟网络名,我们根据这些去创建虚拟网络

func CreateNetwork(driver, subnet, name string) error {
	nw, err := drivers[driver].Create(subnet, name)
	if err != nil {
		return err
	}
	log.Infof("create network success")
	return nw.dump(defaultNetworkPath)
}

下面的网络信息:Network,做了一些小改造,增加了GatewayIP(网关IP)和Subnet(子网网段信息),保存了下来,因为使用IpRange字段,无法正常使用我们的IP回收操作

func (b *BridgeNetworkDriver) Create(subnet string, name string) (*NetWork, error) {
	_, ipRange, _ := net.ParseCIDR(subnet)
	// 得到网段的第一个IP作为网关IP
	ip, err := ipAllocator.Allocate(ipRange)
	if err != nil {
		return nil, err
	}
	ipRange.IP = ip
	n := &NetWork{
		Name:      name,
		IpRange:   ipRange,
		Driver:    b.Name(),
		GatewayIP: ip,
		Subnet:    subnet,
	}
	log.Infof("BridgeNetworkDriver creat network subnet: %s, gateway ip: %s", ipRange.String(), ip.String())
	// 初始化虚拟网络
	return n, b.initBridge(n)
}

下面是具体的虚拟化网络创建,基本是都是使用系统函数(博主目前还不是太懂这个,说不出东西了,先照抄吧……)

func (b *BridgeNetworkDriver) initBridge(n *NetWork) error {
	// try to get bridge by name, if it already exists then just exit
	bridgeName := n.Name
	if err := createBridgeInterface(bridgeName); err != nil {
		return err
	}
	log.Infof("createBridgeInterface success")

	// set bridge ip
	gatewayIp := *n.IpRange
	gatewayIp.IP = n.IpRange.IP

	if err := setInterfaceIp(bridgeName, gatewayIp.String()); err != nil {
		return err
	}
	log.Infof("setInterfaceIp success")

	if err := setInterfaceUp(bridgeName); err != nil {
		return err
	}
	log.Infof("crsetInterfaceUp success")

	if err := setupIpTables(bridgeName, n.IpRange); err != nil {
		return err
	}
	log.Infof("setInterfaceUp success")
	return nil
}

func createBridgeInterface(bridgeName string) error {
	_, err := net.InterfaceByName(bridgeName)
	if err == nil || !strings.Contains(err.Error(), "no such network interface") {
		return err
	}

	// create *netlink.Bridge object
	la := netlink.NewLinkAttrs()
	la.Name = bridgeName

	br := &netlink.Bridge{LinkAttrs: la}
	if err := netlink.LinkAdd(br); err != nil {
		return fmt.Errorf("bridge creating failed for bridge %s, err: %w", bridgeName, err)
	}
	return nil
}

func setInterfaceUp(interfaceName string) error {
	iface, err := netlink.LinkByName(interfaceName)
	if err != nil {
		return fmt.Errorf("error retrieving a link named [ %s ], err: %w", iface.Attrs().Name, err)
	}

	if err := netlink.LinkSetUp(iface); err != nil {
		return fmt.Errorf("error enabling interface for %s, err: %w", interfaceName, err)
	}
	return nil
}

func setInterfaceIp(name string, rawIp string) error {
	retries := 2
	var iface netlink.Link
	var err error
	for i := 0; i < retries; i++ {
		iface, err = netlink.LinkByName(name)
		if err == nil {
			break
		}
		log.Debugf("error retrieving new bridge netlink link [ %s ]... retrying", name)
		time.Sleep(2 * time.Second)
	}
	if err != nil {
		return fmt.Errorf("abandoning retrieving the new bridge ink from netlink, run [ip link] to troubleshoot the err: %w", err)
	}

	ipNet, err := netlink.ParseIPNet(rawIp)
	if err != nil {
		return fmt.Errorf("netlink.ParseIPNet err: %w", err)
	}

	addr := &netlink.Addr{
		IPNet:     ipNet,
		Peer:      ipNet,
		Label:     "",
		Flags:     0,
		Scope:     0,
		Broadcast: nil,
	}
	return netlink.AddrAdd(iface, addr)
}

func setupIpTables(bridgeName string, subnet *net.IPNet) error {
	iptablesCmd := fmt.Sprintf("-t nat -A POSTROUTING -s %s ! -o %s -j MASQUERADE", subnet.String(), bridgeName)
	cmd := exec.Command("iptables", strings.Split(iptablesCmd, " ")...)
	output, err := cmd.Output()
	if err != nil {
		return fmt.Errorf("iptablse err %v, %w", output, err)
	}
	return nil
}

上面就是虚拟网络的创建相关代码

查看已存在的虚拟网络

在查看的时候,需要初始化网络配置信息,及Init函数

我们写了一个网络配置信息的固定位置,所有的网络配置文件都会以其名称,存放到下面,网络驱动和配置也会加载到内存中

var (
	// 网络配置的默认存放位置
	defaultNetworkPath = "/var/run/mydocker/network/network/"
	// 不同网络的网络驱动
	drivers            = map[string]NetworkDriver{}
	// 不同网络的网络配置
	networks           = map[string]*NetWork{}
)

网络信息初始化的基本原理就是读取网络配置默认存放文件夹,加载所有的网络配置即可:

func Init() error {
	var bridgeDriver = BridgeNetworkDriver{}
	drivers[bridgeDriver.Name()] = &bridgeDriver

	if _, err := os.Stat(defaultNetworkPath); err != nil {
		if os.IsNotExist(err) {
			_ = os.MkdirAll(defaultNetworkPath, 0644)
		} else {
			return err
		}
	}

	_ = filepath.Walk(defaultNetworkPath, func(nwPath string, info os.FileInfo, err error) error {
		if strings.HasSuffix(nwPath, "/") {
			return nil
		}
		_, nwName := path.Split(nwPath)
		nw := &NetWork{
			Name: nwName,
		}

		if err := nw.load(nwPath); err != nil {
			log.Errorf("error load network: %v", err)
		}

		networks[nwName] = nw
		return nil
	})
	return nil
}

查看网络配置就比较简单了,遍历打印即可:

func ListNetwork() error {
	w := tabwriter.NewWriter(os.Stdout, 12, 1, 3, ' ', 0)
	_, _ = fmt.Fprint(w, "NAME\tIpRange\tDriver\n")
	for _, nw := range networks {
		fmt.Fprintf(w, "%s\t%s\t\t%s\n",
			nw.Name,
			nw.IpRange.String(),
			nw.Driver,
		)
	}
	if err := w.Flush(); err != nil {
		return fmt.Errorf("flush error: %w", err)
	}
	return nil
}

删除网络

删除网络就是删除相关虚拟网络和配置文件

func DeleteNetwork(networkName string) error {
	nw, ok := networks[networkName]
	if !ok {
		return fmt.Errorf("no such network: %s", networkName)
	}

	// 将网关IP进行释放
	_, ipNet, _ := net.ParseCIDR(nw.Subnet)
	if err := ipAllocator.Release(ipNet, &nw.GatewayIP); err != nil {
		return fmt.Errorf("remove network gateway ip err: %w", err)
	}

	// 删除虚拟网络
	if err := drivers[nw.Driver].Delete(*nw); err != nil {
		return fmt.Errorf("remove network driver err: %w", err)
	}

	// 删除配置文件
	return nw.remove(defaultNetworkPath)
}

网络配置使用系统函数删除即可

func (b *BridgeNetworkDriver) Delete(network NetWork) error {
	bridgeName := network.Name
	br, err := netlink.LinkByName(bridgeName)
	if err != nil {
		return err
	}
	return netlink.LinkDel(br)
}

网络配置文件的删除:

func (nw *NetWork) remove(dumpPath string) error {
	if _, err := os.Stat(path.Join(dumpPath, nw.Name)); err != nil {
		if os.IsNotExist(err) {
			return nil
		} else {
			return fmt.Errorf("remvove path err: %w", err)
		}
	} else {
		return os.Remove(path.Join(dumpPath, nw.Name))
	}
}

运行测试

如下命令所示,创建和查看对应的网络

  • 编译程序,创建网络
  • 查看网络情况
  • 删除网络并确认
root@lw-Code-01-Series-PF5NU1G  ~/code/go/dockerDemo  go build mydocker/main.go                                                                                                      ✔  ⚡  651  11:00:01
root@lw-Code-01-Series-PF5NU1G  ~/code/go/dockerDemo  ./main network create --driver bridge --subnet 192.168.10.1/24 testbridge                                                      ✔  ⚡  652  11:00:06
{"level":"info","msg":"load ipam file from: /var/run/mydocker/network/ipam/subnet.json","time":"2022-04-16T11:00:11+08:00"}
{"level":"info","msg":"allocate subnet: 192.168.10.1/24, ip: 192.168.10.1","time":"2022-04-16T11:00:11+08:00"}
{"level":"info","msg":"dump ipam file from: /var/run/mydocker/network/ipam/","time":"2022-04-16T11:00:11+08:00"}
{"level":"info","msg":"BridgeNetworkDriver creat network subnet: 192.168.10.1/24, gateway ip: 192.168.10.1","time":"2022-04-16T11:00:11+08:00"}
{"level":"info","msg":"createBridgeInterface success","time":"2022-04-16T11:00:11+08:00"}
{"level":"info","msg":"setInterfaceIp success","time":"2022-04-16T11:00:11+08:00"}
{"level":"info","msg":"crsetInterfaceUp success","time":"2022-04-16T11:00:11+08:00"}
{"level":"info","msg":"setInterfaceUp success","time":"2022-04-16T11:00:11+08:00"}
{"level":"info","msg":"create network success","time":"2022-04-16T11:00:11+08:00"}

root@lw-Code-01-Series-PF5NU1G  ~/code/go/dockerDemo  ./main network list                                                                                                            ✔  ⚡  653  11:00:16
NAME         IpRange           Driver
testbridge   192.168.10.1/24               bridge

root@lw-Code-01-Series-PF5NU1G  ~/code/go/dockerDemo  ip link show dev testbridge                                                                                                    ✔  ⚡  654  11:00:19
7: testbridge: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/ether 6a:a8:a1:a4:74:b4 brd ff:ff:ff:ff:ff:ff
    
root@lw-Code-01-Series-PF5NU1G  ~/code/go/dockerDemo  ip addr show dev testbridge                                                                                                    ✔  ⚡  655  11:00:24
7: testbridge: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ether 6a:a8:a1:a4:74:b4 brd ff:ff:ff:ff:ff:ff
    inet 192.168.10.1/24 brd 192.168.10.255 scope global testbridge
       valid_lft forever preferred_lft forever
    inet6 fe80::68a8:a1ff:fea4:74b4/64 scope link
       valid_lft forever preferred_lft forever
       
root@lw-Code-01-Series-PF5NU1G  ~/code/go/dockerDemo  iptables -t nat -vnL POSTROUTING                                                                                               ✔  ⚡  656  11:00:29
Chain POSTROUTING (policy ACCEPT 44 packets, 4188 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0
    0     0 MASQUERADE  all  --  *      !testbridge  192.168.10.0/24      0.0.0.0/0
    0     0 MASQUERADE  all  --  *      !testbridge  192.168.10.0/24      0.0.0.0/0
    0     0 MASQUERADE  all  --  *      !testbridge  192.168.10.0/24      0.0.0.0/0
    0     0 MASQUERADE  all  --  *      !testbridge  192.168.10.0/24      0.0.0.0/0
    0     0 MASQUERADE  all  --  *      !testbridge  192.168.10.0/24      0.0.0.0/0
    
root@lw-Code-01-Series-PF5NU1G  ~/code/go/dockerDemo  ./main network remove testbridge                                                                                               ✔  ⚡  657  11:00:35
{"level":"info","msg":"release subnet: 192.168.10.0/24, ip: 192.168.10.1","time":"2022-04-16T11:00:40+08:00"}
{"level":"info","msg":"load ipam file from: /var/run/mydocker/network/ipam/subnet.json","time":"2022-04-16T11:00:40+08:00"}
{"level":"info","msg":"release index: 0","time":"2022-04-16T11:00:40+08:00"}
{"level":"info","msg":"dump ipam file from: /var/run/mydocker/network/ipam/","time":"2022-04-16T11:00:40+08:00"}

root@lw-Code-01-Series-PF5NU1G  ~/code/go/dockerDemo  ./main network list                                                                                                            ✔  ⚡  658  11:00:42
NAME        IpRange     Driver

root@lw-Code-01-Series-PF5NU1G  ~/code/go/dockerDemo  ip addr show dev testbridge                                                                                                    ✔  ⚡  659  11:00:47
Device "testbridge" does not exist.

总体感觉下来好像是创建桥接网卡?和使用VMware的时候,会生成虚拟网卡,如果在没删除的时候,使用下面的命令查看,会得到

root@lw-Code-01-Series-PF5NU1G  ~/code/go/dockerDemo  ifconfig                                                                                                                       ✔  ⚡  661  11:04:37
testbridge: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.10.1  netmask 255.255.255.0  broadcast 192.168.10.255
        inet6 fe80::fc2b:95ff:fe8d:5c98  prefixlen 64  scopeid 0x20<link>
        ether fe:2b:95:8d:5c:98  txqueuelen 1000  (以太网)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 33  bytes 4700 (4.7 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

总体来说,书上的代码有点乱,抄下来,运行不起来,主要还是ip的分配和释放问题,所以本篇中进行了一些小的改造,初步达到预期

今天的文章自己动手写Docker系列 — 6.2创建网络分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/18243.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注