從零開始寫 Docker(十八)---容器網路實現(下):為容器插上”網線“

探索云原生發表於2024-06-13

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

相關文章