从零开始写 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 := 2
	var iface netlink.Link
	var err error
	for 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 link
	l, err := netlink.LinkByName(bridgeName)
	if err != nil {
		return fmt.Errorf("getting link with name %s failed: %v", bridgeName, err)
	}

	// delete the link
	if 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"`          // 容器IP
	PortMapping []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 network
		containerInfo := &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-xxx
	err = netlink.LinkDel(veth)
	if err != nil {
		return errors.WithMessagef(err, "delete veth [%s] failed", vethNme)
	}
	veth2Name := "cif-" + vethNme
	veth2, 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:80
		iptablesCmd := 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 1000
    link/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 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
19: cif-09561@if20: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue qlen 1000
    link/ether a6:a3:16:22:c1:98 brd ff:ff:ff:ff:ff:ff
    inet 10.10.10.3/24 brd 10.10.10.255 scope global cif-09561
       valid_lft forever preferred_lft forever
    inet6 fe80::a4a3:16ff:fe22:c198/64 scope link
       valid_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 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether fa:16:3e:58:62:ef brd ff:ff:ff:ff:ff:ff
    inet 192.168.10.144/24 brd 192.168.10.255 scope global dynamic ens3
       valid_lft 61063sec preferred_lft 61063sec
    inet6 fe80::f816:3eff:fe58:62ef/64 scope link
       valid_lft forever preferred_lft forever
16: testbr: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 66:9e:1c:28:c2:40 brd ff:ff:ff:ff:ff:ff
    inet 10.10.10.1/24 brd 10.10.10.255 scope global testbr
       valid_lft forever preferred_lft forever
    inet6 fe80::74fa:43ff:feac:74f3/64 scope link
       valid_lft forever preferred_lft forever
20: 09561@if19: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master testbr state UP group default qlen 1000
    link/ether 66:9e:1c:28:c2:40 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::649e:1cff:fe28:c240/64 scope link
       valid_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 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether fa:16:3e:58:62:ef brd ff:ff:ff:ff:ff:ff
    inet 192.168.10.144/24 brd 192.168.10.255 scope global dynamic ens3
       valid_lft 60895sec preferred_lft 60895sec
    inet6 fe80::f816:3eff:fe58:62ef/64 scope link
       valid_lft forever preferred_lft forever
16: testbr: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
    link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
    inet 10.10.10.1/24 brd 10.10.10.255 scope global testbr
       valid_lft forever preferred_lft forever
    inet6 fe80::74fa:43ff:feac:74f3/64 scope link
       valid_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 1000
    link/ether ba:ab:aa:cb:21:70 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::b8ab:aaff:fecb:2170/64 scope link
       valid_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 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether fa:16:3e:58:62:ef brd ff:ff:ff:ff:ff:ff
    inet 192.168.10.144/24 brd 192.168.10.255 scope global dynamic ens3
       valid_lft 60557sec preferred_lft 60557sec
    inet6 fe80::f816:3eff:fe58:62ef/64 scope link
       valid_lft forever preferred_lft forever
16: testbr: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
    link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
    inet 10.10.10.1/24 brd 10.10.10.255 scope global testbr
       valid_lft forever preferred_lft forever
    inet6 fe80::74fa:43ff:feac:74f3/64 scope link
       valid_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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【论文阅读】《Sketch and Refine: Towards Fast and Accurate Lane Detection》

Abstract 车道检测是指确定道路上车道的精确位置和形状。尽管目前的方法已经做出了努力&#xff0c;但由于现实世界场景的复杂性&#xff0c;这仍然是一项具有挑战性的任务。无论是基于建议的方法还是基于关键点的方法&#xff0c;现有方法都无法有效、高效地描绘车道。基于建…

JAVA动态表达式:Antlr4 表达式树解析

接上面 JAVA动态表达式&#xff1a;Antlr4 G4 模板 读取字符串表达式结构树-CSDN博客 目前已经实现了常量及分组常规表达式的解析。 String formula "啦啦啦1 and 11 and 23 and 1123 contains 1 and 23455 notcontains 5"; String formula "啦啦啦1 and (…

能在电脑桌面记笔记的软件是什么 电脑笔记软件

在这个数字化高速发展的时代&#xff0c;电脑已成为我们日常工作和学习的必备工具。而对我来说&#xff0c;电脑桌面不仅仅是一个简单的工作界面&#xff0c;更是一个思考和创造的平台。我时常需要在工作时快速记录一些重要信息或灵感&#xff0c;这时候&#xff0c;能在电脑桌…

文件比对工具Beyond Compare——设置差异项对齐方式并保存导出

一、Beyond Compare怎么对齐 在Beyond Compare中&#xff0c;对比文件时&#xff0c;可能会有某个文件左侧或右侧出现了空白&#xff0c;导致无法直接比较&#xff0c;这种情况被称为“对齐”问题。那么你知道Beyond Compare怎么来对齐吗&#xff1f; 1、打开Beyond Compare&…

CCAA 质量管理 备考核心知识点笔记

第一部分 质量管理体系相关标准 《质量管理体系基础考试大纲》中规定的考试内容&#xff1a; 3.1质量管理体系标准 a) 了解 ISO 9000 系列标准发展概况&#xff1b; b) 理 解 GB/T19000 标准中涉及的基本概念和质量管理原则&#xff1b; c) 理 解GB/T19000 标准中的部分…

一文深度了解基于大模型的Agent(一)

1、什么是 Agent Agent是一种能够在一定程度上模拟人类智能行为的软件实体&#xff0c;它具有感知环境、做出决策和执行动作的能力。Agent可以在预定的规则和目标下自主操作&#xff0c;与用户或其他Agent进行交互&#xff0c;完成特定的任务。 Agent 的火爆起源于一个开源的…

Web应用安全测试-业务功能滥用(一)

Web应用安全测试-业务功能滥用&#xff08;一&#xff09; 1、短信定向转发 漏洞描述&#xff1a;短信接收人可任意指定 测试方法&#xff1a;拦截发送短信的请求&#xff0c;将手机号改为测试人员的手机号&#xff0c;测试是否可接收短信验证码。 风险分析&#xff1a;攻击…

rsa加签验签C#和js以及java互通

js实现rsa加签验签 https://github.com/kjur/jsrsasign 11.1.0版本 解压选择需要的版本&#xff0c;这里选择all版本了 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>JS RSA加签验签</title&g…

软件安全测评有哪些测试流程?第三方检测机构进行安全测评的好处

在今天的高科技时代&#xff0c;软件产品已经成为人们生活和工作的重要组成部分。然而&#xff0c;与其普及和深入应用的&#xff0c;软件安全问题也日益凸显。 为了保障软件产品在使用过程中的安全性&#xff0c;进行安全测评是必不可少的。安全测评可以全面评估软件系统的安…

框架学习之spring学习笔记(一)

一、框架前言 1-什么是spring框架&#xff0c;有哪些主要模块&#xff1f; Spring 框架是一个专门针对于 Java 应用程序开发&#xff0c;并提供了综合、广泛的基础性支持的轻量级框架。Spring框架使用目的是为了提高开发人员的开发效率以及系统的可维护性。 Spring 是以IoC和A…

linux的UDP广播测试:C语言代码

测试代码 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h>#…

解决:安装MySQL 5.7 的时候报错:unknown variable ‘mysqlx_port=0.0‘

目录 1. 背景2. 解决步骤 1. 背景 吐槽1&#xff0c;没被收购之前可以随便下载&#xff0c;现在下载要注册登录吐槽2&#xff0c;5.7安装到初始化数据库的时候就会报错&#xff0c;而8.x的可以一镜到底&#xff0c;一开始以为是国区的特色问题&#xff0c;google了一圈&#x…

重学java 70.IO流 Commons-io工具包

所有人都不看好你&#xff0c;可你偏偏最争气 —— 24.6.14 一、介绍 IO技术开发中&#xff0c;代码量很大&#xff0c;而且代码的重复率较高。如果我们要遍历目录&#xff0c;拷贝自录就需要使用方法的递归调用&#xff0c;也增大了程序的复杂度。 二、添加第三方jar包 1.ja…

Windows MySQL_8.4.0 Navicat报错代码1251不支持认证协议解决方案

Windows MySQL_8.4.0 Navicat报错代码1251不支持认证协议解决方案 前言&#xff1a; Navicat for MySQL 是管理和开发 MySQL 或 MariaDB 的理想解决方案。它是一套单一的应用程序&#xff0c;能同时连接 MySQL 和 MariaDB 数据库&#xff0c;并与 OceanBase 数据库及 Amazon RD…

旅游网站(携程旅行网页学习 vue3+element)

旅游网站 1. 创建项目 在你要创建项目的路径下打开vscode&#xff0c;新建终端&#xff0c;然后输入vue ui,进入Vue项目管理器。选择“创建”&#xff0c;确定项目路径&#xff0c;并点击“在此创建新项目”。在项目文件夹中输入项目名称&#xff0c;点击下一步&#xff1b;选…

经纬恒润助力微宏动力荣获ISO/SAE 21434网络安全流程认证证书

近日&#xff0c;经纬恒润与微宏动力合作的网络安全开发及认证项目顺利完成了阶段性里程碑。作为一家全球化的新能源及储电技术产品及解决方案供应商&#xff0c;微宏动力成功获得了由国际独立第三方检测、检验和认证机构UL Solutions授予的ISO/SAE 21434网络安全流程认证证书。…

社区团购系统搭建部署 :便捷高效,连接消费者与商家新篇章

一、前言 随着科技的快速发展和互联网的普及&#xff0c;社区团购系统作为一种新型的购物模式&#xff0c;正以其便捷高效的特性&#xff0c;逐渐改变着消费者和商家的互动方式。社区团购系统为商家提供丰富的营销活动和便捷高效的门店管理体系&#xff0c;为消费者提供真正实惠…

分享:2024年怎么做选品师项目才能赚钱?

在2024年&#xff0c;成为一名成功的选品师并赚钱&#xff0c;需要明确的策略和行动步骤。选品师作为电商行业中的关键角色&#xff0c;负责选择和管理产品库存&#xff0c;直接影响到销售和利润。以下是一些关键步骤&#xff0c;帮助您在这个领域取得成功。 1. 熟悉市场趋势和…

18、24年--信息系统工程——系统集成

1、集成基础 系统集成的工作再信息系统项目建设中非常重要,它通过硬件平台、网络通信平台、数据库平台、工具平台、应用软件平台将各类资源有机、高效地集成到一起,形成一个完整的工作台面。系统集成工作的好坏对系统开发、维护有极大的影响。因此,在技术上需要遵循的基本原…

labelme安装(通过anaconda)

1.下载安装anaconda 2.安装完成后打开&#xff0c;在环境页里面创建环境 选择3.6.13版本&#xff0c;然后运行 3.安装labelme pip install labelme -i https://pypi.tuna.tsinghua.edu.cn/simple 使用上面命令加速一下 4.labelme打开并标注 总结&#xff1a;现在版本越来越多…