【编者的话】从docker 1.6开始关注docker网络这块,从原来的铁板一块,到后来的libnetwork拆分,到现在的remote driver,docker 一直在改进。功能缺失,实用性不足,特别是不能提供支持生产环境的解决方案,一直为大家所诟病。Overlay, Macvlan, IPVlan驱动的出现,会对这种局面有一定帮助。在我看来,引入专业的网络供应商及解决方案才是更好的出路。在软件定义网络方面,OVS是实际的标准,它可以帮助docker运营者对网络基础设施提供更深入,细粒度的掌控,例如vlan,vxlan隔离,流控,QoS, 监控,更灵活的实时管理等。这些功能对docker来说短期内是无法实现的,也没有必要。

libnetwork是docker 在版本1.9中引入的项目,它有一整套的自定义网络命令和跨主机网络支持。

libnetwork项目从lincontainer和Docker代码的分离早在Docker 1.7版本就已经完成了(从Docker 1.6版本的网络代码中抽离)。在此之后,容器的网络接口就成为了一个个可替换的插件模块。由于这次变化进行的十分平顺,作为Docker的使用者几乎不会感觉到其中的差异,然而这个改变为接下来的一系列扩展埋下了很好的伏笔。它遵从为docker提供高内聚,低耦合的软件模块的目标,以组合的方式为容器提供网络支持。

概括来说,libnetwork所做的最核心事情是定义了一组标准的容器网络模型(Container Network Model,简称CNM),只要符合这个模型的网络接口就能被用于容器之间通信,而通信的过程和细节可以完全由网络接口来实现。

  • Sandbox:对应一个容器中的网络环境,包括相应的网卡配置、路由表、DNS配置等。CNM很形象的将它表示为网络的『沙盒』,因为这样的网络环境是随着容器的创建而创建,又随着容器销毁而不复存在的。沙盒的实现可以是Linux Network Namespace, FreeBSD Jail之类的概念,并包含连接多个网络的Endpoint;
  • Endpoint:实际上就是一个容器中的虚拟网卡,在容器中会显示为eth0、eth1依次类推。它可以是veth对,或者openvswitch的internal port。一个endpoint只能属于一个沙盒;
  • Network:指的是一个能够相互通信的容器网络,加入了同一个网络的容器直接可以直接通过对方的名字相互连接。它的实体本质上是主机上的虚拟网卡或网桥。

docker 最初只有三种网络类型(bridge, none, host),在引入libnetwork之后,docker再也不只有三种,还有overlay, remote driver, 和未来将要支持的ipvlan, macvlan,windows等等。现在用户可以以驱动/插件的形式,使用其它特定类型网络插件实体,例如overlay。libnetwork将以接口的形式,为docker提供网络服务,对于docker来说,具体实现是不可见的,可扩展的,其实体存放在docker源码的vendor目录,调用关系仍然为go库的内部调用, 必须作为docker源码的一部分进行编译。因此,这为以后驱动的维护升级带来了不少的难度。
特别提到的是,为了支持实现第三方的驱动,引入的remote driver类型。通过统一的Json-RPC接口,让更专业的网络供应商加入到docker的生态圈来。使用户不再局限于原生驱动,大大提高了docker网络的升级扩展能力。

这是使用overlay 网络的一个例子。这个命令用于新建或删除一个容器网络,创建时可以用『--driver』参数使用的网络插件,例如:

$ docker network create --driver=overlay br-overlay

b6942f95d04ac2f0ba7c80016eabdbce3739e4dc4abd6d3824a47348c4ef9e54

$ docker run -d --net br-overlay busybox /bin/sh

现在这个主机上有了一个新的overlay类型的Docker网络。当然要实现这种类型的网络,还需要内核版本(不低于3.6)及外部kv存储服务(Consul, etcd, ZooKeeper)的支持。

Libnetwork调用入口

用户提交的与网络相关的指令,都会首先在Docker Daemon做相应的处理。根据驱动类型,调用libnetwork模块的相应实现。
在这个层面里,其主要工作是联结底层驱动,在容器的启动中,初始化NetworkContoller,定义了容器网络的生命周期,并对下层提供调用。

Docker Daemon有关的代码分布如下:

  • daemon结构体中引用了libnetwork

    docker/daemon/daemon.go
    // Daemon holds information about the Docker daemon.
    type Daemon struct {
    ID                        string
    repository                string
    containers                container.Store
    execCommands              *exec.Store
    referenceStore            reference.Store
    downloadManager          *xfer.LayerDownloadManager
    uploadManager            *xfer.LayerUploadManager
    distributionMetadataStore dmetadata.Store
    trustKey                  libtrust.PrivateKey
    idIndex                  *truncindex.TruncIndex
    configStore              *Config
    execDriver                execdriver.Driver
    statsCollector            *statsCollector
    defaultLogConfig          containertypes.LogConfig
    RegistryService          *registry.Service
    EventsService            *events.Events
    netController            libnetwork.NetworkController
  • 容器,网络,endpoint信息与更新,一些网络工具(有删减)
docker/container/container_unix.go
//GetEndpointInNetwork returns the container's endpoint to the provided network.
func (container *Container) GetEndpointInNetwork(n libnetwork.Network) (libnetwork.Endpoint, error) func (container *Container) buildPortMapInfo(ep libnetwork.Endpoint) error func getEndpointPortMapInfo(ep libnetwork.Endpoint) (nat.PortMap, error) func getSandboxPortMapInfo(sb libnetwork.Sandbox) nat.PortMap 
  • 容器内的各种操作,网络相关的操作有初始化,创建,分配,连接,更新,删除网络配置,连接容器等
docker/daemon/container_operations_unix.go
func (daemon *Daemon) buildSandboxOptions(container *container.Container, n libnetwork.Network) ([]libnetwork.SandboxOption, error)  func (daemon *Daemon) updateNetworkSettings(container *container.Container, n libnetwork.Network) error  func (daemon *Daemon) updateEndpointNetworkSettings(container *container.Container, n libnetwork.Network, ep libnetwork.Endpoint) //get removed/unlinked.
func (daemon *Daemon) updateNetwork(container *container.Container) error  //updateContainerNetworkSettings update the network settings
func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container, endpointsConfig map[string]*networktypes.EndpointSettings) error  func (daemon *Daemon) allocateNetwork(container *container.Container) error  func (daemon *Daemon) updateNetworkConfig(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (libnetwork.Network, error)  //ConnectToNetwork connects a container to a network
func (daemon *Daemon) **ConnectToNetwork**(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings) error  func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (err error) func (daemon *Daemon) initializeNetworking(container *container.Container) error 
  • 初始化nw controller, networkOptions, 网络驱动
docker/daemon/daemon_unix.go
func (daemon *Daemon) networkOptions(dconfig *Config) ([]nwconfig.Option, error) func (daemon *Daemon) initNetworkController(config *Config) (libnetwork.NetworkController, error) 

Daemon 与Libnetwork的调用过程

无论使用哪种网络类型,Docker daemon与libnetwok的交互流程都是一样的,它很好地对底层(驱动)实现进行了抽象。如果需要实现自己的驱动,开发者必须关注这部分。
1. Daemon创建网络控制器(controller)实例, 入参包括docker层面的网络全局参数(genericOption),例如缺省的bridge mode, kv store。
2. 网络控制器创建容器容器网络,入参包括网络名,类型,对应驱动参数。从这里开始,会调用至真正的驱动实现。
3. 创建endpoint, 进行IP分配,准备网络设备接口。
4. 创建sandbox, 使用容器对应的网络命令空间,包含该容器的网络环境。
5. 已有的endpoint加入sandbox, 完成容器与网络设备的对接。

以上的实际代码调用过程简要描述一下:
- 创建docker daemon的时候初始化NetworkController, 调用libnetwork.New创建controller实例。顺便创建缺省的三个网络-null, host, bridge。对应上图的1,2。
daemon.NewDaemon() —> driver.initNetworkController() — > libnetwork.New() — > controller.NewNetwork() (default create null and host network)

  • 对于驱动形式的网络插件,例如overlay,docker daemon也会直接用controller.NewNetwork, 指定网络名,驱动类型,及其它参数。最终也会调到libnetwork.NewNetwork的具体实现。
    所以libnetwork的NetworkController以接口的形式,对上层提供了管理网络的具体实现。对应上图的2。
  • 容器启动后需要加入网络,准备好配置实例,在connectToNework()中完成对网络底层的操作。
    container.start() —> initializeNetworking() — > allocateNetwork() — > connectToNetwork() — > container.writeHostConfig()
    container.cleanup() — > releaseNetwork() — > sandbox.delete()
  • 上图第三,四,五步,都包含在Daemon.connectToNetwork()方法下,具体操作包括
    1. buildCreateEndpointOptions,
    2. CreateEndpoint
    3. updateEndpointNetworkSettings
    4. controller.NewSandbox
    5. endpoint.join(sandbox)

Libnetwork 软件架构

Libnetwork 提供了连接容器的GO语言内部实现。其目的是以一致的编程接口,把网络实现给抽象起来
世界上有许多广泛的网络解决方案,适用于不同的应用场景。libnetwork以驱动/插件的形式,以支持这些方案,同时把复杂的网络驱动实现抽象出来,对用户来说展现的是简单而一致的网络模型。
以下是一级目录结构的解析:
$ tree -L 1
.
├── CHANGELOG.md
├── Dockerfile.build
├── Godeps 管理Go依赖库
├── LICENSE
├── MAINTAINERS
├── Makefile 编译连接规则文件, make build-local可生成dnet测试服务接口
├── README.md 项目概要文档
├── ROADMAP.md
├── Vagrantfile
├── api 通过http server暴露对外的API, 消息格式为RESTful。包括网络,服务,沙箱,端点(Endpoint)的处理
├── bin
├── bitseq 长id的生成库,用来生成网络id, 子网id之类的
├── circle.yml
├── client CLI客户端,处理网络增加,删除,查询。服务publish, attach, info等
├── cmd dnet一个libnetwork测试服务器
├── config 包括多个libnetwork模块的配置信息,包括daemon, network, driver, label, cluster, watcher, discovery
├── controller.go controller, libnetwork主入口, CNM 模型主要体现在这里
├── datastore libnetwork用到的各种Key/Value datastore, 包括cache, local(bolted), global(etcd, consul,zk)
├── default_gateway.go
├── default_gateway_freebsd.go
├── default_gateway_linux.go
├── default_gateway_windows.go
├── docs
├── driverapi 以接口的形式定义了实现新driver需要实现的API, 包括Driver, InterfaceInfo,InterfaceNameInfo, JoinInfo,DriverCallback,Capability,DiscoveryType,NodeDiscoveryData,IPAMData
├── drivers 具体的驱动实现,有bridge, host, null, overlay, remote, windows, 要实现ovs的话要在这里加
├── drivers.go
├── drivers_freebsd.go
├── drivers_linux.go 驱动初始化器
├── drivers_windows.go
├── endpoint.go endpoint结构体及接口
├── endpoint_cnt.go
├── endpoint_info.go
├── error.go
├── errors_test.go
├── etchosts 容器内/etc/hosts文件的处理
├── hostdiscovery 服务发现的实现
├── idm id managemet, 用来根据一段字符生成唯一id,使用了bitseq库
├── ipam ip地址管理,分配回收ip pool, ip address, 后端接了kv store实现跨主机的资源管理
├── ipamapi 定义了要实现外部ipam所需要的接口
├── ipams 两种spam,build-in, remote(即外部实现)
├── ipamutils ipam工具
├── iptables iptables, firewalld的处理
├── libnetwork_internal_test.go
├── libnetwork_test.go
├── machines
├── netlabel 各种定义好的net 相关的label, 如 com.docker.network.driver
├── netutils
├── network.go 定义网络结构体,对象接口及实现,用于Netlink网络
├── ns 网络namespace工具类
├── options
├── osl 定义网络相关实体的结构体,接口,如sanbox, interface, interface options, namespace
├── portallocator 端口资源(port)分配器
├── portmapper 实现port mapping, 网络地址转换(NAT), 用于bridge网络
├── resolvconf 工具类,实现/etc/resolv.conf DNS的配置,查询,更新
├── resolver.go
├── sandbox.go sanbox结构体的接口及实现
├── sandbox_externalkey.go
├── sandbox_externalkey_unix.go
├── sandbox_externalkey_windows.go
├── sandbox_store.go
├── sandbox_test.go
├── src
├── store.go 支持跨主机通信的kv store
├── store_test.go
├── test 主要有libnetwork端到端的集成测试,需要依赖一个叫Bats的SHELL测试工具
├── testutils
├── types
└── wrapmake.sh

有关Libnetwork Remote Driver

Remote Driver的出现为业界提供了自定义网络的可能。远程驱动作为服务端,libnetwork作为客户端,两者通过一系列带Json格式的http POST请求进行交互。
这种形式对网络实现的可维护性,可扩展性有很大好处。对于千差万别的网络实施方案而言,docker算是解放了双手,把更多精力放在自己擅长的容器方面。
代码位于libnetwork/drivers/remote。对应的远端驱动,只要实现了以下接口,就能支持docker网络相关的生命周期。

dispatchMap := map[string]func(http.ResponseWriter, *http.Request){
    "/Plugin.Activate":                    activate(hostname),
    "/Plugin.Deactivate":                  deactivate(hostname),
    "/NetworkDriver.GetCapabilities":      getCapability,
    "/NetworkDriver.CreateNetwork":        createNetwork,
    "/NetworkDriver.DeleteNetwork":        deleteNetwork,
    "/NetworkDriver.CreateEndpoint":       createEndpoint(hostname),
    "/NetworkDriver.DeleteEndpoint":       deleteEndpoint(hostname),
    "/NetworkDriver.EndpointOperInfo":     endpointInfo,
    "/NetworkDriver.Join":                 join,
    "/NetworkDriver.Leave":                leave,

}

以思科的contiv netplugin项目为例,其主导开发的以远程插件的形式,基于ovs提供连接docker容器的能力。
插件在/var/run/docker/plugins/注册了自己,处理来自docker libnetwork 的JSON-RPC请求,通过ovsdb-server的管理接口去执行修改ovs流表的操作。
功能上支持VLAN, VXLAN,QoS, 使用docker用户能够获得来自OVS的SDN网络能力,帮助用户在容器中充分利用OVS在网络功能方面的优势。

笔者因工作需要,开始研究基于OVS的docker网络方案,牵涉到未来改造的需要,开始对libnetwork源码有所了解。这是我的类似笔记性质的框架总结,希望为大家带来一些帮助。

libnetwork 源码浅析的更多相关文章

  1. 【深入浅出jQuery】源码浅析--整体架构

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  2. 【深入浅出jQuery】源码浅析2--奇技淫巧

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  3. Struts2源码浅析-ConfigurationProvider

    ConfigurationProvider接口 主要完成struts配置文件 加载 注册过程 ConfigurationProvider接口定义 public interface Configurat ...

  4. (转)【深入浅出jQuery】源码浅析2--奇技淫巧

    [深入浅出jQuery]源码浅析2--奇技淫巧 http://www.cnblogs.com/coco1s/p/5303041.html

  5. HashSet其实就那么一回事儿之源码浅析

    上篇文章<HashMap其实就那么一回事儿之源码浅析>介绍了hashMap,  本次将带大家看看HashSet, HashSet其实就是基于HashMap实现, 因此,熟悉了HashMap ...

  6. Android 手势识别类 ( 三 ) GestureDetector 源码浅析

    前言:上 篇介绍了提供手势绘制的视图平台GestureOverlayView,但是在视图平台上绘制出的手势,是需要存储以及在必要的利用时加载取出手势.所 以,用户绘制出的一个完整的手势是需要一定的代码 ...

  7. Android开发之Theme、Style探索及源码浅析

    1 背景 前段时间群里有伙伴问到了关于Android开发中Theme与Style的问题,当然,这类东西在网上随便一搜一大把模板,所以关于怎么用的问题我想这里也就不做太多的说明了,我们这里把重点放在理解 ...

  8. 【深入浅出jQuery】源码浅析2--使用技巧

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  9. Android手势源码浅析-----手势绘制(GestureOverlayView)

    Android手势源码浅析-----手势绘制(GestureOverlayView)

随机推荐

  1. 【IDEA】本地新建Maven项目+配置Git和GitHub+代码上传和拉取到GitHub+其他IDEA和GitHub实战

    一.本地新建Maven项目并启动成功 1. 按照IDEA提供的模板,构建一个maven webapp的模板项目. 一路Next,到最后的finish.如下图. 2. 新建Tomcat,启动刚建立的项目 ...

  2. 【转】Spring MVC 3.x 基本配置

    WEB-INF/web.xml 例1 <?xml version="1.0" encoding="UTF-8"?> <web-app xmln ...

  3. 网络编程4 网络编程之FTP上传简单示例&socketserver介绍&验证合法性连接

    今日大纲: 1.FTP上传简单示例(详细代码) 2.socketserver简单示例&源码介绍 3.验证合法性连接//[秘钥加密(urandom,sendall)(注意:中文的!不能用)] 内 ...

  4. Spring使用JMS传递消息的两种方式

    方式一:同步收发消息,使用JMS template 消费者阻塞等待消息的到来. 方式二:异步收发消息,使用message listener container 消费者提供一个listener,注册一个 ...

  5. bash短路径显示

    修改.bashrc文件vim 打开.bashrc文件,找到如下这行,有两个,都修改一下: PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ ' 将上面 ...

  6. windows python easy_install ,pip. selenium

    http://www.cnblogs.com/fnng/p/3157639.html 搭建平台windows 准备工具如下: unknown encoding: cp65001异常 python安装后 ...

  7. 总结!linux 消耗内存和cpu 定时任务

    1. c脚本 消耗内存 1)在your_directory目录下,创建文件eatmem.c ,输入以下内容 2)编译:gcc eatmem.c -o eatmem 3) 创建定时任务,每15分钟执行: ...

  8. Pandas 通过追加方式合并多个csv

    常用合并 通常用pandas进行数据拼接.合并的方法有: pandas.merge() pandas.concat() pandas.append() 还有一种方式就是通过 pd.to_csv() 中 ...

  9. sql server中index的REBUILD和REORGANIZE的区别及工作方式

    sql server中index的REBUILD和REORGANIZE 转自:https://www.cnblogs.com/flysun0311/archive/2013/12/05/3459451 ...

  10. position学习终结者(二)

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/wangshuxuncom/article/details/30982863         在博客& ...