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


    mydocker-network-3.png

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


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

    推荐阅读以下文章对 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:  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:  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:  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:  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:  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:  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:  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:  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:  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:  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:  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:  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:  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:  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
    
  • 相关阅读:
    Java8 特性 -- Lambda 详解
    【第29天】给定一个整数 n ,请你求出它的因子数
    002-第一代硬件系统架构确立及产品选型
    wxpython控件textctrl如何设置enter事件
    基于 Keras 的图像分类器
    【TCP】确认应答 与 超时重传
    rsync远程同步
    机器学习模型常用评价指标(Accuracy, Precision, Recall、F1-score、MSE、RMSE、MAE、R方)
    云计算-Linux-mkdir.cd.pwd.touch,cp命令等学习
    agv 路径规划 matlab
  • 原文地址:https://www.cnblogs.com/KubeExplorer/p/18245717