从零开始写 Docker(十八)---容器网络实现(下):为容器插上”网线“

mydocker-network-3.png

本文为从零开始写 Docker 系列第十八篇,利用 linux 下的 Veth、Bridge、iptables 等等相关技术,构建容器网络模型,为容器插上”网线“。


完整代码见:https://github.com/lixd/mydocker
欢迎 Star

推荐阅读以下文章对 docker 基本实现有一个大致认识:

  • 核心原理:深入理解 Docker 核心原理:Namespace、Cgroups 和 Rootfs
  • 基于 namespace 的视图隔离:探索 Linux Namespace:Docker 隔离的神奇背后
  • 基于 cgroups 的资源限制
    • 初探 Linux Cgroups:资源控制的奇妙世界
    • 深入剖析 Linux Cgroups 子系统:资源精细管理
    • Docker 与 Linux Cgroups:资源隔离的魔法之旅
  • 基于 overlayfs 的文件系统:Docker 魔法解密:探索 UnionFS 与 OverlayFS
  • 基于 veth pair、bridge、iptables 等等技术的 Docker 网络:揭秘 Docker 网络:手动实现 Docker 桥接网络

开发环境如下:

root@mydocker:~# lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 20.04.2 LTS
Release:	20.04
Codename:	focal
root@mydocker:~# uname -r
5.4.0-74-generic

注意:需要使用 root 用户

1. 概述

前面两篇文章中已经实现了容器的基本功能,容器之间可以互相访问,同时容器可以访问外网,外部设备也可以通过宿主机端口访问到容器内部服务。

不过还缺少了资源清理逻辑,本篇主要实现在删除网络或者容器时对相关网络资源做一个清理工作

2. 网络清理

创建网络会做以下事情:

  • 创建 bridge 设备
  • 对应子网配置路由规则
  • 对应子网配置 iptables 规则(SNAT)

那么删除时也需要做对应的清理

  • 删除 iptables 规则

  • 删除路由规则

  • 删除 bridge 设备

// Delete 删除网络
func (d *BridgeNetworkDriver) Delete(network *Network) error {// 清除路由规则err := deleteIPRoute(network.Name, network.IPRange.IP.String())if err != nil {return errors.WithMessagef(err, "clean route rule failed after bridge [%s] deleted", network.Name)}// 清除 iptables 规则err = deleteIPTables(network.Name, network.IPRange)if err != nil {return errors.WithMessagef(err, "clean snat iptables rule failed after bridge [%s] deleted", network.Name)}// 删除网桥err = d.deleteBridge(network)if err != nil {return errors.WithMessagef(err, "delete bridge [%s] failed", network.Name)}return nil
}

路由规则清理

使用 netlink.RouteDel 方法删除路由,具体如下:


// 删除路由,ip addr del xxx命令
func deleteIPRoute(name string, rawIP string) error {retries := 2var iface netlink.Linkvar err errorfor i := 0; i < retries; i++ {// 通过LinkByName方法找到需要设置的网络接口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 errors.Wrap(err, "abandoning retrieving the new bridge link from netlink, Run [ ip link ] to troubleshoot")}// 查询对应设备的路由并全部删除list, err := netlink.RouteList(iface, netlink.FAMILY_V4)if err != nil {return err}for _, route := range list {if route.Dst.String() == rawIP { // 根据子网进行匹配err = netlink.RouteDel(&route)if err != nil {log.Errorf("route [%v] del failed,detail:%v", route, err)continue}}}return nil
}

SNAT 规则清理

iptables 删除比较简单,就是把添加命令中的 -A 改成 -D即可,相比于按行号删除,这种方式不会删错。

configIPTables 方法 进行调整,把 action 作为配置,这样添加删除可以复用同一个方法。

func setupIPTables(bridgeName string, subnet *net.IPNet) error {return configIPTables(bridgeName, subnet, false)
}
func deleteIPTables(bridgeName string, subnet *net.IPNet) error {return configIPTables(bridgeName, subnet, true)
}func configIPTables(bridgeName string, subnet *net.IPNet, isDelete bool) error {action := "-A"if isDelete {action = "-D"}// 拼接命令iptablesCmd := fmt.Sprintf("-t nat %s POSTROUTING -s %s ! -o %s -j MASQUERADE", action, subnet.String(), bridgeName)cmd := exec.Command("iptables", strings.Split(iptablesCmd, " ")...)log.Infof("删除 SNAT cmd:%v", cmd.String())// 执行该命令output, err := cmd.Output()if err != nil {log.Errorf("iptables Output, %v", output)}return err
}

网桥设备清理

使用netlink.LinkDel 方法删除 bridge 设备。

// deleteBridge deletes the bridge
func (d *BridgeNetworkDriver) deleteBridge(n *Network) error {bridgeName := n.Name// get the linkl, err := netlink.LinkByName(bridgeName)if err != nil {return fmt.Errorf("getting link with name %s failed: %v", bridgeName, err)}// delete the linkif err = netlink.LinkDel(l); err != nil {return fmt.Errorf("failed to remove bridge interface %s delete: %v", bridgeName, err)}return nil
}

3. 记录容器网络信息

容器启动时需要记录网络相关信息,便于后续查询或者删除时使用。

RecordContainerInfo

ContainerInfo 中新增网络相关字段

type Info struct {NetworkName string   `json:"networkName"` // 容器所在的网络IP          string   `json:"ip"`          // 容器IPPortMapping []string `json:"portmapping"` // 端口映射
}

记录容器信息逻辑后移到绑定网络后,便于记录 IP 信息。

func Run(tty bool, comArray, envSlice []string, res *subsystems.ResourceConfig, volume, containerName, imageName string,net string, portMapping []string) {containerId := container.GenerateContainerID() // 生成 10 位容器 id// 省略...var containerIP string// 如果指定了网络信息则进行配置if net != "" {// config container networkcontainerInfo := &container.Info{Id:          containerId,Pid:         strconv.Itoa(parent.Process.Pid),Name:        containerName,PortMapping: portMapping,}ip, err := network.Connect(net, containerInfo)if err != nil {log.Errorf("Error Connect Network %v", err)return}containerIP = ip.String()}// 在分配 IP 后在记录,便于存储网络相关信息containerInfo, err := container.RecordContainerInfo(parent.Process.Pid, comArray, containerName, containerId,volume, net, containerIP, portMapping)if err != nil {log.Errorf("Record container info error %v", err)return}
}

mydockerps

mydocker ps 命令增加 ip 信息展示

func ListContainers() {// 读取存放容器信息目录下的所有文件files, err := os.ReadDir(container.InfoLoc)if err != nil {log.Errorf("read dir %s error %v", container.InfoLoc, err)return}containers := make([]*container.Info, 0, len(files))for _, file := range files {tmpContainer, err := getContainerInfo(file)if err != nil {log.Errorf("get container info error %v", err)continue}containers = append(containers, tmpContainer)}// 使用tabwriter.NewWriter在控制台打印出容器信息// tabwriter 是引用的text/tabwriter类库,用于在控制台打印对齐的表格w := tabwriter.NewWriter(os.Stdout, 12, 1, 3, ' ', 0)_, err = fmt.Fprint(w, "ID\tNAME\tPID\tIP\tSTATUS\tCOMMAND\tCREATED\n")if err != nil {log.Errorf("Fprint error %v", err)}for _, item := range containers {_, err = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",item.Id,item.Name,item.IP,item.Pid,item.Status,item.Command,item.CreatedTime)if err != nil {log.Errorf("Fprint error %v", err)}}if err = w.Flush(); err != nil {log.Errorf("Flush error %v", err)}
}

4. 容器网络设备清理

容器加入某个网络时需要做以下工作:

  • 创建 veth-pair 设备对,并将一段绑定到 bridge 设备上
  • 容器中添加路由,将 bridge 作为网关
  • 指定端口映射时添加 iptables 规则(DNAT)

在容器退出后,需要做网络信息清理。

  • iptables 规则清理
  • veth 设备从 birdge 设备解绑
  • veth pair 设备清理

都是根据启动时存储的信息,调用 network.Disconnect 方法清理即可。

// Disconnect 将容器中指定网络中移除
func Disconnect(networkName string, info *container.Info) error {networks, err := loadNetwork()if err != nil {return errors.WithMessage(err, "load network from file failed")}// 从networks字典中取到容器连接的网络的信息,networks字典中保存了当前己经创建的网络network, ok := networks[networkName]if !ok {return fmt.Errorf("no Such Network: %s", networkName)}// veth 从 bridge 解绑并删除 veth-pair 设备对drivers[network.Driver].Disconnect(fmt.Sprintf("%s-%s", info.Id, networkName))// 清理端口映射添加的 iptables 规则ep := &Endpoint{ID:          fmt.Sprintf("%s-%s", info.Id, networkName),IPAddress:   net.ParseIP(info.IP),Network:     network,PortMapping: info.PortMapping,}return deletePortMapping(ep)
}

Veth 设备清理

将 veth 设备从 bridge 解绑,并根据命名规则找到另一端的 veth 一起删除。

func (d *BridgeNetworkDriver) Disconnect(endpointID string) error {// 根据名字找到对应的 Veth 设备vethNme := endpointID[:5] // 由于 Linux 接口名的限制,取 endpointID 的前 5 位veth, err := netlink.LinkByName(vethNme)if err != nil {return err}// 从网桥解绑err = netlink.LinkSetNoMaster(veth)if err != nil {return errors.WithMessagef(err, "find veth [%s] failed", vethNme)}// 删除 veth-pair// 一端为 xxx,另一端为 cif-xxxerr = netlink.LinkDel(veth)if err != nil {return errors.WithMessagef(err, "delete veth [%s] failed", vethNme)}veth2Name := "cif-" + vethNmeveth2, err := netlink.LinkByName(veth2Name)if err != nil {return errors.WithMessagef(err, "find veth [%s] failed", veth2Name)}err = netlink.LinkDel(veth2)if err != nil {return errors.WithMessagef(err, "delete veth [%s] failed", veth2Name)}return nil
}

DNAT 规则删除

iptables 清理和 前面 SNAT 清理类似,只需要控制 action 即可,把添加的 -A 替换为-D 即可。

func addPortMapping(ep *Endpoint) error {return configPortMapping(ep, false)
}func deletePortMapping(ep *Endpoint) error {return configPortMapping(ep, true)
}// configPortMapping 配置端口映射
func configPortMapping(ep *Endpoint, isDelete bool) error {action := "-A"if isDelete {action = "-D"}var err error// 遍历容器端口映射列表for _, pm := range ep.PortMapping {// 分割成宿主机的端口和容器的端口portMapping := strings.Split(pm, ":")if len(portMapping) != 2 {logrus.Errorf("port mapping format error, %v", pm)continue}// 由于iptables没有Go语言版本的实现,所以采用exec.Command的方式直接调用命令配置// 在iptables的PREROUTING中添加DNAT规则// 将宿主机的端口请求转发到容器的地址和端口上// iptables -t nat -A PREROUTING ! -i testbridge -p tcp -m tcp --dport 8080 -j DNAT --to-destination 10.0.0.4:80iptablesCmd := fmt.Sprintf("-t nat %s PREROUTING ! -i %s -p tcp -m tcp --dport %s -j DNAT --to-destination %s:%s",action, ep.Network.Name, portMapping[0], ep.IPAddress.String(), portMapping[1])cmd := exec.Command("iptables", strings.Split(iptablesCmd, " ")...)logrus.Infoln("配置端口映射 DNAT cmd:", cmd.String())// 执行iptables命令,添加端口映射转发规则output, err := cmd.Output()if err != nil {logrus.Errorf("iptables Output, %v", output)continue}}return err
}

具体实现

根据前台容器和后台容器,需要在不同地方调用前面的清理逻辑。

前台容器

前台容器直接在 Run 方法中清理即可。

func Run(tty bool, comArray, envSlice []string, res *subsystems.ResourceConfig, volume, containerName, imageName string,net string, portMapping []string) {containerId := container.GenerateContainerID() // 生成 10 位容器 id// 省略...if tty { // 如果是tty,那么父进程等待,就是前台运行,否则就是跳过,实现后台运行_ = parent.Wait()container.DeleteWorkSpace(containerId, volume)container.DeleteContainerInfo(containerId)if net != "" { // 如果指定了网络则在退出时做清理工作network.Disconnect(net, containerInfo)}}
}
后台容器删除

后台容器则在 mydocker stop 命令中做清理。

func removeContainer(containerId string, force bool) {containerInfo, err := getInfoByContainerId(containerId)if err != nil {log.Errorf("Get container %s info error %v", containerId, err)return}switch containerInfo.Status {case container.STOP: // STOP 状态容器直接删除即可// 先删除配置目录,再删除rootfs 目录if err = container.DeleteContainerInfo(containerId); err != nil {log.Errorf("Remove container [%s]'s config failed, detail: %v", containerId, err)return}container.DeleteWorkSpace(containerId, containerInfo.Volume)if containerInfo.NetworkName != "" { // 清理网络资源if err = network.Disconnect(containerInfo.NetworkName, containerInfo); err != nil {log.Errorf("Remove container [%s]'s config failed, detail: %v", containerId, err)return}}case container.RUNNING: // RUNNING 状态容器如果指定了 force 则先 stop 然后再删除if !force {log.Errorf("Couldn't remove running container [%s], Stop the container before attempting removal or"+" force remove", containerId)return}log.Infof("force delete running container [%s]", containerId)stopContainer(containerId)removeContainer(containerId, force)default:log.Errorf("Couldn't remove container,invalid status %s", containerInfo.Status)return}
}

5. 测试

测试以下几部分:

  • 网络资源清理测试

  • 容器信息记录测试

  • 容器网络资源清理测试

网络资源清理测试

测试网络删除后,对应的bridge、路由、iptables 等资源是否清理干净。

root@mydocker:~/feat-network-2/mydocker# go build .
root@mydocker:~/feat-network-2/mydocker# ./mydocker network create --driver bridge --subnet 10.10.10.1/24 testbr
root@mydocker:~/feat-network-3/mydocker# ./mydocker network list
NAME         IpRange        Driver
testbr1      10.10.0.1/24   bridge

查看对应设备

# brigde 设备
root@mydocker:~/feat-network-3/mydocker# ip link show type bridge
15: testbr: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000link/ether 82:80:36:11:30:2f brd ff:ff:ff:ff:ff:ff
# 路由
root@mydocker:~/feat-network-3/mydocker# ip r
10.10.10.0/24 dev testbr proto kernel scope link src 10.10.10.1
# iptables
root@mydocker:~/feat-network-3/mydocker# iptables -t nat -L POSTROUTING
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  10.10.0.0/24         anywhere

接下来进行删除

root@mydocker:~/feat-network-3/mydocker# ./mydocker network remove testbr

分别检查路由、iptables、bridge 设备是否真的删除的。

# 网桥设备
root@mydocker:~/feat-network-3/mydocker# ip link show type bridge
# 路由规则
root@mydocker:~/feat-network-3/mydocker# ip r
# iptables
root@mydocker:~/feat-network-3/mydocker# iptables -t nat -L POSTROUTING
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination

可以看到都删除了,说明网络资源清理是正常的。

容器信息记录测试

测试容器信息是否能够正常记录和展示。

创建一个网络

root@mydocker:~/feat-network-2/mydocker# ./mydocker network create --driver bridge --subnet 10.10.10.1/24 testbr
root@mydocker:~/feat-network-3/mydocker# ./mydocker network list
NAME         IpRange        Driver
testbr1      10.10.0.1/24   bridge

然后启动容器指定使用该网络

./mydocker run -it -net testbr busybox sh

另一个终端查看容器信息

root@mydocker:~/feat-network-3/mydocker# ./mydocker ps
ID           NAME         PID          IP          STATUS      COMMAND     CREATED
3994300986   3994300986   10.10.10.2   103669      running     sh          2024-03-07 13:24:34

退出前台容器

/ # exit

再次查看容器信息

root@mydocker:~/feat-network-3/mydocker# ./mydocker ps
ID          NAME        PID         IP          STATUS      COMMAND     CREATED

删除了,一切正常。

容器网络资源清理测试

前台容器
./mydocker run -it -p 8080:80 -net testbr busybox sh

查看容器中的网络设备

/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope hostvalid_lft forever preferred_lft forever
19: cif-09561@if20: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue qlen 1000link/ether a6:a3:16:22:c1:98 brd ff:ff:ff:ff:ff:ffinet 10.10.10.3/24 brd 10.10.10.255 scope global cif-09561valid_lft forever preferred_lft foreverinet6 fe80::a4a3:16ff:fe22:c198/64 scope linkvalid_lft forever preferred_lft forever

可以看到,其中 19 号设备叫做cif-09561@if20,这就是我们放入容器中的 veth 设备。

查看宿主机

root@mydocker:~/feat-network-3/mydocker# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope hostvalid_lft forever preferred_lft forever
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000link/ether fa:16:3e:58:62:ef brd ff:ff:ff:ff:ff:ffinet 192.168.10.144/24 brd 192.168.10.255 scope global dynamic ens3valid_lft 61063sec preferred_lft 61063secinet6 fe80::f816:3eff:fe58:62ef/64 scope linkvalid_lft forever preferred_lft forever
16: testbr: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000link/ether 66:9e:1c:28:c2:40 brd ff:ff:ff:ff:ff:ffinet 10.10.10.1/24 brd 10.10.10.255 scope global testbrvalid_lft forever preferred_lft foreverinet6 fe80::74fa:43ff:feac:74f3/64 scope linkvalid_lft forever preferred_lft forever
20: 09561@if19: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master testbr state UP group default qlen 1000link/ether 66:9e:1c:28:c2:40 brd ff:ff:ff:ff:ff:ff link-netnsid 0inet6 fe80::649e:1cff:fe28:c240/64 scope linkvalid_lft forever preferred_lft forever

testbr 为网桥,09561@if19 这个就是容器中 veth 的另一端。

查看 iptables 规则

root@mydocker:~/feat-network-3/mydocker# iptables -t nat -L PREROUTING
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
DNAT       tcp  --  anywhere             anywhere             tcp dpt:http-alt to:10.10.10.4:80

确实有一个 DNAT 规则用于处理端口映射。

测试停止容器后,这两个 veth 设备是否会删除。

退出容器

/ # exit

查看宿主机

root@mydocker:~/feat-network-3/mydocker# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope hostvalid_lft forever preferred_lft forever
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000link/ether fa:16:3e:58:62:ef brd ff:ff:ff:ff:ff:ffinet 192.168.10.144/24 brd 192.168.10.255 scope global dynamic ens3valid_lft 60895sec preferred_lft 60895secinet6 fe80::f816:3eff:fe58:62ef/64 scope linkvalid_lft forever preferred_lft forever
16: testbr: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ffinet 10.10.10.1/24 brd 10.10.10.255 scope global testbrvalid_lft forever preferred_lft foreverinet6 fe80::74fa:43ff:feac:74f3/64 scope linkvalid_lft forever preferred_lft forever

09561@if19 被删除了。

root@mydocker:~/feat-network-3/mydocker# iptables -t nat -L PREROUTING
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination

DNAT 规则也清理了。

说明前台容器资源清理一切正常。

后台容器
./mydocker run -d -p 8080:80 -net testbr busybox top

查看运行情况

root@mydocker:~/feat-network-3/mydocker# ./mydocker ps
ID           NAME         PID          IP          STATUS      COMMAND     CREATED
1891821312   1891821312   10.10.10.5   103828      running     top         2024-03-07 13:32:19

同样的,查看 veth 设备和 DNAT 规则

root@mydocker:~/feat-network-3/mydocker# ip a
26: 18918@if25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master testbr state UP group default qlen 1000link/ether ba:ab:aa:cb:21:70 brd ff:ff:ff:ff:ff:ff link-netnsid 0inet6 fe80::b8ab:aaff:fecb:2170/64 scope linkvalid_lft forever preferred_lft forever
root@mydocker:~/feat-network-3/mydocker# iptables -t nat -L PREROUTING
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
DNAT       tcp  --  anywhere             anywhere             tcp dpt:http-alt to:10.10.10.5:80

删除容器

root@mydocker:~/feat-network-3/mydocker# ./mydocker rm 1891821312 -f

查看 veth 设备和 DNAT 规则是否被清理

root@mydocker:~/feat-network-3/mydocker# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope hostvalid_lft forever preferred_lft forever
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000link/ether fa:16:3e:58:62:ef brd ff:ff:ff:ff:ff:ffinet 192.168.10.144/24 brd 192.168.10.255 scope global dynamic ens3valid_lft 60557sec preferred_lft 60557secinet6 fe80::f816:3eff:fe58:62ef/64 scope linkvalid_lft forever preferred_lft forever
16: testbr: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ffinet 10.10.10.1/24 brd 10.10.10.255 scope global testbrvalid_lft forever preferred_lft foreverinet6 fe80::74fa:43ff:feac:74f3/64 scope linkvalid_lft forever preferred_lft forever
root@mydocker:~/feat-network-3/mydocker# iptables -t nat -L PREROUTING
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination

都清理了,说明后台容器清理也是正常的。


**【从零开始写 Docker 系列】**持续更新中,搜索公众号【探索云原生】订阅,文章。


6. 小结

本章主要处理了容器网络收尾工作,包括 veth、bridge、iptables、路由等网络资源的回收。

至此,整个容器网络就算是基本完成了。

最后再次推荐一下 Docker教程(十)—揭秘 Docker 网络:手动实现 Docker 桥接网络


完整代码见:https://github.com/lixd/mydocker
欢迎关注~

相关代码见 feat-network-3 分支,测试脚本如下:

需要提前在 /var/lib/mydocker/image 目录准备好 busybox.tar 文件,具体见第四篇第二节。

# 克隆代码
git clone -b feat-network-3 https://github.com/lixd/mydocker.git
cd mydocker
# 拉取依赖并编译
go mod tidy
go build .
# 测试 
./mydocker network create --driver bridge --subnet 10.10.10.1/24 testbr
./mydocker network list

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/26935.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SwiftUI中UIViewRepresentable的使用(UIKit与SwiftUI的桥梁)

UIViewRepresentable是一个协议&#xff0c;用于创建一个SwiftUI视图&#xff0c;该视图包装了一个UIKit视图。通过实现UIViewRepresentable协议&#xff0c;我们可以在SwiftUI中使用自定义的UIKit视图&#xff0c;并与SwiftUI进行交互。 实现UIViewRepresentable 创建一个遵…

自动控制理论实验---IDFT和FFT算法的原理和MATLAB编程

1、实验设备 PC计算机1台&#xff0c;MATLAB软件1套。 2、实验目的 掌握IDFT&#xff08;逆离散傅里叶变换&#xff09;算法的原理和MATLAB编程方法。了解FFT&#xff08;快速傅里叶变换&#xff09;算法&#xff0c;并能够调用MATLAB的fft函数进行频域变换。验证IDFT程序的…

数据预处理之基于统计的(3σ,Z分数,Boxplot箱线图)异常值检测#matlab

基于统计的异常值检测 1.异常值的含义 异常值是指在数据集中偏离大部分数据的数据&#xff0c;使人怀疑这些数据的偏离并非由随机因素产生&#xff0c;而是产生于完全不同的机制。 异常挖掘(outlier mining)问题由两个子问题构成&#xff1a;(1)如何度量异常。(2)如何有效发…

金融与大模型:引领行业未来的创新融合

前言 在数字化浪潮席卷全球的今天&#xff0c;金融与大模型的结合正成为行业发展的新引擎。这种融合不仅为金融机构带来了前所未有的效率和准确性&#xff0c;也为金融市场的稳定与发展注入了新的活力。本文将基于当前的市场现状&#xff0c;结合金融环境的发展&#xff0c;深…

图片查看器

目录 一 原型 二 源码 一 原型 二 源码 namespace 图片查看器 {public partial class Form1 : Form{public Form1(){InitializeComponent();}private void Form1_Load(object sender, EventArgs e){//默认显示第一张图片pictureBox1.Image imageList1.Images[0];}private v…

《未选择的路》

2024年&#xff0c;计算机相关专业还值得选择吗&#xff1f; 看到这个话题活动&#xff0c;回想起自己过去做的许多选择&#xff0c;思绪良久。 一首诗送给大家吧。 顾子欣 译 列位&#xff0c;共勉。

【PB案例学习笔记】-21小大写金额转换

写在前面 这是PB案例学习笔记系列文章的第21篇&#xff0c;该系列文章适合具有一定PB基础的读者。 通过一个个由浅入深的编程实战案例学习&#xff0c;提高编程技巧&#xff0c;以保证小伙伴们能应付公司的各种开发需求。 文章中设计到的源码&#xff0c;小凡都上传到了gite…

ARM32开发--IIC时钟案例

知不足而奋进 望远山而前行 目录 文章目录 前言 目标 内容 需求 开发流程 移植驱动 修改I2C实现 测试功能 总结 前言 在现代嵌入式系统开发中&#xff0c;移植外设驱动并测试其功能是一项常见的任务。本次学习的目标是掌握移植方法和测试方法&#xff0c;以实现对开…

Undertow学习

Undertow介绍 Undertow是一个用java编写的灵活、高性能的web服务器&#xff0c;提供基于NIO的阻塞和非阻塞API。 Undertow有一个基于组合的体系结构&#xff0c;允许您通过组合小型单用途处理程序来构建web服务器。为您提供了在完整的Java EE servlet 4.0容器或低级别非阻塞处…

C# 设置PDF表单不可编辑、或提取PDF表单数据

PDF表单是PDF中的可编辑区域&#xff0c;允许用户填写指定信息。当表单填写完成后&#xff0c;有时候我们可能需要将其设置为不可编辑&#xff0c;以保护表单内容的完整性和可靠性。或者需要从PDF表单中提取数据以便后续处理或分析。 之前文章详细介绍过如何使用免费Spire.PDF…

PHP在线生成查询产品防伪证书系统源码

源码介绍 PHP在线生成查询产品防伪证书系统源码&#xff0c;源码自带90套授权证书模板&#xff0c;带PSD公章模板&#xff0c;证书PSD源文件。 环境要求&#xff1a;PHPMYSQL&#xff0c;PHP 版本请使用PHP5.1 ~5.3。 图片截图 源码安装说明 1.上传所有文件至你的空间服务器…

免费的端口映射工具哪个好用

端口映射&#xff0c;即从一个网络环境下的端口映射到另一个网络环境下访问的过程。通常由软件方式来提供这一过程的实现&#xff0c;或一些客户端工具。当涉及内外网时&#xff0c;如内网端口地址映射到外网地址&#xff0c;即是内网穿透的原理。免费的端口映射工具有哪些&…

PHP和Mysql前后端交互效果实现

一、连接数据库基本函数 mysqli_connect(); 作用&#xff1a;创建数据库连接&#xff0c;打开一个新的mysql的连接。传参顺序&#xff1a;数据库地址、数据库账号、数据库密码 <?phpecho mysqli_connect("localhost",root,root) ?> /*结果&#xff1a;F…

翻译《The Old New Thing》- The case of the exception that a catch (…) didn’t catch

The case of the exception that a catch (...) didnt catch - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20240405-00/?p109621 Raymond Chen 2024年04月05日 一位客户认为他们修复了一个bug&#xff0c;但他们仍然因为这个bug而崩溃。…

python django初步搭建(一)

记录一次简单的python django使用&#xff0c;后续调用api相关的暂时不想写。。。 一、环境 windows python 3.11.7 django 二、初步搭建 2.1 新建空文件夹 为了方便本次记录&#xff0c;新建了一个空的文件夹来使用。 直接在这里输入cmd 然后按下回车 2.2 安装virtual…

vue页面和 iframe多页面无刷新方案和并行存在解决方案

面临问题 : back的后台以jsp嵌套iframe为主, 所以在前端框架要把iframe无刷新嵌套和vue页面进行并行使用,vue的keep-alive只能对虚拟dom树 vtree 进行缓存无法缓存iframe,所以要对iframe进行处理 tab标签的切换效果具体参考若依框架的tab切换,可以去若依看源码,若依源码没有实…

C++设计模式——Proxy代理模式

一&#xff0c;代理模式简介 代理模式是一种 结构型设计模式&#xff0c;该模式通过引入一个新的代理对象Proxy&#xff0c;来间接访问原始对象&#xff0c;从而使访问方式变得灵活和可控。 代理对象的设定减少了客户端与真实对象之间的直接交互。 通过引入代理对象来间接访问原…

农资投入品系统架构:数字化农业的技术支撑与创新

在当今数字化时代&#xff0c;农业领域也在迅速迈向数字化和智能化的新阶段。农资投入品系统作为农业生产的重要支撑&#xff0c;其系统架构的设计与创新对于提高农业生产效率、保障粮食安全具有重要意义。本文将探讨农资投入品系统架构的设计原则、核心模块以及未来发展趋势。…

OrangePi AIpro测评:性能、应用与开发者体验解析

一、OrangePi AIpro介绍 OrangePi AIpro(8T)采用昇腾AI技术路线&#xff0c;具体为4核64位处理器AI处理器&#xff0c;集成图形处理器&#xff0c;支持8TOPS AI算力&#xff0c;拥有8GB/16GB LPDDR4X&#xff0c;可以外接32GB/64GB/128GB/256GB eMMC模块&#xff0c;支持双4K高…

AI虚拟试穿技术:开启高保真、多场景、多样化服装组合的试穿应用

随着电子商务的快速发展,消费者对于在线购物体验的要求越来越高。特别是在服装领域,消费者渴望能够在购买前直观地了解服装的试穿效果。传统的虚拟试穿技术虽然已有一定的发展,但在不同场景下的高保真度和鲁棒性方面仍面临挑战。为此,我们研发了一种全新的AI虚拟试穿技术,…