במאמר הזה מוסבר איך לעבוד עם מאזני עומסים חיצוניים ברשת להעברת סיגנל ללא שינוי באמצעות פרוטוקול User Datagram (UDP). המסמך הזה מיועד למפתחי אפליקציות, למפעילי אפליקציות ולאדמינים של רשתות.
מידע על UDP
פרוטוקול UDP נמצא בשימוש נפוץ באפליקציות. הפרוטוקול, שמתואר ב-RFC-768, מיישם שירות חבילות של דאטה-גרם לא אמין ללא שמירת מצב. לדוגמה, פרוטוקול QUIC של Google משפר את חוויית המשתמש באמצעות UDP כדי להאיץ אפליקציות מבוססות-סטרימינג.
החלק חסר המצב של פרוטוקול UDP אומר ששכבת התעבורה לא שומרת מצב. לכן, כל מנה ב'חיבור' UDP היא עצמאית. למעשה, אין חיבור אמיתי ב-UDP. במקום זאת, המשתתפים בשיחה משתמשים בדרך כלל ב-2-tuple (ip:port) או ב-4-tuple (src-ip:src-port, dest-ip:dest-port) כדי לזהות אחד את השני.
בדומה לאפליקציות מבוססות TCP, גם אפליקציות מבוססות UDP יכולות להפיק תועלת ממאזן עומסים, ולכן משתמשים במאזנים חיצוניים של עומסי רשת להעברת סיגנל ללא שינוי בתרחישי UDP.
מאזן עומסי רשת חיצוני להעברת סיגנל ללא שינוי
מאזני עומסים חיצוניים להעברת סיגנל ללא שינוי הם מאזני עומסים להעברת סיגנל ללא שינוי. הם מעבדים חבילות נתונים נכנסות ומעבירים אותן לשרתים עורפיים כשהחבילות שלמות. לאחר מכן, שרתי הבק-אנד שולחים את החבילות החוזרות ישירות ללקוחות. הטכניקה הזו נקראת החזרת שרת ישירה (DSR). בכל מכונה וירטואלית (VM) של Linux שפועלת ב-Compute Engine ומשמשת כבק-אנד שלCloud de Confiance by S3NS מאזן עומסי רשת חיצוני להעברת סיגנל ללא שינוי, רשומה בטבלת הניתוב המקומית מנתבת את התעבורה שמיועדת לכתובת ה-IP של מאזן העומסים אל בקר ממשק הרשת (NIC). בדוגמה הבאה אפשר לראות איך עושים את זה:
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
בדוגמה הקודמת, 198.51.100.2 היא כתובת ה-IP של מאזן העומסים. הנציג של google-network-daemon.service אחראי להוספת הרשומה הזו.
עם זאת, כפי שרואים בדוגמה הבאה, למכונה הווירטואלית אין ממשק שבבעלותו כתובת ה-IP של איזון העומסים:
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
מאזן עומסי רשת חיצוני להעברת סיגנל ללא שינוי מעביר את החבילות הנכנסות לשרת הבק-אנד, בלי לשנות את כתובת היעד. הרשומה בטבלת הניתוב המקומית מנתבת את החבילה לתהליך האפליקציה הנכון, וחבילות התגובה מהאפליקציה נשלחות ישירות ללקוח.
הדיאגרמה הבאה מציגה את אופן הפעולה של מאזני עומסי רשת חיצוניים להעברת סיגנל ללא שינוי. החבילות הנכנסות מעובדות על ידי מאזן עומסים שנקרא Maglev, שמפיץ את החבילות לשרתי הבק-אנד. לאחר מכן, החבילות היוצאות נשלחות ישירות ללקוחות דרך DSR.
בעיה בחבילות החזרה של UDP
כשעובדים עם DSR, יש הבדל קל בין האופן שבו ליבת Linux מתייחסת לחיבורי TCP ולחיבורי UDP. מכיוון ש-TCP הוא פרוטוקול מבוסס-מצב, לליבת המערכת יש את כל המידע שהיא צריכה על חיבור ה-TCP, כולל כתובת הלקוח, יציאת הלקוח, כתובת השרת ויציאת השרת. המידע הזה מתועד במבנה הנתונים של השקע שמייצג את החיבור. לכן, בכל חבילה שחוזרת מחיבור TCP, כתובת המקור מוגדרת בצורה נכונה לכתובת השרת. במקרה של מאזן עומסים, הכתובת הזו היא כתובת ה-IP של מאזן העומסים.
חשוב לזכור ש-UDP הוא פרוטוקול חסר מצב, ולכן לאובייקטים של שקע שנוצרים בתהליך של האפליקציה לחיבורי UDP אין את פרטי החיבור. לליבה אין מידע על כתובת המקור של מנות יוצאות, והיא לא יודעת מה הקשר למנות שהתקבלו קודם. במקרה של כתובת המקור של חבילת הנתונים, ליבת המערכת יכולה למלא רק את הכתובת של הממשק שאליו מגיעה חבילת ה-UDP החוזרת. או אם האפליקציה קשרה בעבר את השקע לכתובת מסוימת, ליבת מערכת ההפעלה משתמשת בכתובת הזו ככתובת המקור.
הקוד הבא מציג תוכנית פשוטה של הד:
#!/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)
בהמשך מוצג הפלט של tcpdump במהלך שיחת 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 היא כתובת ה-IP של מאזן העומסים, ו-203.0.113.2 היא כתובת ה-IP של הלקוח.
אחרי שהמנות יוצאות מהמכונה הווירטואלית, מכשיר NAT אחר – שער של Compute Engine – ברשת Cloud de Confiance מתרגם את כתובת המקור לכתובת החיצונית. השער לא יודע באיזו כתובת חיצונית צריך להשתמש, ולכן אפשר להשתמש רק בכתובת החיצונית של המכונה הווירטואלית (ולא בכתובת של מאזן העומסים).
בצד הלקוח, אם בודקים את הפלט מ-tcpdump, החבילות מהשרת נראות כך:
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 היא כתובת ה-IP החיצונית של המכונה הווירטואלית.
מנקודת המבט של הלקוח, חבילות ה-UDP לא מגיעות מכתובת שהלקוח שלח אותן אליה. הדבר גורם לבעיות: ליבת המערכת משמיטה את המנות האלה, ואם הלקוח נמצא מאחורי מכשיר NAT, גם מכשיר ה-NAT משמיט אותן. כתוצאה מכך, אפליקציית הלקוח לא מקבלת תגובה מהשרת. בתרשים הבא מוצג התהליך שבו הלקוח דוחה חבילות חוזרות בגלל חוסר התאמה בכתובות.
פתרון הבעיה ב-UDP
כדי לפתור את הבעיה של חוסר תגובה, צריך לשכתב את כתובת המקור של מנות יוצאות לכתובת ה-IP של מאזן העומסים בשרת שמארח את האפליקציה. בהמשך מפורטות כמה אפשרויות שבהן אפשר להשתמש כדי לבצע את השכתוב הזה של הכותרת. הפתרון הראשון מבוסס על Linux עם iptables;
הפתרונות האחרים מבוססים על אפליקציות.
בתרשים הבא מוצג הרעיון המרכזי של האפשרויות האלה: לשכתב את כתובת ה-IP של המקור של המנות החוזרות כדי שתתאים לכתובת ה-IP של מאזן העומסים.
שימוש במדיניות NAT בשרת הקצה העורפי
הפתרון למדיניות NAT הוא שימוש בפקודה iptables של Linux כדי לשכתב את כתובת היעד מכתובת ה-IP של מאזן העומסים לכתובת ה-IP של המכונה הווירטואלית.
בדוגמה הבאה, מוסיפים כלל iptables DNAT כדי לשנות את כתובת היעד של החבילות הנכנסות:
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
הפקודה הזו מוסיפה שני כללים לטבלת ה-NAT של מערכת iptables. הכלל הראשון עוקף את כל החבילות הנכנסות שמטרגטות את כתובת eth0 המקומית.
כתוצאה מכך, התנועה שלא מגיעה ממאזן העומסים לא מושפעת.
הכלל השני משנה את כתובת ה-IP של היעד של חבילות נכנסות לכתובת ה-IP הפנימית של המכונה הווירטואלית. כללי ה-DNAT הם stateful, כלומר ליבת המערכת עוקבת אחרי החיבורים וכותבת מחדש את כתובת המקור של המנות החוזרות באופן אוטומטי.
| יתרונות | חסרונות |
|---|---|
| הליבה מתרגמת את הכתובת, ולא נדרש שינוי באפליקציות. | ה-NAT צורך מעבד נוסף. בנוסף, מכיוון ש-DNAT הוא stateful, יכול להיות שצריכת הזיכרון תהיה גבוהה. |
| תומך בכמה מאזני עומסים. |
שימוש ב-nftables כדי לשנות את שדות כותרת ה-IP ללא שמירת מצב
בפתרון nftables, משתמשים בפקודה nftables כדי לשנות את כתובת המקור בכותרת ה-IP של מנות יוצאות. השינוי הזה בלי שמירת מצב, ולכן הוא צורך פחות משאבים מאשר שימוש ב-DNAT. כדי להשתמש ב-nftables, צריך גרסת ליבת Linux מעל 4.10.
משתמשים בפקודות הבאות:
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
| יתרונות | חסרונות |
|---|---|
| הליבה מתרגמת את הכתובת, ולא נדרש שינוי באפליקציות. | לא תומך בכמה מאזני עומסים. |
| תהליך התרגום של הכתובות הוא חסר מצב (stateless), ולכן צריכת המשאבים נמוכה בהרבה. | נעשה שימוש נוסף במעבד (CPU) כדי לבצע את ה-NAT. |
nftables זמינים רק בגרסאות חדשות יותר של ליבת Linux. במערכות הפעלה מסוימות, כמו Centos 7.x, אי אפשר להשתמש ב-nftables.
|
לאפשר לאפליקציה לבצע קישור מפורש לכתובת ה-IP של מאזן העומסים
בפתרון הקישור, משנים את האפליקציה כך שהיא תקושר באופן מפורש לכתובת ה-IP של מאזן העומסים. במקרה של שקע UDP, הפעולה bind מאפשרת לליבת מערכת ההפעלה לדעת באיזו כתובת להשתמש ככתובת המקור כששולחים מנות UDP שמשתמשות בשקע הזה.
בדוגמה הבאה אפשר לראות איך מתבצעת התקשרות לכתובת ספציפית ב-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
הקוד שלמעלה הוא שרת UDP. הוא מחזיר את הבייטים שהתקבלו, עם "ECHO: " לפני. שימו לב לשורות 12 ו-13, שבהן השרת קשור לכתובת 198.51.100.2, שהיא כתובת ה-IP של מאזן העומסים.
| יתרונות | חסרונות |
|---|---|
| אפשר להשיג את זה באמצעות שינוי פשוט בקוד של האפליקציה. | לא תומך בכמה מאזני עומסים. |
כדי לציין את הכתובת, משתמשים ב-recvmsg/sendmsg במקום ב-recvfrom/sendto
בפתרון הזה, משתמשים בשיחות recvmsg/sendmsg במקום בשיחות recvfrom/sendto. בהשוואה לקריאות recvfrom/sendto, קריאות recvmsg/sendmsg יכולות לטפל בהודעות בקרה משניות יחד עם נתוני המטען הייעודי. הודעות הבקרה הנלוות האלה כוללות את כתובת המקור או היעד של החבילות. הפתרון הזה מאפשר לכם לאחזר כתובות יעד מחבילות נכנסות, ומכיוון שהכתובות האלה הן כתובות אמיתיות של מאזן העומסים, אתם יכולים להשתמש בהן ככתובות מקור כשאתם שולחים תשובות.
הדוגמה הבאה מציגה את הפתרון הזה:
#!/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)
התוכנית הזו מדגימה איך להשתמש בשיחות recvmsg/sendmsg. כדי לאחזר מידע על כתובות מחבילות, צריך להשתמש בקריאה setsockopt כדי להגדיר את האפשרות IP_PKTINFO.
| יתרונות | חסרונות |
|---|---|
| האפשרות הזו פועלת גם אם יש כמה מאזני עומסים – למשל, אם מוגדרים מאזני עומסים פנימיים וחיצוניים שמפנים לאותו בק-אנד. | האפשרות הזו מחייבת לבצע שינויים מורכבים באפליקציה. במקרים מסוימים, יכול להיות שאי אפשר יהיה לבצע את השינויים האלה. |
המאמרים הבאים
- במאמר הגדרה של מאזן עומסי רשת חיצוני להעברת סיגנל ללא שינוי מוסבר איך להגדיר מאזן עומסי רשת חיצוני להעברת סיגנל ללא שינוי ולחלק את התעבורה.
- מידע נוסף על מאזני עומסי רשת חיצוניים להעברת סיגנל ללא שינוי
- מידע נוסף על טכניקת Maglev שמאחורי מאזני עומסים חיצוניים ברשת להעברת סיגנל ללא שינוי