自己动手写 Docker 系列 -- 6.2 创建网络
- 2022 年 4 月 16 日
本文字数:7460 字
阅读完需:约 24 分钟
简介
在前面的文章中,我们完成了一个 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 的分配和释放问题,所以本篇中进行了一些小的改造,初步达到预期
版权声明: 本文为 InfoQ 作者【萧】的原创文章。
原文链接:【http://xie.infoq.cn/article/ed21f70063e4987cecfebd4c4】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
萧
还未添加个人签名 2018.09.09 加入
代码是门手艺活,也是门艺术活
评论