HAProxy บรรจุกระสุนอย่างสง่างามโดยไม่มีการสูญเสียแพ็กเก็ต


42

ฉันใช้ HAProxy load balancing server เพื่อปรับสมดุล load ให้กับหลาย ๆ Apache server ฉันต้องโหลด HAProxy อีกครั้งในเวลาที่กำหนดเพื่อเปลี่ยนอัลกอริทึมการทำโหลดบาลานซ์

ทั้งหมดนี้ทำงานได้ดียกเว้นความจริงที่ว่าฉันต้องโหลดเซิร์ฟเวอร์ใหม่โดยไม่สูญเสียแพ็กเก็ตเดียว (ในขณะที่การโหลดซ้ำทำให้ฉันประสบความสำเร็จโดยเฉลี่ย 99.76% โดยมี 1,000 คำร้องต่อวินาทีเป็นเวลา 5 วินาที) ฉันได้ทำการค้นคว้าหลายชั่วโมงเกี่ยวกับเรื่องนี้และได้พบคำสั่งต่อไปนี้สำหรับ "การรีโหลดอย่างสง่างาม" เซิร์ฟเวอร์ HAProxy:

haproxy -D -f /etc/haproxy/haproxy.cfg -p /var/run/haproxy.pid -sf $(cat /var/run/haproxy.pid)

อย่างไรก็ตามสิ่งนี้มีผลเพียงเล็กน้อยหรือไม่มีเลยเมื่อเทียบกับแบบเก่าservice haproxy reloadมันยังคงลดลงโดยเฉลี่ยอยู่ที่ 0.24%

มีวิธีการโหลดไฟล์การกำหนดค่า HAProxy อีกครั้งโดยไม่ต้องแพ็คเก็ตที่หลุดจากผู้ใช้คนเดียวหรือไม่?


6
หากคุณต้องการความเชื่อถือได้มากวิธีแก้ปัญหาที่ดีกว่าคือการใช้งาน HAproxy มากกว่าหนึ่งอินสแตนซ์ซึ่งคุณสามารถนำบริการออกจากบริการหนึ่งครั้งเพื่อโหลดซ้ำใส่กลับเข้าไปและทำซ้ำสำหรับอีกรายการหนึ่ง
yoonix

คำตอบ:


32

ตามhttps://github.com/aws/opsworks-cookbooks/pull/40และด้วยเหตุนี้http://www.mail-archive.com/haproxy@formilux.org/msg06885.htmlคุณสามารถ:

iptables -I INPUT -p tcp --dport $PORT --syn -j DROP
sleep 1
service haproxy restart
iptables -D INPUT -p tcp --dport $PORT --syn -j DROP

สิ่งนี้มีผลกระทบจากการดร็อป SYN ก่อนที่จะรีสตาร์ทเพื่อให้ไคลเอนต์จะส่ง SYN อีกครั้งนี้จนกว่าจะถึงกระบวนการใหม่



ทั้งสองคำสั่งเหล่านี้ให้ฉันนี้:iptables v1.4.14: invalid port/service
Dmitri DB

5
@DmitriDB คุณควรจะแทนที่$PORTด้วยพอร์ตที่แท้จริงhaproxyกำลังฟังอยู่ หาก haproxy จะฟังบนพอร์ตหลายเขียนแทน--dport $PORTด้วยเช่น--dports $PORTS_SEPARATED_BY_COMMAS --dports 80,443
pepoluan

1
iptables 1.4.7 (Centos 6.7) - คุณต้องระบุ -m mulitport ด้วยถ้าคุณต้องการใช้ --dports ดังนั้น "iptables -I INPUT -p tcp -m multiport - กีฬา 80,443 --syn -j DROP" และเช่นเดียวกันสำหรับ -D
carpii

25

Yelp แบ่งปันวิธีการที่ซับซ้อนมากขึ้นจากการทดสอบที่พิถีพิถัน บทความในบล็อกเป็นจุดดำน้ำที่ลึกและคุ้มค่ากับการลงทุนเวลาเพื่อชื่นชมมันอย่างเต็มที่

True Zero Downtime HAProxy Reloads

tl; dr ใช้ Linux tc (การควบคุมปริมาณการใช้งาน) และ iptables เพื่อจัดคิว SYN แพ็คเก็ตชั่วคราวขณะที่ HAProxy กำลังโหลดใหม่และมีสอง pids ที่ต่ออยู่กับพอร์ตเดียวกัน ( SO_REUSEPORT)

ฉันไม่สบายใจที่จะเผยแพร่บทความทั้งหมดอีกครั้งใน ServerFault อย่างไรก็ตามนี่คือข้อความที่ตัดตอนมาเล็กน้อยที่จะทำให้คุณสนใจ:

การหน่วงเวลาแพ็คเก็ต SYN ที่เข้ามาในตัวโหลดบาลานซ์ HAProxy ของเราที่ทำงานบนเครื่องแต่ละเครื่องทำให้เราสามารถส่งผลกระทบต่อการรับส่งข้อมูลระหว่างการโหลด HAProxy ซึ่งช่วยให้เราสามารถเพิ่มลบและเปลี่ยนแบ็กเอนด์บริการภายใน SOA ของเรา

# plug_manipulation.sh
nl-qdisc-add --dev=lo --parent=1:4 --id=40: --update plug --buffer
service haproxy reload
nl-qdisc-add --dev=lo --parent=1:4 --id=40: --update plug --release-indefinite

# setup_iptables.sh
iptables -t mangle -I OUTPUT -p tcp -s 169.254.255.254 --syn -j MARK --set-mark 1

# setup_qdisc.sh
## Set up the queuing discipline
tc qdisc add dev lo root handle 1: prio bands 4
tc qdisc add dev lo parent 1:1 handle 10: pfifo limit 1000
tc qdisc add dev lo parent 1:2 handle 20: pfifo limit 1000
tc qdisc add dev lo parent 1:3 handle 30: pfifo limit 1000

## Create a plug qdisc with 1 meg of buffer
nl-qdisc-add --dev=lo --parent=1:4 --id=40: plug --limit 1048576
## Release the plug
nl-qdisc-add --dev=lo --parent=1:4 --id=40: --update plug --release-indefinite

## Set up the filter, any packet marked with “1” will be
## directed to the plug
tc filter add dev lo protocol ip parent 1:0 prio 1 handle 1 fw classid 1:4

สรุป: https://gist.github.com/jolynch/97e3505a1e92e35de2c0

ไชโยถึง Yelp เพื่อแบ่งปันข้อมูลเชิงลึกที่น่าทึ่งนี้


ลิงค์สุดยอด! แต่บางทีคุณอาจต้องการสรุปที่นี่ในกรณีที่ลิงก์หมดอายุ นั่นเป็นเหตุผลเดียวที่ไม่มีการโหวต
แมตต์

@Matt ได้เพิ่มข้อความที่ตัดตอนมาและตัวอย่างโค้ด
Steve Jansen

8

มีอีกวิธีที่ง่ายกว่ามากในการโหลด haproxy อีกครั้งด้วยการหยุดทำงานที่แท้จริงของศูนย์ - มันชื่อว่าiptables พลิก (บทความจริง ๆ แล้วเป็นการตอบกลับ Unbounce สำหรับโซลูชั่น Yelp) มันสะอาดกว่าคำตอบที่ยอมรับเนื่องจากไม่จำเป็นต้องวางแพ็กเก็ตใด ๆ ซึ่งอาจทำให้เกิดปัญหากับการโหลดซ้ำนาน

โดยย่อโซลูชันประกอบด้วยขั้นตอนต่อไปนี้:

  1. มามีอินสแตนซ์ของ haproxy กันครั้งแรก - แอคทีฟแรกที่ได้รับทราฟฟิกและสแตนบายที่สองในโหมดสแตนด์บายซึ่งไม่ได้รับทราฟฟิกใด ๆ
  2. คุณกำหนดค่าอินสแตนซ์สแตนด์บาย (โหลดใหม่) ได้ตลอดเวลา
  3. เมื่อสแตนด์บายพร้อมกับการตั้งค่าใหม่ที่คุณเบี่ยงเบนความสนใจการเชื่อมต่อใหม่ทั้งหมดไปยังโหนดสแตนด์บายซึ่งจะกลายเป็นใหม่ที่ใช้งาน Unbounce ให้ทุบตีสคริปต์ซึ่งจะพลิกมีง่ายๆเพียงไม่กี่iptableคำสั่ง
  4. ในขณะที่คุณมีสองอินสแตนซ์ที่ใช้งานอยู่ คุณต้องรอจนกว่าการเชื่อมต่อที่เปิดอยู่กับการใช้งานแบบเก่าจะหยุดลง เวลาขึ้นอยู่กับพฤติกรรมการบริการและการตั้งค่า
  5. การรับส่งข้อมูลไปยังการหยุดที่ใช้งานอยู่ซึ่งจะกลายเป็นสแตนด์บายใหม่ - คุณกลับมาในขั้นตอนที่ 1

ยิ่งกว่านั้นโซลูชันนี้ยังสามารถปรับใช้กับบริการทุกประเภท (nginx, apache และอื่น ๆ ) และสามารถทนต่อความผิดพลาดได้มากกว่าเนื่องจากคุณสามารถทดสอบการกำหนดค่าสแตนด์บายก่อนที่มันจะออนไลน์


4

แก้ไข: คำตอบของฉันทำให้สมมติฐานว่าเคอร์เนลส่งทราฟฟิกไปยังพอร์ตล่าสุดที่จะเปิดด้วย SO_REUSEPORT เท่านั้นในขณะที่มันส่งทราฟฟิกไปยังกระบวนการทั้งหมดตามที่อธิบายไว้ในความคิดเห็นอย่างใดอย่างหนึ่ง กล่าวอีกนัยหนึ่งก็คือการเต้นรำ iptables ยังคงต้องการ :(

หากคุณใช้เคอร์เนลที่รองรับ SO_REUSEPORT ปัญหานี้จะไม่เกิดขึ้น

กระบวนการที่ haproxy ใช้เมื่อรีสตาร์ทคือ:

1) ลองตั้งค่า SO_REUSEPORT เมื่อเปิดพอร์ต ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/proto_tcp.c#L792-L798 )

2) ลองเปิดพอร์ต (จะประสบความสำเร็จกับ SO_REUSEPORT)

3) หากไม่สำเร็จให้ส่งสัญญาณกระบวนการเก่าให้ปิดพอร์ตรอ 10 มิลลิวินาทีแล้วลองอีกครั้ง ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/haproxy.c#L1554-L1577 )

มันได้รับการสนับสนุนครั้งแรกในเคอร์เนล Linux 3.9 แต่ distros บางตัวได้ backported ตัวอย่างเช่นเมล็ด EL6 จาก 2.6.32-417.el6 รองรับ


มันจะเกิดขึ้นSO_REUSEPORTภายใต้สถานการณ์บางอย่างโดยเฉพาะอย่างยิ่งภายใต้การจราจรหนาแน่น เมื่อ SYN ถูกส่งไปยังกระบวนการ haproxy แบบเก่าและในขณะเดียวกันก็ปิดซ็อกเก็ตการฟังซึ่งส่งผลให้ RST ดูบทความ Yelp ที่กล่าวถึงในคำตอบอื่น ๆ ด้านบน
gertas

4
นั่นห่วย ... เพียงเพื่อสรุปปัญหาลีนุกซ์กระจายการเชื่อมต่อใหม่ระหว่างกระบวนการทั้งหมดที่ฟังบนพอร์ตเฉพาะเมื่อใช้ SO_REUSEPORT ดังนั้นจึงมีเวลาสั้น ๆ ที่กระบวนการเก่าจะยังคงได้รับการเชื่อมต่อที่เข้าสู่คิว
Jason Stubbs

2

ฉันจะอธิบายการตั้งค่าของฉันและฉันจะแก้ไข reloads ที่สง่างามได้อย่างไร:

ฉันมีการตั้งค่าทั่วไปที่มี 2 โหนดที่ทำงาน HAproxy และเก็บไว้ ติดตามส่วนต่อประสาน dummy0 ดังนั้นฉันสามารถทำ "ifconfig dummy0 down" เพื่อบังคับให้สลับ

ปัญหาที่แท้จริงคือฉันไม่รู้ว่าทำไม "haproxy reload" ยังคงปล่อยการเชื่อมต่อที่สร้างไว้ทั้งหมด :( ฉันลอง "iptables flipping" ที่เสนอโดย gertas แต่ฉันพบปัญหาบางอย่างเพราะมันทำ NAT บนปลายทาง ที่อยู่ IP ซึ่งไม่ใช่วิธีแก้ปัญหาที่เหมาะสมในบางสถานการณ์

แต่ฉันตัดสินใจที่จะใช้ CONNMARK แฮ็คสกปรกเพื่อทำเครื่องหมายแพ็คเก็ตที่เป็นของการเชื่อมต่อใหม่จากนั้นเปลี่ยนเส้นทางแพ็คเก็ตที่ทำเครื่องหมายไปยังโหนดอื่น

นี่คือกฎของ iptables:

iptables -t mangle -A PREROUTING -i eth1 -d 123.123.123.123/32 -m conntrack --ctstate NEW -j CONNMARK --set-mark 1
iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
iptables -t mangle -A PREROUTING -i eth1 -p tcp --tcp-flags FIN FIN -j MARK --set-mark 2
iptables -t mangle -A PREROUTING -i eth1 -p tcp --tcp-flags RST RST -j MARK --set-mark 2
iptables -t mangle -A PREROUTING -i eth1 -m mark ! --mark 0 -j TEE --gateway 192.168.0.2
iptables -t mangle -A PREROUTING -i eth1 -m mark --mark 1 -j DROP

กฎสองข้อแรกทำเครื่องหมายแพ็กเก็ตที่เป็นของกระแสใหม่ (123.123.123.123 เป็น VIP แบบ keepalived ที่ใช้บน haproxy เพื่อผูกส่วนหน้าไว้)

แพ็กเก็ตมาร์คกฎที่สามและสี่แพ็คเก็ต FIN / RST (ฉันไม่รู้ว่าทำไมเป้าหมาย TEE "ละเว้น" แพ็คเก็ต FIN / RST)

กฎข้อที่ห้าส่งสำเนาของแพ็กเก็ตที่ทำเครื่องหมายไว้ทั้งหมดไปยัง HAproxy อื่น ๆ (192.168.0.2)

กฎข้อที่หกวางแพ็กเก็ตที่เป็นของโฟลว์ใหม่เพื่อป้องกันการเข้าถึงปลายทางดั้งเดิม

อย่าลืมปิดการใช้งาน rp_filter บนอินเทอร์เฟซหรือเคอร์เนลจะปล่อยแพ็คเก็ตดาวอังคารเหล่านั้น

และสุดท้าย แต่ไม่ท้ายสุดให้นึกถึงแพ็คเก็ตที่กลับมา! ในกรณีของฉันมีการกำหนดเส้นทางแบบไม่สมมาตร (คำขอมาถึงลูกค้า -> haproxy1 -> haproxy2 -> เว็บเซิร์ฟเวอร์และการตอบกลับไปจากเว็บเซิร์ฟเวอร์ -> haproxy1 -> ไคลเอ็นต์) แต่ไม่มีผล มันใช้งานได้ดี

ฉันรู้ว่าทางออกที่ดีที่สุดจะใช้ iproute2 เพื่อเบี่ยงเบนความสนใจ แต่มันก็ใช้ได้กับแพ็คเก็ต SYN แรกเท่านั้น เมื่อได้รับ ACK (แพ็คเก็ตที่ 3 ของการจับมือ 3 ทาง) มันไม่ได้ทำเครื่องหมายว่า :( ฉันไม่สามารถใช้เวลามากในการตรวจสอบทันทีที่ฉันเห็นมันทำงานได้กับเป้าหมาย TEE มันเหลือไว้ที่นั่น แน่นอนคุณสามารถลองกับ iproute2

โดยพื้นฐานแล้ว "การรีโหลดอย่างงดงาม" ทำงานได้ดังนี้:

  1. ฉันเปิดใช้งานชุดกฎ iptables และเห็นการเชื่อมต่อใหม่ไปยัง HAproxy อื่นทันที
  2. ฉันจับตาดู "netstat -an | grep ESTABLISHED | wc -l" เพื่อดูแลกระบวนการ "draining"
  3. เมื่อมีการเชื่อมต่อเพียงไม่กี่ (หรือเป็นศูนย์) ให้ "ifconfig dummy0 down" เพื่อบังคับให้เก็บข้อมูลไว้เป็น failover ดังนั้นทราฟฟิกทั้งหมดจะไปยัง HAproxy อื่น
  4. ฉันลบชุดกฎ iptables
  5. (เฉพาะสำหรับ "การไม่กำหนดค่าล่วงหน้า" กำหนดค่า keepalive) "ifconfig dummy0 up"

ชุดกฎ IPtables สามารถรวมเข้ากับสคริปต์เริ่ม / หยุดได้อย่างง่ายดาย:

#!/bin/sh

case $1 in
start)
        echo Redirection for new sessions is enabled

#       echo 0 > /proc/sys/net/ipv4/tcp_fwmark_accept
        for f in /proc/sys/net/ipv4/conf/*/rp_filter; do echo 0 > $f; done
        iptables -t mangle -A PREROUTING -i eth1 ! -d 123.123.123.123 -m conntrack --ctstate NEW -j CONNMARK --set-mark 1
        iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
        iptables -t mangle -A PREROUTING -i eth1 -p tcp --tcp-flags FIN FIN -j MARK --set-mark 2
        iptables -t mangle -A PREROUTING -i eth1 -p tcp --tcp-flags RST RST -j MARK --set-mark 2
        iptables -t mangle -A PREROUTING -i eth1 -m mark ! --mark 0 -j TEE --gateway 192.168.0.2
        iptables -t mangle -A PREROUTING -i eth1 -m mark --mark 1 -j DROP
        ;;
stop)
        iptables -t mangle -D PREROUTING -i eth1 -m mark --mark 1 -j DROP
        iptables -t mangle -D PREROUTING -i eth1 -m mark ! --mark 0 -j TEE --gateway 192.168.0.2
        iptables -t mangle -D PREROUTING -i eth1 -p tcp --tcp-flags RST RST -j MARK --set-mark 2
        iptables -t mangle -D PREROUTING -i eth1 -p tcp --tcp-flags FIN FIN -j MARK --set-mark 2
        iptables -t mangle -D PREROUTING -j CONNMARK --restore-mark
        iptables -t mangle -D PREROUTING -i eth1 ! -d 123.123.123.123 -m conntrack --ctstate NEW -j CONNMARK --set-mark 1

        echo Redirection for new sessions is disabled
        ;;
esac
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.