HAProxy tải lại duyên dáng với mất gói không


42

Tôi đang chạy một máy chủ cân bằng tải HAProxy để cân bằng tải cho nhiều máy chủ Apache. Tôi cần tải lại HAProxy tại bất kỳ thời điểm nào để thay đổi thuật toán cân bằng tải.

Tất cả đều hoạt động tốt, ngoại trừ việc tôi phải tải lại máy chủ mà không mất một gói nào (tại thời điểm tải lại mang lại cho tôi trung bình 99,76%, với 1000 yêu cầu mỗi giây trong 5 giây). Tôi đã thực hiện nhiều giờ nghiên cứu về điều này và đã tìm thấy lệnh sau đây để "tải lại một cách duyên dáng" máy chủ HAProxy:

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

Tuy nhiên, điều này ít hoặc không có tác dụng so với cũ service haproxy reload, nó vẫn giảm trung bình 0,24%.

Có cách nào để tải lại tệp cấu hình HAProxy mà không có một gói bị rơi từ bất kỳ người dùng nào không?


6
Nếu bạn cần độ tin cậy cao như vậy, một giải pháp tốt hơn sẽ là chạy nhiều hơn một phiên bản HAproxy nơi bạn có thể lấy một dịch vụ ra để tải lại, đặt lại và lặp lại cho (các) cái khác.
yoonix

Câu trả lời:


32

Theo https://github.com/aws/opswork-cookbooks/pull/40 và do đó http://www.mail-archive.com/haproxy@formilux.org/msg06885.html bạn có thể:

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

Điều này có tác dụng loại bỏ SYN trước khi khởi động lại, do đó khách hàng sẽ gửi lại SYN này cho đến khi đạt được quy trình mới.



cả hai lệnh này đã cho tôi điều này: iptables v1.4.14: invalid port/service --syn 'được chỉ định`
Dmitri DB

5
@DmitriDB bạn phải thay thế $PORTbằng cổng thực tế haproxyđang lắng nghe. Nếu haproxy đang nghe trên nhiều cổng, hãy viết thay thế --dport $PORTbằng --dports $PORTS_SEPARATED_BY_COMMAS, ví dụ : --dports 80,443.
pepoluan

1
iptables 1.4.7 (Centos 6.7) - bạn cũng phải chỉ định -m mulitport nếu bạn muốn sử dụng --dports. Vì vậy, "iptables -I INPUT -p tcp -m Multiport --dports 80,443 --syn -j DROP" và tương tự cho -D
carpii

25

Yelp đã chia sẻ một cách tiếp cận tinh vi hơn dựa trên thử nghiệm tỉ mỉ. Các bài viết blog là một lặn sâu, và đáng đầu tư thời gian để đánh giá đầy đủ nó.

True Zero Dftimeime Tải lại HAProxy

tl; dr sử dụng Linux tc (điều khiển lưu lượng) và iptables để tạm thời xếp hàng các gói SYN trong khi HAProxy đang tải lại và có hai pids được gắn vào cùng một cổng ( SO_REUSEPORT).

Tôi không thoải mái khi xuất bản lại toàn bộ bài viết trên ServerFault; tuy nhiên, đây là một vài trích đoạn để khơi gợi sự quan tâm của bạn:

Bằng cách trì hoãn các gói SYN đi vào bộ cân bằng tải HAProxy chạy trên mỗi máy, chúng tôi có thể tác động tối thiểu lưu lượng trong quá trình tải lại HAProxy, cho phép chúng tôi thêm, xóa và thay đổi phụ trợ dịch vụ trong SOA mà không sợ ảnh hưởng đến lưu lượng người dùng.

# 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

Chúc mừng Yelp đã chia sẻ những hiểu biết tuyệt vời như vậy.


Liên kết tuyệt vời! Nhưng có lẽ bạn muốn tóm tắt nó ở đây trong trường hợp liên kết hết hạn. Đó là lý do duy nhất để không upvote.
Matt

@Matt đã thêm một số trích đoạn và mẫu mã
Steve Jansen

8

Có một cách khác đơn giản hơn nhiều để tải lại haproxy với thời gian chết bằng không thực sự - nó được đặt tên là lật iptables (bài viết thực sự là phản hồi Unbounce cho giải pháp Yelp). Nó sạch hơn câu trả lời được chấp nhận vì không cần bỏ bất kỳ gói nào có thể gây ra vấn đề với tải lại lâu.

Tóm lại, giải pháp bao gồm các bước sau:

  1. Chúng ta hãy có một cặp trường hợp haproxy - hoạt động đầu tiên nhận được lưu lượng truy cập và lần thứ hai ở chế độ chờ không nhận được bất kỳ lưu lượng truy cập nào.
  2. Bạn cấu hình lại (tải lại) cá thể dự phòng bất cứ lúc nào.
  3. Khi chế độ chờ đã sẵn sàng với cấu hình mới, bạn chuyển hướng tất cả các kết nối MỚI sang nút dự phòng sẽ hoạt động mới . Unbounce cung cấp tập lệnh bash thực hiện lật với một vài iptablelệnh đơn giản .
  4. Trong một khoảnh khắc bạn có hai trường hợp hoạt động. Bạn cần đợi cho đến khi các kết nối mở đến hoạt động cũ sẽ chấm dứt. Thời gian phụ thuộc vào hành vi dịch vụ của bạn và cài đặt duy trì.
  5. Lưu lượng truy cập đến các điểm dừng hoạt động cũ trở thành chế độ chờ mới - bạn quay lại bước 1.

Ngoài ra, giải pháp có thể được áp dụng cho bất kỳ loại dịch vụ nào (nginx, apache, v.v.) và có khả năng chịu lỗi cao hơn vì bạn có thể kiểm tra cấu hình dự phòng trước khi trực tuyến.


4

Chỉnh sửa: Câu trả lời của tôi đưa ra giả định rằng hạt nhân chỉ gửi lưu lượng truy cập đến cổng gần đây nhất được mở bằng SO_REUSEPORT, trong khi nó thực sự gửi lưu lượng truy cập đến tất cả các quy trình như được mô tả trong một trong các nhận xét. Nói cách khác, điệu nhảy iptables vẫn được yêu cầu. :(

Nếu bạn đang sử dụng kernel hỗ trợ SO_REUSEPORT thì vấn đề này không nên xảy ra.

Quá trình mà haproxy thực hiện khi khởi động lại là:

1) Hãy thử thiết lập SO_REUSEPORT khi mở cổng ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/proto_tcp.c#L792-L798 )

2) Thử mở cổng (sẽ thành công với SO_REUSEPORT)

3) Nếu không thành công, hãy báo hiệu quy trình cũ để đóng cổng, đợi 10ms và thử lại tất cả. ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/haproxy.c#L1554-L1577 )

Nó lần đầu tiên được hỗ trợ trong kernel Linux 3.9 nhưng một số distro đã backport nó. Ví dụ, hạt nhân EL6 từ 2.6.32-417.el6 hỗ trợ nó.


Nó sẽ xảy ra với SO_REUSEPORTmột số kịch bản cụ thể - đặc biệt là dưới lưu lượng lớn. Khi SYN được gửi đến quá trình haproxy cũ và trong cùng thời điểm đó, nó sẽ đóng ổ cắm nghe dẫn đến RST. Xem bài viết Yelp được đề cập trong câu trả lời khác ở trên.
gertas

4
Thật tệ ... Chỉ để tóm tắt vấn đề, Linux phân phối các kết nối mới giữa tất cả các quy trình lắng nghe trên một cổng cụ thể khi SO_REUSEPORT được sử dụng để có một thời gian ngắn mà quy trình cũ vẫn sẽ đưa các kết nối vào hàng đợi.
Jason Stubbs

2

Tôi sẽ giải thích thiết lập của mình và cách tôi giải quyết các tải lại duyên dáng:

Tôi có một thiết lập điển hình với 2 nút chạy HAproxy và được giữ nguyên. Giao diện theo dõi được giữ nguyên dummy0, vì vậy tôi có thể thực hiện "ifconfig dummy0 down" để buộc chuyển đổi.

Vấn đề thực sự là, tôi không biết tại sao, "tải lại haproxy" vẫn làm giảm tất cả các kết nối THÀNH LẬP :( Tôi đã thử "lật iptables" do gertas đề xuất, nhưng tôi thấy một số vấn đề vì nó thực hiện NAT ở đích Địa chỉ IP, không phải là một giải pháp phù hợp trong một số tình huống.

Thay vào đó, tôi quyết định sử dụng hack bẩn CONNophone để đánh dấu các gói thuộc các kết nối MỚI và sau đó chuyển hướng các gói được đánh dấu sang nút khác.

Đây là quy tắc 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

Hai quy tắc đầu tiên đánh dấu các gói thuộc các luồng mới (123.123.123.123 là VIP được giữ lại được sử dụng trên haproxy để liên kết các giao diện trên).

Quy tắc thứ ba và thứ tư đánh dấu các gói FIN / RST. (Tôi không biết tại sao, mục tiêu TEE "bỏ qua" các gói FIN / RST).

Quy tắc thứ năm gửi một bản sao của tất cả các gói được đánh dấu đến HAproxy khác (192.168.0.2).

Quy tắc thứ sáu bỏ các gói thuộc các luồng mới để ngăn chặn đến đích ban đầu của chúng.

Hãy nhớ tắt rp_filter trên các giao diện hoặc kernel sẽ loại bỏ các gói martian đó.

Và cuối cùng nhưng không kém phần quan trọng, hãy nhớ các gói trả về! Trong trường hợp của tôi, có định tuyến không đối xứng (yêu cầu đến máy khách -> haproxy1 -> haproxy2 -> máy chủ web và trả lời đi từ máy chủ web -> haproxy1 -> máy khách), nhưng nó không ảnh hưởng. Nó hoạt động tốt.

Tôi biết giải pháp tao nhã nhất là sử dụng iproute2 để thực hiện chuyển hướng, nhưng nó chỉ hoạt động cho gói SYN đầu tiên. Khi nhận được ACK (gói thứ 3 của bắt tay 3 chiều), nó đã không đánh dấu nó :( Tôi không thể dành nhiều thời gian để điều tra, ngay khi tôi thấy nó hoạt động với mục tiêu TEE, nó đã để nó ở đó. Tất nhiên, hãy thử dùng nó với iproute2.

Về cơ bản, "tải lại duyên dáng" hoạt động như thế này:

  1. Tôi kích hoạt bộ quy tắc iptables và ngay lập tức thấy các kết nối mới sẽ đến HAproxy khác.
  2. Tôi để mắt đến "netstat -an | grep THÀNH LẬP | wc -l" để giám sát quá trình "rút cạn".
  3. Khi chỉ có một vài kết nối (hoặc không), "ifconfig dummy0 down" để buộc giữ lại chuyển đổi dự phòng, vì vậy tất cả lưu lượng truy cập sẽ chuyển sang HAproxy khác.
  4. Tôi loại bỏ các quy tắc iptables
  5. (Chỉ dành cho cấu hình keepalive "không ưu tiên") "ifconfig dummy0 up".

Bộ quy tắc IPtables có thể dễ dàng được tích hợp vào tập lệnh 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.