HAProxy zarif sıfır paket kaybıyla yeniden yükleme


42

Yükü birden fazla Apache sunucusuna dengelemek için bir HAProxy yük dengeleme sunucusu çalıştırıyorum. Yük dengeleme algoritmasını değiştirmek için herhangi bir zamanda HAProxy'i yeniden yüklemem gerekiyor.

Tek bir paketi kaybetmeden sunucuyu yeniden yüklemem gerekmesi haricinde, tüm bunlar iyi çalışıyor (şu anda bir yeniden yükleme, ortalama olarak% 99.76, saniyede 1000 istekle 5 saniye için bana% 99 başarı veriyor). Bununla ilgili saatlerce araştırma yaptım ve HAProxy sunucusunu "incelikle yeniden yüklemek" için aşağıdaki komutu buldum:

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

Bununla birlikte, bunun düz yaşlıya karşı çok az etkisi ya da hiçbir etkisi yoktur service haproxy reload, hala ortalama olarak% 0.24 oranında düşmektedir.

Herhangi bir kullanıcıdan tek bir bırakılan paket olmadan HAProxy config dosyasını yeniden yüklemenin bir yolu var mı?


6
Bu kadar güvenilirliğe ihtiyacınız varsa, daha iyi bir çözüm, birini yeniden yüklemek için hizmet dışı bırakabileceğiniz birden fazla HAproxy örneği çalıştırmak olacaktır, tekrar yerleştirin ve diğer (ler) için tekrarlayın.
yoonix

Yanıtlar:


32

Göre https://github.com/aws/opsworks-cookbooks/pull/40 ve sonuç http://www.mail-archive.com/haproxy@formilux.org/msg06885.html şunları yapabilirsiniz:

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

Bu, yeniden başlatmadan önce SYN'yi bırakma etkisine sahiptir, böylece istemciler bu SYN'yi yeni işleme erişinceye kadar yeniden gönderecektir.



bu komutların ikisi de bana şunu verdi: iptables v1.4.14: invalid port/service --syn 'belirtildi'
Dmitri DB

5
@DmitriDB $PORT, gerçek portun haproxydinleyeceği ile değiştirmeniz gerekiyor . HAProxy sayıda portu dinliyor ise yazma yerine --dport $PORTbirlikte --dports $PORTS_SEPARATED_BY_COMMAS, örneğin --dports 80,443.
pepoluan

1
iptables 1.4.7 (Centos 6.7) - --dports kullanmak istiyorsanız -m mulitport belirtmelisiniz. Yani "iptables -I INPUT -p tcp -m multiport - 80,443 - sin -j DROP" 'u kullanır ve aynı şekilde -D
carpii

25

Yelp titiz testlere dayanarak daha sofistike bir yaklaşım paylaştı. Blog makalesi derin bir dalış ve tamamen takdir etmek için zaman ayırmaya değer.

Gerçek Sıfır Kalma Süresi HAProxy Yeniden Yükleme

dr, Linux tc (trafik kontrolü) ve iptables'ı kullanarak geçici olarak SYN paketlerini sıraya sokuyor, HAProxy yeniden yüklenirken ve aynı porta bağlı iki tane pid var ( SO_REUSEPORT).

Tüm makaleyi ServerFault'da yeniden yayınlamaktan rahat değilim; Bununla birlikte, işte ilginizi çekmek için birkaç alıntı:

Her makinede çalışan HAProxy yük dengeleyicimize gelen SYN paketlerini geciktirerek, kullanıcı trafiğini önemli ölçüde etkilemekten korkmadan, SOA'mızdaki servis arka uçlarını eklememize, kaldırmamıza ve değiştirmemize olanak tanıyan HAProxy yüklemeleri sırasında trafiği minimum düzeyde etkileyebiliriz.

# 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

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

Böyle inanılmaz bilgileri paylaştığı için Yelp'e şerefe.


Mükemmel bağlantı! Fakat belki de bağlantının sona ermesi durumunda burada özetlemek istersiniz. Olumsuz oy vermemenin tek sebebi bu.
Matt

@Matt bazı alıntılar ve kod örnekleri ekledi
Steve Jansen

8

Haproxy'i gerçek sıfır kesinti süresiyle yeniden yüklemenin çok daha basit bir yolu var - çevrilen iptables olarak adlandırılıyor (makale aslında Yelp çözümüne Unbounce yanıtı). Uzun yeniden yükleme işlemlerinde sorunlara neden olabilecek herhangi bir paketi düşürmeye gerek olmadığından, kabul edilen yanıttan daha temizdir.

Kısaca, çözüm aşağıdaki adımlardan oluşur:

  1. Bir çift haproxy örneği alalım - ilk önce trafik alan ve ikinci sırada beklemede olan ve hiç trafik almayan.
  2. Bekleme vakasını istediğiniz zaman yeniden yapılandırırsınız (yeniden yükleyin).
  3. Bekleme yeni yapılandırma ile hazır olduğunda, tüm YENİ bağlantıları yeni aktif hale gelen bekleme düğümüne yönlendirirsiniz . Unbounce, çeviriyi birkaç basit iptablekomutla yapan bash betiğini sunar .
  4. Bir an için iki aktif durumunuz var. Eski aktif bağlantıların açılıncaya kadar beklemeniz gerekir . Zaman servis davranışınıza ve canlı tutma ayarlarınıza bağlıdır.
  5. Yeni aktif hale gelen eski aktif duraklara giden trafik - 1. adıma geri döndünüz.

Dahası, çözüm herhangi bir hizmete uygulanabilir (nginx, apache vb.) Ve bekleme yapılandırmasını çevrimiçi duruma geçmeden önce test edebileceğiniz için hataya daha dayanıklıdır.


4

Düzenleme: Cevabım, çekirdeğin yalnızca SO_REUSEPORT ile açılacak olan en son limana trafik gönderdiğini, oysa yorumlardan birinde açıklandığı gibi tüm işlemlere trafik gönderdiğini varsayıyor. Başka bir deyişle, iptables dansı hala gereklidir. :(

SO_REUSEPORT'u destekleyen bir çekirdeğin içindeyseniz, bu sorun olmamalıdır.

Haproxy yeniden başlatıldığında gerçekleştirdiği işlem şudur:

1) Bağlantı noktasını açarken SO_REUSEPORT ayarını deneyin ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/proto_tcp.c#L792-L798 )

2) Portu açmayı deneyin (SO_REUSEPORT ile başarılı olacak)

3) Başarılı olmadıysa, eski işlemi portunu kapatması için işaretleyin, 10ms bekleyin ve tekrar deneyin. ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/haproxy.c#L1554-L1577 )

İlk olarak Linux 3.9 çekirdeğinde desteklendi, ancak bazı dağıtımlar bunu destekledi. Örneğin, 2.6.32-417.el6'daki EL6 çekirdekleri bunu desteklemektedir.


O ile gerçekleşecek SO_REUSEPORTözellikle yoğun trafik altında - bazı belirli senaryosunda. SYN eski haproxy işlemine gönderildiğinde ve aynı anda RST ile sonuçlanan dinleme soketini kapatır. Yukarıdaki diğer cevapta belirtilen Yelp makalesine bakın.
gertas

4
Bu berbat ... Sadece sorunu özetlemek için, Linux, SO_REUSEPORT kullanıldığında belirli bir limanda dinleyen tüm işlemler arasında yeni bağlantılar dağıtıyor, bu yüzden eski sürecin hala kuyruğuna bağlanan bağlantıları alacağı kısa bir süre var.
Jason Stubbs

2

Kurulumumu ve zarif yüklemeleri nasıl çözdüğümü açıklayacağım:

HAproxy çalışan ve keepalived 2 düğüm ile tipik bir kurulum var. Keepalived izler arayüzü dummy0, bu yüzden geçişi zorlamak için "ifconfig down down" yapabilirim.

Asıl sorun, neden bilmiyorum, bir "haproxy yeniden yükleme" hala KURULAN tüm bağlantıları bırakıyor :( Gertalar tarafından önerilen "iptables flipping" denedim, ancak hedefte bir NAT gerçekleştirdiği için bazı sorunlar buldum Bazı senaryolarda uygun bir çözüm olmayan IP adresi.

Bunun yerine, YENİ bağlantılara ait paketleri işaretlemek için bir CONNMARK kirli hack kullanmaya karar verdim ve ardından işaretli paketleri diğer düğüme yönlendirdim.

İşte iptables kural kümesi:

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

İlk iki kural, yeni akışlara ait paketleri işaretler (123.123.123.123, ön uçları bağlamak için haroside kullanılan sürekli VIP.

Üçüncü ve dördüncü kurallar paketleri FIN / RST paketlerini işaretler. (Nedenini bilmiyorum, TEE hedefi FIN / RST paketlerini "görmezden geliyor").

Beşinci kural, işaretli tüm paketlerin bir kopyasını diğer HAproxy'ye (192.168.0.2) gönderir.

Altıncı kural, orijinal hedeflerine ulaşılmasını önlemek için yeni akışlara ait paketleri bırakır.

Arabirimlerde rp_filter'i devre dışı bırakmayı unutmayın, yoksa çekirdek bu Mars paketlerini düşürür.

Ve son fakat en az değil, geri dönen paketleri dikkate alın! Benim durumumda asimetrik yönlendirme var (istekler istemciye geliyor -> haproxy1 -> haproxy2 -> web sunucusu ve cevaplar web sunucusu -> haproxy1 -> istemcisinden geliyor), ancak etkilenmiyor. İyi çalışıyor.

En şık çözümün, yönlendirme yapmak için iproute2 kullanmak olacağını biliyorum, ancak yalnızca ilk SYN paketi için işe yaradı. ACK (3 yollu el sıkışmasının 3. paketi) alındığında işaretlemedi :( Araştırmak için fazla zaman harcadım, TEE hedefiyle çalıştığını görür görmez orada bıraktı. Elbette, iproute2 ile denemek için çekinmeyin.

Temel olarak, "zarif yeniden yükleme" şu şekilde çalışır:

  1. İptables kural kümesini etkinleştiriyorum ve derhal diğer HAproxy'ye giden yeni bağlantıları görüyorum.
  2. "Boşaltma" işlemini denetlemek için "netstat -an | grep ESTABLISHED | wc -l" konusuna göz kulak oluyorum.
  3. Sadece birkaç (veya sıfır) bağlantı oluştuktan sonra, "ifconfig dummy0 down" işlevini yerine getirmeyi yerine devretmeye zorlamak için tüm trafik diğer HAproxy'ye gider.
  4. İptables kural kümesini kaldırıyorum
  5. (Sadece "önlenmeyen" keepalive config için) "ifconfig dummy0 up".

IPtables kuralları, bir start / stop betiğine kolayca entegre edilebilir:

#!/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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.