HAProxy memuat ulang anggun dengan nol paket loss

42

Saya menjalankan server penyeimbang beban HAProxy untuk menyeimbangkan beban ke beberapa server Apache. Saya perlu memuat ulang HAProxy kapan saja untuk mengubah algoritma load balancing.

Ini semua berfungsi dengan baik, kecuali kenyataan bahwa saya harus memuat ulang server tanpa kehilangan satu paket (saat ini memuat ulang rata-rata memberi saya 99,76% keberhasilan rata-rata, dengan 1000 permintaan per detik selama 5 detik). Saya telah melakukan banyak penelitian tentang hal ini, dan telah menemukan perintah berikut untuk "memuat ulang secara anggun" server HAProxy:

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

Namun, ini memiliki sedikit atau tidak ada efek versus yang lama service haproxy reload, itu masih turun rata-rata 0,24%.

Apakah ada cara untuk memuat ulang file konfigurasi HAProxy tanpa satu paket pun yang dijatuhkan dari pengguna mana pun?

Conor Taylor
sumber
6
Jika Anda membutuhkan keandalan sebanyak itu, solusi yang lebih baik adalah menjalankan lebih dari satu instance HAproxy di mana Anda dapat mengambil satu dari layanan untuk dimuat ulang, masukkan kembali dan ulangi untuk yang lain.
yoonix

Jawaban:

32

Menurut https://github.com/aws/opsworks-cookbooks/pull/40 dan akibatnya http://www.mail-archive.com/[email protected]/msg06885.html Anda dapat:

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

Ini memiliki efek menjatuhkan SYN sebelum memulai ulang, sehingga klien akan mengirim ulang SYN ini hingga mencapai proses baru.

Mxx
sumber
kedua perintah ini memberi saya ini: iptables v1.4.14: invalid port/service --syn 'ditentukan`
Dmitri DB
5
@ DmitriDB Anda seharusnya mengganti $PORTdengan port yang sebenarnya haproxysedang mendengarkan. Jika haproxy mendengarkan pada banyak port, tulis ganti --dport $PORTdengan --dports $PORTS_SEPARATED_BY_COMMAS, misalnya --dports 80,443,.
pepoluan
1
iptables 1.4.7 (Centos 6.7) - Anda juga harus menentukan -m mulitport jika Anda ingin menggunakan --dports. Jadi "iptables -I INPUT -p tcp -m multiport --dport 80.443 --syn -j DROP" dan juga untuk -D
carpii
25

Yelp berbagi pendekatan yang lebih canggih berdasarkan pengujian yang cermat. Artikel blog adalah menyelam dalam, dan layak investasi waktu untuk sepenuhnya menghargainya.

True Zero Downtime HAProxy Reloads

tl; dr menggunakan Linux tc (traffic control) dan iptables untuk sementara mengantri paket SYN sementara HAProxy sedang memuat ulang dan memiliki dua pids yang terpasang pada port yang sama ( SO_REUSEPORT).

Saya tidak nyaman menerbitkan kembali seluruh artikel di ServerFault; namun demikian, berikut adalah beberapa kutipan untuk menyinggung minat Anda:

Dengan menunda paket SYN yang masuk ke penyeimbang beban HAProxy kami yang berjalan pada setiap mesin, kami dapat memengaruhi lalu lintas secara minimal selama pengisian ulang HAProxy, yang memungkinkan kami untuk menambah, menghapus, dan mengubah backend layanan dalam SOA kami tanpa takut akan berdampak signifikan terhadap lalu lintas pengguna.

# 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

Intisari: https://gist.github.com/jolynch/97e3505a1e92e35de2c0

Bersorak untuk Yelp karena berbagi wawasan yang luar biasa.

Steve Jansen
sumber
Tautan luar biasa! Tapi mungkin Anda ingin merangkumnya di sini kalau-kalau tautannya kedaluwarsa. Itulah satu-satunya alasan untuk tidak ada suara positif.
Matt
@Matt menambahkan beberapa cuplikan dan contoh kode
Steve Jansen
8

Ada cara lain yang jauh lebih sederhana untuk memuat ulang haproxy dengan benar-benar downtime - dinamakan iptables flipping (artikelnya sebenarnya adalah Unbounce response to Yelp solution). Ini lebih bersih daripada jawaban yang diterima karena tidak perlu menjatuhkan paket yang dapat menyebabkan masalah dengan isi ulang yang lama.

Secara singkat, solusinya terdiri dari langkah-langkah berikut:

  1. Mari kita memiliki sepasang instance haproxy - aktif pertama yang menerima lalu lintas dan yang kedua dalam keadaan siaga yang tidak menerima lalu lintas apa pun.
  2. Anda mengkonfigurasi ulang (reload) instance siaga kapan saja.
  3. Setelah siaga siap dengan konfigurasi baru, Anda mengalihkan semua koneksi BARU ke node siaga yang menjadi aktif baru . Unbounce menyediakan skrip bash yang melakukan flip dengan beberapa iptableperintah sederhana .
  4. Untuk sesaat Anda memiliki dua instance aktif. Anda harus menunggu sampai koneksi yang terbuka ke yang lama aktif akan berhenti. Waktu tergantung pada perilaku layanan Anda dan pengaturan tetap hidup.
  5. Lalu lintas ke perhentian aktif lama yang menjadi siaga baru - Anda kembali ke langkah 1.

Selain itu, solusinya dapat diadopsi untuk semua jenis layanan (nginx, apache dll) dan lebih toleran terhadap kesalahan karena Anda dapat menguji konfigurasi siaga sebelum online.

gertas
sumber
4

Sunting: Jawaban saya membuat asumsi bahwa kernel hanya mengirimkan lalu lintas ke port terbaru untuk dibuka dengan SO_REUSEPORT, padahal sebenarnya mengirimkan lalu lintas ke semua proses seperti yang dijelaskan dalam salah satu komentar. Dengan kata lain, tarian iptables masih diperlukan. :(

Jika Anda menggunakan kernel yang mendukung SO_REUSEPORT, maka masalah ini seharusnya tidak terjadi.

Proses yang haproxy lakukan saat restart adalah:

1) Coba atur SO_REUSEPORT saat membuka port ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/proto_tcp.c#L792-L798 )

2) Coba buka port (akan berhasil dengan SO_REUSEPORT)

3) Jika tidak berhasil, beri tanda proses lama untuk menutup port-nya, tunggu 10 ms dan coba semuanya lagi. ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/haproxy.c#L1554-L1577 )

Pertama kali didukung di kernel Linux 3.9 tetapi beberapa distro telah mendukungnya. Sebagai contoh, kernel EL6 dari 2.6.32-417.el6 mendukungnya.

Jason Stubbs
sumber
Ini akan terjadi dengan SO_REUSEPORTbeberapa skenario tertentu - terutama di bawah lalu lintas padat. Ketika SYN dikirim ke proses haproxy lama dan pada saat yang sama ia menutup soket pendengaran yang menghasilkan RST. Lihat artikel Yelp yang disebutkan dalam jawaban lain di atas.
gertas
4
Itu menyebalkan ... Hanya untuk meringkas masalah ini, Linux mendistribusikan koneksi baru antara semua proses mendengarkan pada port tertentu ketika SO_REUSEPORT digunakan sehingga ada waktu singkat di mana proses lama masih akan mendapatkan koneksi yang dimasukkan ke dalam antriannya.
Jason Stubbs
2

Saya akan menjelaskan pengaturan saya dan bagaimana saya memecahkan ulang yang anggun:

Saya memiliki pengaturan khas dengan 2 node yang menjalankan HAproxy dan terus dihidupkan. Keepalived melacak antarmuka dummy0, jadi saya bisa melakukan "ifconfig dummy0 down" untuk memaksa beralih.

Masalah sebenarnya adalah bahwa, saya tidak tahu mengapa, "haproxy reload" masih menjatuhkan semua koneksi ESTABLISHED :( Saya mencoba "iptables flipping" yang diajukan oleh gertas, tapi saya menemukan beberapa masalah karena melakukan NAT pada tujuan. Alamat IP, yang bukan solusi yang cocok dalam beberapa skenario.

Sebagai gantinya, saya memutuskan untuk menggunakan hack kotor CONNMARK untuk menandai paket-paket milik koneksi BARU, dan kemudian mengarahkan paket-paket yang ditandai ke node lain.

Berikut adalah aturan dasar 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

Dua aturan pertama menandai paket-paket milik arus baru (123.123.123.123 adalah VIP yang disimpan tetap yang digunakan pada haproxy untuk mengikat frontend).

Paket tanda aturan ketiga dan keempat paket FIN / RST. (Saya tidak tahu mengapa, TEE menargetkan "mengabaikan" paket FIN / RST).

Aturan kelima mengirim duplikat dari semua paket yang ditandai ke HAproxy lainnya (192.168.0.2).

Aturan keenam menjatuhkan paket milik arus baru untuk mencegah mencapai tujuan awal mereka.

Ingatlah untuk menonaktifkan rp_filter pada antarmuka atau kernel akan menjatuhkan paket-paket martian itu.

Dan last but not least, keberatan paket kembali! Dalam kasus saya ada perutean asimetris (permintaan datang ke klien -> haproxy1 -> haproxy2 -> webserver, dan balasan pergi dari server web -> haproxy1 -> client), tetapi tidak mempengaruhi. Ini bekerja dengan baik.

Saya tahu solusi paling elegan adalah dengan menggunakan iproute2 untuk melakukan pengalihan, tetapi hanya bekerja untuk paket SYN pertama. Ketika menerima ACK (paket ke-3 dari jabat tangan 3 arah), itu tidak menandainya :( Saya tidak bisa menghabiskan banyak waktu untuk menyelidiki, begitu saya melihatnya bekerja dengan target TEE, ia meninggalkannya di sana. Tentu saja, silakan mencobanya dengan iproute2.

Pada dasarnya, "reload anggun" berfungsi seperti ini:

  1. Saya mengaktifkan iptables ruleset dan segera melihat koneksi baru pergi ke HAproxy lainnya.
  2. Saya mengawasi "netstat -an | grep ESTABLISHED | wc -l" untuk mengawasi proses "pengeringan".
  3. Setelah hanya ada beberapa (atau nol) koneksi, "ifconfig dummy0 down" untuk memaksa terus di-failover, sehingga semua traffic akan menuju keproxy HA lainnya.
  4. Saya menghapus aturan iptables
  5. (Hanya untuk "non-preempting" konfigurasi keepalive) "ifconfig dummy0 up".

Aturan IPtables dapat dengan mudah diintegrasikan ke dalam skrip start / stop:

#!/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
Vins Vilaplana
sumber