containerLab的简单用法

containerLab 支持很多 节点 和 类型 设置,相对比较复杂。实际使用中只需要掌握基本的组网即可

安装

网络布线

如果没有指定 网络模式 ,则使用默认的 bridge 。

container mode 用于于其他容器共享网络命名空间

my-node: kind: linux sidecar-node: kind: linux network-mode: container:my-node #my-node为另一个容器 startup-delay: 10name: srl02 topology: kinds: srl: type: ixrd3 #srlinux支持的类型,用于模拟硬件 image: ghcr.io/nokia/srlinux #使用的容器镜像 nodes: srl1: #节点1信息 kind: srl srl2: #节点2信息 kind: srl links: - endpoints: ["srl1:e1-1", "srl2:e1-1"] #节点1和节点2的点对点连接信息

上述配置包含两个SR Linux节点 srl1 和 srl2 ,它们通过以下两种方式互通:

都通过接口 mgmt 连接到了默认的容器网桥 clab (使用 docker network ls 查看)通过接口 e1-1 进行了点到点连接。点到点连接是通过一对veth实现的。 enpoints 描述了一对 veth ,因此数组中有且只能有2个元素

执行如下命令部署网络:

# containerlab deploy -t srl02.clab.yml

生成的容器网络如下:

IPv4: subnet 172.20.20.0/24, gateway 172.20.20.1 IPv6: subnet 2001:172:20:20::/64, gateway 2001:172:20:20::1

配置管理网络

用户自定义网络

一般情况下使用默认默认配置即可,但如果默认的网络于现有网络出现冲突,则可以手动指定网段:

mgmt: network: custom_mgmt # management network name ipv4_subnet: 172.100.100.0/24 # ipv4 range ipv6_subnet: 2001:172:100:100::/80 # ipv6 range (optional) topology: # the rest of the file is omitted for brevity

可以手动给节点指定特定IP,相当于静态IP,但此时需要给所有容器手动指定IP:

mgmt: network: fixedips #指定容器网络名称(默认的容器网络名称为clab) bridge: mybridge #指定网桥名称(默认的网桥名称为 br-<network-id>) ipv4_subnet: 172.100.100.0/24 ipv6_subnet: 2001:172:100:100::/80 topology: nodes: n1: kind: srl mgmt_ipv4: 172.100.100.11 # set ipv4 address on management network mgmt_ipv6: 2001:172:100:100::11 # set ipv6 address on management network

查看拓扑图

执行如下 命令 可以查看拓扑图:

# containerlab graph -t srl02.clab.yml

重新配置网络

如果修改了配置文件可以使用如下命令重新配置网络:

# containerlab deploy -t srl02.clab.yml --reconfigure

例子

官方给出了很多配置组网的例子 。组网中一般涉及两种实例:VM和路由器,后者可以使用 FRR 组件模拟。

原文配置解析

Kubernetes配置

下面使用kind创建了一个kubernetes集群,其中包含一个控制节点和3个工作节点,并分配和节点IP和pod网段。

注意配置中禁用了默认的CNI,因此使用kind部署之后,节点之间由于无法通信而不会 Ready 。

# cluster.yaml kind: Cluster name: clab-bgp-cplane-demo apiVersion: kind.x-k8s.io/v1alpha4 networking: disableDefaultCNI: true # 禁用默认 CNI podSubnet: "10.1.0.0/16" # Pod CIDR nodes: - role: control-plane # 节点角色 kubeadmConfigPatches: - | kind: InitConfiguration nodeRegistration: kubeletExtraArgs: node-ip: 10.0.1.2 # 节点 IP node-labels: "rack=rack0" # 节点标签 - role: worker kubeadmConfigPatches: - | kind: JoinConfiguration nodeRegistration: kubeletExtraArgs: node-ip: 10.0.2.2 node-labels: "rack=rack0" - role: worker kubeadmConfigPatches: - | kind: JoinConfiguration nodeRegistration: kubeletExtraArgs: node-ip: 10.0.3.2 node-labels: "rack=rack1" - role: worker kubeadmConfigPatches: - | kind: JoinConfiguration nodeRegistration: kubeletExtraArgs: node-ip: 10.0.4.2 node-labels: "rack=rack1"

Cilium安装

原文中的验证步骤可能不大合理,应该是先启动kubernetes和cilium,然后再启动containerlab,否则kubernetes因为没有CNI,也无法生成路由。

# values.yaml tunnel: disabled ipam: mode: kubernetes ipv4NativeRoutingCIDR: 10.0.0.0/8 # 开启 BGP 功能支持,等同于命令行执行 --enable-bgp-control-plane=true bgpControlPlane: enabled: true k8s: requireIPv4PodCIDR: truehelm repo add cilium https://helm.cilium.io/ helm install -n kube-system cilium cilium/cilium --version v1.12.1 -f values.yaml

完成上述配置之后kubernetes集群就启动了,节点也 Ready 了,下面进行BGP的配置。

BPG配置

原文中使用 frrouting/frr:v8.2.2 镜像来实现BGP路由发现。更多参数配置可以参见官方 手册 。文中的containerlab的topo文件如下:

# topo.yaml name: bgp-cplane-demo topology: kinds: linux: cmd: bash nodes: router0: kind: linux image: frrouting/frr:v8.2.2 labels: app: frr exec: - iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE - ip addr add 10.0.0.0/32 dev lo - ip route add blackhole 10.0.0.0/8 - touch /etc/frr/vtysh.conf - sed -i -e 's/bgpd=no/bgpd=yes/g' /etc/frr/daemons - usr/lib/frr/frrinit.sh start - >- vtysh -c 'conf t' -c 'router bgp 65000' -c ' bgp router-id 10.0.0.0' -c ' no bgp ebgp-requires-policy' -c ' neighbor ROUTERS peer-group' -c ' neighbor ROUTERS remote-as external' -c ' neighbor ROUTERS default-originate' -c ' neighbor net0 interface peer-group ROUTERS' -c ' neighbor net1 interface peer-group ROUTERS' -c ' address-family ipv4 unicast' -c ' redistribute connected' -c ' exit-address-family' -c '!' tor0: kind: linux image: frrouting/frr:v8.2.2 labels: app: frr exec: - ip link del eth0 - ip addr add 10.0.0.1/32 dev lo - ip addr add 10.0.1.1/24 dev net1 - ip addr add 10.0.2.1/24 dev net2 - touch /etc/frr/vtysh.conf - sed -i -e 's/bgpd=no/bgpd=yes/g' /etc/frr/daemons - /usr/lib/frr/frrinit.sh start - >- vtysh -c 'conf t' -c 'frr defaults datacenter' -c 'router bgp 65010' -c ' bgp router-id 10.0.0.1' -c ' no bgp ebgp-requires-policy' -c ' neighbor ROUTERS peer-group' -c ' neighbor ROUTERS remote-as external' -c ' neighbor SERVERS peer-group' -c ' neighbor SERVERS remote-as internal' -c ' neighbor net0 interface peer-group ROUTERS' -c ' neighbor 10.0.1.2 peer-group SERVERS' -c ' neighbor 10.0.2.2 peer-group SERVERS' -c ' address-family ipv4 unicast' -c ' redistribute connected' -c ' exit-address-family' -c '!' tor1: kind: linux image: frrouting/frr:v8.2.2 labels: app: frr exec: - ip link del eth0 - ip addr add 10.0.0.2/32 dev lo - ip addr add 10.0.3.1/24 dev net1 - ip addr add 10.0.4.1/24 dev net2 - touch /etc/frr/vtysh.conf - sed -i -e 's/bgpd=no/bgpd=yes/g' /etc/frr/daemons - /usr/lib/frr/frrinit.sh start - >- vtysh -c 'conf t' -c 'frr defaults datacenter' -c 'router bgp 65011' -c ' bgp router-id 10.0.0.2' -c ' no bgp ebgp-requires-policy' -c ' neighbor ROUTERS peer-group' -c ' neighbor ROUTERS remote-as external' -c ' neighbor SERVERS peer-group' -c ' neighbor SERVERS remote-as internal' -c ' neighbor net0 interface peer-group ROUTERS' -c ' neighbor 10.0.3.2 peer-group SERVERS' -c ' neighbor 10.0.4.2 peer-group SERVERS' -c ' address-family ipv4 unicast' -c ' redistribute connected' -c ' exit-address-family' -c '!' server0: kind: linux image: nicolaka/netshoot:latest network-mode: container:control-plane exec: - ip addr add 10.0.1.2/24 dev net0 - ip route replace default via 10.0.1.1 server1: kind: linux image: nicolaka/netshoot:latest network-mode: container:worker exec: - ip addr add 10.0.2.2/24 dev net0 - ip route replace default via 10.0.2.1 server2: kind: linux image: nicolaka/netshoot:latest network-mode: container:worker2 exec: - ip addr add 10.0.3.2/24 dev net0 - ip route replace default via 10.0.3.1 server3: kind: linux image: nicolaka/netshoot:latest network-mode: container:worker3 exec: - ip addr add 10.0.4.2/24 dev net0 - ip route replace default via 10.0.4.1 links: - endpoints: ["router0:net0", "tor0:net0"] - endpoints: ["router0:net1", "tor1:net0"] - endpoints: ["tor0:net1", "server0:net0"] - endpoints: ["tor0:net2", "server1:net0"] - endpoints: ["tor1:net1", "server2:net0"] - endpoints: ["tor1:net2", "server3:net0"]

该topo中涉及3个路由器:router0、tor0、tor1。以及4个普通节点:server0、server1、server2、server3,这4个节点与kubernetes的节点(容器部署)共享相同的网络命名空间。

下面看下各个节点是如何

router0的配置

下面是router0的bgp配置,其地址为 10.0.0.0 :

vtysh -c 'conf t' -c 'router bgp 65000' -c ' bgp router-id 10.0.0.0' -c ' no bgp ebgp-requires-policy' -c ' neighbor ROUTERS peer-group' -c ' neighbor ROUTERS remote-as external' -c ' neighbor ROUTERS default-originate' -c ' neighbor net0 interface peer-group ROUTERS' -c ' neighbor net1 interface peer-group ROUTERS' -c ' address-family ipv4 unicast' -c ' redistribute connected' -c ' exit-address-family' -c '!'vtysh -c 'conf t' :通过vtysh命令进入交互界面,然后进入配置界面'router bgp 65000' :配置BGP路由器的ASN(AS number),BGP协议使用该数值来判断BGP连接的是内部还是外部。输入该命令之后就可以执行BGP命令。'bgp router-id 10.0.0.0' :指定router-ID,用于标识路由器。此处使用IP作为路由标识'no bgp ebgp-requires-policy' :不需要使用策略来交换路由信息。'neighbor ROUTERS peer-group' :定义一个peer group,用于交换路由,一个peer group中可以有多个peer'neighbor ROUTERS remote-as external' :router0的邻居为tor0和tor1,它们都使用不同的ASN,因此将tor0和tor1作为EBGP,EBGP会在传播路由的时候修改下一跳。参考: EBGP vs IBGP'neighbor ROUTERS default-originate' :将默认路由 0.0.0.0 发送给邻居。'neighbor net0 interface peer-group ROUTERS' / 'neighbor net0 interface peer-group ROUTERS' :将对端绑定到一个peer group。这里的对端可以是接口名称或是邻居标签'address-family ipv4 unicast' :进入IPv4单播配置界面'redistribute connected' :将路由从其他协议重新分发到BGP,此处为系统的直连路由。'exit-address-family' :退出地址族配置。

上述配置中,为 router0 添加了邻居 net0 (连接到 tor0 )和 net1 (连接到 tor1 ),并在BGP中引入了ipv4的直连路由。此时组网如下:

tor0配置

vtysh -c 'conf t' -c 'frr defaults datacenter' -c 'router bgp 65010' -c ' bgp router-id 10.0.0.1' -c ' no bgp ebgp-requires-policy' -c ' neighbor ROUTERS peer-group' -c ' neighbor ROUTERS remote-as external' -c ' neighbor SERVERS peer-group' -c ' neighbor SERVERS remote-as internal' -c ' neighbor net0 interface peer-group ROUTERS' -c ' neighbor 10.0.1.2 peer-group SERVERS' -c ' neighbor 10.0.2.2 peer-group SERVERS' -c ' address-family ipv4 unicast' -c ' redistribute connected' -c ' exit-address-family'

此处配置与 router0 大体相同,它同样创建了一个EBGP类型的peer group ROUTERS ,将net0(连接到 router0 )作为邻居。同时它创建一个IBGP类型的peer group SERVERS ,并将 server0 和 server1 的地址作为邻居。

tor1 与 tor0 的配置类似,此处不再详述。最后的组网如下。其中 tor0 、 tor1 和 router0 建立了邻居关系。另外需要注意的是,containerlab网络中的server0~3分别与kubernetes的对应节点共享网络命名空间。

在 router0 上查看bgp邻居关系,可以看到 router0 与 tor0 (net0)、 tor1 (net1)建立了邻居关系:

router0# show bgp summary IPv4 Unicast Summary (VRF default): BGP router identifier 10.0.0.0, local AS number 65000 vrf-id 0 BGP table version 8 RIB entries 15, using 2760 bytes of memory Peers 2, using 1433 KiB of memory Peer groups 1, using 64 bytes of memory Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc net0 4 65010 15 15 0 0 0 00:00:20 3 9 N/A net1 4 65011 15 15 0 0 0 00:00:20 3 9 N/A Total number of neighbors 2

在 tor0 上查看邻居关系,可以看到, tor0 并没有与kubernetes节点建立邻居关系,因此无法获取kubernetes pod节点的路由信息。

tor0# show bgp summary IPv4 Unicast Summary (VRF default): BGP router identifier 10.0.0.1, local AS number 65010 vrf-id 0 BGP table version 9 RIB entries 15, using 2760 bytes of memory Peers 3, using 2149 KiB of memory Peer groups 2, using 128 bytes of memory Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc router0(net0) 4 65000 19 20 0 0 0 00:00:33 6 9 N/A 10.0.1.2 4 0 0 0 0 0 0 never Active 0 N/A 10.0.2.2 4 0 0 0 0 0 0 never Active 0 N/ Total number of neighbors 3

在 router0 上查看bgp发现的路由,可以看到不存在pod网段( 10.1.0.0/16 )的路由

router0# show bgp ipv4 all For address family: IPv4 Unicast BGP table version is 8, local router ID is 10.0.0.0, vrf id 0 Default local pref 100, local AS 65000 Status codes: s suppressed, d damped, h history, * valid, > best, = multipath, i internal, r RIB-failure, S Stale, R Removed Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self Origin codes: i - IGP, e - EGP, ? - incomplete RPKI validation codes: V valid, I invalid, N Not found Network Next Hop Metric LocPrf Weight Path *> 10.0.0.0/32 0.0.0.0 0 32768 ? *> 10.0.0.1/32 net0 0 0 65010 ? *> 10.0.0.2/32 net1 0 0 65011 ? *> 10.0.1.0/24 net0 0 0 65010 ? *> 10.0.2.0/24 net0 0 0 65010 ? *> 10.0.3.0/24 net1 0 0 65011 ? *> 10.0.4.0/24 net1 0 0 65011 ? *> 172.20.20.0/24 0.0.0.0 0 32768 ? Displayed 8 routes and 8 total paths

与kubernetes建立BGP

上述配置中, tor0 和 tor1 已经将kubernetes的节点作为IBGP,下面进行kubernetes侧BPG配置。cilium的 CiliumBGPPeeringPolicy CRD中可以配置BGP peer信息。

apiVersion: "cilium.io/v2alpha1" kind: CiliumBGPPeeringPolicy metadata: name: rack0 spec: nodeSelector: matchLabels: rack: rack0 virtualRouters: - localASN: 65010 exportPodCIDR: true # 自动宣告 Pod CIDR neighbors: - peerAddress: "10.0.0.1/32" # tor0 的 IP 地址 peerASN: 65010 --- apiVersion: "cilium.io/v2alpha1" kind: CiliumBGPPeeringPolicy metadata: name: rack1 spec: nodeSelector: matchLabels: rack: rack1 virtualRouters: - localASN: 65011 exportPodCIDR: true neighbors: - peerAddress: "10.0.0.2/32" # tor1 的 IP 地址 peerASN: 65011

上述配置中将标签为 rack=rack0 的节点(即 control-plane 和 worker )与 tor0 建立邻居,将标签为 rack=rack1 的节点(即 worker 和 worker2 )与 tor1 建立邻居:

# k get node -l rack=rack0 NAME STATUS ROLES AGE VERSION clab-bgp-cplane-demo-control-plane Ready control-plane 2d11h v1.24.0 clab-bgp-cplane-demo-worker Ready <none> 2d11h v1.24.0 # k get node -l rack=rack1 NAME STATUS ROLES AGE VERSION clab-bgp-cplane-demo-worker2 Ready <none> 2d11h v1.24.0 clab-bgp-cplane-demo-worker3 Ready <none> 2d11h v1.24.0

CiliumBGPPeeringPolicy 各个字段的 说明 如下

nodeSelector: Nodes which are selected by this label selector will apply the given policy virtualRouters: One or more peering configurations outlined below. Each peering configuration can be thought of as a BGP router instance. virtualRouters[*].localASN: The local ASN for this peering configuration virtualRouters[*].exportPodCIDR: Whether to export the private pod CIDR block to the listed neighbors virtualRouters[*].neighbors: A list of neighbors to peer with neighbors[*].peerAddress: The address of the peer neighbor neighbors[*].peerASN: The ASN of the peer

完成上述配置之后,containerlab的 router0 、 tor0 、 tor1 就学习到了kubernetes的路由信息:

查看tor0的bgp邻居,可以看到它与 clab-bgp-cplane-demo-control-plane(10.0.1.2) 和 clab-bgp-cplane-demo-worker(10.0.2.2) 成功建立了邻居关系:

tor0# show bgp summary IPv4 Unicast Summary (VRF default): BGP router identifier 10.0.0.1, local AS number 65010 vrf-id 0 BGP table version 13 RIB entries 23, using 4232 bytes of memory Peers 3, using 2149 KiB of memory Peer groups 2, using 128 bytes of memory Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc router0(net0) 4 65000 1430 1431 0 0 0 01:10:58 8 13 N/A clab-bgp-cplane-demo-control-plane(10.0.1.2) 4 65010 46 52 0 0 0 00:02:12 1 11 N/A clab-bgp-cplane-demo-worker(10.0.2.2) 4 65010 47 53 0 0 0 00:02:15 1 11 N/A Total number of neighbors 3

查看 tor1 的bgp邻居,可以看到它与 clab-bgp-cplane-demo-worker2(10.0.3.2) 和 clab-bgp-cplane-demo-worker3(10.0.4.2) 成功建立了邻居关系:

tor1# show bgp summary IPv4 Unicast Summary (VRF default): BGP router identifier 10.0.0.2, local AS number 65011 vrf-id 0 BGP table version 13 RIB entries 23, using 4232 bytes of memory Peers 3, using 2149 KiB of memory Peer groups 2, using 128 bytes of memory Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc router0(net0) 4 65000 1436 1437 0 0 0 01:11:15 8 13 N/A clab-bgp-cplane-demo-worker2(10.0.3.2) 4 65011 53 60 0 0 0 00:02:31 1 11 N/A clab-bgp-cplane-demo-worker3(10.0.4.2) 4 65011 54 61 0 0 0 00:02:33 1 11 N/A Total number of neighbors 3

查看 route0 的配置可以发现其获取到了Pod的路由信息:

router0# show bgp ipv4 all For address family: IPv4 Unicast BGP table version is 12, local router ID is 10.0.0.0, vrf id 0 Default local pref 100, local AS 65000 Status codes: s suppressed, d damped, h history, * valid, > best, = multipath, i internal, r RIB-failure, S Stale, R Removed Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self Origin codes: i - IGP, e - EGP, ? - incomplete RPKI validation codes: V valid, I invalid, N Not found Network Next Hop Metric LocPrf Weight Path *> 10.0.0.0/32 0.0.0.0 0 32768 ? *> 10.0.0.1/32 net0 0 0 65010 ? *> 10.0.0.2/32 net1 0 0 65011 ? *> 10.0.1.0/24 net0 0 0 65010 ? *> 10.0.2.0/24 net0 0 0 65010 ? *> 10.0.3.0/24 net1 0 0 65011 ? *> 10.0.4.0/24 net1 0 0 65011 ? *> 10.1.0.0/24 net0 0 65010 i *> 10.1.1.0/24 net1 0 65011 i *> 10.1.2.0/24 net0 0 65010 i *> 10.1.3.0/24 net1 0 65011 i *> 172.20.20.0/24 0.0.0.0 0 32768 ? Displayed 12 routes and 12 total paths

kubernetes的Pod网络

本环境下,kubernetes的Pod通过分别连接到pod命名空间和系统命名空间的一对veth实现互通,当报文从pod命名空间传递到系统命名空间之后就会通过系统路由进行报文分发。

思考

如果将 router0 、 tor0 、 tor1 和kubernetes的所有节点作为一个IBGP会怎么样(即所有的ASN都相同,都是internal类型的)?

答:此时由于 tor0 和 tor1 无法将学习到的路由转发给 router0 ,将导致 router0 缺少pod路由,进而导致网络 tor0 和 tor1 不通

EBGP和IBGP在技术实现上的第三个区别在路由转发的行为上。 通过IBGP学习到的路由,不能传递给其他的IBGP。 这么作是为了防止路由环路(loop)。EBGP通过BGP协议里面的AS_PATH和其他元素过滤来自于自己的路由,但是IBGP运行在一个AS内部,没有AS_PATH,所以IBGP干脆不转发来自于其他IBGP的路由。

由于不能转发路由,这要求所有的IBGP router两两相连,组成一个full-mesh的网络。Full-mesh的连接数与节点的关系是n*(n-1),连接数随着节点数的增加而迅速增加,这给配置和管理带来了问题。

参考

bgp-in-the-data-centerPacket_Walks_In_Kubernetes-v4cilium-life-of-a-packet-pod-to-service

TIPs:

BGP简单调试:首先使用 show bgp summary 查看本节点与邻居是否协商成功,然后使用 show bgp ipv4 wide 查看本节点学习到的路由即可此外还可以通过 show bgp neighbor 查看邻居状态,以及通过 show bgp peer-group 查看peer group的信息,使用 show bgp nexthop 查看下一跳表