Questo documento illustra come lavorare con bilanciatori del carico di rete passthrough esterni mediante il User Datagram Protocol UDP. Il documento è destinato all'app sviluppatori, operatori di app e amministratori di rete.
Informazioni su UDP
Il protocollo UDP è comunemente utilizzato nelle app. Il protocollo, descritto in RFC-768 implementa un servizio di pacchetti di datagrammi stateless e inaffidabile. Ad esempio, l'interfaccia utente di Google QUIC migliora l'esperienza utente utilizzando UDP per accelerare i dati basati su stream app.
La parte stateless di UDP significa che il livello di trasporto non mantiene
stato. Di conseguenza, ogni pacchetto in una "connessione" UDP è indipendente. Infatti,
non c'è una connessione reale in UDP. Al contrario, i partecipanti di solito usano un
a 2 tuple (ip:port
) o a 4 tuple (src-ip:src-port
, dest-ip:dest-port
) per
riconoscersi a vicenda.
Analogamente alle app basate su TCP, anche le app basate su UDP possono trarre vantaggio da un bilanciatore del carico, Per questo motivo, negli scenari UDP vengono utilizzati i bilanciatori del carico di rete passthrough esterni.
Bilanciatore del carico di rete passthrough esterno
I bilanciatori del carico di rete passthrough esterni sono bilanciatori del carico passthrough, loro elaborano i pacchetti in entrata e li consegnano ai server di backend con i pacchetti intatti. I server di backend inviano quindi i pacchetti restituiti direttamente clienti. Questa tecnica è chiamata Direct Server Return (DSR). In ogni piattaforma Linux macchina virtuale (VM) in esecuzione su Compute Engine, che è il backend di un Bilanciatore del carico di rete passthrough esterno di Google Cloud, una voce nelle route della tabella di routing locale traffico destinato all'indirizzo IP del bilanciatore del carico verso la rete NIC. Il seguente esempio illustra questa tecnica:
root@backend-server:~# ip ro ls table local
local 10.128.0.2 dev eth0 proto kernel scope host src 10.128.0.2
broadcast 10.128.0.2 dev eth0 proto kernel scope link src 10.128.0.2
local 198.51.100.2 dev eth0 proto 66 scope host
broadcast 127.0.0.0 dev lo proto kernel scope link src 127.0.0.1
local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1
local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1
broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1
Nell'esempio precedente, 198.51.100.2
è l'indirizzo IP del bilanciatore del carico. La
google-network-daemon.service
agente è responsabile dell'aggiunta di questa voce.
Tuttavia, come mostra l'esempio seguente, la VM non ha una
proprietaria dell'indirizzo IP del bilanciatore del carico:
root@backend-server:~# ip ad ls
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1460 qdisc mq state UP group default qlen 1000
link/ether 42:01:0a:80:00:02 brd ff:ff:ff:ff:ff:ff
inet 10.128.0.2/32 brd 10.128.0.2 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::4001:aff:fe80:2/64 scope link
valid_lft forever preferred_lft forever
Il bilanciatore del carico di rete passthrough esterno trasmette i pacchetti in entrata, con la destinazione non modificato, al server di backend. Le route di ingresso della tabella di routing locale il pacchetto al processo corretto dell'app e i pacchetti di risposta dall'app vengono inviati direttamente al cliente.
Il seguente diagramma mostra come funzionano i bilanciatori del carico di rete passthrough esterni. La vengono elaborati da un bilanciatore del carico chiamato Maglev che distribuisce i pacchetti ai server di backend. I pacchetti in uscita vengono quindi inviati direttamente ai clienti tramite DSR.
Un problema con i pacchetti restituiti UDP
Quando lavori con DSR, c'è una leggera differenza tra il modo in cui Linux il kernel tratta le connessioni TCP e UDP. Poiché TCP è un protocollo stateful, del kernel abbia tutte le informazioni necessarie sulla connessione TCP, tra cui indirizzo client, porta client, indirizzo server e porta server. Queste informazioni viene registrato nella struttura dati socket che rappresenta la connessione. Pertanto, per ogni pacchetto che restituisce una connessione TCP sia impostato correttamente l'indirizzo di origine all'indirizzo del server. Per un bilanciatore del carico, l'indirizzo è quello del bilanciatore Indirizzo IP.
Ricorda che UDP, però, è stateless, per cui gli oggetti socket che vengono creati nel processo dell'app per le connessioni UDP non includono le informazioni di connessione. Il kernel non dispone delle informazioni sull'indirizzo di origine di un e non conosce la relazione con un pacchetto ricevuto in precedenza. Per all'indirizzo di origine del pacchetto, il kernel può inserire solo l'indirizzo a cui va il pacchetto UDP di ritorno. Oppure se l'app era in precedenza associata dal socket a un certo indirizzo, il kernel usa quell'indirizzo come origine .
Il seguente codice mostra un semplice programma echo:
#!/usr/bin/python3
import socket,struct
def loop_on_socket(s):
while True:
d, addr = s.recvfrom(1500)
print(d, addr)
s.sendto("ECHO: ".encode('utf8')+d, addr)
if __name__ == "__main__":
HOST, PORT = "0.0.0.0", 60002
sock = socket.socket(type=socket.SocketKind.SOCK_DGRAM)
sock.bind((HOST, PORT))
loop_on_socket(sock)
Di seguito è riportato l'output tcpdump
durante una conversazione UDP:
14:50:04.758029 IP 203.0.113.2.40695 > 198.51.100.2.60002: UDP, length 3 14:50:04.758396 IP 10.128.0.2.60002 > 203.0.113.2.40695: UDP, length 2T
198.51.100.2
è l'indirizzo IP del bilanciatore del carico, mentre 203.0.113.2
è l'indirizzo
all'indirizzo IP del client.
Quando i pacchetti lasciano la VM, viene inviato un altro dispositivo NAT, ovvero Gateway: nella rete Google Cloud l'indirizzo di origine viene convertito all'indirizzo esterno. Il gateway non sa quale indirizzo esterno dovrebbe essere , quindi può essere utilizzato solo l'indirizzo esterno della VM (non quello del bilanciatore del carico).
Dal lato client, se controlli l'output da tcpdump
, i pacchetti
il server avrà questo aspetto:
23:05:37.072787 IP 203.0.113.2.40695 > 198.51.100.2.60002: UDP, length 5 23:05:37.344148 IP 198.51.100.3.60002 > 203.0.113.2.40695: UDP, length 4
198.51.100.3
è l'indirizzo IP esterno della VM.
Dal punto di vista del client, i pacchetti UDP non provengono da un indirizzo a cui il client li ha indirizzati. Questo causa problemi: il kernel elimina questi e se il client si trova dietro un dispositivo NAT, lo stesso vale per il dispositivo NAT. Come l'app client non riceve risposta dal server. Il seguente diagramma mostra questo processo in cui il client rifiuta i pacchetti restituiti a causa dell'indirizzo errate corrispondenze.
Risoluzione del problema UDP
Per risolvere il problema senza risposta, devi riscrivere l'indirizzo di origine del
di pacchetti in uscita all'indirizzo IP del bilanciatore del carico sul server che ospita
l'app. Di seguito sono riportate diverse opzioni che puoi utilizzare a questo scopo.
riscrittura dell'intestazione. La prima soluzione utilizza un approccio basato su Linux con iptables
;
mentre le altre adottano approcci basati sulle app.
Il seguente diagramma mostra l'idea alla base di queste opzioni: riscrivi il l'indirizzo IP di origine dei pacchetti restituiti in modo che corrispondano all'indirizzo Indirizzo IP.
Utilizza il criterio NAT nel server di backend
La soluzione del criterio NAT è utilizzare il comando Linux iptables
per riscrivere il
dall'indirizzo IP del bilanciatore del carico all'indirizzo IP della VM.
Nell'esempio seguente, aggiungi una regola DNAT iptables
per modificare
di destinazione dei pacchetti in entrata:
iptables -t nat -A POSTROUTING -j RETURN -d 10.128.0.2 -p udp --dport 60002
iptables -t nat -A PREROUTING -j DNAT --to-destination 10.128.0.2 -d 198.51.100.2 -p udp --dport 60002
Questo comando aggiunge due regole alla tabella NAT del sistema iptables
. La
la prima regola ignora tutti i pacchetti in arrivo che hanno come target l'indirizzo eth0
locale.
Di conseguenza, il traffico che non proviene dal bilanciatore del carico non è interessato.
La seconda regola modifica l'indirizzo IP di destinazione dei pacchetti in entrata
Indirizzo IP interno della VM. Le regole del DNAT sono stateful, il che significa che
il kernel tiene traccia delle connessioni e riscrive i pacchetti restituiti indirizzo di origine
automaticamente.
Vantaggi | Svantaggi |
---|---|
Il kernel traduce l'indirizzo, senza bisogno di apportare modifiche in app. | Per il NAT viene utilizzata una CPU aggiuntiva. E poiché DNAT è stateful, potrebbe anche essere elevato. |
Supporta più bilanciatori del carico. |
Usa nftables
per gestire in modo stateless i campi dell'intestazione IP
Nella soluzione nftables
, utilizzerai il comando nftables
per gestire l'origine
nell'intestazione IP dei pacchetti in uscita. Questa gestione è stateless, quindi
consuma meno risorse rispetto a DNAT. Per usare nftables
, è necessario un computer Linux
versione kernel successiva alla 4.10.
Puoi utilizzare i seguenti comandi:
nft add table raw
nft add chain raw postrouting {type filter hook postrouting priority 300)
nft add rule raw postrouting ip saddr 10.128.0.2 udp sport 60002 ip saddr set 198.51.100.2
Vantaggi | Svantaggi |
---|---|
Il kernel traduce l'indirizzo, senza bisogno di apportare modifiche in app. | Non supporta più bilanciatori del carico. |
Il processo di traduzione degli indirizzi è stateless, è molto più basso. | Per il NAT viene utilizzata una CPU aggiuntiva. |
nftables sono disponibili solo per i kernel Linux più recenti
e versioni successive. Alcune distribuzioni, come Centos 7.x, non possono utilizzare
nftables .
|
Consenti all'app di associarsi in modo esplicito all'indirizzo IP del bilanciatore del carico
Nella soluzione di associazione, modifica la tua app in modo che si colleghi esplicitamente alla
l'indirizzo IP del bilanciatore
del carico. Per un socket UDP, l'operazione bind
consente
kernel sapere quale indirizzo utilizzare come indirizzo di origine per l'invio di pacchetti UDP
che utilizzano quel socket.
L'esempio seguente mostra come eseguire l'associazione a un indirizzo specifico in Python:
#!/usr/bin/python3
import socket
def loop_on_socket(s):
while True:
d, addr = s.recvfrom(1500)
print(d, addr)
s.sendto("ECHO: ".encode('utf8')+d, addr)
if __name__ == "__main__":
# Instead of setting HOST to "0.0.0.0",
# we set HOST to the Load Balancer IP
HOST, PORT = "198.51.100.2", 60002
sock = socket.socket(type=socket.SocketKind.SOCK_DGRAM)
sock.bind((HOST, PORT))
loop_on_socket(sock)
# 198.51.100.2 is the load balancer's IP address
# You can also use the DNS name of the load balancer's IP address
Il codice precedente è un server UDP; riecheggia i byte ricevuti, con una
precedente "ECHO: "
. Presta attenzione alle righe 12 e 13, dove il server
è associato all'indirizzo 198.51.100.2
, che corrisponde all'indirizzo IP del bilanciatore del carico.
Vantaggi | Svantaggi |
---|---|
È possibile farlo semplicemente modificando il codice dell'app. | Non supporta più bilanciatori del carico. |
Utilizza recvmsg
/sendmsg
anziché recvfrom
/sendto
per specificare l'indirizzo
In questa soluzione, utilizzi recvmsg
/sendmsg
chiamate anziché
recvfrom
/sendto
chiamate. Rispetto a recvfrom
/sendto
chiamate, il
Le chiamate recvmsg
/sendmsg
possono gestire i messaggi di controllo accessori insieme
e i dati del payload. Questi messaggi di controllo accessori includono l'origine o la destinazione
l'indirizzo IP dei pacchetti. Questa soluzione consente di recuperare gli indirizzi di destinazione
e poiché questi indirizzi sono veri e propri bilanciatori del carico
di destinazione, puoi utilizzarli come indirizzi di origine quando invii le risposte.
Il programma di esempio seguente illustra questa soluzione:
#!/usr/bin/python3
import socket,struct
def loop_on_socket(s):
while True:
d, ctl, flg, addr = s.recvmsg(1500, 1024)
# ctl contains the destination address information
s.sendmsg(["ECHO: ".encode("utf8"),d], ctl, 0, addr)
if __name__ == "__main__":
HOST, PORT = "0.0.0.0", 60002
s = socket.socket(type=socket.SocketKind.SOCK_DGRAM)
s.setsockopt(0, # level is 0 (IPPROTO_IP)
8, # optname is 8 (IP_PKTINFO)
1)
s.bind((HOST, PORT))
loop_on_socket(s)
Questo programma illustra come utilizzare le chiamate recvmsg
/sendmsg
. Per
di recuperare le informazioni sugli indirizzi dai pacchetti, devi usare la chiamata setsockopt
imposta l'opzione IP_PKTINFO
.
Vantaggi | Svantaggi |
---|---|
Funziona anche in presenza di più bilanciatori del carico, ad esempio quando sono configurati bilanciatori del carico interni ed esterni lo stesso backend. | Richiede modifiche complesse all'app. In alcuni casi, potrebbe non essere possibile. |
Passaggi successivi
- Scopri come configurare un bilanciatore del carico di rete passthrough esterno e distribuire il traffico nel Configura un bilanciatore del carico di rete passthrough esterno.
- Scopri di più su bilanciatori del carico di rete passthrough esterni.
- Scopri di più sulle Maglev dietro bilanciatori del carico di rete passthrough esterni.