图3 网络创建与配置过程
结合图3,Docker源码和libcontainer源码,我们来详细看一下Docker在bridge模式下网络创建与配置的过程。
1.在Docker daemon启动的时候,daemon会创建一个bridge驱动所对应的netcontroller,这个controller可以在后面的流程里面对network和sandbox等进行创建。
2.接下来daemon通过调用controller.NewNetwork()来创建指定类型(bridge类型)和指定名称(即docker0)的network。Libnetwork在接收到创建命令后会使用系统linux的系统调用,创建一个名为docker0的网桥。我们可以在Docker daemon启动后,使用ifconfig命令看到这个名为docker0的网桥。
3.在Docker daemon成功启动后,我们就可以使用Docker Client进行容器创建了。容器的网络栈会在容器真正启动之前完成创建。在为容器创建网络栈的时候,首先会取得daemon中的netController,其值就是libnetwork中的向外提供的一组接口,即NetworkController。
接下来,Docker会调用BuildCreateEndpointOptions()来创建此容器中endpoint的配置信息。然后再调用CreateEndpoint()使用上面配置好的信息创建对应的endpoint。在bridge模式中,libnetwork创建的设备是veth pair。Libnetwork中调用netlink.LinkAdd(veth)进行了veth pair的创建,得到的一个veth设备是为了host所准备的,另一个是为了sandbox所准备的。将host端的veth加入到网桥(docker0)中。然后调用netlink.LinkSetUp(host),启动主机端的veth。最后对endpoint中的端口映射进行配置。
从本质上来讲,这一部分所做的工作就是调用linux系统调用,进行veth pair的创建。然后将veth pair的一端,作为docker0网桥的一个接口加入到这个网桥中。
4.创建SandboxOptions,然后调用controller.NewSandbox()来创建属于此container的新的sandbox。在接收到Docker创建sandbox的请求后,libnetwork会使用系统调用为容器创建一个新的netns,并将这个netns的路径返回给Docker。
5.调用ep.Join(sb)将endpoint加入到容器对应的sandbox中。先将endpoint加入到容器对应的sandbox中,然后对endpoint的ip信息和gateway等信息进行配置。
6.Docker在调用libcontainer来启动容器之后,libcontainer会在容器中的init进程初始化容器坏境的时候,将容器中的所有进程都加入到4中得到的netns中。这样容器就拥有了属于自己独立的网络栈,进而完成了网络部分的创建和配置工作。
正如前面所说,libnetwork不仅内置了bridge,host,container和overlay四种驱动,也对第三方插件提供了支持。第三方插件为了对libnetwork提供网络支持,需要实现并提供如下的API。
driver.Config
driver.CreateNetwork
driver.DeleteNetwork
driver.CreateEndpoint
driver.DeleteEndpoint
driver.Join
driver.Leave
这些驱动不使用设备的名称作为其API的参数,而是使用唯一的id作为其参数,如networkid,endpointid等。
当用户指定自定义的网络驱动时,Docker会将libnetwork中的驱动设置为remote。Remote实际是上并没有进行具体的驱动实现,而是通过调用第三方插件来完成网络的创建,配置和管理工作。libnetwork与第三方插件的交互��式如图4所示。
图4 libnetwork与第三方插件交互过程
首先,我们需要手动启动libnetwork的第三方插件,这样libnetwork才能与第三方插件进行交互工作。
然后,我们在使用Docker创建容器的时候,需要指定第三方插件作为Docker的网络驱动。当libnetwork初始化其中的remote启动的时候,就会将第三方插件在libnetwork中进行注册,并且写入NetworkController当中。Libnetwork也会通过socket来和第三方插件进行连接和通信,并且进行libnetwork中所规定的握手协议等,来进行信息交换。
这样Docker可以像使用内置驱动一样来调用第三方插件的各种API。当libnetwork接收到Docker的调用请求之后,libnetwork会调用remote驱动中所对应的函数进行处理。这些函数都是通过对请求参数的封装,然后编码成JSON串的形式,通过之前建立的socket连接发送调用请求。
第三方插件在接收到请求之后,会解析请求中的JSON串,完成JSON串中所请求的操作,然后将执行结果打包成JSON串,并且通过socket返回给libnetwork。libnetwork解析第三方插件发送过来的返回信息,并将执行结果返回给Docker。
===========================
作者高相林,浙江大学SEL实验室硕士研究生,目前在云平台团队从事科研和开发工作。浙大团队对PaaS、Docker、大数据和主流开源云计算技术有深入的研究和二次开发经验,团队现联合社区将部分技术文章贡献出来,希望能对读者有所帮助。
更多Docker相关教程见以下内容: