Calico と MetalLB を BGP モードで共存させる環境を作ってみた【Kubernetes】

Kubernetes 環境で Calico と MetalLB を BGP モードで共存させる場合、1つの Kubernetes ノードから、外部のルーターに対して2つの BGP セッションを張る必要があります。BGP ではノード間で1つのセッションしか張ることができないため、1つの K8s ノードと1つのルーターの間で Calico と MetalLB の2つのセッションを張ることができません。

f:id:naoki029:20190818073107p:plain

これに対するワークアラウンド公式ドキュメントで紹介されています。この中で、VRF を分ける方法を試してみました。

構成

f:id:naoki029:20190817173650p:plain

ルーターとして CSR1000v を用意し、VRFを分けてルートリークさせています。

ルーターの設定

CSR1000v は AS 65000 とし、vrf k8s-metallb と vrf k8s-calico を作成し、Calico と MetalLB の BGP Speaker と BGP セッションを貼っています。K8s ノードはデフォルトゲートウェイを設定してあるので、K8sノードに対して余計なルートを広告しないようにしています。

ip vrf k8s-calico
 rd 65000:2
 route-target export 65000:2
 route-target import 65000:2
 route-target import 65000:1
!
ip vrf k8s-metallb
 rd 65000:1
 route-target export 65000:1
 route-target import 65000:2
 route-target import 65000:1
!
interface Loopback0
 ip address 1.1.1.0 255.255.255.255
!
interface Loopback2
 ip vrf forwarding k8s-calico
 ip address 1.1.1.2 255.255.255.255
!
interface GigabitEthernet2
 ip vrf forwarding k8s-metallb
 ip address 10.0.0.254 255.255.255.0
!
interface GigabitEthernet3
 ip vrf forwarding k8s-calico
 ip address 10.0.1.254 255.255.255.0
!
router bgp 65000
 bgp router-id 1.0.0.0
 bgp log-neighbor-changes
 !
 address-family ipv4 vrf k8s-calico
  bgp router-id 1.0.0.2
  redistribute connected
  neighbor 10.0.0.1 remote-as 65002
  neighbor 10.0.0.1 ebgp-multihop 32
  neighbor 10.0.0.1 update-source Loopback2
  neighbor 10.0.0.1 activate
  neighbor 10.0.0.1 prefix-list deny-all-routes out
  neighbor 10.0.0.11 remote-as 65002
  neighbor 10.0.0.11 ebgp-multihop 32
  neighbor 10.0.0.11 update-source Loopback2
  neighbor 10.0.0.11 activate
  neighbor 10.0.0.11 prefix-list deny-all-routes out
  neighbor 10.0.0.12 remote-as 65002
  neighbor 10.0.0.12 ebgp-multihop 32
  neighbor 10.0.0.12 update-source Loopback2
  neighbor 10.0.0.12 activate
  neighbor 10.0.0.12 prefix-list deny-all-routes out
  neighbor 10.0.0.13 remote-as 65002
  neighbor 10.0.0.13 ebgp-multihop 32
  neighbor 10.0.0.13 update-source Loopback2
  neighbor 10.0.0.13 activate
  neighbor 10.0.0.13 prefix-list deny-all-routes out
 exit-address-family
 !
 address-family ipv4 vrf k8s-metallb
  bgp router-id 1.0.0.1
  redistribute connected
  neighbor 10.0.0.1 remote-as 65001
  neighbor 10.0.0.1 update-source GigabitEthernet2
  neighbor 10.0.0.1 activate
  neighbor 10.0.0.1 prefix-list deny-all-routes out
  neighbor 10.0.0.11 remote-as 65001
  neighbor 10.0.0.11 update-source GigabitEthernet2
  neighbor 10.0.0.11 activate
  neighbor 10.0.0.11 prefix-list deny-all-routes out
  neighbor 10.0.0.12 remote-as 65001
  neighbor 10.0.0.12 update-source GigabitEthernet2
  neighbor 10.0.0.12 activate
  neighbor 10.0.0.12 prefix-list deny-all-routes out
  neighbor 10.0.0.13 remote-as 65001
  neighbor 10.0.0.13 update-source GigabitEthernet2
  neighbor 10.0.0.13 activate
  neighbor 10.0.0.13 prefix-list deny-all-routes out
  maximum-paths 4
 exit-address-family
!
ip prefix-list deny-all-routes seq 5 deny 0.0.0.0/0 le 32

Calico の設定

Calico の AS Number とピア(CSR1000v)の設定をします。CSR1000v の vrf k8s-calico に割り当てている Loopback 2 をピアとして設定しています。

[root@k8s-master1 ~]# cat calico-bgp.yaml 
apiVersion: projectcalico.org/v3
kind: BGPConfiguration
metadata:
  creationTimestamp: null
  name: default
spec:
  asNumber: 65002
  logSeverityScreen: Info
  nodeToNodeMeshEnabled: true
[root@k8s-master1 ~]# 
[root@k8s-master1 ~]# calicoctl create -f calico-bgp.yaml 
Successfully created 1 'BGPConfiguration' resource(s)
[root@k8s-master1 ~]# 
[root@k8s-master1 ~]# cat calico-peer.yaml 
apiVersion: projectcalico.org/v3
kind: BGPPeer
metadata:
  creationTimestamp: null
  name: bgppeer-csr1000v
spec:
  asNumber: 65000
  peerIP: 1.1.1.2
[root@k8s-master1 ~]#
[root@k8s-master1 ~]# calicoctl create -f calico-peer.yaml 
Successfully created 1 'BGPPeer' resource(s)
[root@k8s-master1 ~]# 

MetalLB のデプロイと設定

MetalLB を K8s にデプロイします。

[root@k8s-master1 ~]# kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.8.1/mani
fests/metallb.yaml
namespace/metallb-system unchanged
podsecuritypolicy.policy/speaker unchanged
serviceaccount/controller unchanged
serviceaccount/speaker unchanged
clusterrole.rbac.authorization.k8s.io/metallb-system:controller unchanged
clusterrole.rbac.authorization.k8s.io/metallb-system:speaker unchanged
role.rbac.authorization.k8s.io/config-watcher unchanged
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:controller unchanged
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:speaker unchanged
rolebinding.rbac.authorization.k8s.io/config-watcher unchanged
daemonset.apps/speaker unchanged
deployment.apps/controller unchanged
[root@k8s-master1 ~]# 

MetalLB の AS Number とピアの設定を行います。CSR1000v の vrf k8s-metallb に割り当てている Gi 2 をピアとして設定しています。

[root@k8s-master1 ~]# cat metallb-config.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    peers:
    - peer-address: 10.0.0.254
      peer-asn: 65000
      my-asn: 65001
    address-pools:
    - name: default
      protocol: bgp
      addresses:
      - 10.100.0.0/24
[root@k8s-master1 ~]# kubectl apply -f metallb-config.yaml 
configmap/config created
[root@k8s-master1 ~]#

動作確認

MetalLB と CSR1000v の BGP の状態

CSR1000v の vrf k8s-metallb の状態を確認してみます。各 k8s ノードと BGP ネイバーを確立していることがわかります。

csr1000v#show bgp vpnv4 unicast vrf k8s-metallb summary 
BGP router identifier 1.0.0.1, local AS number 65000
BGP table version is 3193, main routing table version 3193
7 network entries using 1792 bytes of memory
7 path entries using 952 bytes of memory
4/3 BGP path/bestpath attribute entries using 1184 bytes of memory
1 BGP AS-PATH entries using 24 bytes of memory
2 BGP extended community entries using 48 bytes of memory
0 BGP route-map cache entries using 0 bytes of memory
0 BGP filter-list cache entries using 0 bytes of memory
BGP using 4000 total bytes of memory
BGP activity 158/144 prefixes, 1611/1585 paths, scan interval 60 secs

Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
10.0.0.1        4        65001    5512    6058     3193    0    0 1d21h           0
10.0.0.11       4        65001    5344    5839     3193    0    0 1d20h           0
10.0.0.12       4        65001    5344    5833     3193    0    0 1d20h           0
10.0.0.13       4        65001    5511    6072     3193    0    0 1d21h           0
csr1000v#

Calico と CSR1000v の BGP の状態

Calico で定義しているIP pool から払い出されている IP Block は以下のように確認できます。

[root@k8s-master1 ~]# calicoctl ipam show --show-blocks
+----------+--------------------+-----------+------------+--------------+
| GROUPING |        CIDR        | IPS TOTAL | IPS IN USE |   IPS FREE   |
+----------+--------------------+-----------+------------+--------------+
| IP Pool  | 192.168.0.0/16     |     65536 | 8 (0%)     | 65528 (100%) |
| Block    | 192.168.116.0/26   |        64 | 1 (2%)     | 63 (98%)     |
| Block    | 192.168.160.0/26   |        64 | 1 (2%)     | 63 (98%)     |
| Block    | 192.168.165.192/26 |        64 | 4 (6%)     | 60 (94%)     |
| Block    | 192.168.195.128/26 |        64 | 2 (3%)     | 62 (97%)     |
+----------+--------------------+-----------+------------+--------------+
[root@k8s-master1 ~]# 

Calico から払い出されている IP pool の範囲が CSR1000v に BGP で広告されてきていることがわかります。

csr1000v#show bgp vpnv4 unicast vrf k8s-calico 
     Network          Next Hop            Metric LocPrf Weight Path
Route Distinguisher: 65000:2 (default for vrf k8s-calico) VRF Router ID 1.0.0.2
 *>   1.1.1.2/32       0.0.0.0                  0         32768 ?
 *>   10.0.0.0/24      0.0.0.0                  0         32768 ?
 *>   10.0.1.0/24      0.0.0.0                  0         32768 ?
 *m   192.168.116.0/26 10.0.0.13                              0 65002 i
 *m                    10.0.0.11                              0 65002 i
 *m                    10.0.0.12                              0 65002 i
 *>                    10.0.0.1                               0 65002 i
 *m   192.168.160.0/26 10.0.0.13                              0 65002 i
 *m                    10.0.0.1                               0 65002 i
 *m                    10.0.0.12                              0 65002 i
 *>                    10.0.0.11                              0 65002 i
 *m   192.168.165.192/26
                      10.0.0.13                              0 65002 i
 *m                    10.0.0.11                              0 65002 i
 *m                    10.0.0.1                               0 65002 i
 *>                    10.0.0.12                              0 65002 i
 *m   192.168.195.128/26
                      10.0.0.13                              0 65002 i
 *m                    10.0.0.11                              0 65002 i
 *m                    10.0.0.12                              0 65002 i
 *>                    10.0.0.1                               0 65002 i
csr1000v#

nginx + LoadBalancer (MetalLB) を起動

nginx のコンテナ 3 つと、MetalLB を使った LB Service を起動させて nginx へのアクセスに対してロードバランシングさせるようにします。

[root@k8s-master1 ~]# cat nginx.yaml 
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - name: http
          containerPort: 80

---
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  type: LoadBalancer
[root@k8s-master1 ~]# kubectl apply -f nginx.yaml 
deployment.apps/nginx created
service/nginx created
[root@k8s-master1 ~]# 

Pod と Service が作成され、nginx が紐付いている Service に 10.100.0.0 が割当られていることがわかります。

[root@k8s-master1 ~]# kubectl get pods -o wide
NAME                    READY   STATUS    RESTARTS   AGE    IP                NODE                        NOMINATED NODE   READINESS GATES
nginx-5b688b67d-7tv9n   1/1     Running   0          8m6s   192.168.195.141   k8s-worker2.mylab178.tech   <none>           <none>
nginx-5b688b67d-jphvf   1/1     Running   0          8m6s   192.168.160.9     k8s-worker1.mylab178.tech   <none>           <none>
nginx-5b688b67d-lp5pq   1/1     Running   0          8m6s   192.168.165.209   k8s-worker3.mylab178.tech   <none>           <none>
[root@k8s-master1 ~]# 
[root@k8s-master1 ~]# kubectl get services
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP      10.96.0.1       <none>        443/TCP        3d23h
nginx        LoadBalancer   10.111.193.73   10.100.0.0    80:30225/TCP   8m10s
[root@k8s-master1 ~]#

ロードバランスの動作を確認するために、各 Pod(nginx) の index.html に Pod 名が表示されるようにしておきます。

[root@k8s-master1 ~]# for  PODNAME in `kubectl get pods -l app=nginx -o jsonpath='{.items[*].metadata.name}' `; do
>       kubectl exec -it  ${PODNAME} -- cp /etc/hostname /usr/share/nginx/html/index.html;
> done
[root@k8s-master1 ~]# 

CSR1000v に Service の External IP、10.100.0.0 が広告されてきていることがわかります。

csr1000v#show ip route vrf k8s-metallb         
      1.0.0.0/32 is subnetted, 2 subnets
C        1.1.1.1 is directly connected, Loopback1
B        1.1.1.2 is directly connected, 01:20:31, Loopback2
      10.0.0.0/8 is variably subnetted, 5 subnets, 2 masks
C        10.0.0.0/24 is directly connected, GigabitEthernet2
L        10.0.0.254/32 is directly connected, GigabitEthernet2
B        10.0.1.0/24 is directly connected, 01:20:31, GigabitEthernet3
L        10.0.1.254/32 is directly connected, GigabitEthernet3
B        10.100.0.0/32 [20/0] via 10.0.0.13, 00:09:14
                       [20/0] via 10.0.0.12, 00:09:14
                       [20/0] via 10.0.0.11, 00:09:14
                       [20/0] via 10.0.0.1, 00:09:14
      192.168.116.0/26 is subnetted, 1 subnets
B        192.168.116.0 [20/0] via 10.0.0.1 (k8s-calico), 00:45:42
      192.168.160.0/26 is subnetted, 1 subnets
B        192.168.160.0 [20/0] via 10.0.0.11 (k8s-calico), 00:43:47
      192.168.165.0/26 is subnetted, 1 subnets
B        192.168.165.192 [20/0] via 10.0.0.13 (k8s-calico), 00:42:16
      192.168.195.0/26 is subnetted, 1 subnets
B        192.168.195.128 [20/0] via 10.0.0.12 (k8s-calico), 00:43:35
csr1000v#show bgp vpnv4 unicast vrf k8s-metallb
     Network          Next Hop            Metric LocPrf Weight Path
Route Distinguisher: 65000:1 (default for vrf k8s-metallb) VRF Router ID 1.0.0.1
 *>   1.1.1.1/32       0.0.0.0                  0         32768 ?
 *>   1.1.1.2/32       0.0.0.0                  0         32768 ?
 *>   10.0.0.0/24      0.0.0.0                  0         32768 ?
 *>   10.0.1.0/24      0.0.0.0                  0         32768 ?
 *m   10.100.0.0/32    10.0.0.13                              0 65001 ?
 *m                    10.0.0.12                              0 65001 ?
 *m                    10.0.0.1                               0 65001 ?
 *>                    10.0.0.11                              0 65001 ?
 *>   192.168.116.0/26 10.0.0.1                               0 65002 i
 *>   192.168.160.0/26 10.0.0.11                              0 65002 i
 *>   192.168.165.192/26
                      10.0.0.13                              0 65002 i
 *>   192.168.195.128/26
                      10.0.0.12                              0 65002 i
csr1000v#

External IP への接続

Client から External IP に対して http アクセスをしてみます。各 Pod にロードバランスされていることが確認できます。

user1@WSL:~$ for i in `seq 10` ; do curl -s 10.100.0.0 ;  done
nginx-5b688b67d-jphvf
nginx-5b688b67d-7tv9n
nginx-5b688b67d-jphvf
nginx-5b688b67d-lp5pq
nginx-5b688b67d-7tv9n
nginx-5b688b67d-jphvf
nginx-5b688b67d-lp5pq
nginx-5b688b67d-7tv9n
nginx-5b688b67d-lp5pq
nginx-5b688b67d-7tv9n
user1@WSL:~$

Pod IP への接続

続いて Client から Pod IP に対して hpttp アクセスしてみます。

user1@WSL:~$ curl 192.168.195.141
nginx-5b688b67d-7tv9n
user1@WSL:~$ curl 192.168.195.141
nginx-5b688b67d-7tv9n
user1@WSL:~$ curl 192.168.195.141
nginx-5b688b67d-7tv9n
user1@WSL:~$ curl 192.168.160.9
nginx-5b688b67d-jphvf
user1@WSL:~$ curl 192.168.160.9
nginx-5b688b67d-jphvf
user1@WSL:~$ curl 192.168.160.9
nginx-5b688b67d-jphvf
user1@WSL:~$ curl 192.168.165.209
nginx-5b688b67d-lp5pq
user1@WSL:~$ curl 192.168.165.209
nginx-5b688b67d-lp5pq
user1@WSL:~$ curl 192.168.165.209
nginx-5b688b67d-lp5pq
user1@WSL:~$

Pod IP に対して外部から直接アクセス出来ることを確認できました。