美文网首页
[mydocker]---容器地址分配

[mydocker]---容器地址分配

作者: nicktming | 来源:发表于2019-05-12 08:42 被阅读0次

    1. 准备工作

    1.1 准备环境

    root@nicktming:~/go/src/github.com/nicktming/mydocker# git clone https://github.com/nicktming/mydocker.git
    root@nicktming:~/go/src/github.com/nicktming/mydocker# git checkout code-5.8
    root@nicktming:~/go/src/github.com/nicktming/mydocker# git checkout -b dev-6.1
    

    2. 容器地址分配

    根据前面几篇文章的铺垫, 从这篇文章就开始准备实现网络部分的一些功能. 首先需要解决的是如何分配容器地址, 因为一台主机上可以启动很多容器, 正常情况下(比如bridge模型)每个容器都会有属于自己的ip地址.所以需要有一套机制来管理容器地址的分配与释放.

    2.1 bitmap算法介绍

    bitmap-01.png

    由上图可知网络192.168.0.0/24的前缀占24位, 所以主机号占了8位, 因此该网络最多可分配2的8次方 256位, 除了192.168.0.0, 所以可分配255个ip, 从192.168.0.1~192.168.0.255中的ip地址都是属于该网络.

    由于需要给容器分配地址, 显然需要在机器上保存哪些ip地址已经被分配, 哪些没有分配, 所以可以用bitmap来做这个事情而且效率也比较快.
    从上图可知, 该map会维护一个数组长度为255(在该例子中), 数组下标加1的值就是该网络中的第几个ip, 比如下标为5, 该下标代表的ip地址为192.168.0.5.

    然后数组里面的值标记该位置的ip是否被分配, 0表示没有被分配, 1表示已经被分配.

    2.2 数据结构定义

    关于网络地址信息都会保存在宿主机的/var/run/mydocker/network/ipam/subnet.json位置.

    IPAM中的Subnets属性是一个map类型, key为某个网络比如192.168.0.0/24, value为该网络对应的ip地址分配情况表, 是一个string,其实就是把一个char数组转化成一个string.

    {"192.168.0.0/24":"0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}
    该map表示192.168.0.2已经被分配出去了, 其余的ip未分配出去.

    const ipamDefaultAllocatorPath = "/var/run/mydocker/network/ipam/subnet.json"
    
    type IPAM struct {
        SubnetAllocatorPath string
        Subnets *map[string]string
    }
    
    var ipAllocator = &IPAM{
        SubnetAllocatorPath: ipamDefaultAllocatorPath,
    }
    

    2.3 持久化和加载数据

    持久化数据 dump, 很简单就是把IPAM中的Subnets持久化到IPAM的SubnetAllocatorPath(/var/run/mydocker/network/ipam/subnet.json), 因为Subnets里面保存着容器网络的地址分配情况.

    func (ipam *IPAM) dump() error {
        // ipam.SubnetAllocatorPath 文件夹与文件分离开
        ipamConfigFileDir, _ := path.Split(ipam.SubnetAllocatorPath)
        if _, err := os.Stat(ipamConfigFileDir); err != nil {
            // 如果不存在 就逐级生成文件夹
            if os.IsNotExist(err) {
                os.MkdirAll(ipamConfigFileDir, 0644)
            } else {
                return err
            }
        }
        // O_CREATE int = syscall.O_CREAT  // create a new file if none exists.
        // O_WRONLY int = syscall.O_WRONLY // open the file write-only.
        // O_TRUNC  int = syscall.O_TRUNC  // truncate regular writable file when opened.
        // 如果该文件不存在 就创建一个
        subnetConfigFile, err := os.OpenFile(ipam.SubnetAllocatorPath, os.O_TRUNC | os.O_WRONLY | os.O_CREATE, 0644)
        defer subnetConfigFile.Close()
        if err != nil {
            return err
        }
        // 将ipam.Subnets的内容持久化
        // 也就是将所有网络的分配情况保存到该文件中
        ipamConfigJson, err := json.Marshal(ipam.Subnets)
        if err != nil {
            return err
        }
    
        _, err = subnetConfigFile.Write(ipamConfigJson)
        if err != nil {
            return err
        }
    
        return nil
    }
    

    加载数据, 就是把(/var/run/mydocker/network/ipam/subnet.json)里面的内容加载到IPAM中的Subnets中, 方便操作.

    func (ipam *IPAM) load() error {
        // 如果该文件不存在 返回
        if _, err := os.Stat(ipam.SubnetAllocatorPath); err != nil {
            log.Printf("load error err:%v\n", err)
            if os.IsNotExist(err) {
                return nil
            } else {
                return err
            }
        }
        // 打开该文件
        subnetConfigFile, err := os.Open(ipam.SubnetAllocatorPath)
        defer subnetConfigFile.Close()
        if err != nil {
            return err
        }
        // 将该文件的内容读到subnetJson中
        subnetJson := make([]byte, 2000)
        n, err := subnetConfigFile.Read(subnetJson)
        if err != nil {
            return err
        }
    
        log.Printf("n:%d\n", n)
        log.Println(subnetJson)
    
        // 将subnetJson中内容加载到ipam.Subnets中
        err = json.Unmarshal(subnetJson[:n], ipam.Subnets)
        if err != nil {
            log.Printf("Error dump allocation info, %v", err)
            return err
        }
        return nil
    }
    

    2.4 地址分配

    地址分配分为三个步骤:

    1. 先从/var/run/mydocker/network/ipam/subnet.json中加载数据到ipam的subnets, 如果该文件不存在, subnets是一个空map,里面什么网络信息都没有.
    2. 根据bitmap分配ip
    3. 将已经有数据的subnets持久化到/var/run/mydocker/network/ipam/subnet.json中.

    func (ipam *IPAM) Allocate(subnet *net.IPNet) (ip net.IP, err error) {
        // 存放网段中地址分配信息的数组
        // 无论ipamDefaultAllocatorPath是否存在都先new一个
        ipam.Subnets = &map[string]string{}
    
        // 从文件中加载已经分配的网段信息
        err = ipam.load()
        if err != nil {
            log.Printf("Error dump allocation info, %v", err)
        }
        // 得到网络号
        _, subnet, _ = net.ParseCIDR(subnet.String())
    
        log.Printf("Allocate subnet:%s, ipam.Subnets:%v\n", subnet, ipam.Subnets)
        // one表示前缀的个数 size表示ip地址的个数 ipv4==>size=32
        one, size := subnet.Mask.Size()
    
        log.Printf("Allocate one:%d, size:%d\n", one, size)
    
        // 如果该网络还不在ipam.Subnets中, 则初始化一个
        // 那怎么知道该网络有多少个ip地址呢 size-one就表示主机号占的位数 2的(size-one)方就有多少个主机ip
        if _, exist := (*ipam.Subnets)[subnet.String()]; !exist {
            (*ipam.Subnets)[subnet.String()] = strings.Repeat("0", 1 << uint8(size - one))
        }
    
        log.Printf("Allocate one:%s\n", (*ipam.Subnets)[subnet.String()])
    
        for c := range((*ipam.Subnets)[subnet.String()]) {
            // 如果第c个ip没有被分配 则分配
            if (*ipam.Subnets)[subnet.String()][c] == '0' {
                ipalloc := []byte((*ipam.Subnets)[subnet.String()])
                ipalloc[c] = '1'
                (*ipam.Subnets)[subnet.String()] = string(ipalloc)
    
                // 查一下c 用32位如何表示
                ip = subnet.IP
                for t := uint(4); t > 0; t-=1 {
                    []byte(ip)[4-t] += uint8(c >> ((t - 1) * 8))
                }
                ip[3]+=1
                break
            }
        }
        // 持久化数据
        ipam.dump()
        return
    }
    

    这里需要做几点说明:

    1. one, size := subnet.Mask.Size()
    比如subnet为192.168.0.0/24 那子网掩码就是255.255.255.0, 另外前缀个数为24, 由于这是个ipv4, ip地址位数是32, 所以主机号占了32-24=8位, 那么主机个数为2的8次幂为256. 所以one=24, size=32.

    2. []byte(ip)[4-t] += uint8(c >> ((t - 1) * 8)) 这个部分是为了找到被分配的ip, 可以看到网络如果是192.168.0.0/24, 表明地址从192.168.0.1~192.168.0.255, 如果c8, 表明第8个ip地址, 8用32位表示的话为0.0.0.8, 如何得到的呢? 0(8>>24).0(8>>16).0(8>>8).8(8>>0)
    另外ip = subnet.IP192.168.0.0, 所以依次对应加起来就是192.168.0.8, ip[3]+=1==>192.168.0.9. 所以分配的地址会是192.168.0.9.

    再加一个例子比如网络为172.16.0.0/12, 所以该网络有2的20次幂个ip地址, 从172.00010000.0.0~172.00011111.255.255, 此时如果c为65555表示为00000000 00000001 00000000 000100110.1.0.19, 挨个加上就得到172.17.0.19, 所以最终分配的ip地址为172.17.0.20.

    测试

    func Test002(t *testing.T)  {
        hostip, ipnet, _ := net.ParseCIDR("192.168.0.1/24")
        log.Printf("ip: %s ipnet ip:%s, mask:%s\n", hostip, ipnet.IP, ipnet.Mask)
        ip, _ := ipAllocator.Allocate(ipnet)
        log.Printf("alloc ip : %v\n", ip)
    }
    

    结果

    root@nicktming:~/go/src/github.com/nicktming/mydocker/network# pwd
    /root/go/src/github.com/nicktming/mydocker/network
    root@nicktming:~/go/src/github.com/nicktming/mydocker/network# ls 
    ipam.go  ipam_test.go
    root@nicktming:~/go/src/github.com/nicktming/mydocker/network# ls -l /var/run/mydocker
    ls: cannot access /var/run/mydocker: No such file or directory
    root@nicktming:~/go/src/github.com/nicktming/mydocker/network# go test -v ipam_test.go -test.run Test002
    === RUN   Test002
    2019/05/04 19:08:58 ip: 192.168.0.1 ipnet ip:192.168.0.0, mask:ffffff00
    2019/05/04 19:08:58 load error err:stat /var/run/mydocker/network/ipam/subnet.json: no such file or directory
    2019/05/04 19:08:58 Allocate subnet:192.168.0.0/24, ipam.Subnets:&map[]
    2019/05/04 19:08:58 Allocate one:24, size:32
    2019/05/04 19:08:58 Allocate one:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    2019/05/04 19:08:58 dump ipamConfigFileDir:/var/run/mydocker/network/ipam/
    2019/05/04 19:08:58 MkdirAll
    2019/05/04 19:08:58 dump ipamConfigJson:{"192.168.0.0/24":"1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}
    2019/05/04 19:08:58 alloc ip : 192.168.0.1
    --- PASS: Test002 (0.00s)
    PASS
    ok      command-line-arguments  0.003s
    root@nicktming:~/go/src/github.com/nicktming/mydocker/network# cat /var/run/mydocker/network/ipam/subnet.json 
    {"192.168.0.0/24":"1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}
    root@nicktming:~/go/src/github.com/nicktming/mydocker/network# 
    root@nicktming:~/go/src/github.com/nicktming/mydocker/network# 
    root@nicktming:~/go/src/github.com/nicktming/mydocker/network# go test -v ipam_test.go -test.run Test002
    root@nicktming:~/go/src/github.com/nicktming/mydocker/network# cat /var/run/mydocker/network/ipam/subnet.json 
    {"192.168.0.0/24":"1100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}
    

    可以看到分配了两个地址192.168.0.1192.168.0.2

    2.5 地址释放

    理解了地址分配, 地址释放就是一个反向的, 就不多说了.

    func (ipam *IPAM) Release(subnet *net.IPNet, ipaddr *net.IP) error {
        ipam.Subnets = &map[string]string{}
    
        _, subnet, _ = net.ParseCIDR(subnet.String())
    
        err := ipam.load()
        if err != nil {
            log.Printf("Error dump allocation info, %v", err)
        }
    
        c := 0
        releaseIP := ipaddr.To4()
        releaseIP[3]-=1
        for t := uint(4); t > 0; t-=1 {
            c += int(releaseIP[t-1] - subnet.IP[t-1]) << ((4-t) * 8)
        }
    
        ipalloc := []byte((*ipam.Subnets)[subnet.String()])
        ipalloc[c] = '0'
        (*ipam.Subnets)[subnet.String()] = string(ipalloc)
    
        ipam.dump()
        return nil
    }
    

    测试

    func Test003(t *testing.T)  {
        hostip, ipnet, _ := net.ParseCIDR("192.168.0.1/24")
        log.Printf("ip: %s ipnet ip:%s, mask:%s\n", hostip, ipnet.IP, ipnet.Mask)
        ipAllocator.Release(ipnet, &hostip)
    }
    

    结果

    root@nicktming:~/go/src/github.com/nicktming/mydocker/network# cat /var/run/mydocker/network/ipam/subnet.json 
    {"192.168.0.0/24":"1100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}root@nicktming:~/go/src/github.com/nicktming/mydocker/network# 
    root@nicktming:~/go/src/github.com/nicktming/mydocker/network# go test -v ipam_test.go -test.run Test003
    root@nicktming:~/go/src/github.com/nicktming/mydocker/network# cat /var/run/mydocker/network/ipam/subnet.json 
    {"192.168.0.0/24":"0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}
    root@nicktming:~/go/src/github.com/nicktming/mydocker/network# 
    

    3. 参考

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

    4. 全部内容

    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]---容器地址分配

          本文链接:https://www.haomeiwen.com/subject/kkttoqtx.html