上次的愿望没有实现,但是我还是更新了。没办法,谁让我爱你们呢。
容器的网络部分是最简单的部分(没办法,哥们是最懂网络的程序员),Docker预留的一大段IP地址(在我的机器上是172.17.0.0/16,一共65536个IP地址),借助Linux bridge为每个容器提供一个IP地址,这样容器之间通讯就直接通过Linux bridge实现。外部网络通讯部分是基于IPTables实现的DNAT(导出端口到Host)和SNAT(借助Host访问外部网络)。
其实你可能已经猜到了,我们会用到Linux bridge、network namespace、IPtables三个东西,这三个都是Linux内置的工具。虽然上次我只讲了一个API,但是居然还有人声称没看懂,非要看我表演胸口碎大石。所以这次我决定——一个API也不用,全部用Linux命令行工具来实现。
涉及到三个工具
iproute2
,你可以通过输入ip link
来判断是否已经拥有这个工具。这是Linux主张使用的网络管理工具,旨在代替route
、ifconfig
这些命令。 (iproute2
是基于netlink实现的,这是内核空间提供的标准网络控制接口,vishvananda/netlink是golang对netlink的封装,Docker是基于vishvananda/netlink的)
bridge-utils
,Linux Bridge工具集,通过brctl show
来判断是否已经拥有它。
IPTables,Linux提供了数据包处理的扩展点(Hook),这个模块叫netfilter。IPTables是一组用户空间工具,通过这组工具为Hook增加一些“转发规则”。比如实现数据包过滤(防火墙),SNAT、DNAT。通过输入iptables -V
判断是否已经安装。
先把上次做的“容器”启动,centos7提供的rootfs没有相关的网络管理工具,只提供了一个ping命令,待会我们会用这个“简陋”的命令判断网络是否联通。
新启动一个Shell,查看上面容器中的namespace下面有什么。1243是我上面容器的PID。
这里的net就是上面容器对应的network namespace,我们把它link到/var/run/netns/下。(ip netns命令只会管理/var/run/netns/下的文件,所以我们必须link到这个目录下面)
注意/var/run/netns目录如果不存在请自己创建,然后我们就可以通过ip netns管理容器的网络了。
我们先创建一对VETH。这是一种虚拟设备,一端连接容器,一端连接我们稍后创建的Linux Bridge。ip netns exec表示在指定的namespace中执行网络命令。
通过brctl创建一个名叫“mini-docker-br0”的bridge。
把之前创建的veth的“另一端”加入到bridge里。
把veth的两端和Linux Bridge都设置为up状态
为容器的“eth0”和mini-docker-br0设置IP地址(我们也预留一大段IP地址为容器使用)
然后就是在容器里面用ping测试一下到网关(mini-docker-br0)
现在只能实现两个容器之间互访,如果要让容器访问公网必须在Host上通过IPTable做SNAT映射。
首先是修改Forward表,容器访问公网的数据包首先会经过Forward表;同样公网返回到容器的数据包也会经过Forward表。这里直接放行。
其次是NAT表,源地址10.100.0.0/16的数据包都用网卡ens33对应的IP地址完成SNAT。
最后为容器设置默认路由信息,指向网关(mini-docker-br0)。
然后在容器中ping一个公网IP
通过修改/etc/resolv.conf指定DNS服务器,就可以通过域名访问了。
当然yum也完全没有问题
总结一下,我们上面使用iproute工具创建了veth,为容器分配网卡,管理容器的网卡、路由表。通过bridge-utils创建了桥作为所有容器的网关。使用iptables为容器设置NAT。
剩下的事情其实很简单,只要把上面的命令变成程序就行了,我在代码中封装了command.py
用于调用外部命令;把iproute相关的命令都封装在ip_link
中;iptables相关的iptables.py
中。
相关代码已经放到github上了
https://github.com/fireflyc/mini-docker/tree/v0.2
其实还可以写overlayfs和Docker的镜像文件对接,以后直接使用Docker Hub上的镜像。但是——毕竟这仅仅是一个“玩具”,就像我前面说的那样,因为依赖Python运行库所以没有办法用Centos以外的“镜像”。至于代码,出于简单性考虑我会尽量“粗犷”一些。所以它仅仅是一个玩具。
如果你真的需要自己写一个容器那么我建议你看一下runC和libcontainer,它完整的实现了namespace和cgroup,而且用golang编写仅仅需要libc就可以了。
欢迎关注公众账号了解更多信息“写程序的康德——思考、批判、理性”