ovn 入门资料

662 阅读10分钟

1. ovn 架构解析, 手动编译,启动集群,入门试验

image.png

  • CMS: kube-ovn-controller 和 neutron
  • OVN NB DB: 管理并存储集群逻辑网络资源
  • OVN SB DB: 管理并存储集群逻辑流表( OVN-specific 格式)
  • OVN Northbound: 承上启下,负责转化
  • OVN-CONTROllER:上报信息,同步本节点需要的流表,转化为 openflow 流表,下发到 ovs-vswitchd,并存储到 - ovs-db
  • OVS-VSWITCHED: 软件交换机
  • OVS-DB:数据库管理

kube-ovn-controller 与 neutron 相当于 cms 的角色,

ovn-central 包括,ovn-northd,ovn-sb-db,ovn-nbdb, ovn-northd(启动依赖)ovn-sb-db,ovn-nbdb

参考: blog.csdn.net/fengcai_ke/…

流表相关:

datapath 类型:(logical switch 或者 logical router) 匹配报文方向: (ingress or egress) table id: (每个 table 实现不同功能)


/* OpenFlow table numbers.
 *
 * These are heavily documented in ovn-architecture(7), please update it if
 * you make any changes. */
//table 0 专门用来将物理端口转换成其对应的逻辑端口
#define OFTABLE_PHY_TO_LOG            0 
//table1-7 暂时还没用到
//table 8 用来对应 logical_flow ingress 方向的第一个 table,
//直到 table 32(8+24)
#define OFTABLE_LOG_INGRESS_PIPELINE  8 /* First of LOG_PIPELINE_LEN tables. */
//table33-36 暂时还没用到
//table37 用来存放输出端口为其他 chassis 的流表
#define OFTABLE_REMOTE_OUTPUT        37
//table38 用来存放输出端口为本 chassis 的流表
#define OFTABLE_LOCAL_OUTPUT         38
//table39 用来做 loopback 检查,即输出端口不能为输入端口
#define OFTABLE_CHECK_LOOPBACK       39
//table40 用来对应 logical_flow egress 方向的第一个table,
//直到 table 50(40+10)
#define OFTABLE_LOG_EGRESS_PIPELINE  40 /* First of LOG_PIPELINE_LEN tables. */
#define OFTABLE_SAVE_INPORT          64
//table65 用来将逻辑端口转换成物理端口
#define OFTABLE_LOG_TO_PHY           65
#define OFTABLE_MAC_BINDING          66
#define OFTABLE_MAC_LOOKUP           67
#define OFTABLE_CHK_LB_HAIRPIN       68
#define OFTABLE_CHK_LB_HAIRPIN_REPLY 69
#define OFTABLE_CT_SNAT_FOR_VIP      70
#define OFTABLE_GET_FDB              71
#define OFTABLE_LOOKUP_FDB           72

/* A stage within an OVN logical switch or router.
 *
 * An "enum ovn_stage" indicates whether the stage is part of a logical switch
 * or router, whether the stage is part of the ingress or egress pipeline, and
 * the table within that pipeline.  The first three components are combined to
 * form the stage's full name, e.g. S_SWITCH_IN_PORT_SEC_L2,
 * S_ROUTER_OUT_DELIVERY. */
enum ovn_stage {
#define PIPELINE_STAGES                                                   \
    /* Logical switch ingress stages. */                                  \
    PIPELINE_STAGE(SWITCH, IN,  PORT_SEC_L2,    0, "ls_in_port_sec_l2")   \
    PIPELINE_STAGE(SWITCH, IN,  PORT_SEC_IP,    1, "ls_in_port_sec_ip")   \
    PIPELINE_STAGE(SWITCH, IN,  PORT_SEC_ND,    2, "ls_in_port_sec_nd")   \
    PIPELINE_STAGE(SWITCH, IN,  LOOKUP_FDB ,    3, "ls_in_lookup_fdb")    \
    PIPELINE_STAGE(SWITCH, IN,  PUT_FDB,        4, "ls_in_put_fdb")       \
    PIPELINE_STAGE(SWITCH, IN,  PRE_ACL,        5, "ls_in_pre_acl")       \
    PIPELINE_STAGE(SWITCH, IN,  PRE_LB,         6, "ls_in_pre_lb")        \
    PIPELINE_STAGE(SWITCH, IN,  PRE_STATEFUL,   7, "ls_in_pre_stateful")  \
    PIPELINE_STAGE(SWITCH, IN,  ACL_HINT,       8, "ls_in_acl_hint")      \
    PIPELINE_STAGE(SWITCH, IN,  ACL,            9, "ls_in_acl")           \
    PIPELINE_STAGE(SWITCH, IN,  QOS_MARK,      10, "ls_in_qos_mark")      \
    PIPELINE_STAGE(SWITCH, IN,  QOS_METER,     11, "ls_in_qos_meter")     \
    PIPELINE_STAGE(SWITCH, IN,  LB,            12, "ls_in_lb")            \
    PIPELINE_STAGE(SWITCH, IN,  STATEFUL,      13, "ls_in_stateful")      \
    PIPELINE_STAGE(SWITCH, IN,  PRE_HAIRPIN,   14, "ls_in_pre_hairpin")   \
    PIPELINE_STAGE(SWITCH, IN,  NAT_HAIRPIN,   15, "ls_in_nat_hairpin")   \
    PIPELINE_STAGE(SWITCH, IN,  HAIRPIN,       16, "ls_in_hairpin")       \
    PIPELINE_STAGE(SWITCH, IN,  ARP_ND_RSP,    17, "ls_in_arp_rsp")       \
    PIPELINE_STAGE(SWITCH, IN,  DHCP_OPTIONS,  18, "ls_in_dhcp_options")  \
    PIPELINE_STAGE(SWITCH, IN,  DHCP_RESPONSE, 19, "ls_in_dhcp_response") \
    PIPELINE_STAGE(SWITCH, IN,  DNS_LOOKUP,    20, "ls_in_dns_lookup")    \
    PIPELINE_STAGE(SWITCH, IN,  DNS_RESPONSE,  21, "ls_in_dns_response")  \
    PIPELINE_STAGE(SWITCH, IN,  EXTERNAL_PORT, 22, "ls_in_external_port") \
    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,       23, "ls_in_l2_lkup")       \
    PIPELINE_STAGE(SWITCH, IN,  L2_UNKNOWN,    24, "ls_in_l2_unknown")    \
                                                                          \
    /* Logical switch egress stages. */                                   \
    PIPELINE_STAGE(SWITCH, OUT, PRE_LB,       0, "ls_out_pre_lb")         \
    PIPELINE_STAGE(SWITCH, OUT, PRE_ACL,      1, "ls_out_pre_acl")        \
    PIPELINE_STAGE(SWITCH, OUT, PRE_STATEFUL, 2, "ls_out_pre_stateful")   \
    PIPELINE_STAGE(SWITCH, OUT, LB,           3, "ls_out_lb")             \
    PIPELINE_STAGE(SWITCH, OUT, ACL_HINT,     4, "ls_out_acl_hint")       \
    PIPELINE_STAGE(SWITCH, OUT, ACL,          5, "ls_out_acl")            \
    PIPELINE_STAGE(SWITCH, OUT, QOS_MARK,     6, "ls_out_qos_mark")       \
    PIPELINE_STAGE(SWITCH, OUT, QOS_METER,    7, "ls_out_qos_meter")      \
    PIPELINE_STAGE(SWITCH, OUT, STATEFUL,     8, "ls_out_stateful")       \
    PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_IP,  9, "ls_out_port_sec_ip")    \
    PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2, 10, "ls_out_port_sec_l2")    \
                                                                      \
    /* Logical router ingress stages. */                              \
    PIPELINE_STAGE(ROUTER, IN,  ADMISSION,       0, "lr_in_admission")    \
    PIPELINE_STAGE(ROUTER, IN,  LOOKUP_NEIGHBOR, 1, "lr_in_lookup_neighbor") \
    PIPELINE_STAGE(ROUTER, IN,  LEARN_NEIGHBOR,  2, "lr_in_learn_neighbor") \
    PIPELINE_STAGE(ROUTER, IN,  IP_INPUT,        3, "lr_in_ip_input")     \
    PIPELINE_STAGE(ROUTER, IN,  DEFRAG,          4, "lr_in_defrag")       \
    PIPELINE_STAGE(ROUTER, IN,  UNSNAT,          5, "lr_in_unsnat")       \
    PIPELINE_STAGE(ROUTER, IN,  DNAT,            6, "lr_in_dnat")         \
    PIPELINE_STAGE(ROUTER, IN,  ECMP_STATEFUL,   7, "lr_in_ecmp_stateful") \
    PIPELINE_STAGE(ROUTER, IN,  ND_RA_OPTIONS,   8, "lr_in_nd_ra_options") \
    PIPELINE_STAGE(ROUTER, IN,  ND_RA_RESPONSE,  9, "lr_in_nd_ra_response") \
    PIPELINE_STAGE(ROUTER, IN,  IP_ROUTING,      10, "lr_in_ip_routing")   \
    PIPELINE_STAGE(ROUTER, IN,  IP_ROUTING_ECMP, 11, "lr_in_ip_routing_ecmp") \
    PIPELINE_STAGE(ROUTER, IN,  POLICY,          12, "lr_in_policy")       \
    PIPELINE_STAGE(ROUTER, IN,  POLICY_ECMP,     13, "lr_in_policy_ecmp")  \
    PIPELINE_STAGE(ROUTER, IN,  ARP_RESOLVE,     14, "lr_in_arp_resolve")  \
    PIPELINE_STAGE(ROUTER, IN,  CHK_PKT_LEN   ,  15, "lr_in_chk_pkt_len")  \
    PIPELINE_STAGE(ROUTER, IN,  LARGER_PKTS,     16, "lr_in_larger_pkts")  \
    PIPELINE_STAGE(ROUTER, IN,  GW_REDIRECT,     17, "lr_in_gw_redirect")  \
    PIPELINE_STAGE(ROUTER, IN,  ARP_REQUEST,     18, "lr_in_arp_request")  \
                                                                      \
    /* Logical router egress stages. */                               \
    PIPELINE_STAGE(ROUTER, OUT, UNDNAT,    0, "lr_out_undnat")        \
    PIPELINE_STAGE(ROUTER, OUT, SNAT,      1, "lr_out_snat")          \
    PIPELINE_STAGE(ROUTER, OUT, EGR_LOOP,  2, "lr_out_egr_loop")      \
    PIPELINE_STAGE(ROUTER, OUT, DELIVERY,  3, "lr_out_delivery")
 
#define PIPELINE_STAGE(DP_TYPE, PIPELINE, STAGE, TABLE, NAME)   \
    S_##DP_TYPE##_##PIPELINE##_##STAGE                          \
        = OVN_STAGE_BUILD(DP_##DP_TYPE, P_##PIPELINE, TABLE),
    PIPELINE_STAGES
#undef PIPELINE_STAGE
};
 
// 前 8 位表示 table id,第 8 位表示 pipeline,第 9 位表示 dp 类型
/* Returns an "enum ovn_stage" built from the arguments.
 *
 * (It's better to use ovn_stage_build() for type-safety reasons, but inline
 * functions can't be used in enums or switch cases.) */
#define OVN_STAGE_BUILD(DP_TYPE, PIPELINE, TABLE) \
    (((DP_TYPE) << 9) | ((PIPELINE) << 8) | (TABLE))

sbctl 查看整个集群的逻辑流表信息

root@master:~# ovn-sbctl lflow-list
Datapath: "ls1" (845314a0-ad79-4ac8-ac44-9fe2421478c2)  Pipeline: ingress
  //eth.src[40] 表示源mac为组播/广播地址
  table=0 (ls_in_port_sec_l2  ), priority=100  , match=(eth.src[40]), action=(drop;)
  table=0 (ls_in_port_sec_l2  ), priority=100  , match=(vlan.present), action=(drop;)
  table=0 (ls_in_port_sec_l2  ), priority=50   , match=(inport == "ls1-vm1" && eth.src == {00:00:00:00:00:01}), action=(next;)
  table=0 (ls_in_port_sec_l2  ), priority=50   , match=(inport == "ls1-vm2" && eth.src == {00:00:00:00:00:02}), action=(next;)
  table=0 (ls_in_port_sec_l2  ), priority=50   , match=(inport == "ls1-vm3" && eth.src == {00:00:00:00:00:03}), action=(next;)
  //match 1 表示匹配所有报文

2. ovn-northd

它将 ovn-nb-db 数据库中的逻辑网元配置转换成 ovn-sb-db 数据库中的集群流表。

blog.csdn.net/fengcai_ke/…

3. ovn-nb-db

Northbound DB 是 OVN 和 CMS 之间的接口。CMS 产生 Northbound DB 里面的几乎所有的内容。ovn-northd 监听这个数据库的内容变化,然后转换并保存到 Southbound DB 里面。

Northbound DB中有将近二十张表,下面是一些常用表:

  • Logical_Switch: 每行代表一个二层逻辑交换机。有两种类型的逻辑交换机,一种是全虚拟化网络,也叫 overlay 逻辑交换机,另一种用来和物理网络连接,称为桥接逻辑交换机。如果用来连接同 chassis 上的逻辑端口,这两种交换机的工作方式是一样的,但是如果用来连接不同 chassis 上的逻辑端口,方式就不一样了。overlay 逻辑交换机通过隧道来连接远端逻辑端口,而桥接逻辑交换机在 localnet 类型端口的帮助下,直接通过物理网络连接远端逻辑端口。每个桥接逻辑交换机有一个或者多个 localnet 类型的端口,此类型端口的地址类型为 unknown。在它上面可以配置 acl,qos 和 load balancer 等功能。
  • Logical_Switch_Port: 每行代表一个逻辑交换机端口。

type 字段: 如果 type 为空,则表示一个 VM 接口,如果 type 为 router,则表示连接的是逻辑路由器; 如果 type 为 localnet,则表示连接外部网络(可能会绑定到多个 chassis); 如果类型为 l2gateway,则表示连接外部网络(只能绑定到指定 chassis上);等等

up 字段:此字段由 ovn-northd 修改,而不是 CMS 。如果逻辑端口绑定到了物理位置 (Southbound DB 中port_binding 表 up 字段为 true ),ovn-northd 就会设置 up 字段为 true。CMS通过此字段用来等待 VM 网络变成可用后,再启动 VM。但是有一个例外,如果端口类型为 router,这 up 字段永远是true。

  • Logical_Router: 每行代表一个三层逻辑路由器。在它上面可以配置静态路由,策略,nat 和 load balancer 功能。它是在每个 chassis 上存在的,通过 openflow 流表实现。但是有一个例外,如果它通过 chassis 选项绑定指定了指定 chassis,则它就变为网关路由器。
  • Logical_Router_Port: Logical_Router上的端口,每行代表一个逻辑路由器端口。通过 networks 指定它的网段。还能通过 ha_chassis_group 或者 gateway_chassis 将它设置为分布式网关端口。
  • Gateway_Chassis: 用来关联到逻辑路由端口,实现分布式网关端口。发送到分布式网关端口的流量会被重定向到关联的 chassis,可以设置多行,即多个 chassis,同时指定优先级,优先级最高的生效。
  • HA_Chassis_Group: 每行代表一组 chassis,这组 chassis 用来提供高可用。组里面的每个 chassis 使用另一个 table HA_Chassis 来表示。也是用来关联到逻辑路由端口,实现分布式网关端口。
  • HA_Chassis: HA_Chassis_Group 组里面的 chassis,每行表示一个chassis,同时可以通过 priority 指定此chassis 的优先级。
  • Load_Balancer : 每行代表一个load balancer。可以应用在逻辑交换机和路由器上。
  • ACL: 每行代表一个 acl 规则。可以应用在逻辑交换机和 port group 上。
  • NAT : 每行代表一个 nat 规则。可以应用在逻辑路由器上。
  • DHCP_Options : 用来响应 VM 的 DHCP 请求。

4. ovn-sb-db

参考: blog.csdn.net/fengcai_ke/…

这个数据库包含了 OVN 的逻辑和物理的配置和状态。它位于 OVN 架构的核心位置,北向通过 ovn-northd 和 CMS 通信, 南向通过 ovn-controller 和 hypervisor 通信。

数据库,根据数据属性,大致分为如下几类:

  • 物理网络: 物理网络相关的表主要包含 chassis 节点信息,比如节点的 hostname,ip地址和隧道类型。由节点上的ovn-controller 来更新这些表,相当于向集群注册自己的信息。 同时也可以获取到其他节点的连接信息。物理网络数据量一般比较小,不会频繁更新,所以可将这些数据复制到所有节点。物理网络相关的表主要包含 Chassis, Encap两张表。
  • 逻辑网络: 逻辑网络相关的表包含逻辑交换机,逻辑路由器,acl,防火墙规则等逻辑网络资源转换到逻辑流表。 逻辑网络数据量可能很大,因为它包含逻辑端口,acl规则等信息。 为了提高扩展性,每个节点应该只接收和本节点相关的数据。逻辑网络相关的表有 Logical_Flow, Multicast_Group, Address_Group, DHCP_Options, DHCPv6_Options, and DNS tables。
  • 逻辑-物理 网络绑定: 这些表用来绑定逻辑和物理网络。并且变化很频繁,比如在vm开始,关机或者迁移时,或者在容器环境中,变化更快。 主要有 Port_Binding and Datapath_Binding。
  • mac 地址绑定: 用来绑定ip和mac地址。表为 MAC_Binding。

数据库表介绍:

  • Chassis : 每行代表物理网络中的一个 hypervisor 节点。在每个节点上,由 ovn-controller 进程在 chassis 表中增加一行用来更新本节点的信息,包括本节点的 hostname,ip 地址和隧道类型( ip 和类型引用另一个 table encap),同时保存一份 Chassis 中其他行的信息,根据这些信息创建到其他节点的连接。
  • Encap : Chassis 表中的 encaps 指向 Encap 表中的一行,用来保存本节点的 ip 地址和隧道类型。
  • Logical_Flow : 每行代表一条逻辑流表。这些流表由 ovn-northd 进程更新,用来实现 Northbound DB 中指定的二层和三层拓扑。在每个节点上,再由 ovn-controller 进程将这些逻辑流表中和本节点相关的流表转换成openflow 流表,安装到ovs中。 逻辑流表使用 OVN-specific 格式。逻辑流表和 openflow 流表很像,但是逻辑流表中用的是逻辑端口和逻辑 datapath,而 openflow 流表用的是物理端口和物理 datapath。逻辑流表的默认动作是丢包。
  • Datapath_Binding : 每行代表一个逻辑 datapath,比如一个逻辑交换机或者逻辑路由器。主要是为逻辑datapath 提供物理绑定。逻辑 datapath 实际上没有物理位置,即不会绑定到某个 chassis 上,所以这里说的物理绑定仅仅指的是和 tunnel_key 绑定。每个 datapath 会对应唯一的 tunnel_key,报文跨节点转发时,会将tunnel_key 封装到隧道报文中,以便另一个节点接收报文后,知道应该哪个 datapath (交换机或路由器)来处理。
  • Port_Binding : 每行代表一个逻辑端口的具体实现。对于大部分逻辑端口来说,通常指绑定到指定的物理位置,比如将逻辑端口绑定到一个 VIF,此 VIF 为某个 hypervisor 上的 VM 的端口。

5. ovn-controller

blog.csdn.net/fengcai_ke/…

ovn-controller 是运行在 chassis/hypervisor 上的后台进程,向上通过 OVSDB 协议连接到 OVN sourthbound数据库,向下通过 OVSDB 协议连接到 ovs 数据库,并通过 openflow 协议连接到 ovs-vswitchd。

以前我认为 external_ids 只是一个 meatadata, 对于 ovn, ovs 没有业务设计,目前看来可能不是: ovn-controller 从本地 ovs 数据库的 Open_vSwitch 表中获取下列参数:

external_ids:system-id: 
  # 会更新到 sbdb 的 Chassis 的 name 字段。ovn-controller 在运行过程中,不能动态修改 system-id。
  # 有两种办法可以修改 system-id: 先停掉 ovn-controller,再修改。或者修改完后,收到删除 Chassis 表中旧的字段。
external_ids:hostname:
   # 会更新到 sbdb 的 Chassis 的 hostname 字段
external_ids:ovn-bridge
  # 用来连接逻辑端口的集成桥。如果不指定,则 ovn-controller 启动过程中会自动创建,集成桥名字默认为 br-int。
external_ids:ovn-remote:
  # 指定连接 ovn southbound 数据库的方式。
external_ids:ovn-encap-type:
  # 指定其他 chassis 连接本节点时用的隧道类型。可以同时指定多个隧道类型。
  # 用来连接 chassis 的隧道类型有 geneve 和 stt,用来连接网关和 chassis 的隧道类型有 geneve,stt 和vxlan。
external_ids:ovn-encap-ip:
  # 指定其他 chassis 连接本节点时用的 ip 地址。
external_ids:ovn-bridge-mappings:
  # 键值对列表,用来映射物理网络名字到本地 ovs 网桥,意思为可通过此 ovs 网桥连接到物理网络名字所在的物理网络。
  # 比如 phys‐net1:br-eth0, physnet2:br-eth1
  

OVN southbound 数据库
ovn-controller 从 sbdb 中读取内容来指定它的操作。同时也会更新下面的表

ovn-controller 的作用是将 sbdb 中的信息转换成当前节点的 chassis 上的 openflow 流表信息,而且只会将和本 chassis 相关的信息进行转换,所以 ovn-controller 首先得识别出来哪些信息是和本 chassis 相关的。比如一个 vm 的 vif 接口在本 chassis 上,则其对应的 logical_switch,logical_switch_port 都属于和本chassis 相关的信息。


Chassis:
  # ovn-controller 启动时会在此表中创建一行,相当于上报自己的信息。
Encap:
  # ovn-controller 启动时会在此表中创建一行,设置隧道类型和 ip,用来告诉其他 chassis 如何连接本节点。
Port_Binding:
  # 一旦逻辑端口对应的物理实体创建在 chassis 上,此 chassis 上的 ovn-controller 就会将 Port_Binding中的 Chassis 字段更新为本节点。
MAC_Binding:
  ovn-controller 会根据 put_arp 和 put_nd action 更新此表。

ovn-controller 转换流表: 参考: blog.csdn.net/fengcai_ke/…

6. ovn 通过(集中|高可用)网关 chassis 上公网

参考: blog.csdn.net/fengcai_ke/…

关于网关是集中式的还是分布式的?

东西向的流量都是分布式的

ovn的逻辑路由器是分布式的,这意味着它没有绑定到某个节点上,而是存在于所有节点上的,同时它是通过每个节点的 openflow 流表来实现的,所有 vm 之间的东西向流量可以在本节点就能找到目的节点。

南北向的流量

但是对于一些有状态的服务是有问题的,比如 SNAT 和 DNAT,这些服务需要在同一个节点上实现。为了解决这个问题,引入了网关路由器,其和逻辑路由器的区别是,网关路由器会通过 Logical_Router 表的选项 options:chassis 绑定到指定的节点上。

但 FIP 是分布式的,基于 localnet port, 可以实现本地直接进出,这种方式也叫 l2 gateway 通过 ovn 的 localnet 类型端口将 ovn 网络连接到外部网络。也是一种 l2gateway,但是相比 l2gateway 来说,localnet 类型的端口会在 vm 所在的 chassis上 都直接连接到外部网络,减少了东西向流量。 参考: blog.csdn.net/fengcai_ke/…

l2 gateway 参考: blog.csdn.net/fengcai_ke/…

参考: 分享放大价值: blog.csdn.net/fengcai_ke/…