zoukankan      html  css  js  c++  java
  • libnetwork 源码浅析

    【编者的话】从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),只要符合这个模型的网络接口就能被用于容器之间通信,而通信的过程和细节可以完全由网络接口来实现。

    A95EDD70-E2F6-4B52-8A55-65BFD8BDBEBF.jpg
    • 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的调用过程

    libnetwork-design.png



    无论使用哪种网络类型,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-sw-arch.png



    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在网络功能方面的优势。

    58F4969C-E610-4E90-8B91-8CB54E29A333.png


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

  • 相关阅读:
    python模块—socket
    mac os系统的快捷键
    教你如何将UIImageView视图中的图片变成圆角
    关于ASP.NET MVC
    iOS 日期格式的转换
    将App通过XCode上传到AppStore 出现这个错误“An error occurred uploading to the iTunes Store”的解决方法
    关于MAC OS下面两个软件的功能改进——Dictionary和Fit 输入法
    分享一下上个星期的香港行程
    【博客园IT新闻】博客园IT新闻 iPhone 客户端发布
    解决Entity Framework Code First 的问题——Model compatibility cannot be checked because the database does not contain model metadata
  • 原文地址:https://www.cnblogs.com/allcloud/p/7612048.html
Copyright © 2011-2022 走看看