首页 > 其他分享 >500代码行代码手写docker-设置网络命名空间

500代码行代码手写docker-设置网络命名空间

时间:2023-05-25 16:45:24浏览次数:54  
标签:容器 return err ip 代码 网络 docker bit 500

(4)500代码行代码手写docker-设置网络命名空间

本系列教程主要是为了弄清楚容器化的原理,纸上得来终觉浅,绝知此事要躬行,理论始终不及动手实践来的深刻,所以这个系列会用go语言实现一个类似docker的容器化功能,最终能够容器化的运行一个进程。

本章的源码已经上传到github,地址如下:

https://github.com/HobbyBear/tinydocker/tree/chapter4

前文我们已经为容器替换了新的根文件系统,但是由于我们启动容器的时候是在一个新的网络命名空间,目前的容器还不能访问外部网络,我们需要在这一节,让容器能够访问外部网络,并且能够实现同一个主机上的容器能够网络互通。

这节代码运行效果

eb3aa-2hs6n.gif

容器互通的原理

在正式开始编码之前,我将基于最简单的情况,则同一个主机上的容器能够通过ip互相访问的情况,简单的介绍下,容器网络互联的原理,我们是在一个新的网络命名空间 启动的子进程,不同网络命名空间拥有自己的防火墙,路由表,网络设备,所以需要对新生成的网络命名空间进行配置。让网络命名空间内部的网络包能够从网络命名空间内部出去到达主机上。

在linux上,可以用veth虚拟网络设备去连接两个不同网络命名空间,veth设备是成队出现,分别连接到不同的命名空间中, 从veth设备一端进入的网络包能够到达veth设备的另一端, 但在配置容器网络时并不是将veth设备直接连接在另一端的网络命名空间内,因为如果主机上容器过多的话,采用直接两两相连的方式,将会让网络拓扑过于复杂。所以一般是将veth设备连接到一个叫做网桥bridge的虚拟网络设备上,通过它对网络包进行转发。

关于veth设备和bridge的原理和使用,我之前出过一期视频讲解,可以去哪里深入的学习下:

容器网络原理之包流转路径

之前也出过许多对容器网络讲解的系列视频,如果有对容器网络不熟悉的同学,请看这里:

k8s容器互联 flannel host-gw 原理

k8s容器互联 flannel vxlan 模式原理

容器系列 笔记分享

实现ipam(ip地址分配管理)

现在,来让我们实现下关于容器网络配置的逻辑,首先容器在创建的时候,得先为它分配一个ip地址,本质上就是为它内部的veth设备分配一个ip地址。这就涉及到如何分配ip地址的问题,这里有两个问题需要解决:
1,当知道一个网络的网段后,如何知道网段内部哪个ip进行了分配,哪个ip没有进行分配。
2,如果知道了这个网段内某个ip没有被分配,如何根据偏移量计算最终没有被分配的ip,比如我知道第8个ip没有被分配,网络网段为192.168.0.0/24 ,那么第8个ip是多少?

首先来看下ip存储的问题,也就是看哪些ip进行了分配,哪些没有进行分配。

bitmap 存储ip地址分配记录

这是一个看某个值是否存在的问题,可以通过bitmap去存储,这在快速判断ip是否存在的前提下,也能极大的降低存储成本。

如下所示,如果第一个字节的第1位和第3位被置为1了,说明在这个网段内,第一个ip和第3个ip都被占用了。
image.png

一个byte是8个bit,也就可以表示8个ip是否被占用,而一个网段中的ip个数=2的N次方个,其中N=32-网段位数。

用一个实际的例子举例,比如子网掩码是255.255.0.0,说明网段是前面的16位,那么ip个数就是由后16位bit数表示,排除掉其中主机号全为0的网络号和主机号全为1的广播号,可用ip数=2的16次方-2 ,要表示那么多的ip数就需要 (2的16次方-2)/8 大小的字节 约等于8kb,转换成字节数组长度就是8192。

具体实现如下:

一个bitmap用一个byte数组表示

type bitMap struct {
	Bitmap []byte
}

bitmap的方法也就3个,

1, 查看第n个ip是不是被分配。

func (b *bitMap) BitExist(pos int) bool {
	aIndex := arrIndex(pos)
	bIndex := bytePos(pos)
	return 1 == 1&(b.Bitmap[aIndex]>>bIndex)
}

arrIndex和bytePos 方法实现如下:

func arrIndex(pos int) int {
	return pos / 8
}

func bytePos(pos int) int {
	return pos % 8
}

我们最终是要找到这地n个ip所在的bit位,然后查找该bit位是否被置为1,置为1就代表这第n个ip是被分配了。用n/8得到的就是第n个ip所在bit位的字节数组的索引,用n%8得到的余数就是在字节里的第几个bit位,如何取出对应的bit位呢?

首先是b.Bitmap[aIndex]得到对应的字节,然后将该字节右移对应的bit位数,这样第n个ip的bit位就变到了第一个bit位上。整个过程像下面这样:

image.png
与运算是双1结果才是1,所以如果最后一个bit位是1则最后与运算的结果就是数字1,如果最后一位bit位是0,则最后运算的结果就是0。

2,设置第n个ip被分配。

设置第n个ip被分配,即设置它对应的bit位为1,首先还是要找到这第n个ip在数组中的位置,然后取出对应字节byte,通过位运算设置其对应的bit位。

func (b *bitMap) BitSet(pos int) {
	aIndex := arrIndex(pos)
	bIndex := bytePos(pos)
	b.Bitmap[aIndex] = b.Bitmap[aIndex] | (1 << bIndex)
}

零bit的或位运算不会改变原bit位值大小,而1的bit的或位运算会将原来bit位置为1,利用这个特性便可以很容易的写出来上面的代码。

整个过程如图所示:
image.png

3,释放第n个ip的分配记录。

释放第n个ip原理和前面类似,设置第n个ip对应的bit位为0。

func (b *bitMap) BitClean(pos int) {
	aIndex := arrIndex(pos)
	bIndex := bytePos(pos)
	b.Bitmap[aIndex] = b.Bitmap[aIndex] & (^(1 << bIndex))
}

零bit的与位运算会让原bit位置为0,而1的与位运算不会改变原bit位的值,知道了这个特性再看上述代码应该就很容易了,其中^ 运算符为取反的意思,这样00000100 就会变为 11111011,这样与原bit位进行与位运算就能将索引为2的bit位置为0了。

通过ip偏移计算ip地址

通过上述bitmap的实现可以解决ip分配的存储问题,但还有一个问题要解决,那就是目前只知道了第几个ip没有分配,如何通过这个ip偏移量获取到具体的ip地址?现在来解决这个问题。

一个网段里第一个ip的主机号全为0,被称为网络号,其ip偏移为0,拿192.168.0.0/16网段举例,第一个ip就是192.168.0.0,第二个ip地址其ip偏移量为1,ip地址是192.168.0.1,以此类推,可以得到下面的公式:

ip地址=ip网络号+ip地址偏移

所以关键就是要得到一个ip的ip网络号,用ipv4举例,在golang里面ip类型本质上就是一个长度为4字节数组

type IP []byte

所以现在要把这个4字节的数组转换为32位整形,可以像下面这样转换

func ipToUint32(ip net.IP) uint32 {
	if ip == nil {
		return 0
	}
	ip = ip.To4()
	if ip == nil {
		return 0
	}
	return binary.BigEndian.Uint32(ip)
}

func (bigEndian) Uint32(b []byte) uint32 {
	_ = b[3] // bounds check hint to compiler; see golang.org/issue/14808
	return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24
}

由于ip地址是大端排序,网段号排在字节数组前面,所以binary.BigEndian进行转换。

这样获取ip的逻辑就是一个简单的加法了

firstIP := ipToUint32(ip.Mask(cidr.Mask))
ip = uint32ToIP(firstIP + uint32(pos))

通过ip地址计算ip偏移

关于ip的分配还有最后一个比较关键的点,那就是释放ip,前面已经提到我们已经可以办到释放第n个ip了,其中n就是ip的偏移量,那么如何通过ip地址去计算ip的偏移量呢?
其实很容易,拿当前ip减去网络号就是ip偏移量了

ip偏移量=当前ip-网络号ip

这里的具体代码我就不再展示了。

创建网络设备实现容器互联

知道如何为容器分配ip地址了,还需要在网络命名空间内 创建新的网络设备,然后设置上这个ip。为了让整个逻辑变的简单,我们创建一个默认的网络,让容器创建的时候自动在这个默认的网络下,并为其分配ip。

整个过程分为两个阶段,一个是程序启动的时候,会去检查主机上是否存在这个默认网络需要的配置,如果有则不再创建相关网络设备,我将这个阶段称为网络初始化阶段,第二个阶段是容器创建时候,需要为容器创建相关网络配置的阶段。我们挨个来看看。

标签:容器,return,err,ip,代码,网络,docker,bit,500
From: https://www.cnblogs.com/hobbybear/p/17431786.html

相关文章

  • docker 方式部署的gitlab 升级
    升级背景:docker部署的gitlab版本11.1.4,需升级至16.0.1思路:为了不影响目前正在使用的gitlab。1.将备份拷贝至另外服务器上,升级至最高版本后,另行还原2.直接将挂载目录中的文件拷贝至另外服务器上,升级至最高版本,另行还原--经测试,失败,不推荐使用此方法2种方式将都进行实验。以......
  • Debian 11(apt系 )docker安装
    apt系(Debian11)安装Docker参考:dockerdebian安装教程:https://docs.docker.com/engine/install/debian/使用非roor用户组运行docker:https://docs.docker.com/compose/completion/dockercompose安装教程:https://docs.docker.com/compose/completion/1.卸载旧版本docke......
  • 【全国产龙芯平台】迅为iTOP-LS3A5000_7A2000开发板+银河麒麟操作系统
       硬件准备 1.M.2.ssd硬盘(最好大于等于128G);2.迅为LS3A5000开发板;  3.U盘(需大于8g),制作启动盘使用;4.hdmi显示器;5.搭载linux环境的计算机。   安装步骤1制作启动盘我们首先使用命令查看U盘挂载节点位置。我们将iso镜像放入linux操作系统中,使用dd命令......
  • IOS快捷指令代码分享
    IOS快捷指令分享制作快捷指令首先在快捷指令APP上制作快捷指令添加一些逻辑,具体可以自己体验然后点击共享,获取iCloud链接类似于这种https://www.icloud.com/shortcuts/02b1494ad19e49a29ce1189f0391a99a可能在手机上分享会报未登录iCloud,但是也没给出登陆的地方,其实在设......
  • 系统展示 | 全国产3A5000龙芯电脑运行loongnix/ 银河麒麟 / 统信系统
    龙芯生态体系越来越完善,目前iTOP-3A5000全国产龙芯电脑支持loongnix、银河麒麟和统信系统。接下来一起看下以上系统在iTOP-3A5000全国产龙芯电脑上的运行效果吧。《视频B站搜索》迅为基于龙芯3A5000处理器设计开发的iTOP-3A5000开发板3A500主板参数:主要参数处理器 龙芯3A5000主频......
  • 通过Java代码备份Mysql数据库
    MainpublicclassMain{publicstaticvoidmain(String[]args){//备份数据库BakDb.bakDB();//获取本地备份的sql文件List<String>names=Unitls.sqlFileNameByLocal();//获取最后一个文件名Stringname=names......
  • 源代码管理工具
    什么是源代码管理工具?源代码管理(SCM)工具是一种软件应用程序,它提供了一个集中位置来存储、管理和版本控制源代码文件和其他软件开发工件。当多个开发人员对同一文件进行更改时,它允许开发人员团队跟踪更改、协作处理功能并解决冲突,从而帮助开发人员团队在代码库上协同工作。使......
  • 关于源代码管理工具的介绍与实际使用技巧
      当谈到主流的源代码管理工具时,GitHub和AzureDevOps(之前称为TFS,即TeamFoundationServer)是两个备受推崇的选择。我将重点介绍GitHub,并说明如何将其与团队项目结合起来。  GitHub是一个在线软件源代码托管服务平台,使用Git作为版本控制软件,由开发者ChrisWanstrath、P.J.H......
  • docker安装rabbitMQ
    输入命令dockerpullrabbitmq:3.7.7-management  设置账号和密码dockerrun-d--namerabbitmq3.7.7-p5672:5672-p15672:15672-v`pwd`/data:/var/lib/rabbitmq--hostnamemyRabbit-eRABBITMQ_DEFAULT_VHOST=my_vhost-eRABBITMQ_DEFAULT_USER=admin-eRABBITM......
  • dockerfile中安装miniconda
    要在Dockerfile中安装Miniconda,你可以按照以下步骤进行操作:1.在Dockerfile中选择一个基础映像作为你的基础操作系统。例如,你可以选择一个适合的Linux发行版,如Ubuntu或CentOS。2.在Dockerfile中添加安装Miniconda的命令。可以使用以下示例命令:```dockerfile#设......