Libnetwork 远程网络驱动设计

什么是 Kuryr

Kuryr 实现了一个 libnetwork 远程网络驱动,并将它的调用映射到 OpenStack Neutron。它充当 libnetwork 的 容器网络模型 (CNM) 和 Neutron 的网络模型 之间的翻译器。Kuryr 还充当一个 libnetwork IPAM 驱动

目标

通过 Kuryr,任何 Neutron 插件都可以用作 libnetwork 后端,而无需额外的工作。Neutron API 是供应商无关的,因此所有 Neutron 插件都将能够提供 Docker 的网络后端,就像它们在 nova 中一样,只需要一个小的插件片段。

Kuryr 还负责将 veth 对中的一个绑定到主机上的网络接口,例如 Linux bridge、Open vSwitch 数据平面等等。

Kuryr 工作流程 - 主机网络

Kuryr 驻留在运行 Docker 容器的每个主机上,并提供 API,这些 API 是 libnetwork 远程网络驱动程序所必需的。计划使用 Kuryr 的 为资源添加标签 新 Neutron 功能,以在 Neutron 资源 ID 和 Docker ID(UUID)之间进行映射。

  1. libnetwork 通过 插件发现机制第一次请求发出之前 发现 Kuryr。

    • 在此过程中,libnetwork 会对 /Plugin.Activate 进行 HTTP POST 调用,并检查驱动程序类型,默认值为 "NetworkDriver""IpamDriver"

    • libnetwork 还调用以下两个 API 端点

      1. /NetworkDriver.GetCapabilities 以获取 Kuryr 的功能,默认值为 "local"

      2. /IpamDriver.GetDefaultAddressSpcaces 以获取 IPAM 使用的默认地址空间

  2. libnetwork 将 Kuryr 注册为远程驱动程序

  3. 用户使用 Kuryr 的网络驱动程序说明符对 libnetwork 发出请求

    • 例如,--driver=kuryr-d kuryr --ipam-driver=kuryr 用于 Docker CLI

  4. libnetwork 对 Kuryr 发出 API 调用

  5. Kuryr 接收请求并使用 Neutron 客户端 调用 Neutron API

  6. Kuryr 接收来自 Neutron 的响应,并为 libnetwork 组合响应

  7. Kuryr 将响应返回给 libnetwork

  8. libnetwork 将返回的信息存储到它的键/值数据存储后端

    • 键/值数据存储由 libkv 抽象化

Libnetwork 用户工作流程(将 Kuryr 作为远程网络驱动程序)- 主机网络

  1. 用户创建一个名为 foo 的网络,并带有子网信息

    $ sudo docker network create --driver=kuryr --ipam-driver=kuryr \
      --subnet 10.0.0.0/16 --gateway 10.0.0.1 --ip-range 10.0.0.0/24 foo
    286eddb51ebca09339cb17aaec05e48ffe60659ced6f3fc41b020b0eb506d364
    

    这会向 /IpamDriver.RequestPool 发出 HTTP POST 调用,并带有以下 JSON 数据

    {
        "AddressSpace": "global_scope",
        "Pool": "10.0.0.0/16",
        "SubPool": "10.0.0.0/24",
        "Options": {},
        "V6": false
    }
    

    SubPool 的值来自上述命令中 --ip-range 选项中指定的值,AddressSpace 的值将是 global_scopelocal_scope,具体取决于 capability_scope 配置选项。Kuryr 创建一个子网池,然后返回以下响应

    {
        "PoolID": "941f790073c3a2c70099ea527ee3a6205e037e84749f2c6e8a5287d9c62fd376",
        "Pool": "10.0.0.0/24",
    }
    

    如果指定了 --gateway,例如上述命令,则会接着对 /IpamDriver.RequestAddress 发出另一个 HTTP POST 调用,并带有以下 JSON 数据

    {
        "Address": "10.0.0.1",
        "PoolID": "941f790073c3a2c70099ea527ee3a6205e037e84749f2c6e8a5287d9c62fd376",
        "Options": {"RequestAddressType": "com.docker.network.gateway"},
    }
    

    作为 IPAM 驱动程序,Kuryr 分配一个请求的 IP 地址并返回以下响应

    {
        "Address": "10.0.0.1/24",
        "Data": {}
    }
    

    最后,向 /NetworkDriver.CreateNetwork 发出 HTTP POST 调用,并带有以下 JSON 数据

    {
        "NetworkID": "286eddb51ebca09339cb17aaec05e48ffe60659ced6f3fc41b020b0eb506d364",
        "IPv4Data": [{
            "Pool": "10.0.0.0/24",
            "Gateway": "10.0.0.1/24",
            "AddressSpace": ""
        }],
        "IPv6Data": [],
        "Options": {"com.docker.network.enable_ipv6", false, "com.docker.network.generic": {}}
    }
    

    Kuryr 远程网络驱动程序将生成一个 Neutron API 请求,以使用池 cidr 创建子网和底层 Neutron 网络。当 Neutron 子网和网络创建完成后,Kuryr 远程网络驱动程序将生成一个空的成功响应到 docker 守护进程。Kuryr 将 Neutron 网络标记为来自 docker 的 NetworkID。

  2. 用户针对网络 foo 启动一个容器

    $ sudo docker run --net=foo -itd --name=container1 busybox
    78c0458ba00f836f609113dd369b5769527f55bb62b5680d03aa1329eb416703
    

    这会向 /IpamDriver.RequestAddress 发出 HTTP POST 调用,并带有以下 JSON 数据

    {
        "Address": "",
        "PoolID": "941f790073c3a2c70099ea527ee3a6205e037e84749f2c6e8a5287d9c62fd376",
        "Options": {"com.docker.network.endpoint.macaddress": "08:22:e0:a8:7d:db"},
    }
    

    IPAM 驱动程序 Kuryr 向 neutron 发送端口创建请求,并返回带有 neutron 提供的 ip 地址的以下响应

    {
        "Address": "10.0.0.2/24",
        "Data": {}
    }
    

    然后,向 /NetworkDriver.CreateEndpoint 发出另一个 HTTP POST 调用,并带有以下 JSON 数据

    {
        "NetworkID": "286eddb51ebca09339cb17aaec05e48ffe60659ced6f3fc41b020b0eb506d364",
        "Interface": {
            "AddressIPv6": "",
            "MacAddress": "08:22:e0:a8:7d:db",
            "Address": "10.0.0.2/24"
        },
        "Options": {
            "com.docker.network.endpoint.exposedports": [],
            "com.docker.network.portmap": []
        },
        "EndpointID": "edb23d36d77336d780fe25cdb5cf0411e5edd91b0777982b4b28ad125e28a4dd"
    }
    

    Kuryr 远程网络驱动程序然后生成一个 Neutron API 请求,以获取与请求中接口的匹配字段的端口。Kuryr 然后更新此端口的名称,并将其标记为端点 ID。

    将采取以下步骤

    1. 在端点创建时,Kuryr 检查是否有与请求的地址或 AddressIPv6 对应的 CIDR 的端口。

    2. 如果存在端口,Kuryr 会尝试重用它,而无需创建新端口。否则,它将使用给定的地址创建一个新端口。

    3. Kuryr 将 Neutron 端口标记为 EndpointID。

    当 Neutron 端口更新后,Kuryr 远程驱动程序将以以下形式生成响应到 docker 守护进程:(https://github.com/docker/libnetwork/blob/2025.2/docs/remote.md#create-endpoint

    {
        "Interface": {"MacAddress": ""}
    }
    

    收到成功响应后,libnetwork 会向 /NetworkDriver.Join 发出 HTTP POST 调用,并带有以下 JSON 数据

    {
        "NetworkID": "286eddb51ebca09339cb17aaec05e48ffe60659ced6f3fc41b020b0eb506d364",
        "SandboxKey": "/var/run/docker/netns/052b9aa6e9cd",
        "Options": null,
        "EndpointID": "edb23d36d77336d780fe25cdb5cf0411e5edd91b0777982b4b28ad125e28a4dd"
    }
    

    Kuryr 通过执行以下步骤将容器连接到相应的 neutron 网络

    1. 生成一个 veth 对。

    2. 将 veth 对的一端连接到容器(在 Docker 创建的命名空间中运行)。

    3. 使用 VIF 绑定层,并根据特定的端口类型,执行依赖于 neutron 端口类型的 VIF 绑定到相应的 Neutron 端口。

    完成 VIF 绑定后,Kuryr 远程网络驱动程序将根据 libnetwork 文档中的 join 请求生成响应到 Docker 守护进程。(https://github.com/docker/libnetwork/blob/2025.2/docs/remote.md#join

  3. 用户请求有关网络的信息

    $ sudo docker network inspect foo
     [
         {
             "Name": "foo",
             "Id": "286eddb51ebca09339cb17aaec05e48ffe60659ced6f3fc41b020b0eb506d364",
             "Scope": "local",
             "Driver": "kuryr",
             "EnableIPv6": false,
             "IPAM": {
                 "Driver": "kuryr",
                 "Options": {},
                 "Config": [{
                     "Subnet": "10.0.0.0/16",
                     "IPRange": "10.0.0.0/24",
                     "Gateway": "10.0.0.1"
                 }]
             },
             "Internal": false,
             "Containers": {
                 "78c0458ba00f836f609113dd369b5769527f55bb62b5680d03aa1329eb416703": {
                     "endpoint": "edb23d36d77336d780fe25cdb5cf0411e5edd91b0777982b4b28ad125e28a4dd",
                     "mac_address": "02:42:c0:a8:7b:cb",
                     "ipv4_address": "10.0.0.2/24",
                     "ipv6_address": ""
                 }
             },
             "Options": {},
             "Labels": {}
         }
     ]
    
  4. 用户将另一个容器连接到网络

    $ sudo docker network connect foo container2
     d7fcc280916a8b771d2375688b700b036519d92ba2989622627e641bdde6e646
    
    $ sudo docker network inspect foo
     [
         {
             "Name": "foo",
             "Id": "286eddb51ebca09339cb17aaec05e48ffe60659ced6f3fc41b020b0eb506d364",
             "Scope": "local",
             "Driver": "kuryr",
             "EnableIPv6": false,
             "IPAM": {
                 "Driver": "kuryr",
                 "Options": {},
                 "Config": [{
                     "Subnet": "10.0.0.0/16",
                     "IPRange": "10.0.0.0/24",
                     "Gateway": "10.0.0.1"
                 }]
             },
             "Internal": false,
             "Containers": {
                 "78c0458ba00f836f609113dd369b5769527f55bb62b5680d03aa1329eb416703": {
                     "endpoint": "edb23d36d77336d780fe25cdb5cf0411e5edd91b0777982b4b28ad125e28a4dd",
                     "mac_address": "02:42:c0:a8:7b:cb",
                     "ipv4_address": "10.0.0.2/24",
                     "ipv6_address": ""
                 },
                 "d7fcc280916a8b771d2375688b700b036519d92ba2989622627e641bdde6e646": {
                     "endpoint": "a55976bafaad19f2d455c4516fd3450d3c52d9996a98beb4696dc435a63417fc",
                     "mac_address": "02:42:c0:a8:7b:cc",
                     "ipv4_address": "10.0.0.3/24",
                     "ipv6_address": ""
                 }
             },
             "Options": {},
             "Labels": {}
         }
     ]
    
  5. 用户断开容器与网络的连接

    $ CID=d7fcc280916a8b771d2375688b700b036519d92ba2989622627e641bdde6e646
    $ sudo docker network disconnect foo $CID
    

    这会向 /NetworkDriver.Leave 发出 HTTP POST 调用,并带有以下 JSON 数据

    {
        "NetworkID": "286eddb51ebca09339cb17aaec05e48ffe60659ced6f3fc41b020b0eb506d364",
        "EndpointID": "a55976bafaad19f2d455c4516fd3450d3c52d9996a98beb4696dc435a63417fc"
    }
    

    Kuryr 远程网络驱动程序将删除容器和 Neutron 端口之间的 VIF 绑定,并生成一个空的响应到 Docker 守护进程。

    然后 libnetwork 会向 /NetworkDriver.DeleteEndpoint 发出 HTTP POST 调用,并带有以下 JSON 数据

    {
        "NetworkID": "286eddb51ebca09339cb17aaec05e48ffe60659ced6f3fc41b020b0eb506d364",
        "EndpointID": "a55976bafaad19f2d455c4516fd3450d3c52d9996a98beb4696dc435a63417fc"
    }
    

    Kuryr 远程网络驱动程序生成一个 Neutron API 请求,以删除关联的 Neutron 端口,如果相关的端口子网为空,Kuryr 还会使用 Neutron API 删除子网对象,并生成一个空的响应到 Docker 守护进程

    {}
    

    最后,libnetwork 会向 /IpamDriver.ReleaseAddress 发出 HTTP POST 调用,并带有以下 JSON 数据

    {
        "Address": "10.0.0.3",
        "PoolID": "941f790073c3a2c70099ea527ee3a6205e037e84749f2c6e8a5287d9c62fd376"
    }
    

    Kuryr 远程 IPAM 驱动程序生成一个 Neutron API 请求,以删除关联的 Neutron 端口。作为 IPAM 驱动程序,Kuryr 释放 IP 地址并返回以下响应

    {}
    
  6. 用户删除网络

    $ sudo docker network rm foo
    

    这会向 /NetworkDriver.DeleteNetwork 发出 HTTP POST 调用,并带有以下 JSON 数据

    {
        "NetworkID": "286eddb51ebca09339cb17aaec05e48ffe60659ced6f3fc41b020b0eb506d364"
    }
    

    Kuryr 远程网络驱动程序生成一个 Neutron API 请求,以删除相应的 Neutron 网络和子网。当 Neutron 网络和子网被删除后,Kuryr 远程网络驱动程序生成一个空的响应到 docker 守护进程:{}

    然后,向 /IpamDriver.ReleasePool 发出另一个 HTTP POST 调用,并带有以下 JSON 数据

    {
        "PoolID": "941f790073c3a2c70099ea527ee3a6205e037e84749f2c6e8a5287d9c62fd376"
    }
    

    Kuryr 删除相应的子网池并返回以下响应

    {}
    

CNM 和 Neutron 的网络模型之间的映射

Kuryr 通过 Neutron 客户端 与 Neutron 通信,并通过翻译他们的网络模型来连接 libnetwork 和 Neutron。下表描述了 libnetwork 和 Neutron 模型之间的当前映射

libnetwork

Neutron

网络

网络

Sandbox

子网、端口和 netns

端点

端口

libnetwork 的 Sandbox 和 Endpoint 可以映射到 Neutron 的 Subnet 和 Port,但是,Sandbox 对用户来说是不可见的,而 Endpoint 才是用户可以查看和编辑的资源实体,可以附加到容器上。Sandbox 在 Endpoint 后面自动管理暴露的信息。

关于在 Kuryr 中实现 libnetwork 远程驱动程序 API 的说明

  1. DiscoverNew 通知:Neutron 不使用与发现新资源(例如新节点)相关的信息,因此此 API 方法的实现不执行任何操作。

  2. DiscoverDelete 通知:Neutron 不使用与发现资源(例如正在删除的节点)相关的信息,因此此 API 方法的实现不执行任何操作。