[mydocker]---网络实现

1. 前言

在 [mydocker]---网络net/netlink api 使用解析 文中已经基本涵盖了大部分的内容. 本文将整个流程梳理起来.

2. 网络

在本机上查看默认的网络.

docker network ls

NETWORK ID          NAME                DRIVER              SCOPE
3547024b25e7        bridge              bridge              local
974d4b4a2465        host                host                local
88561b2dac7f        none                null                local

可以看到有三个网络, 每个网络都属于哪种驱动DRIVER, 其实说白了就是属于哪种类型. 比如最常见的网桥bridge类型. 所以接下来会看一下bridge类型如何实现.

2.1 数据结构

可以看到网络Network的结构, 有一个Name为网络名字, IpRange为该网络的网段, Driver就是该网络是用什么驱动.

type Network struct {
    Name string
    IpRange *net.IPNet
    Driver string
}

网络端点Endpoint是用于连接容器与网络的,保证容器内部与网络的通信.

type Endpoint struct {
    ID string `json:"id"`   
    Device netlink.Veth `json:"dev"`
    IPAddress net.IP `json:"ip"`
    MacAddress net.HardwareAddr `json:"mac"`
    Network    *Network
    PortMapping []string
}

网络驱动, 是一个接口, 包括了5个方法, 所有的驱动类型都需要实现这些方法.

type NetworkDriver interface {
    Name() string   // 名字
    Create(subnet string, name string) (*Network, error)  // 创建网络 
    Delete(network Network) error  // 删除网络
    Connect(network *Network, endpoint *Endpoint) error  
    Disconnect(network Network, endpoint *Endpoint) error
}

3. 驱动bridge的实现

先看看驱动的实现, 这些方法[mydocker]---网络net/netlink api 使用解析 基本上全部已经用过了.

3.1 结构

type BridgeNetworkDriver struct {
}

func (d *BridgeNetworkDriver) Name() string {
    return "bridge"
}

3.2 公共方法

initBridge方法就相当于
ip link add bridgeName type bridge
ip addr add ip dev bridgeName
ip link set bridgeName up

func (d *BridgeNetworkDriver) initBridge(n *Network) error {
    // try to get bridge by name, if it already exists then just exit
    bridgeName := n.Name
    if err := createBridgeInterface(bridgeName); err != nil {
        return fmt.Errorf("Error add bridge: %s, Error: %v", bridgeName, err)
    }

    // Set bridge IP
    gatewayIP := *n.IpRange
    gatewayIP.IP = n.IpRange.IP

    if err := setInterfaceIP(bridgeName, gatewayIP.String()); err != nil {
        return fmt.Errorf("Error assigning address: %s on bridge: %s with an error of: %v", gatewayIP, bridgeName, err)
    }

    if err := setInterfaceUP(bridgeName); err != nil {
        return fmt.Errorf("Error set bridge up: %s, Error: %v", bridgeName, err)
    }

    // Setup iptables
    if err := setupIPTables(bridgeName, n.IpRange); err != nil {
        return fmt.Errorf("Error setting iptables for %s: %v", bridgeName, err)
    }

    return nil
}

// 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
}

func createBridgeInterface(bridgeName string) error {
    _, err := net.InterfaceByName(bridgeName)
    if err == nil || !strings.Contains(err.Error(), "no such network interface") {
        return err
    }

    // create *netlink.Bridge object
    la := netlink.NewLinkAttrs()
    la.Name = bridgeName

    br := &netlink.Bridge{la}
    if err := netlink.LinkAdd(br); err != nil {
        return fmt.Errorf("Bridge creation failed for bridge %s: %v", bridgeName, err)
    }
    return nil
}

func setInterfaceUP(interfaceName string) error {
    iface, err := netlink.LinkByName(interfaceName)
    if err != nil {
        return fmt.Errorf("Error retrieving a link named [ %s ]: %v", iface.Attrs().Name, err)
    }

    if err := netlink.LinkSetUp(iface); err != nil {
        return fmt.Errorf("Error enabling interface for %s: %v", interfaceName, err)
    }
    return nil
}


// Set the IP addr of a netlink interface
func setInterfaceIP(name string, rawIP string) error {
    retries := 2
    var iface netlink.Link
    var err error
    for i := 0; i < retries; i++ {
        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 fmt.Errorf("Abandoning retrieving the new bridge link from netlink, Run [ ip link ] to troubleshoot the error: %v", err)
    }
    ipNet, err := netlink.ParseIPNet(rawIP)
    if err != nil {
        return err
    }
    addr := &netlink.Addr{ipNet, "", 0, 0, nil}
    return netlink.AddrAdd(iface, addr)
}

func setupIPTables(bridgeName string, subnet *net.IPNet) error {
    iptablesCmd := fmt.Sprintf("-t nat -A POSTROUTING -s %s ! -o %s -j MASQUERADE", subnet.String(), bridgeName)
    cmd := exec.Command("iptables", strings.Split(iptablesCmd, " ")...)
    //err := cmd.Run()
    output, err := cmd.Output()
    if err != nil {
        log.Errorf("iptables Output, %v", output)
    }
    return err
}

3.3 Creat方法

返回一个Network, 该Network会有一个网桥, 名字为传入的name, 网段为传入的网段subnet, 并且该网络已经up并且已经设置好iptablesMASQUERADE规则.

func (d *BridgeNetworkDriver) Create(subnet string, name string) (*Network, error) {
    ip, ipRange, _ := net.ParseCIDR(subnet)
    ipRange.IP = ip
    n := &Network {
        Name: name,
        IpRange: ipRange,
        Driver: d.Name(),
    }
    err := d.initBridge(n)
    if err != nil {
        log.Errorf("error init bridge: %v", err)
    }

    return n, err
}

3.4 删除方法

删除该网络的网桥设备相当于ip link delete bridgeName type bridge.

func (d *BridgeNetworkDriver) Delete(network Network) error {
    bridgeName := network.Name
    br, err := netlink.LinkByName(bridgeName)
    if err != nil {
        return err
    }
    return netlink.LinkDel(br)
}

3.5 关联设备

Connect方法生成一个设备Veth并且将其中一端attach到该网络的网桥上. 具体测试参考[mydocker]---网络net/netlink api 使用解析中的TestNet003.

func (d *BridgeNetworkDriver) Connect(network *Network, endpoint *Endpoint) error {
    bridgeName := network.Name
    br, err := netlink.LinkByName(bridgeName)
    if err != nil {
        return err
    }

    la := netlink.NewLinkAttrs()
    la.Name = endpoint.ID[:5]
    la.MasterIndex = br.Attrs().Index

    endpoint.Device = netlink.Veth{
        LinkAttrs: la,
        PeerName:  "cif-" + endpoint.ID[:5],
    }

    if err = netlink.LinkAdd(&endpoint.Device); err != nil {
        return fmt.Errorf("Error Add Endpoint Device: %v", err)
    }

    if err = netlink.LinkSetUp(&endpoint.Device); err != nil {
        return fmt.Errorf("Error Add Endpoint Device: %v", err)
    }
    return nil
}

func (d *BridgeNetworkDriver) Disconnect(network Network, endpoint *Endpoint) error {
    return nil
}

4. 网络Network的实现

有了驱动, Network基本上都是调用某个驱动的方法.

4.1 基本方法

不多说了, dump就是把当前网络nw保存到dumpPath的路径上, load就是把dumpPath路径上的信息加载到nw中.

var (
    defaultNetworkPath = "/var/run/mydocker/network/network/"
    drivers = map[string]NetworkDriver{}
    networks = map[string]*Network{}
)
func (nw *Network) dump(dumpPath string) error {
    if _, err := os.Stat(dumpPath); err != nil {
        if os.IsNotExist(err) {
            os.MkdirAll(dumpPath, 0644)
        } else {
            return err
        }
    }

    nwPath := path.Join(dumpPath, nw.Name)
    nwFile, err := os.OpenFile(nwPath, os.O_TRUNC | os.O_WRONLY | os.O_CREATE, 0644)
    if err != nil {
        logrus.Errorf("error:", err)
        return err
    }
    defer nwFile.Close()

    nwJson, err := json.Marshal(nw)
    if err != nil {
        logrus.Errorf("error:", err)
        return err
    }

    _, err = nwFile.Write(nwJson)
    if err != nil {
        logrus.Errorf("error:", err)
        return err
    }
    return nil
}
func (nw *Network) load(dumpPath string) error {
    nwConfigFile, err := os.Open(dumpPath)
    defer nwConfigFile.Close()
    if err != nil {
        return err
    }
    nwJson := make([]byte, 2000)
    n, err := nwConfigFile.Read(nwJson)
    if err != nil {
        return err
    }

    err = json.Unmarshal(nwJson[:n], nw)
    if err != nil {
        logrus.Errorf("Error load nw info", err)
        return err
    }
    return nil
}

4.2 初始化方法

可以看到初始化方法做了三件事

1. 初始化一个网桥驱动BridgeNetworkDriver并放到drviers中.
2. 如果默认路径不存在则创建.
3. 加载所有现存网络并放到networks中.

func Init() error {
    var bridgeDriver = BridgeNetworkDriver{}
    drivers[bridgeDriver.Name()] = &bridgeDriver

    if _, err := os.Stat(defaultNetworkPath); err != nil {
        if os.IsNotExist(err) {
            os.MkdirAll(defaultNetworkPath, 0644)
        } else {
            return err
        }
    }

    filepath.Walk(defaultNetworkPath, func(nwPath string, info os.FileInfo, err error) error {
        if strings.HasSuffix(nwPath, "/") {
            return nil
        }
        _, nwName := path.Split(nwPath)
        nw := &Network{
            Name: nwName,
        }

        if err := nw.load(nwPath); err != nil {
            logrus.Errorf("error load network: %s", err)
        }

        networks[nwName] = nw
        return nil
    })

    //logrus.Infof("networks: %v", networks)

    return nil
}

4.3 增加删除查找网络

可以看到增加一个网络的时候需要传入driver(比如bridge就会对应到上面说的BridgeNetworkDriver), 网段subnet, 网络的名字name.

如果driver是网桥bridgedrivers[driver]=BridgeNetworkDriver, drivers[driver].Create(cidr.String(), name)会调用BridgeNetworkDriverCreate方法创建一个网桥并设置好iptables规则.

ListNetwork就是把Init方法中的networks输出.
DeleteNetwork 首先归还ip并调用驱动的删除方法, (如果是bridge驱动则会删除宿主机上该网络对应的网桥),最后移除本地保存的文件.

func CreateNetwork(driver, subnet, name string) error {
    _, cidr, _ := net.ParseCIDR(subnet)
    ip, err := ipAllocator.Allocate(cidr)
    if err != nil {
        return err
    }
    cidr.IP = ip

    nw, err := drivers[driver].Create(cidr.String(), name)
    if err != nil {
        return err
    }

    return nw.dump(defaultNetworkPath)
}

func ListNetwork() {
    w := tabwriter.NewWriter(os.Stdout, 12, 1, 3, ' ', 0)
    fmt.Fprint(w, "NAME\tIpRange\tDriver\n")
    for _, nw := range networks {
        fmt.Fprintf(w, "%s\t%s\t%s\n",
            nw.Name,
            nw.IpRange.String(),
            nw.Driver,
        )
    }
    if err := w.Flush(); err != nil {
        logrus.Errorf("Flush error %v", err)
        return
    }
}

func DeleteNetwork(networkName string) error {
    nw, ok := networks[networkName]
    if !ok {
        return fmt.Errorf("No Such Network: %s", networkName)
    }

    if err := ipAllocator.Release(nw.IpRange, &nw.IpRange.IP); err != nil {
        return fmt.Errorf("Error Remove Network gateway ip: %s", err)
    }

    if err := drivers[nw.Driver].Delete(*nw); err != nil {
        return fmt.Errorf("Error Remove Network DriverError: %s", err)
    }

    return nw.remove(defaultNetworkPath)
}

func (nw *Network) remove(dumpPath string) error {
    if _, err := os.Stat(path.Join(dumpPath, nw.Name)); err != nil {
        if os.IsNotExist(err) {
            return nil
        } else {
            return err
        }
    } else {
        return os.Remove(path.Join(dumpPath, nw.Name))
    }
}

4.4 关联

enterContainerNetnsconfigEndpointIpAddressAndRoute在[mydocker]---网络net/netlink api 使用解析 已经测试使用过, 其实该部分对应着TestNet006, Connect其实就是首先获得容器的Ip, 以网桥驱动为例drivers[network.Driver].Connect(network, ep)会创建一对Veth设备, 并将某一端放到与网桥相联, configEndpointIpAddressAndRouteVeth其中一端放到容器中并且配置容器ip, 路由等等.

func Connect(networkName string, cinfo *container.ContainerInfo) error {
    network, ok := networks[networkName]
    if !ok {
        return fmt.Errorf("No Such Network: %s", networkName)
    }

    // 分配容器IP地址
    ip, err := ipAllocator.Allocate(network.IpRange)
    if err != nil {
        return err
    }

    // 创建网络端点
    ep := &Endpoint{
        ID: fmt.Sprintf("%s-%s", cinfo.Id, networkName),
        IPAddress: ip,
        Network: network,
        PortMapping: cinfo.PortMapping,
    }
    // 调用网络驱动挂载和配置网络端点
    if err = drivers[network.Driver].Connect(network, ep); err != nil {
        return err
    }
    // 到容器的namespace配置容器网络设备IP地址
    if err = configEndpointIpAddressAndRoute(ep, cinfo); err != nil {
        return err
    }

    return configPortMapping(ep, cinfo)
}

func Disconnect(networkName string, cinfo *container.ContainerInfo) error {
    return nil
}

func enterContainerNetns(enLink *netlink.Link, cinfo *container.ContainerInfo) func() {
    f, err := os.OpenFile(fmt.Sprintf("/proc/%s/ns/net", cinfo.Pid), os.O_RDONLY, 0)
    if err != nil {
        logrus.Errorf("error get container net namespace, %v", err)
    }

    nsFD := f.Fd()
    runtime.LockOSThread()

    // 修改veth peer 另外一端移到容器的namespace中
    if err = netlink.LinkSetNsFd(*enLink, int(nsFD)); err != nil {
        logrus.Errorf("error set link netns , %v", err)
    }

    // 获取当前的网络namespace
    origns, err := netns.Get()
    if err != nil {
        logrus.Errorf("error get current netns, %v", err)
    }

    // 设置当前进程到新的网络namespace,并在函数执行完成之后再恢复到之前的namespace
    if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
        logrus.Errorf("error set netns, %v", err)
    }
    return func () {
        netns.Set(origns)
        origns.Close()
        runtime.UnlockOSThread()
        f.Close()
    }
}

func configEndpointIpAddressAndRoute(ep *Endpoint, cinfo *container.ContainerInfo) error {
    peerLink, err := netlink.LinkByName(ep.Device.PeerName)
    if err != nil {
        return fmt.Errorf("fail config endpoint: %v", err)
    }

    defer enterContainerNetns(&peerLink, cinfo)()

    interfaceIP := *ep.Network.IpRange
    interfaceIP.IP = ep.IPAddress

    if err = setInterfaceIP(ep.Device.PeerName, interfaceIP.String()); err != nil {
        return fmt.Errorf("%v,%s", ep.Network, err)
    }

    if err = setInterfaceUP(ep.Device.PeerName); err != nil {
        return err
    }

    if err = setInterfaceUP("lo"); err != nil {
        return err
    }

    _, cidr, _ := net.ParseCIDR("0.0.0.0/0")

    defaultRoute := &netlink.Route{
        LinkIndex: peerLink.Attrs().Index,
        Gw: ep.Network.IpRange.IP,
        Dst: cidr,
    }

    if err = netlink.RouteAdd(defaultRoute); err != nil {
        return err
    }

    return nil
}

另外configPortMapping就是实现iptables DNAT.

func configPortMapping(ep *Endpoint, cinfo *container.ContainerInfo) error {
    for _, pm := range ep.PortMapping {
        portMapping :=strings.Split(pm, ":")
        if len(portMapping) != 2 {
            logrus.Errorf("port mapping format error, %v", pm)
            continue
        }
        iptablesCmd := fmt.Sprintf("-t nat -A PREROUTING -p tcp -m tcp --dport %s -j DNAT --to-destination %s:%s",
            portMapping[0], ep.IPAddress.String(), portMapping[1])
        cmd := exec.Command("iptables", strings.Split(iptablesCmd, " ")...)
        //err := cmd.Run()
        output, err := cmd.Output()
        if err != nil {
            logrus.Errorf("iptables Output, %v", output)
            continue
        }
    }
    return nil
}

5. 参考

1. 自己动手写docker.(基本参考此书,加入一些自己的理解,加深对docker的理解)

6. 全部内容

[mydocker]---网络实现_第1张图片
mydocker.png

1. [mydocker]---环境说明
2. [mydocker]---urfave cli 理解
3. [mydocker]---Linux Namespace
4. [mydocker]---Linux Cgroup
5. [mydocker]---构造容器01-实现run命令
6. [mydocker]---构造容器02-实现资源限制01
7. [mydocker]---构造容器02-实现资源限制02
8. [mydocker]---构造容器03-实现增加管道
9. [mydocker]---通过例子理解存储驱动AUFS
10. [mydocker]---通过例子理解chroot 和 pivot_root
11. [mydocker]---一步步实现使用busybox创建容器
12. [mydocker]---一步步实现使用AUFS包装busybox
13. [mydocker]---一步步实现volume操作
14. [mydocker]---实现保存镜像
15. [mydocker]---实现容器的后台运行
16. [mydocker]---实现查看运行中容器
17. [mydocker]---实现查看容器日志
18. [mydocker]---实现进入容器Namespace
19. [mydocker]---实现停止容器
20. [mydocker]---实现删除容器
21. [mydocker]---实现容器层隔离
22. [mydocker]---实现通过容器制作镜像
23. [mydocker]---实现cp操作
24. [mydocker]---实现容器指定环境变量
25. [mydocker]---网际协议IP
26. [mydocker]---网络虚拟设备veth bridge iptables
27. [mydocker]---docker的四种网络模型与原理实现(1)
28. [mydocker]---docker的四种网络模型与原理实现(2)
29. [mydocker]---容器地址分配
30. [mydocker]---网络net/netlink api 使用解析
31. [mydocker]---网络实现
32. [mydocker]---网络实现测试

你可能感兴趣的:([mydocker]---网络实现)