Ottimizzazione della routing per MetalLB in modalità L2

Ottimizzazione della routing per MetalLB in modalità L2
Non molto tempo fa mi sono trovato ad affrontare un compito piuttosto insolito per la configurazione della routing per MetalLB. Di per sé nulla di strano, poiché di solito per MetalLB non sono necessarie ulteriori azioni, ma nel nostro caso abbiamo un cluster piuttosto grande con una configurazione di rete piuttosto semplice.

In questo articolo spiegherò come configurare il routing basato su sorgente e quello basato su policy per la rete esterna del tuo cluster.

Non mi soffermerò a fondo sull'installazione e la configurazione di MetalLB, poiché presumo tu abbia già una certa esperienza. Propongo di passare direttamente al sodo, ovvero alla configurazione del routing. Dunque abbiamo quattro casi:

Caso 1: Quando la configurazione non è necessaria

Analizziamo il caso semplice.

Ottimizzazione della routing per MetalLB in modalità L2

Non è necessaria alcuna configurazione aggiuntiva del routing quando gli indirizzi forniti da MetalLB sono nella stessa subnet degli indirizzi dei tuoi nodi.

Ad esempio hai una subnet 192.168.1.0/24, in essa c'è un router 192.168.1.1, e i tuoi nodi ricevono indirizzi: 192.168.1.10-30, allora per MetalLB puoi configurare il range 192.168.1.100-120 ed essere sicuro che funzioneranno senza alcuna configurazione aggiuntiva.

Perché è così? Perché i tuoi nodi hanno già rotte configurate:

# ip route
default via 192.168.1.1 dev eth0 onlink 
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.10

E gli indirizzi dello stesso range riutilizzeranno queste ultime senza ulteriori sforzi.

Caso 2: Quando è necessaria una configurazione aggiuntiva

Ottimizzazione della routing per MetalLB in modalità L2

Dovrai configurare rotte aggiuntive ogni volta che i tuoi nodi non hanno configurato Indirizzi IP o una rotta alla subnet per cui MetalLB fornisce indirizzi.

Spiegherò un po' più nel dettaglio. Ogni volta che MetalLB assegna un indirizzo, questo può essere paragonato a una semplice assegnazione del tipo:

ip addr add 10.9.8.7/32 dev lo

Nota bene, su:

  • a) L'indirizzo viene assegnato con un prefisso /32 cioè la rotta alla subnet per esso non verrà automaticamente aggiunta (è solo un indirizzo)
  • b) L'indirizzo viene assegnato a qualsiasi interfaccia del nodo (ad esempio il loopback). Qui vale la pena menzionare la peculiarità dello stack di rete Linux. Non importa su quale interfaccia tu aggiunga l'indirizzo, il kernel gestirà sempre le richieste arp e invierà risposte arp su qualsiasi di esse, questo comportamento è considerato corretto e, inoltre, viene utilizzato abbastanza ampiamente in un ambiente dinamico come Kubernetes.

Questo comportamento può essere configurato, ad esempio, attivando l'arp rigoroso:

echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce

In questo caso, le risposte arp verranno inviate solo se l'interfaccia contiene esplicitamente un indirizzo IP specifico. Questa impostazione è obbligatoria se si prevede di utilizzare MetalLB e il kube-proxy è in modalità IPVS.

Tuttavia, MetalLB non utilizza il kernel per elaborare le richieste arp, ma lo fa autonomamente in user-space, quindi questa opzione non influenzerà il funzionamento di MetalLB.

Torniamo al nostro compito. Se non esiste un percorso per gli indirizzi emessi sui tuoi nodi, aggiungilo in anticipo su tutti i nodi:

ip route add 10.9.8.0/24 dev eth1

Caso 3: Quando sarà necessario il routing basato sulla sorgente

Il routing basato sulla sorgente deve essere configurato quando si ricevono pacchetti tramite un gateway separato, diverso da quello impostato come predefinito, e quindi anche i pacchetti di risposta devono passare attraverso questo stesso gateway.

Ad esempio, hai la stessa subnet 192.168.1.0/24 dedicata ai tuoi nodi, ma desideri emettere indirizzi esterni tramite MetalLB. Supponiamo che tu abbia diversi indirizzi dalla subnet 1.2.3.0/24 che si trovano nella VLAN 100 e desideri utilizzarli per accedere ai servizi Kubernetes dall'esterno.

Ottimizzazione della routing per MetalLB in modalità L2

Quando accedi a 1.2.3.4 effettuerai richieste da una subnet diversa rispetto a 1.2.3.0/24 e ti aspetterai una risposta. Il nodo che attualmente è master per l'indirizzo emesso da MetalLB 1.2.3.4, riceverà un pacchetto dal router 1.2.3.1, ma la risposta deve necessariamente passare per lo stesso percorso, attraverso 1.2.3.1.

. Poiché il nostro nodo ha già configurato un gateway predefinito 192.168.1.1, di default la risposta andrà a lui, e non a 1.2.3.1, tramite il quale abbiamo ricevuto il pacchetto.

Come affrontare questa situazione?

In questo caso, è necessario preparare tutti i tuoi nodi affinché siano pronti a gestire indirizzi esterni senza ulteriori configurazioni. Cioè, per l'esempio sopra, è necessario creare in anticipo un'interfaccia VLAN sul nodo:

ip link add link eth0 name eth0.100 type vlan id 100
ip link set eth0.100 up

E quindi aggiungere le rotte:

ip route add 1.2.3.0/24 dev eth0.100 table 100
ip route add default via 1.2.3.1 table 100

Nota che le rotte vengono aggiunte a una tabella di routing separata 100 che conterrà solo due rotte necessarie per inviare il pacchetto di risposta attraverso il gateway 1.2.3.1, che si trova dietro l'interfaccia eth0.100.

Ora dobbiamo aggiungere una semplice regola:

ip rule add from 1.2.3.0/24 lookup 100

che dice esplicitamente: se l'indirizzo di origine del pacchetto si trova in 1.2.3.0/24, utilizzare la tabella di routing 100. In essa abbiamo già descritto il percorso che lo invierà attraverso 1.2.3.1

Caso 4: Quando sarà necessario il routing basato su policy

La topologia della rete è come nell'esempio precedente, ma supponiamo che tu voglia anche avere la possibilità di contattare indirizzi esterni del pool 1.2.3.0/24 dai tuoi pod:

Ottimizzazione della routing per MetalLB in modalità L2

La particolarità è che, quando si contatta un qualsiasi indirizzo in 1.2.3.0/24, il pacchetto di risposta, entrando nel nodo e avendo l'indirizzo sorgente nell'intervallo 1.2.3.0/24 , verrà inviato obbedientemente a eth0.100, ma noi vogliamo che Kubernetes lo reindirizzi al nostro primo pod, che ha generato la richiesta originale.

Risolvendere questo problema si è rivelato complicato, ma è diventato possibile grazie al routing basato su policy:

Per comprendere meglio il processo, fornirò uno schema a blocchi di netfilter:
Ottimizzazione della routing per MetalLB in modalità L2

Iniziamo, come nell'esempio precedente, creando una tabella di routing aggiuntiva:

ip route add 1.2.3.0/24 dev eth0.100 table 100
ip route add default via 1.2.3.1 table 100

Ora aggiungiamo alcune regole in iptables:

iptables -t mangle -A PREROUTING -i eth0.100 -j CONNMARK --set-mark 0x100
iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
iptables -t mangle -A PREROUTING -m mark ! --mark 0 -j RETURN
iptables -t mangle -A POSTROUTING -j CONNMARK --save-mark

Queste regole contrassegneranno le connessioni in entrata sull'interfaccia eth0.100, contrassegnando tutti i pacchetti con il tag 0x100, questo stesso tag sarà usato anche per le risposte all'interno della stessa connessione.

Ora possiamo aggiungere una regola di routing:

ip rule add from 1.2.3.0/24 fwmark 0x100 lookup 100

Cioè, tutti i pacchetti con indirizzo sorgente 1.2.3.0/24 e tag 0x100 devono essere instradati utilizzando la tabella 100.

Così, gli altri pacchetti ricevuti su un'altra interfaccia non cadono sotto questa regola, il che consentirà loro di essere instradati utilizzando gli strumenti standard di Kubernetes.

C'è però un altro aspetto: in Linux esiste il cosiddetto reverse path filter, che ostacola tutto e compie un semplice controllo: per tutti i pacchetti in entrata cambia l'indirizzo sorgente del pacchetto con l'indirizzo del mittente e verifica se il pacchetto può uscire attraverso la stessa interfaccia su cui è stato ricevuto; in caso contrario, lo filtra.

Il problema è che nel nostro caso funzionerà in modo errato, ma possiamo disattivarlo:

echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter
echo 0 > /proc/sys/net/ipv4/conf/eth0.100/rp_filter

Nota che il primo comando controlla il comportamento globale del rp_filter; se non viene disattivato, il secondo comando non avrà alcun effetto. Tuttavia, le altre interfacce rimarranno con il rp_filter attivato.

Per non limitare completamente il funzionamento del filtro, possiamo utilizzare l'implementazione di rp_filter per netfilter. Utilizzando rpfilter come modulo di iptables, è possibile impostare regole abbastanza flessibili, ad esempio:

iptables -t raw -A PREROUTING -i eth0.100 -d 1.2.3.0/24 -j RETURN
iptables -t raw -A PREROUTING -i eth0.100 -m rpfilter --invert -j DROP

attivare rp_filter sull'interfaccia eth0.100 per tutti gli indirizzi tranne 1.2.3.0/24.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster