Openvswitch Agent 的分布式元数据数据通路

RFE: https://bugs.launchpad.net/neutron/+bug/1933222

当实例启动时,它们将尝试通过 Neutron 虚拟交换机(桥接)、虚拟设备、命名空间和元数据代理从 Nova 获取元数据。此后,元数据代理将没有其他功能。在大型场景中,大量部署的元数据代理将导致主机和消息队列浪费资源。因为元数据代理会根据用户的资源数量启动大量外部进程,并向 Neutron 服务器报告状态以保持活动连接。

我们将克服这个问题。本规范描述了如何为 Neutron openvswitch 代理实现一个代理扩展,以使元数据数据通路分布式。

问题描述

对于大规模云部署,应该运行多少个元数据代理? 这个问题没有确切的答案。 云运维人员可以将元数据代理设置到所有主机,类似于 DHCP 代理。

Config drive [1] 可以作为云提供虚拟机元数据的替代方案。 但 config drive 存在一些限制和安全问题。 例如,如果您将 config drive 设置为使用本地磁盘,则实时迁移可能不可用。 Config drive 使用额外的存储设备并在虚拟机内部挂载它。 无法为用户的特定脚本在线更改 userdata。 安全问题在于,由于挂载的文件系统可以被所有用户访问,如果元数据包含 root 密码或密钥,则密码和密钥可以被非 root 用户访问。

如果您正在运行 Neutron 代理,则没有替代方案可以取代云部署的元数据代理。

正如我们所见,元数据数据通路通过许多设备、命名空间和代理非常漫长。 一条元数据路径中断,例如代理宕机或外部进程死亡,不仅会影响主机,还会影响所有将启动新虚拟机的相关主机。

提议的变更

为 Neutron openvswitch 代理添加一个代理扩展,以使元数据数据通路分布式。

注意

此扩展仅适用于 openvswitch 代理,其他机制驱动程序将不被考虑,因为此新扩展将依赖于 openflow 协议和原理。

提出的解决方案

元数据专用 IP 和 MAC

主机中的每个 VM 将生成一个 Meta IP + MAC 对,该对在主机上的 VM 中是唯一的,并将用于标识元数据请求者 vm/port。 代理扩展将在第一次处理端口时完成生成工作,并将 Meta IP + MAC 存储到 ovsdb 中。 当 ovs-agent 重新启动时,handle_port 函数将首先尝试读取 ovsdb 以重新加载 Meta IP + MAC,如果不存在,则重新生成。

在代理扩展初始化期间,它将在 ovs-bridge br-meta 上添加一个名为“tap-meta”的内部端口。 此 tap 设备将具有 Meta Gateway MAC + IP。

潜在的配置

带有此扩展的 Neutron openvswitch 代理的配置选项

[METADATA]
provider_cidr = 100.100.0.0/16
provider_vlan_id = 998
provider_base_mac = "fa:16:ee:00:00:00"
  • provider_cidr 将用作生成 VM 元数据 IP 的 IP 范围。

  • provider_vlan_id 将是来自本地 tap-meta 设备的固定 vlan。

  • provider_base_mac 将是生成 VM META MAC 的 MAC 前缀。

我们可以重用元数据代理的现有配置选项

METADATA_PROXY_HANDLER_OPTS = [
    cfg.StrOpt('auth_ca_cert',
               help=_("Certificate Authority public key (CA cert) "
                      "file for ssl")),
    cfg.HostAddressOpt('nova_metadata_host',
                       default='127.0.0.1',
                       help=_("IP address or DNS name of Nova metadata "
                              "server.")),
    cfg.PortOpt('nova_metadata_port',
                default=8775,
                help=_("TCP Port used by Nova metadata server.")),
    cfg.StrOpt('metadata_proxy_shared_secret',
               default='',
               help=_('When proxying metadata requests, Neutron signs the '
                      'Instance-ID header with a shared secret to prevent '
                      'spoofing. You may select any string for a secret, '
                      'but it must match here and in the configuration used '
                      'by the Nova Metadata Server. NOTE: Nova uses the same '
                      'config key, but in [neutron] section.'),
               secret=True),
    cfg.StrOpt('nova_metadata_protocol',
               default='http',
               choices=['http', 'https'],
               help=_("Protocol to access nova metadata, http or https")),
    cfg.BoolOpt('nova_metadata_insecure', default=False,
                help=_("Allow to perform insecure SSL (https) requests to "
                       "nova metadata")),
    cfg.StrOpt('nova_client_cert',
               default='',
               help=_("Client certificate for nova metadata api server.")),
    cfg.StrOpt('nova_client_priv_key',
               default='',
               help=_("Private key of client certificate."))
]

使用 http 作为 haproxy 的基本后端配置将是

backend backend_{{ instance_x.uuid }}_{{ metadata_ip_x }}
server metasrv http://nova_metadata_host:nova_metadata_port

如果在 haproxy 侧启用了 ssl 验证,则后端将是

backend backend_{{ instance_x.uuid }}_{{ metadata_ip_x }}
server metasrv https://nova_metadata_host:nova_metadata_port ssl verify required ca-file /path/to/auth_ca_cert

或者使用客户端证书的 ssl

backend backend_{{ instance_x.uuid }}_{{ metadata_ip_x }}
server metasrv https://nova_metadata_host:nova_metadata_port ssl verify required ca-file /path/to/nova_client_cert crt /path/to/nova_client_priv_key

注意

如果云具有自己的客户端证书,则 crt 参数可以指向您的客户端证书文件。 但这仅在 haproxy 版本 >= 2.4 时才受支持。

元数据数据管道

示例资源数据
  • VM1 - network1 local_vlan_id=1, fixed_ip 192.168.1.10, port mac fa:16:3e:4a:fd:c1, Meta_IP 100.100.0.10, Meta_MAC fa:16:ee:00:00:11

  • VM2 - network2 local_vlan_id=2, fixed_ip 192.168.2.10, port mac fa:16:3e:4a:fd:c2, Meta_IP 100.100.0.11, Meta_MAC fa:16:ee:00:00:22

  • VM3 - network1 local_vlan_id=1, fixed_ip 192.168.1.20, port mac fa:16:3e:4a:fd:c3, Meta_IP 100.100.0.12, Meta_MAC fa:16:ee:00:00:33

  • VM4 - network3 local_vlan_id=3, fixed_ip 192.168.3.10, port mac fa:16:3e:4a:fd:c4, Meta_IP 100.100.0.13, Meta_MAC fa:16:ee:00:00:44

  • META Gateway IP 100.100.0.1, META Gateway MAC: fa:16:ee:00:00:01

TCP 出站

来自 VM 的 HTTP 请求直接发送到 br-meta,并将 IP 标头更改为 tap-meta,在主机 haproxy 中添加 HTTP 标头,然后发送到 nova-metadata API。 数据通路

+----+ TCP +-----------------------------------+ TCP +---------------------------------------------+ TCP +------------------------+ TCP +-----------------------+
|    +----->             Br-int                +----->                   Br-meta                   +----->        tap-Meta        +----->        Haproxy        |
| VM |     | From VM port + 169.254.169.254:80 |     |   Source (VM MAC + IP --> Meta MAC + IP)    |     |  Meta Gateway MAC + IP |     |   Match Meta IP       |
|    |     |          add local vlan           |     |  Dest (MAC + IP --> Meta Gateway MAC + IP)  |     |       Listened by      |     |     Add Http header   |
|    |     |           to Br-meta              |     |                 to tap-Meta                 |     |        Haproxy         |     |  to Nova-Metadata-API |
+----+     +-----------------------------------+     +---------------------------------------------+     +------------------------+     +-----------------------+

br-int 上的流程(一些关键字是伪代码)

Table=0
Match: ip,in_port=<of_vm1>,nw_dst=169.254.169.254 actions=mod_local_vlan:1,output:"To_br_meta"
Match: ip,in_port=<of_vm2>,nw_dst=169.254.169.254 actions=mod_local_vlan:2,output:"To_br_meta"
Match: ip,in_port=<of_vm3>,nw_dst=169.254.169.254 actions=mod_local_vlan:1,output:"To_br_meta"
Match: ip,in_port=<of_vm4>,nw_dst=169.254.169.254 actions=mod_local_vlan:3,output:"To_br_meta"

当您的 VM 尝试访问 169.254.169.254:80 时,目标 MAC + IP 应该是什么? 目标 IP 很明确,是 169.254.169.254。 复杂的情况是目标 MAC。 我们有三种场景

a. 如果您的 VM 只有一个指向网关的默认路由,那么请求的目标 MAC 应该是网关 MAC。

b. 如果您的 VM 具有直接指向 169.254.169.254 的路由(例如,通过 192.168.1.2 <DHCP 端口 IP>,通常由原始 DHCP 代理和元数据机制设置),那么一些 ARP 响应器将为这些 DHCP 端口 IP 添加,以防升级。 对于这些 DHCP 端口 IP,将响应一个假的 mac。

c. 如果您的 VM 具有链路路由,该路由告诉来宾操作系统 169.254.169.254 可直接访问。 因此,将添加一个用于 169.254.169.254 的 ARP 响应器。 因此,目标 MAC 也是假的。

br-meta 上的流程

Table=0
Match: ip,in_port=<patch_br_int>,nw_dst=169.254.169.254 Action: resubmit(,80)

Table=80
Match: dl_vlan=<local_vlan_1>,dl_src=fa:16:3e:4a:fd:c1,nw_src=192.168.1.10 Action: strip_vlan,mod_dl_src:fa:16:ee:00:00:11,mod_nw_src:100.100.0.10, resubmit(,87)
Match: dl_vlan=<local_vlan_2>,dl_src=fa:16:3e:4a:fd:c2,nw_src=192.168.2.10 Action: strip_vlan,mod_dl_src:fa:16:ee:00:00:22,mod_nw_src:100.100.0.11, resubmit(,87)
Match: dl_vlan=<local_vlan_1>,dl_src=fa:16:3e:4a:fd:c3,nw_src=192.168.1.20 Action: strip_vlan,mod_dl_src:fa:16:ee:00:00:33,mod_nw_src:100.100.0.12, resubmit(,87)
Match: dl_vlan=<local_vlan_3>,dl_src=fa:16:3e:4a:fd:c4,nw_src=192.168.3.10 Action: strip_vlan,mod_dl_src:fa:16:ee:00:00:44,mod_nw_src:100.100.0.13, resubmit(,87)

Table=87
Match: tcp,nw_dst=169.254.169.254,tp_dst=80 Action: mod_nw_dst:100.100.0.1, mod_dl_dst:fa:16:ee:00:00:01,output:"tap-meta"
TCP 入站

HTTP 数据包从 tap-meta 直接到达 br-meta,然后到达 br-int,最终直接到达 VM。 数据通路

+----+     +---------------------+     +---------------------------------------+     +---------------+     +------------------+
|    |     |      Br-int         |     |                Br-meta                |     |    tap-Meta   |     |      Haproxy     |
| VM |     | From patch_br_meta  |     |    Source (IP ---> 169.254.169.254)   |     |               |     |  Http Response   |
|    | TCP |   mac_dst is VM     | TCP |  Dest(Meta MAC + IP ---> VM MAC + IP) | TCP |      To       | TCP |   To Client IP   |
|    <-----+   output to VM      <-----+               to  Br-int              <-----+ Meta MAC + IP <-----+  (Meta MAC + IP) |
+----+     +---------------------+     +---------------------------------------+     +---------------+     +------------------+

br-meta 上的流程

Table=0
Match: ip,in_port="tap-meta" actions=push_vlan,goto_table:91

Table=91
Match: dl_vlan=<998>,ip,nw_dst=100.100.0.10 Action: mod_vlan_vid:1,mod_dl_dst:fa:16:3e:4a:fd:c1,mod_nw_dst:192.168.1.10,mod_nw_src:169.254.169.254,output:"to-br-int"
Match: dl_vlan=<998>,ip,nw_dst=100.100.0.11 Action: mod_vlan_vid:2,mod_dl_dst:fa:16:3e:4a:fd:c2,mod_nw_dst:192.168.2.10,mod_nw_src:169.254.169.254,output:"to-br-int"
Match: dl_vlan=<998>,ip,nw_dst=100.100.0.12 Action: mod_vlan_vid:3,mod_dl_dst:fa:16:3e:4a:fd:c3,mod_nw_dst:192.168.1.20,mod_nw_src:169.254.169.254,output:"to-br-int"
Match: dl_vlan=<998>,ip,nw_dst=100.100.0.13 Action: mod_vlan_vid:4,mod_dl_dst:fa:16:3e:4a:fd:c4,mod_nw_dst:192.168.3.10,mod_nw_src:169.254.169.254,output:"to-br-int"

br-int 上的流程

Table=0
Match: ip,in_port=<patch_br_meta>,dl_vlan=1,dl_dst=<vm1_mac_fa:16:3e:4a:fd:c1>,nw_src=169.254.169.254 actions=strip_vlan,output:<of_vm1>
Match: ip,in_port=<patch_br_meta>,dl_vlan=2,dl_dst=<vm2_mac_fa:16:3e:4a:fd:c2>,nw_src=169.254.169.254 actions=strip_vlan,output:<of_vm2>
Match: ip,in_port=<patch_br_meta>,dl_vlan=3,dl_dst=<vm3_mac_fa:16:3e:4a:fd:c3>,nw_src=169.254.169.254 actions=strip_vlan,output:<of_vm3>
Match: ip,in_port=<patch_br_meta>,dl_vlan=4,dl_dst=<vm4_mac_fa:16:3e:4a:fd:c4>,nw_src=169.254.169.254 actions=strip_vlan,output:<of_vm4>
元数据 IP 的 ARP

Tap-meta 设备将驻留在主机内核 IP 堆栈上,在第一次响应 TCP 之前,主机(协议堆栈)需要知道 META_IP 的 MAC 地址。 因此,ARP 请求是广播的。 ARP 将从 tap-meta 设备发送到 br-meta 响应器。 ARP 响应器数据通路

+---------------------------+      +---------------+
|         Br-meta           |      |    tap-Meta   |
| ARP Responder for Meta IP |      |     Learn     |
|           to              | ARP  | Meta IP's MAC |
|         INPORT            <------+     (ARP)     |
+---------------------------+      +---------------+

br-meta 上的流程将是

Ingress:
Table=0
Match: arp,in_port="tap-meta" Action: resubmit(,90)

Table=90
Match: arp,arp_tpa=100.100.0.10 Action: ARP Responder with Meta_MAC fa:16:ee:00:00:11,IN_PORT
Match: arp,arp_tpa=100.100.0.11 Action: ARP Responder with Meta_MAC fa:16:ee:00:00:22,IN_PORT
Match: arp,arp_tpa=100.100.0.12 Action: ARP Responder with Meta_MAC fa:16:ee:00:00:33,IN_PORT
Match: arp,arp_tpa=100.100.0.13 Action: ARP Responder with Meta_MAC fa:16:ee:00:00:44,IN_PORT

主机 haproxy 配置

主机 haproxy 是用于所有 VM 的唯一进程。 主机 haproxy 将向元数据请求添加 HTTP 标头,这些标头是元数据 API 所需要的。 标头具有易于组装的固定算法。 对于每个 VM 的请求,haproxy 将添加一个独立的后端和一个检查源 IP(也称为 Meta_IP)的匹配规则。 当来自一个 VM 的请求(Meta_IP)时,它将被发送到匹配的后端,该后端添加 HTTP 标头,然后发送到真实的 nova-metadata-api。

配置

global
    log         /dev/log local0 {{ log_level }}
    user        {{ user }}
    group       {{ group }}
    maxconn     {{ maxconn }}
    daemon

frontend public
    bind            *:80 name clear
    mode            http
    log             global
    option          httplog
    option          dontlognull
    maxconn         {{ maxconn }}
    timeout client  30s

    acl instance_{{ instance_1.uuid }}_{{ metadata_ip_1 }} src {{ metadata_ip_1 }}
    acl instance_{{ instance_2.uuid }}_{{ metadata_ip_2 }} src {{ metadata_ip_2 }}
    acl instance_{{ instance_3.uuid }}_{{ metadata_ip_3 }} src {{ metadata_ip_3 }}
    acl instance_{{ instance_4.uuid }}_{{ metadata_ip_4 }} src {{ metadata_ip_4 }}

    use_backend backend_{{ instance_1.uuid }}_{{ metadata_ip_1 }} if instance_{{ instance_1.uuid }}_{{ metadata_ip_1 }}
    use_backend backend_{{ instance_2.uuid }}_{{ metadata_ip_2 }} if instance_{{ instance_2.uuid }}_{{ metadata_ip_2 }}
    use_backend backend_{{ instance_3.uuid }}_{{ metadata_ip_3 }} if instance_{{ instance_3.uuid }}_{{ metadata_ip_3 }}
    use_backend backend_{{ instance_4.uuid }}_{{ metadata_ip_4 }} if instance_{{ instance_4.uuid }}_{{ metadata_ip_4 }}

backend backend_{{ instance_1.uuid }}_{{ metadata_ip_1 }}
    balance         roundrobin
    retries         3
    option redispatch
    timeout http-request    30s
    timeout connect         30s
    timeout server          30s

    http-request set-header X-Instance-ID {{ instance_1.uuid }}
    http-request set-header X-Tenant-ID {{ instance_1.project_id }}
    http-request set-header X-Instance-ID-Signature {{ instance_1.signature }}

    server metasrv ...

backend backend_{{ instance_2.uuid }}_{{ metadata_ip_2 }}
    balance         roundrobin
    retries         3
    option redispatch
    timeout http-request    30s
    timeout connect         30s
    timeout server          30s

    http-request set-header X-Instance-ID {{ instance_2.uuid }}
    http-request set-header X-Tenant-ID {{ instance_2.project_id }}
    http-request set-header X-Instance-ID-Signature {{ instance_2.signature }}

    server metasrv ...

backend backend_{{ instance_3.uuid }}_{{ metadata_ip_3 }}
    ...

backend backend_{{ instance_4.uuid }}_{{ metadata_ip_4 }}
    ...

IPv6 元数据

仅具有类似地址 fe80::a9fe:a9fe 的 IPv6 元数据 [2],因此所有这些工作都可以镜像到 IPv6。 对于 IPv6,生成器将使用范围 fe80:ffff:a9fe:a9fe::/64 来分配 Meta_IPv6 地址。 Meta_gateway_IPv6 地址将是 fe80:ffff:a9fe:a9fe::1,Gateway MAC 仍然是相同的 fa:16:ee:00:00:01。 用于 Meta_IPv6 地址 fe80:ffff:a9fe:a9fe::abcd 和 Meta_MAC fa:16:ee:00:00:11 的 NA 响应器将是

Table=0
Match: icmp6,icmp_type=135,icmp_code=0,nd_sll=fa:16:ee:00:00:01,
actions=set_field:136->icmpv6_type,set_field:0->icmpv6_code,set_field:2->icmpv6_options_type,goto_table:90

Table=90
Match: icmp6,icmp_type=136,icmp_code=0,nd_target=fe80:ffff:a9fe:a9fe::abcd
actions=move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],set_field:fa:16:ee:00:00:11->eth_src,
move:NXM_NX_IPV6_SRC[]->NXM_NX_IPV6_DST[],set_field:fe80:ffff:a9fe:a9fe::abcd->ipv6_src,
set_field:fa:16:ee:00:00:11->nd-tll,set_field:0xE000->OFPXMT_OFB_ICMPV6_ND_RESERVED,IN_PORT

实现

负责人

工作项

  • 为 ovs 桥接添加流程操作

  • 为 ovs-agent 添加主机 haproxy 管理器

  • 添加具有 ovsdb 设置的主机元数据 IP 和 Mac 生成器

  • 添加 ovs-agent 扩展以设置 VM 端口的流程

  • 测试。

  • 文档。

依赖项

测试

测试用例以验证元数据是否可以正确设置,这可以使用带有新元数据驱动程序的新作业来完成。

参考资料