Perfezionamento del routing per MetalLB in modalità L2

Perfezionamento del routing per MetalLB in modalità L2
Non molto tempo fa mi sono trovato di fronte al compito davvero insolito di impostare il routing per MetalLB. Andrebbe tutto bene, perché... Solitamente MetalLB non richiede alcuna azione aggiuntiva, ma nel nostro caso abbiamo un cluster abbastanza grande con una configurazione di rete molto semplice.

In questo articolo ti spiegherò come configurare il routing basato sull'origine e sulle policy per la rete esterna del tuo cluster.

Non entrerò nei dettagli sull'installazione e configurazione di MetalLB, poiché presumo che tu abbia già una certa esperienza. Suggerisco di andare direttamente al punto, ovvero impostare il routing. Quindi abbiamo quattro casi:

Caso 1: quando non è richiesta alcuna configurazione

Consideriamo un caso semplice.

Perfezionamento del routing per MetalLB in modalità L2

Non è necessaria un'ulteriore configurazione del routing quando gli indirizzi emessi da MetalLB si trovano nella stessa sottorete degli indirizzi dei tuoi nodi.

Ad esempio, hai una sottorete 192.168.1.0/24, ha un router 192.168.1.1e i tuoi nodi ricevono indirizzi: 192.168.1.10-30, quindi per MetalLB è possibile regolare l'intervallo 192.168.1.100-120 ed essere sicuri che funzioneranno senza alcuna configurazione aggiuntiva.

Perché? Poiché i tuoi nodi hanno già percorsi configurati:

# 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 intervallo li riutilizzeranno senza alcuna azione aggiuntiva.

Caso 2: quando è necessaria un'ulteriore personalizzazione

Perfezionamento del routing per MetalLB in modalità L2

Dovresti configurare percorsi aggiuntivi ogni volta che i tuoi nodi non hanno un indirizzo IP configurato o un percorso verso la sottorete per la quale MetalLB emette indirizzi.

Ti spiegherò un po' più nel dettaglio. Ogni volta che MetalLB emette un indirizzo, può essere paragonato a un semplice assegnazione come:

ip addr add 10.9.8.7/32 dev lo

Presta attenzione a:

  • a) L'indirizzo viene assegnato con un prefisso /32 ovvero, un percorso non verrà aggiunto automaticamente alla sottorete (è solo un indirizzo)
  • b) L'indirizzo è collegato a qualsiasi interfaccia del nodo (ad esempio loopback). Vale la pena menzionare qui le caratteristiche dello stack di rete Linux. Indipendentemente dall'interfaccia a cui aggiungi l'indirizzo, il kernel elaborerà sempre le richieste arp e invierà risposte arp a ognuna di esse, questo comportamento è considerato corretto e, inoltre, è abbastanza ampiamente utilizzato in un ambiente così dinamico come Kubernetes.

Questo comportamento può essere personalizzato, ad esempio abilitando 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 prevedi di utilizzare MetalLB e il tuo proxy kube è in esecuzione in modalità IPVS.

Tuttavia, MetalLB non utilizza il kernel per elaborare le richieste arp, ma lo fa da solo nello spazio utente, quindi questa opzione non influirà sul funzionamento di MetalLB.

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

ip route add 10.9.8.0/24 dev eth1

Caso 3: quando è necessario il routing basato sull'origine

Sarà necessario configurare il routing basato sull'origine quando si ricevono pacchetti attraverso un gateway separato, non quello configurato per impostazione predefinita, pertanto anche i pacchetti di risposta dovrebbero passare attraverso lo stesso gateway.

Ad esempio, hai la stessa sottorete 192.168.1.0/24 dedicato ai tuoi nodi, ma vuoi rilasciare indirizzi esterni utilizzando MetalLB. Supponiamo che tu abbia più indirizzi da una sottorete 1.2.3.0/24 situati nella VLAN 100 e desideri utilizzarli per accedere ai servizi Kubernetes esternamente.

Perfezionamento del routing per MetalLB in modalità L2

Quando si contatta 1.2.3.4 effettuerai richieste da una sottorete diversa da 1.2.3.0/24 e aspetta una risposta. Il nodo che attualmente è il master per l'indirizzo emesso da MetalLB 1.2.3.4, riceverà il pacchetto dal router 1.2.3.1, ma la risposta per lui deve necessariamente passare per la stessa strada, attraverso 1.2.3.1.

Poiché il nostro nodo ha già un gateway predefinito configurato 192.168.1.1, quindi per impostazione predefinita la risposta andrà a lui e non a 1.2.3.1, attraverso il quale abbiamo ricevuto il pacco.

Come affrontare questa situazione?

In questo caso, devi preparare tutti i tuoi nodi in modo tale che siano pronti a servire indirizzi esterni senza ulteriore configurazione. 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 poi aggiungi percorsi:

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

Tieni presente che aggiungiamo le rotte a una tabella di routing separata 100 conterrà solo due percorsi necessari per inviare un pacchetto di risposta attraverso il gateway 1.2.3.1, situato 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 è in 1.2.3.0/24, allora è necessario utilizzare la tabella di routing 100. In esso abbiamo già descritto il percorso che lo farà attraversare 1.2.3.1

Caso 4: quando è necessario il routing basato su policy

La topologia di rete è la stessa dell'esempio precedente, ma supponiamo che tu voglia poter accedere anche agli indirizzi del pool esterno 1.2.3.0/24 dai tuoi pod:

Perfezionamento del routing per MetalLB in modalità L2

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

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

Per una migliore comprensione del processo, ecco un diagramma a blocchi di netfilter:
Perfezionamento del routing per MetalLB in modalità L2

Per prima cosa, come nell'esempio precedente, creiamo 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 a 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 all'interfaccia eth0.100, contrassegnando tutti i pacchetti con il tag 0x100, anche le risposte all'interno della stessa connessione verranno contrassegnate con lo stesso tag.

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 un indirizzo di origine 1.2.3.0/24 e etichetta 0x100 deve essere instradato utilizzando una tabella 100.

Pertanto, altri pacchetti ricevuti su un'altra interfaccia non sono soggetti a questa regola, che consentirà loro di essere instradati utilizzando gli strumenti Kubernetes standard.

C'è ancora una cosa, in Linux c'è un cosiddetto filtro del percorso inverso, che rovina tutto; fa un semplice controllo: per tutti i pacchetti in arrivo, cambia l'indirizzo di origine del pacchetto con l'indirizzo del mittente e controlla se il pacchetto potrà uscire attraverso la stessa interfaccia su cui è stato ricevuto, in caso contrario lo filtrerà.

Il problema è che nel nostro caso non funzionerà correttamente, ma possiamo disabilitarlo:

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

Tieni presente che il primo comando controlla il comportamento globale di rp_filter; se non è disabilitato, il secondo comando non avrà alcun effetto. Tuttavia, le restanti interfacce rimarranno con rp_filter abilitato.

Per non limitare completamente il funzionamento del filtro, possiamo utilizzare l'implementazione rp_filter per netfilter. Usando rpfilter come modulo iptables, puoi configurare 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

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

Fonte: habr.com

Aggiungi un commento