从零开始写 Docker(十八)---容器网络实现(下):为容器插上”网线“
本文为从零开始写 Docker 系列第十八篇,利用 linux 下的 Veth、Bridge、iptables 等等相关技术,构建容器网络模型,为容器插上”网线“。
完整代码见:https://github.com/lixd/mydocker
欢迎 Star
推荐阅读以下文章对 docker 基本实现有一个大致认识:
- 核心原理:深入理解 Docker 核心原理:Namespace、Cgroups 和 Rootfs
- 基于 namespace 的视图隔离:探索 Linux Namespace:Docker 隔离的神奇背后
- 基于 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
都清理了,说明后台容器清理也是正常的。
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
从零开始写 Docker(十八)---容器网络实现(下):为容器插上”网线“的更多相关文章
- 腾讯云TKE-基于 Cilium 统一混合云容器网络(下)
前言 在 腾讯云TKE - 基于 Cilium 统一混合云容器网络(上) 中,我们介绍 TKE 混合云的跨平面网络互通方案和 TKE 混合云 Overlay 网络方案.公有云 TKE 集群添加第三方 ...
- Jmeter(二十八)_Docker+Jmeter+Gitlab+Jenkins+Ant(容器化的接口自动化持续集成平台)
这套接口自动化持续集成环境已经部署差不多了,现在说说我的设计思路 1:利用Docker容器化Gitlab,Jenkins,Jmeter,Ant,链接如下 Docker_容器化gitlab Docker ...
- WPF,Silverlight与XAML读书笔记第四十八 - Silverlight网络与通讯
说明:本系列基本上是<WPF揭秘>的读书笔记.在结构安排与文章内容上参照<WPF揭秘>的编排,对内容进行了总结并加入一些个人理解. 这一部分我们重点讨论下Silverlight ...
- Docker(十八)-Docker配置DNS
Linux系统配置DNS的时候有一个问题,就是你在/ect/resolv.conf文件中添加上nameserver XXX.XXX.XXX.XXX的时候,当时是生效的,但是机器重启之后就失效了,所以我 ...
- Java从零开始学三十八(JAVA IO- 重定向IO)
一.三个静态变量 java.lang.System提供了三个静态变量 System.in(默认键盘) System.out(默认显示器) System.err 二.重写向方法 System提供了三个重 ...
- python学习笔记(十八)网络编程之requests模块
上篇博客中我们使用python自带的urllib模块去请求一个网站,或者接口,但是urllib模块太麻烦了,传参数的话,都得是bytes类型,返回数据也是bytes类型,还得解码,想直接把返回结果拿出 ...
- python六十八课——网络编程之UDP协议
1.1 概述 UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接.简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到 ...
- Java从零开始学二十八(Math类和Random类)
一.Math概述 提供了常用的数学运算方法和两个静态常量E(自然对数的底数)和PI(圆周率) 二.常用方法 package com.pb.demo1; public class MathTest { ...
- 从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十八║Vue基础: 指令(下)+计算属性+watch
回顾 今天来晚辣,给公司做了一个小项目,一个瀑布流+动态视频控制的DEMO,有需要的可以联系我,公司的项目就不对外展示了(一个后端程序员真的要干前端了哈哈哈). 书接上文,昨天正式的开始了Vue的代码 ...
- 从零开始学习html(八)CSS选择器——下
六.子选择器 <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" ...
随机推荐
- 无缝融入 Kubernetes 生态 | 云原生网关支持 Ingress 资源
简介:Kubernetes 一贯的作风是通过定义标准来解决同一类问题,在解决集群对外流量管理的问题也不例外.Kubernetes 对集群入口点进行了进一步的统一抽象,提出了 3 种解决方案:Node ...
- [FAQ] docker-ce depends on containerd.io, docker-ce depends on docker-ce-cli
安装 docker 缺少依赖会提示你安装,一般是以下两个: Package containerd.io is not installed Package docker-ce-cli is not in ...
- Unlink原理和一些手法
Unlink原理和一些手法 简单介绍一下unlink相关的知识 unlink是利用glibc malloc 的内存回收机制造成攻击的,核心就在于当两个free的堆块在物理上相邻时,会将他们合并,并将原 ...
- 鸿蒙HarmonyOS实战-ArkUI事件(键鼠事件)
前言 键鼠事件是指在计算机操作中,用户通过键盘和鼠标来与计算机进行交互的行为.常见的键鼠事件包括按下键盘上的键.移动鼠标.点击鼠标左键或右键等等.键鼠事件可以触发许多不同的操作,比如在文本编辑器中输入 ...
- 一些简单的Post方式WebApi接收参数和传递参数的方法及总结
原文地址:https://www.zhaimaojun.top/Note/5475297(我自己的博客网站) 各种Post方式上传参数到服务器,服务器接收各种参数的示例 webapi可以说是很常用了, ...
- AI 编译器CINN中的OpLowering优化Pass
一.Lower 主逻辑 在 OpLower::Lower() 接口中,主要分为两大类: Elementwise类,主要涉及的 OpPattern 包括:kElementwise .kBroadcast ...
- C数据结构:哈夫曼树算法实现与应用
学习哈夫曼树(编码) 带权二叉树 认识WPL 最优二叉树 构造哈夫曼树的过程 哈夫曼树的应用 建立哈夫曼树 代码如下: 结构体代码部分 建立操作代码 找到最小结点(※难点) 附上建立哈夫曼树源代码 带 ...
- Vue实现商品详情鼠标移动+放大显示图片细节
效果图 代码实现 <template> <div> <div style="position: relative;" class="box& ...
- vue实现的常见的动画效果
本文包括的动画: zoom-in zoom-in-left zoom-in-right zoom-in-top zoom-in-bottom zoom-in-center-x zoom-in-cent ...
- RBD与Cephfs
目录 1. RBD 1. RBD特性 2. 创建rbd池并使用 2.1 创建rbd 2.2 创建用户 2.3 下发用户key与ceph.conf 2.4 客户端查看pool 2.5 创建rbd块 2. ...