Cilium is een open source-project om netwerken, beveiliging en observeerbaarheid te bieden voor cloud-native omgevingen zoals Kubernetes-clusters en andere containerorkestratieplatforms.

Hulp nodig met Cilium of Kubernetes?

Zorg ervoor dat jij onze whitepaper ontvangt of vraag een van de experts gewoon alles wat te maken heeft met Cilium, netwerken, Kubernetes, clustors of setups. We staan meer dan klaar om je te helpen!

Gratis white paper
Vraag onze experts

Bekijk zeker onze laatste evenementen over Cilium TIP
Sluit je aan bij SUE HQ of meld je aan voor onze livestreams. Leer alles wat er te weten valt en inzichten over Cilium en zijn mogelijkheden. Leer meer >

BGP

Border Gateway Protocol (BGP) is een gestandaardiseerd extern gateway-protocol dat is ontworpen om routerings- en bereikbaarheidsinformatie uit te wisselen tussen autonome systemen (AS) op internet. BGP is geclassificeerd als een pad-vectorrouteringsprotocol. Het neemt routeringsbeslissingen op basis van paden, netwerkbeleid of regelsets die zijn geconfigureerd door een netwerkbeheerder.

Cilium and BGP

In release 1.10 integreerde Cilium BGP-ondersteuning met MetalLB, waardoor het Kubernetes Service-ip-adressen van het type LoadBalancer kan aankondigen met behulp van BGP. Het resultaat is dat services van buiten het Kubernetes-netwerk bereikbaar zijn zonder extra componenten, zoals een Ingress Router. Vooral het ‘zonder extra componenten’ gedeelte is fantastisch nieuws, aangezien elk onderdeel latency toevoegt – dus zonder die minder latency.

Lab environment

Laat ons eerst uitleggen hoe het lab is opgezet en wat het eindresultaat zal zijn.

Het lab bestaat uit een clientnetwerk (192.168.10.0/24) en een Kubernetes-netwerk (192.168.1.1/24). Wanneer een Service een LoadBalancer ip-adres krijgt, wordt dat adres bediend vanuit de pool 172.16.10.0/24. In ons lab zijn de volgende knooppunten aanwezig:

Name IP addresses Description
bgp-router1 192.168.1.1/24 (k8s network), 192.168.10.1/24 (client network) The BGP router
k8s-control 192.168.1.5/24 (k8s network), 192.168.10.233/24 (client network) Management node
k8s-master1 192.168.1.10/24 (k8s network) k8s master
k8s-worker1 192.168.1.21/24 (k8s network) k8s worker
k8s-worker2 192.168.1.22/24 (k8s network) k8s worker
k8s-worker3 192.168.1.23/24 (k8s network) k8s worker
k8s-worker4 192.168.1.24/24 (k8s network) k8s worker
k8s-worker5 192.168.1.25/24 (k8s network) k8s worker

Nadat alle onderdelen zijn geconfigureerd, is het mogelijk om vanuit het clientnetwerk een Service in het Kubernetes-netwerk te bereiken met behulp van het aangekondigde LoadBalancer IP-adres. Zie afbeelding hieronder.

Lab configuration

BGP router

De router is een Red Hat 8-systeem met drie netwerkinterfaces (extern-, kubernetes- en clientnetwerk) met FRRouting (FRR) verantwoordelijk voor de afhandeling van het BGP-verkeer. FRR is een gratis en open source internetrouteringsprotocolsuite voor Linux- en Unix-platforms. Het implementeert veel routeringsprotocollen zoals BGP, OSPF en RIP. In ons lab is alleen BGP ingeschakeld.

dnf install frr
systemctl enable frr
firewall-cmd --permanent --add-port=179/tcp
echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/01-sysctl.conf
sysctl -w net.ipv4.ip_forward=1

Na het instaleren FRR, the BGP daemon is configured to start by changing bgpd=no to bgpd=yes in the configuration file /etc/frr/daemons, using the following BGP configuration in /etc/frr/bgpd.conf

log syslog notifications
frr defaults traditional
!
router bgp 64512
no bgp ebgp-requires-policy
bgp router-id 192.168.1.1
neighbor 192.168.1.10 remote-as 64512
neighbor 192.168.1.10 update-source enp7s0
neighbor 192.168.1.21 remote-as 64512
neighbor 192.168.1.21 update-source enp7s0
neighbor 192.168.1.22 remote-as 64512
neighbor 192.168.1.22 update-source enp7s0
neighbor 192.168.1.23 remote-as 64512
neighbor 192.168.1.23 update-source enp7s0
neighbor 192.168.1.24 remote-as 64512
neighbor 192.168.1.24 update-source enp7s0
neighbor 192.168.1.25 remote-as 64512
neighbor 192.168.1.25 update-source enp7s0
address-family ipv4 unicast
neighbor 192.168.1.10 next-hop-self
neighbor 192.168.1.21 next-hop-self
neighbor 192.168.1.22 next-hop-self
neighbor 192.168.1.23 next-hop-self
neighbor 192.168.1.24 next-hop-self
neighbor 192.168.1.25 next-hop-self
exit-address-family
!
address-family ipv6 unicast
exit-address-family
!
line vty

In the config file above the AS number 64512 is used, which is reserved for private use. The Kubernetes master node and worker nodes are configured as neighbor. The ip address of the router’s interface in the Kubernetes network (192.168.1.1) is used as router id.
After the configuration above is applied and the FRR daemon is started using the systemctl start frr command, the command vtysh -c ‘show bgp summary’ shows the following output.

IPv4 Unicast Summary:
BGP router identifier 192.168.1.1, local AS number 64512 vrf-id 0
BGP table version 0
RIB entries 0, using 0 bytes of memory
Peers 6, using 86 KiB of memory

Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt
192.168.1.10 4 64512 0 0 0 0 0 never Active 0
192.168.1.21 4 64512 0 0 0 0 0 never Active 0
192.168.1.22 4 64512 0 0 0 0 0 never Active 0
192.168.1.23 4 64512 0 0 0 0 0 never Active 0
192.168.1.24 4 64512 0 0 0 0 0 never Active 0
192.168.1.25 4 64512 0 0 0 0 0 never Active 0

Total number of neighbors 6

Kubernetes and Cilium

It goes beyond the scope of this blog to explain how to install the Kubernetes nodes and the Kubernetes cluster. For your information: in this lab Red Hat 8 (minimal installation) is used as the operating system for all the nodes and Kubeadm was subsequently used to set up the cluster.

The Cilium Helm chart version v1.10.5 is used to install and configure Cilium on the cluster, using these values:

debug:
enabled: true
externalIPs:
enabled: true
hostPort:
enabled: true
hostServices:
enabled: true
kubeProxyReplacement: strict 
k8sServiceHost: 192.168.1.10
k8sServicePort: 6443
nodePort:
enabled: true
operator:
replicas: 1
bgp:
enabled: true
announce:
loadbalancerIP: true
hubble:
enabled: true
metrics:
enabled:
- dns
- drop
- tcp
- flow
- port-distribution
- icmp
- http
listenAddress: ":4244"
relay:
enabled: true
ui:
enabled: true

To get Cilium up and running with BGP, only the bgp key and subkeys are needed from the settings above. The other settings are used to get a fully working Cilium environment with,for example, the Hubble user interface.

Expose a service

When Kubernetes is running and Cilium is configured, it is time to create a deployment and expose it to the network using BGP. The following YAML file creates a Deployment web1, which is just a simple NGINX web server serving the default web page. The file also creates a Service web1-lb with a Service type LoadBalancer. This results in an external ip address that is announced to our router using BGP.

---
apiVersion: v1
kind: Service
metadata:
name: web1-lb
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
selector:
svc: web1-lb


apiVersion: apps/v1
kind: Deployment
metadata:
name: web1
spec:
selector:
matchLabels:
svc: web1-lb
template:
metadata:
labels:
svc: web1-lb
spec:
containers:
– name: web1
image: nginx
imagePullPolicy: IfNotPresent
ports:
– containerPort: 80
readinessProbe:
httpGet:
path: /
port: 80

After applying the YAML file above, the command kubectl get svc shows that Service web1-lb has an external ip address:

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 7d3h
web1-lb LoadBalancer 10.106.236.120 172.16.10.0 80:30256/TCP 7d2h

The address 172.16.10.0 seems strange, but it is fine. Often the .0 address is skipped and the .1 address is used as the first address. One of the reasons is that in the early days the .0 address was used for broadcast, which was later changed to .255. Since .0 is still a valid address MetalLB, which is responsible for the address pool, hands it out as the first address. The command vtysh -c ‘show bgp summary’ on router bgp-router1 shows that it has received one prefix:

IPv4 Unicast Summary:
BGP router identifier 192.168.1.1, local AS number 64512 vrf-id 0
BGP table version 17
RIB entries 1, using 192 bytes of memory
Peers 6, using 128 KiB of memory

Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt
192.168.1.10 4 64512 445 435 0 0 0 03:36:56 1 0
192.168.1.21 4 64512 446 435 0 0 0 03:36:54 1 0
192.168.1.22 4 64512 445 435 0 0 0 03:36:56 1 0
192.168.1.23 4 64512 445 435 0 0 0 03:36:56 1 0
192.168.1.24 4 64512 446 435 0 0 0 03:36:56 1 0
192.168.1.25 4 64512 445 435 0 0 0 03:36:56 1 0

Total number of neighbors 6

The following snippet of the routing table (ip route) tells us that for that specific ip address 172.16.10.0, 6 possible routes/destinations are present. In other words, all Kubernetes nodes announced that they can handle traffic for that address. Cool!!

172.16.10.0 proto bgp metric 20 
nexthop via 192.168.1.10 dev enp7s0 weight 1 
nexthop via 192.168.1.21 dev enp7s0 weight 1 
nexthop via 192.168.1.22 dev enp7s0 weight 1 
nexthop via 192.168.1.23 dev enp7s0 weight 1 
nexthop via 192.168.1.24 dev enp7s0 weight 1 
nexthop via 192.168.1.25 dev enp7s0 weight 1

Indeed, the web page is now visible from our router.

$ curl -s -v http://172.16.10.0/ -o /dev/null 
* Trying 172.16.10.0...
* TCP_NODELAY set
* Connected to 172.16.10.0 (172.16.10.0) port 80 (#0)
> GET / HTTP/1.1
> Host: 172.16.10.0
> User-Agent: curl/7.61.1
> Accept: */*
> 
< HTTP/1.1 200 OK
< Server: nginx/1.21.3
< Date: Sun, 31 Oct 2021 14:19:17 GMT
< Content-Type: text/html
< Content-Length: 615
< Last-Modified: Tue, 07 Sep 2021 15:21:03 GMT
< Connection: keep-alive
< ETag: "6137835f-267"
< Accept-Ranges: bytes
< 
{ [615 bytes data]
* Connection #0 to host 172.16.10.0 left intact

And a client in our client network can also reach that same page, since it uses bgp-router1 as default route.

More details

Nu werkt het allemaal, de meeste ingenieurs willen meer details zien, dus ik zal je niet teleurstellen.

Ping

Een van de eerste dingen die opvalt, is dat het LoadBalanced ip-adres niet bereikbaar is via ping. Een beetje dieper duiken onthult waarom, maar laten we eerst de Cilium-aliassen maken om het gemakkelijker te maken om cilium te gebruiken, dat aanwezig is in elke Cilium-agentpod.

CILIUM_POD=$(kubectl -n kube-system get pods -l k8s-app=cilium --output=jsonpath='{.items[*].metadata.name}' --field-selector=spec.nodeName=k8s-master1)
alias cilium="kubectl -n kube-system exec -ti ${CILIUM_POD} -c cilium-agent -- cilium"

First see the output of this snippet of cilium bpf lb list, that shows the configured load balancing configuration inside Cilium for our Service *web1-lb`:

172.16.10.0:80 0.0.0.0:0 (5) [LoadBalancer] 
10.0.3.150:80 (5)

Hier kan je zien dat er een mapping wordt gemaakt tussen bronpoort 80 en bestemmingspoort 80. Deze mapping wordt uitgevoerd met behulp van eBPF-logica op de interface en is aanwezig op alle knooppunten. Deze mapping laat zien dat alleen(!) verkeer voor poort 80 in evenwicht is. Al het andere verkeer, inclusief de ping, wordt niet opgevangen. Daarom kun je zien dat het icmp-pakket het knooppunt bereikt, maar er wordt nooit een antwoord verzonden.

Observe traffic

Hubble is het netwerk- en beveiligings observatieplatform dat is gebouwd op eBPF en Cilium. Via de opdrachtregel en via een grafische web-GUI is het mogelijk om actueel en historisch verkeer te zien. In dit lab wordt Hubble op de k8s-control node geplaatst, die direct toegang heeft tot de API van Hubble Relay. Hubble Relay is het onderdeel dat de benodigde informatie van de Cilium-knooppunten verkrijgt. Houd er rekening mee dat het hubble-commando ook aanwezig is in elke Cilium-agentpod, maar dat deze alleen informatie voor die specifieke agent zal tonen!

export HUBBLE_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/hubble/master/stable.txt)
curl -L --remote-name-all https://github.com/cilium/hubble/releases/download/$HUBBLE_VERSION/hubble-linux-amd64.tar.gz{,.sha256sum}
sha256sum --check hubble-linux-amd64.tar.gz.sha256sum
sudo tar xzvfC hubble-linux-amd64.tar.gz /usr/local/bin
rm hubble-linux-amd64.tar.gz{,.sha256sum}

The following outputs show the observer information which is a result of the curl http://172.16.10.0/ command on the router.

$ hubble observe --namespace default --follow

Oct 31 15:43:41.382: 192.168.1.1:36946 <> default/web1-696bfbbbc4-jnxbc:80 to-overlay FORWARDED (TCP Flags: SYN)
Oct 31 15:43:41.384: 192.168.1.1:36946 <> default/web1-696bfbbbc4-jnxbc:80 to-overlay FORWARDED (TCP Flags: ACK)
Oct 31 15:43:41.384: 192.168.1.1:36946 <> default/web1-696bfbbbc4-jnxbc:80 to-overlay FORWARDED (TCP Flags: ACK, PSH)
Oct 31 15:43:41.385: 192.168.1.1:36946 <> default/web1-696bfbbbc4-jnxbc:80 to-overlay FORWARDED (TCP Flags: ACK)
Oct 31 15:43:41.385: 192.168.1.1:36946 <> default/web1-696bfbbbc4-jnxbc:80 to-overlay FORWARDED (TCP Flags: ACK)
Oct 31 15:43:41.386: 192.168.1.1:36946 <> default/web1-696bfbbbc4-jnxbc:80 to-overlay FORWARDED (TCP Flags: ACK, FIN)
Oct 31 15:43:41.386: 192.168.1.1:36946 <> default/web1-696bfbbbc4-jnxbc:80 to-overlay FORWARDED (TCP Flags: ACK)

Eerder waarschuwde ik voor het niet gebruiken van het hubble-commando in de Cilium-agentpod, maar het kan ook zeer informatief zijn om het specifieke knooppuntverkeer te zien. In dit geval wordt een hubble-observatie –namespace default –follow uitgevoerd binnen elke Cilium-agentpod en wordt de krul van de router eenmaal uitgevoerd. Op het knooppunt waar de pod ‘leeft’ (k8s-worker2), zien we dezelfde uitvoer als hierboven. Op een andere pod (k8s-worker1) zien we echter de volgende uitvoer:

Oct 31 15:56:05.220: 10.0.3.103:48278 -> default/web1-696bfbbbc4-jnxbc:80 to-endpoint FORWARDED (TCP Flags: SYN)
Oct 31 15:56:05.220: 10.0.3.103:48278 <- default/web1-696bfbbbc4-jnxbc:80 to-stack FORWARDED (TCP Flags: SYN, ACK)
Oct 31 15:56:05.220: 10.0.3.103:48278 -> default/web1-696bfbbbc4-jnxbc:80 to-endpoint FORWARDED (TCP Flags: ACK)
Oct 31 15:56:05.221: 10.0.3.103:48278 -> default/web1-696bfbbbc4-jnxbc:80 to-endpoint FORWARDED (TCP Flags: ACK, PSH)
Oct 31 15:56:05.221: 10.0.3.103:48278 <- default/web1-696bfbbbc4-jnxbc:80 to-stack FORWARDED (TCP Flags: ACK, PSH)
Oct 31 15:56:05.222: 10.0.3.103:48278 -> default/web1-696bfbbbc4-jnxbc:80 to-endpoint FORWARDED (TCP Flags: ACK, FIN)
Oct 31 15:56:05.222: 10.0.3.103:48278 <- default/web1-696bfbbbc4-jnxbc:80 to-stack FORWARDED (TCP Flags: ACK, FIN)
Oct 31 15:56:05.222: 10.0.3.103:48278 -> default/web1-696bfbbbc4-jnxbc:80 to-endpoint FORWARDED (TCP Flags: ACK)
Oct 31 15:56:12.739: 10.0.4.105:36956 -> default/web1-696bfbbbc4-jnxbc:80 to-endpoint FORWARDED (TCP Flags: SYN)
Oct 31 15:56:12.739: default/web1-696bfbbbc4-jnxbc:80 <> 10.0.4.105:36956 to-overlay FORWARDED (TCP Flags: SYN, ACK)
Oct 31 15:56:12.742: 10.0.4.105:36956 -> default/web1-696bfbbbc4-jnxbc:80 to-endpoint FORWARDED (TCP Flags: ACK)
Oct 31 15:56:12.742: 10.0.4.105:36956 -> default/web1-696bfbbbc4-jnxbc:80 to-endpoint FORWARDED (TCP Flags: ACK, PSH)
Oct 31 15:56:12.745: default/web1-696bfbbbc4-jnxbc:80 <> 10.0.4.105:36956 to-overlay FORWARDED (TCP Flags: ACK, PSH)
Oct 31 15:56:12.749: 10.0.4.105:36956 -> default/web1-696bfbbbc4-jnxbc:80 to-endpoint FORWARDED (TCP Flags: ACK, FIN)
Oct 31 15:56:12.749: default/web1-696bfbbbc4-jnxbc:80 <> 10.0.4.105:36956 to-overlay FORWARDED (TCP Flags: ACK, FIN)

Wat we hier zien is dat onze router het verkeer voor ip-adres 172.16.10.0 naar k8s-worker1 stuurt, maar die worker host onze web1-container niet, dus stuurt hij het verkeer door naar k8s-worker2 die het verkeer afhandelt. Alle doorstuurlogica wordt afgehandeld met: eBPF – een klein BPF-programma dat aan de interface is gekoppeld, stuurt het verkeer en de routes indien nodig naar een andere werknemer. Dat is ook de reden dat het draaien van tcpdump op k8s-worker1, waar de pakketten aanvankelijk worden ontvangen, geen verkeer laat zien. Het is al omgeleid naar k8s-worker2 voordat het in de ip-stack van k8s-worker1 kon belanden.

Cilium.io heeft veel informatie over eBPF en de interne onderdelen. Als je nog nooit van eBPF hebt gehoord en je houdt van Linux en/of netwerken, doe jezelf dan een plezier en leer in ieder geval de basis. Naar mijn bescheiden mening zal eBPF netwerken in Linux in de nabije toekomst drastisch veranderen en vooral voor cloud-native omgevingen!

Hubble Web GUI

Met een werkende BGP-configuratie is het vrij eenvoudig om de Hubble Web GUI ook voor de buitenwereld beschikbaar te maken.

kubectl -n kube-system expose deployment hubble-ui --type=LoadBalancer --port=80 --target-port=8081 --name hubble-ui-lb

Laatste woorden

Door de geïntegreerde MetalLB, het is heel eenvoudig in te stellen Cilium met BGP. Bovendien heb jr geen dure netwerkhardware nodig. Cilium/BGP, in combinatie met het uitschakelen van kube-proxy, verlaagt de latentie naar jouw cloudgebaseerde services en geeft een duidelijk beeld van wat wordt blootgesteld aan de buitenwereld door alleen de ip-adressen van LoadBalancers aan te kondigen. Hoewel een Ingress-controller niet vereist is bij deze opstelling, zou ik er toch een aanbevelen voor de meeste HTTP-services. Ze hebben grote waarde op protocolniveau voor het herschrijven van URL’s of snelheidsbeperkende verzoeken. Voorbeelden zijn NGINX of Traefik