Calico BGP 功能介绍:实现

2021-06-09 16:56:31 浏览数 (1)

Calico 作为一种常用的 Kubernetes 网络插件,使用 BGP 协议对各节点的容器网络进行路由交换。本文是《Calico BGP 功能介绍》系列的第二篇,介绍 Calico 中 BGP 功能的实现。所使用的 Calico 版本为 v3.17.3。

Calico BGP 功能

BGP Peer

Calico 中通过定义 BGP Peer 对象,来建立 BGP 连接。BGP Peer 对象的主要参数如下:

参数

描述

node

指定 BGP Peer 应用在哪个 node 上。如果指定此字段,则为 node 级别,否则为 global 级别。

peerIP

指定远端的 Peer 地址,可以是 IP 加端口的形式,端口可选。支持 IPV4 和 IPV6。

asNumber

远端 Peer 的 AS 号。

nodeSelector

用于通过标签来选择一组 node,作为 BGP Peer 应用的节点,注意这里的 node 为 Calico 中的 node,而非 K8s 中的 node。如果指定了此字段,则 node 应该为空。

peerSelector

用于通过标签来选择一组 node(同样为 Calico 中的 node),作为远端 Peer 的节点。如果指定了此字段,则 peerIP 和 asNumber 都应该为空。

keepOriginalNextHop

对于 EBGP,保持并转发原始的 next hop,不将自身加入到 Path 中。

password

BGP 会话的身份验证。

在过去的版本,Calico 中包含了BGP Peer对象和Global BGP Peer对象,目前已统一为BGP Peer对象,根据是指定node参数还是nodeSelector参数来区分。

BGP Configuration

除了 BGP Peer 外,Calico 通过 BGP Configuration 对象来控制全局的 BGP 行为。主要参数包括:

参数

描述

默认值

nodeToNodeMeshEnabled

开启 Calico 节点之间的 node-to-node mesh。

true

asNumber

Calico node 默认的节点 AS。

64512

serviceClusterIPs

Calico 需要对外 BGP 的 service ClusterIP 地址段。

serviceExternalIPs

Calico 需要对外 BGP 的 service ExternalIPs 地址段。

communities

用于定义 BGP community,由 name 和 value 组成,value 支持标准 community 以及 large community。

prefixAdvertisements

指定网段与 community 的隶属关系,可以通过 communities 中的 name 指定,也可以通过 community value 直接指定。

默认情况下,Calico 所有节点通过 IBGP 来交换各个节点的 workload(容器)路由信息,由于从 IBGP Peer 中学习到的路由不会被再次转发,因此需要使用 node-to-node mesh 的方式两两互连。

serviceClusterIPsserviceExternalIPs字段的功能类似于 MetalLB 的 BGP 模式,可以将 K8s Service 的访问地址(ClusterIP 和 ExternalIP)BGP 到集群外的设备(例如 TOR)。结合 ECMP,可以将外部访问 K8s Service 的流量负载到 K8s 节点上,由 Kube-proxy 转发到真正的容器后端。

communitiesprefixAdvertisements可以控制 Calico BGP 路由的 community 字段,支持RFC 1997[1]中的well-known communities。使用样例如下:

代码语言:javascript复制
communities:
- name: bgp-large-community
  value: 63400:300:100
prefixAdvertisements:
- cidr: 172.218.4.0/26
  communities:
  - bgp-large-community
  - 63400:120

Calico BGP 功能实现

简单来说,Calico 是通过 confd 组件来渲染 bird 的配置文件,动态配置 bird,从而实现 BGP 功能。其中,为了使 confd 支持 Calico 后端存储,在原生的 confd 上,添加了类型为 Calico 的 backend,主要逻辑是 watch 后端 BGPPeer、BGPConfiguration、Node 资源(由 libcalico-go 中的bgpsyncer实现),缓存到内存中,供 confd 渲染使用。

可以看到,主要的配置文件分为 6 个,其中 IPv4 和 IPv6 各 3 个。以 IPv4 为例,IPv6 类似,bird.cfg 为 bird 的主要配置文件,包括几个主要部分:

1)全局的配置:包括 route id、debug 属性、listen bgp port,这一部分主要由 node、bgp configuration 中的字段产生。

2)固定的协议配置:包括 kernel、device、direct;其功能在上一篇Calico BGP 功能介绍:BIRD 简介[2]中有详细介绍。

代码语言:javascript复制
# Configure synchronization between routing tables and kernel.
protocol kernel {
  learn;             # Learn all alien routes from the kernel
  persist;           # Don't remove routes on bird shutdown
  scan time 2;       # Scan kernel routing table every 2 seconds
  import all;
  export filter calico_kernel_programming; # Default is export none
  graceful restart;  # Turn on graceful restart to reduce potential flaps in
                     # routes when reloading BIRD configuration.  With a full
                     # automatic mesh, there is no way to prevent BGP from
                     # flapping since multiple nodes update their BGP
                     # configuration at the same time, GR is not guaranteed to
                     # work correctly in this scenario.
  merge paths on;    # Allow export multipath routes (ECMP)
}

# Watch interface up/down events.
protocol device {
{{- template "LOGGING"}}
  scan time 2;    # Scan interfaces every 2 seconds
}

protocol direct {
{{- template "LOGGING"}}
  interface -"cali*", -"kube-ipvs*", "*"; # Exclude cali* and kube-ipvs* but
                                          # include everything else.  In
                                          # IPVS-mode, kube-proxy creates a
                                          # kube-ipvs0 interface. We exclude
                                          # kube-ipvs0 because this interface
                                          # gets an address for every in use
                                          # cluster IP. We use static routes
                                          # for when we legitimately want to
                                          # export cluster IPs.
}

kernel 中 export 用到了 filter calico_kernel_programming,其定义在 bird_ipam.cfg.template 中。主要分两部分:

一是获取/calico/rejectcidrs的值,对属于此 cidr 范围内的路由 reject。/calico/rejectcidrs的值是由 confd 的calico backend写入,其值为 BGP Configuration 中设置的serviceClusterIPsserviceExternalIPs。在开启了 Calico 的advertise service[3]功能后(通过配置 BGP Configuration 的serviceClusterIPsserviceClusterIPs),可以避免将其他节点发送过来的 Service 路由写入 kernel 路由表中。

二是对于属于 Calico IPPool 的路由,根据 Calico IPPool 设置的模式(VXLAN 或 IPIP),来判断是否需要写入 kernel 路由表。对于 VXLAN 模式,由 felix 负责数据包的路由,不再写入 kernel 中;对于 IPIP,根据 Calico IPPool 的 IPIP 配置(Always、Cross-subnet)以及 BGP 协议的 bgp_next_hop 属性(判断是否跨网段),决定是生成 IPIP 的路由,还是非 IPIP 路由。这里使用到的 bird 参数krt_tunnel来传递 IPIP 设备,krt_tunnel并非原生 bird 所提供的参数,而是由 Calico 添加的,以实现 bird 支持 IPIP 协议。

代码语言:javascript复制
{{$network_key := printf "/bgp/v1/host/%s/network_v4" (getenv "NODENAME")}}
filter calico_kernel_programming {
{{- $reject_key := "/rejectcidrs"}}
{{- if ls $reject_key}}

  # Don't program static routes into kernel.
  {{- range ls $reject_key}}
    {{- $parts := split . "-"}}
    {{- $cidr := join $parts "/"}}
  if ( net ~ {{$cidr}} ) then { reject; }
  {{- end}}

{{- end}}
{{- if exists $network_key}}{{$network := getv $network_key}}
{{range ls "/v1/ipam/v4/pool"}}{{$data := json (getv (printf "/v1/ipam/v4/pool/%s" .))}}
  if ( net ~ {{$data.cidr}} ) then {
{{- if $data.vxlan_mode}}
    # Don't program VXLAN routes into the kernel - these are handled by Felix.
    reject;
  }
{{- else if $data.ipip_mode}}{{if eq $data.ipip_mode "cross-subnet"}}
    if defined(bgp_next_hop) && ( bgp_next_hop ~ {{$network}} ) then
      krt_tunnel = "";                     {{- /* Destination in ipPool, mode is cross sub-net, route from-host on subnet, do not use IPIP */}}
    else
      krt_tunnel = "{{$data.ipip}}";       {{- /* Destination in ipPool, mode is cross sub-net, route from-host off subnet, set the tunnel (if IPIP not enabled, value will be "") */}}
    accept;
  } {{- else}}
    krt_tunnel = "{{$data.ipip}}";         {{- /* Destination in ipPool, mode not cross sub-net, set the tunnel (if IPIP not enabled, value will be "") */}}
    accept;
  } {{- end}} {{- else}}
    krt_tunnel = "{{$data.ipip}}";         {{- /* Destination in ipPool, mode field is not present, set the tunnel (if IPIP not enabled, value will be "") */}}
    accept;
  } {{- end}}
{{end}}
{{- end}}{{/* End of 'exists $network_key' */}}
  accept;                                  {{- /* Destination is not in any ipPool, accept  */}}
}

3)BGP 协议部分。

BGP 协议部分是最主要的部分,使用了 bird 的 template,template 中 export 方向使用 filter calico_export_to_bgp_peers过滤,其定义在 bird_ipam.cfg.template 中,主要功能是:先调用 bird.cfg.template 中的apply_communities()方法(根据 BGP Configuration 中的communitiesprefixAdvertisements字段),为发送的 BGP 路由添加 community 参数;调用 bird_aggr.cfg.template 中的calico_aggr()方法,确保宣告的 BGP 路由的目标地址段为完整的 block;最后,判断路由的目标地址是否在/calico/staticroutes或 Calico IPPool 所指定的地址范围内,若在,则 accept,其他的 reject。

Calico 中 block 是容器 IP 资源池最小的分配单位,最初 Calico 会为每个节点分配一个 block,当某个节点 block 使用完,则会再次为节点分配一个 block

代码语言:javascript复制
filter calico_export_to_bgp_peers {
  # filter code terminates when it calls `accept;` or `reject;`, call apply_communities() before calico_aggr()
  apply_communities();
  calico_aggr();
{{- $static_key := "/staticroutes"}}
{{- if ls $static_key}}

  # Export static routes.
  {{- range ls $static_key}}
    {{- $parts := split . "-"}}
    {{- $cidr := join $parts "/"}}
  if ( net ~ {{$cidr}} ) then { accept; }
  {{- end}}
{{- end}}
{{range ls "/v1/ipam/v4/pool"}}{{$data := json (getv (printf "/v1/ipam/v4/pool/%s" .))}}
  if ( net ~ {{$data.cidr}} ) then {
    accept;
  }
{{- end}}
  reject;
}

/calico/staticroutes中的值也是由 confd 的calico backend写入,其值主要包含两部分:BGP Configuration 中设置的serviceClusterIPsserviceExternalIPsexternalTrafficPolicy=Local的 Service 地址。因此 filter calico_export_to_bgp_peers保证了 Calico 只对容器网络相关的路由进行 BGP,对于手动配置或从其他 EBGP 学习到的非容器网络相关的路由,则不会进行 BGP。

BGP 协议部分主要分三部分:node-to-node mesh 的配置、global peers 的配置、node-specific peers 的配置,都是使用上面的 template 完成。

其中 node-to-node mesh 配置部分,会判断两个 node ip,当远端 peer 的 node ip“较大”时,开启passive,也就是说,始终由“较大”IP 的 node 发起 BGP 连接,保证 mesh 是单向的。

global peers 配置和 node-specific peers 配置部分基本相同,会根据 BGP Peer 中的keepOriginalNextHoppassword配置协议的next hop keep以及password属性,另外会根据本节点是否配置 route reflector clusterID,决定是否开启rr client(用于 Calico route reflector 模式)。

4)static 协议部分在 bird_aggr.cfg.template 中,主要是将本机的 block 和/calico/staticroutes中的值配置为 Blackhole 路由。这样一来,即可通过“bird 路由表”,由 BGP 协议将本机的容器网络和 Service 网络的路由信息发送出去。而根据上面/calico/staticroutes的介绍,对于externalTrafficPolicy=Cluster的 Service 是以整个 ServiceCIRD(BGP Configuration 中设置的serviceClusterIPsserviceExternalIPs)作为目标地址进行 BGP 的,对于externalTrafficPolicy=Local的 Service,则会判断本节点上是否有相应的 workloadEndpoint,如果有,则以单个地址(子网掩码/32 或/128)作为目标地址进行 BGP。以此,Calico 实现了对 Service externalTrafficPolicy 属性的支持。

代码语言:javascript复制
{{- $block_key := printf "/calico/ipam/v2/host/%s/ipv4/block" (getenv "NODENAME")}}
{{- $static_key := "/calico/staticroutes"}}
{{if or (ls $block_key) (ls $static_key)}}
protocol static {
{{- if ls $block_key}}
   # IP blocks for this host.
{{- range ls $block_key}}
{{- $parts := split . "-"}}
{{- $cidr := join $parts "/"}}
   route {{$cidr}} blackhole;
{{- end}}
{{- end}}
{{- if ls $static_key}}
   # Static routes.
{{- range ls $static_key}}
{{- $parts := split . "-"}}
{{- $cidr := join $parts "/"}}
   route {{$cidr}} blackhole;
{{- end}}
{{- end}}
}
{{else}}# No IP blocks or static routes for this host.{{end}}

这里有个问题,由于 kernel 中的 export filter 过滤了目标地址属于 BGP Configuration 的serviceClusterIPsserviceClusterIPs的路由,实际上 static 协议中的/calico/staticroutes部分的 Blackhole 是无法配置到节点的路由表上的(externalTrafficPolicy=Cluster的 Service 地址也在这个范围内)。这会导致在使用 Calico 与 TOR 进行 BGP 的场景中,在开启了 advertise service 后,容器访问某个属于 ServiceCIDR 范围内,但并未分配给任何 Service 的地址时,数据包会根据默认路由发送到 TOR,再由 TOR 发送回集群的某个节点,如此反复直至 TTL 消耗完。而如果尝试将 ServiceCIDR 对应的 Blackhole 路由写入系统的路由表中,可以解决这个问题,但又会导致宿主机对 Service 无法访问,流量直接被丢弃。

参考

  • https://github.com/projectcalico/confd/pull/322[4]
  • https://github.com/projectcalico/confd[5]
  • https://github.com/projectcalico/calico/issues/3689[6]

脚注

[1]RFC 1997: http://www.rfc-editor.org/info/rfc1997

[2]Calico BGP 功能介绍:BIRD 简介: https://maao.cloud/2021/01/26/Calico-BGP功能介绍:BIRD简介/

[3]advertise service: https://docs.projectcalico.org/networking/advertise-service-ips

[4]https://github.com/projectcalico/confd/pull/322: https://github.com/projectcalico/confd/pull/322

[5]https://github.com/projectcalico/confd: https://github.com/projectcalico/confd

[6]https://github.com/projectcalico/calico/issues/3689: https://github.com/projectcalico/calico/issues/3689

原文链接:https://maao.cloud/2021/02/23/Calico-BGP功能介绍:实现/

0 人点赞