1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
|
-------------------
HAProxy
Architecture Guide
-------------------
version 1.1.34
willy tarreau
2006/01/29
This document provides real world examples with working configurations.
Please note that except stated otherwise, global configuration parameters
such as logging, chrooting, limits and time-outs are not described here.
===================================================
1. Simple HTTP load-balancing with cookie insertion
===================================================
A web application often saturates the front-end server with high CPU loads,
due to the scripting language involved. It also relies on a back-end database
which is not much loaded. User contexts are stored on the server itself, and
not in the database, so that simply adding another server with simple IP/TCP
load-balancing would not work.
+-------+
|clients| clients and/or reverse-proxy
+---+---+
|
-+-----+--------+----
| _|_db
+--+--+ (___)
| web | (___)
+-----+ (___)
192.168.1.1 192.168.1.2
Replacing the web server with a bigger SMP system would cost much more than
adding low-cost pizza boxes. The solution is to buy N cheap boxes and install
the application on them. Install haproxy on the old one which will spread the
load across the new boxes.
192.168.1.1 192.168.1.11-192.168.1.14 192.168.1.2
-------+-----------+-----+-----+-----+--------+----
| | | | | _|_db
+--+--+ +-+-+ +-+-+ +-+-+ +-+-+ (___)
| LB1 | | A | | B | | C | | D | (___)
+-----+ +---+ +---+ +---+ +---+ (___)
haproxy 4 cheap web servers
Config on haproxy (LB1) :
-------------------------
listen webfarm 192.168.1.1:80
mode http
balance roundrobin
cookie SERVERID insert indirect
option httpchk HEAD /index.html HTTP/1.0
server webA 192.168.1.11:80 cookie A check
server webB 192.168.1.12:80 cookie B check
server webC 192.168.1.13:80 cookie C check
server webD 192.168.1.14:80 cookie D check
Description :
-------------
- LB1 will receive clients requests.
- if a request does not contain a cookie, it will be forwarded to a valid
server
- in return, a cookie "SERVERID" will be inserted in the response holding the
server name (eg: "A").
- when the client comes again with the cookie "SERVERID=A", LB1 will know that
it must be forwarded to server A. The cookie will be removed so that the
server does not see it.
- if server "webA" dies, the requests will be sent to another valid server
and a cookie will be reassigned.
Flows :
-------
(client) (haproxy) (server A)
>-- GET /URI1 HTTP/1.0 ------------> |
( no cookie, haproxy forwards in load-balancing mode. )
| >-- GET /URI1 HTTP/1.0 ---------->
| <-- HTTP/1.0 200 OK -------------<
( the proxy now adds the server cookie in return )
<-- HTTP/1.0 200 OK ---------------< |
Set-Cookie: SERVERID=A |
>-- GET /URI2 HTTP/1.0 ------------> |
Cookie: SERVERID=A |
( the proxy sees the cookie. it forwards to server A and deletes it )
| >-- GET /URI2 HTTP/1.0 ---------->
| <-- HTTP/1.0 200 OK -------------<
( the proxy does not add the cookie in return because the client knows it )
<-- HTTP/1.0 200 OK ---------------< |
>-- GET /URI3 HTTP/1.0 ------------> |
Cookie: SERVERID=A |
( ... )
Limits :
--------
- if clients use keep-alive (HTTP/1.1), only the first response will have
a cookie inserted, and only the first request of each session will be
analyzed. This does not cause trouble in insertion mode because the cookie
is put immediately in the first response, and the session is maintained to
the same server for all subsequent requests in the same session. However,
the cookie will not be removed from the requests forwarded to the servers,
so the server must not be sensitive to unknown cookies. If this causes
trouble, you can disable keep-alive by adding the following option :
option httpclose
- if for some reason the clients cannot learn more than one cookie (eg: the
clients are indeed some home-made applications or gateways), and the
application already produces a cookie, you can use the "prefix" mode (see
below).
- LB1 becomes a very sensible server. If LB1 dies, nothing works anymore.
=> you can back it up using keepalived (see below)
- if the application needs to log the original client's IP, use the
"forwardfor" option which will add an "X-Forwarded-For" header with the
original client's IP address. You must also use "httpclose" to ensure
that you will rewrite every requests and not only the first one of each
session :
option httpclose
option forwardfor
- if the application needs to log the original destination IP, use the
"originalto" option which will add an "X-Original-To" header with the
original destination IP address. You must also use "httpclose" to ensure
that you will rewrite every requests and not only the first one of each
session :
option httpclose
option originalto
The web server will have to be configured to use this header instead.
For example, on apache, you can use LogFormat for this :
LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b " combined
CustomLog /var/log/httpd/access_log combined
Hints :
-------
Sometimes on the internet, you will find a few percent of the clients which
disable cookies on their browser. Obviously they have troubles everywhere on
the web, but you can still help them access your site by using the "source"
balancing algorithm instead of the "roundrobin". It ensures that a given IP
address always reaches the same server as long as the number of servers remains
unchanged. Never use this behind a proxy or in a small network, because the
distribution will be unfair. However, in large internal networks, and on the
internet, it works quite well. Clients which have a dynamic address will not
be affected as long as they accept the cookie, because the cookie always has
precedence over load balancing :
listen webfarm 192.168.1.1:80
mode http
balance source
cookie SERVERID insert indirect
option httpchk HEAD /index.html HTTP/1.0
server webA 192.168.1.11:80 cookie A check
server webB 192.168.1.12:80 cookie B check
server webC 192.168.1.13:80 cookie C check
server webD 192.168.1.14:80 cookie D check
==================================================================
2. HTTP load-balancing with cookie prefixing and high availability
==================================================================
Now you don't want to add more cookies, but rather use existing ones. The
application already generates a "JSESSIONID" cookie which is enough to track
sessions, so we'll prefix this cookie with the server name when we see it.
Since the load-balancer becomes critical, it will be backed up with a second
one in VRRP mode using keepalived under Linux.
Download the latest version of keepalived from this site and install it
on each load-balancer LB1 and LB2 :
http://www.keepalived.org/
You then have a shared IP between the two load-balancers (we will still use the
original IP). It is active only on one of them at any moment. To allow the
proxy to bind to the shared IP on Linux 2.4, you must enable it in /proc :
# echo 1 >/proc/sys/net/ipv4/ip_nonlocal_bind
shared IP=192.168.1.1
192.168.1.3 192.168.1.4 192.168.1.11-192.168.1.14 192.168.1.2
-------+------------+-----------+-----+-----+-----+--------+----
| | | | | | _|_db
+--+--+ +--+--+ +-+-+ +-+-+ +-+-+ +-+-+ (___)
| LB1 | | LB2 | | A | | B | | C | | D | (___)
+-----+ +-----+ +---+ +---+ +---+ +---+ (___)
haproxy haproxy 4 cheap web servers
keepalived keepalived
Config on both proxies (LB1 and LB2) :
--------------------------------------
listen webfarm 192.168.1.1:80
mode http
balance roundrobin
cookie JSESSIONID prefix
option httpclose
option forwardfor
option httpchk HEAD /index.html HTTP/1.0
server webA 192.168.1.11:80 cookie A check
server webB 192.168.1.12:80 cookie B check
server webC 192.168.1.13:80 cookie C check
server webD 192.168.1.14:80 cookie D check
Notes: the proxy will modify EVERY cookie sent by the client and the server,
so it is important that it can access to ALL cookies in ALL requests for
each session. This implies that there is no keep-alive (HTTP/1.1), thus the
"httpclose" option. Only if you know for sure that the client(s) will never
use keep-alive (eg: Apache 1.3 in reverse-proxy mode), you can remove this
option.
Configuration for keepalived on LB1/LB2 :
-----------------------------------------
vrrp_script chk_haproxy { # Requires keepalived-1.1.13
script "killall -0 haproxy" # cheaper than pidof
interval 2 # check every 2 seconds
weight 2 # add 2 points of prio if OK
}
vrrp_instance VI_1 {
interface eth0
state MASTER
virtual_router_id 51
priority 101 # 101 on master, 100 on backup
virtual_ipaddress {
192.168.1.1
}
track_script {
chk_haproxy
}
}
Description :
-------------
- LB1 is VRRP master (keepalived), LB2 is backup. Both monitor the haproxy
process, and lower their prio if it fails, leading to a failover to the
other node.
- LB1 will receive clients requests on IP 192.168.1.1.
- both load-balancers send their checks from their native IP.
- if a request does not contain a cookie, it will be forwarded to a valid
server
- in return, if a JESSIONID cookie is seen, the server name will be prefixed
into it, followed by a delimiter ('~')
- when the client comes again with the cookie "JSESSIONID=A~xxx", LB1 will
know that it must be forwarded to server A. The server name will then be
extracted from cookie before it is sent to the server.
- if server "webA" dies, the requests will be sent to another valid server
and a cookie will be reassigned.
Flows :
-------
(client) (haproxy) (server A)
>-- GET /URI1 HTTP/1.0 ------------> |
( no cookie, haproxy forwards in load-balancing mode. )
| >-- GET /URI1 HTTP/1.0 ---------->
| X-Forwarded-For: 10.1.2.3
| <-- HTTP/1.0 200 OK -------------<
( no cookie, nothing changed )
<-- HTTP/1.0 200 OK ---------------< |
>-- GET /URI2 HTTP/1.0 ------------> |
( no cookie, haproxy forwards in lb mode, possibly to another server. )
| >-- GET /URI2 HTTP/1.0 ---------->
| X-Forwarded-For: 10.1.2.3
| <-- HTTP/1.0 200 OK -------------<
| Set-Cookie: JSESSIONID=123
( the cookie is identified, it will be prefixed with the server name )
<-- HTTP/1.0 200 OK ---------------< |
Set-Cookie: JSESSIONID=A~123 |
>-- GET /URI3 HTTP/1.0 ------------> |
Cookie: JSESSIONID=A~123 |
( the proxy sees the cookie, removes the server name and forwards
to server A which sees the same cookie as it previously sent )
| >-- GET /URI3 HTTP/1.0 ---------->
| Cookie: JSESSIONID=123
| X-Forwarded-For: 10.1.2.3
| <-- HTTP/1.0 200 OK -------------<
( no cookie, nothing changed )
<-- HTTP/1.0 200 OK ---------------< |
( ... )
Hints :
-------
Sometimes, there will be some powerful servers in the farm, and some smaller
ones. In this situation, it may be desirable to tell haproxy to respect the
difference in performance. Let's consider that WebA and WebB are two old
P3-1.2 GHz while WebC and WebD are shiny new Opteron-2.6 GHz. If your
application scales with CPU, you may assume a very rough 2.6/1.2 performance
ratio between the servers. You can inform haproxy about this using the "weight"
keyword, with values between 1 and 256. It will then spread the load the most
smoothly possible respecting those ratios :
server webA 192.168.1.11:80 cookie A weight 12 check
server webB 192.168.1.12:80 cookie B weight 12 check
server webC 192.168.1.13:80 cookie C weight 26 check
server webD 192.168.1.14:80 cookie D weight 26 check
========================================================
2.1 Variations involving external layer 4 load-balancers
========================================================
Instead of using a VRRP-based active/backup solution for the proxies,
they can also be load-balanced by a layer4 load-balancer (eg: Alteon)
which will also check that the services run fine on both proxies :
| VIP=192.168.1.1
+----+----+
| Alteon |
+----+----+
|
192.168.1.3 | 192.168.1.4 192.168.1.11-192.168.1.14 192.168.1.2
-------+-----+------+-----------+-----+-----+-----+--------+----
| | | | | | _|_db
+--+--+ +--+--+ +-+-+ +-+-+ +-+-+ +-+-+ (___)
| LB1 | | LB2 | | A | | B | | C | | D | (___)
+-----+ +-----+ +---+ +---+ +---+ +---+ (___)
haproxy haproxy 4 cheap web servers
Config on both proxies (LB1 and LB2) :
--------------------------------------
listen webfarm 0.0.0.0:80
mode http
balance roundrobin
cookie JSESSIONID prefix
option httpclose
option forwardfor
option httplog
option dontlognull
option httpchk HEAD /index.html HTTP/1.0
server webA 192.168.1.11:80 cookie A check
server webB 192.168.1.12:80 cookie B check
server webC 192.168.1.13:80 cookie C check
server webD 192.168.1.14:80 cookie D check
The "dontlognull" option is used to prevent the proxy from logging the health
checks from the Alteon. If a session exchanges no data, then it will not be
logged.
Config on the Alteon :
----------------------
/c/slb/real 11
ena
name "LB1"
rip 192.168.1.3
/c/slb/real 12
ena
name "LB2"
rip 192.168.1.4
/c/slb/group 10
name "LB1-2"
metric roundrobin
health tcp
add 11
add 12
/c/slb/virt 10
ena
vip 192.168.1.1
/c/slb/virt 10/service http
group 10
Note: the health-check on the Alteon is set to "tcp" to prevent the proxy from
forwarding the connections. It can also be set to "http", but for this the
proxy must specify a "monitor-net" with the Alteons' addresses, so that the
Alteon can really check that the proxies can talk HTTP but without forwarding
the connections to the end servers. Check next section for an example on how to
use monitor-net.
============================================================
2.2 Generic TCP relaying and external layer 4 load-balancers
============================================================
Sometimes it's useful to be able to relay generic TCP protocols (SMTP, TSE,
VNC, etc...), for example to interconnect private networks. The problem comes
when you use external load-balancers which need to send periodic health-checks
to the proxies, because these health-checks get forwarded to the end servers.
The solution is to specify a network which will be dedicated to monitoring
systems and must not lead to a forwarding connection nor to any log, using the
"monitor-net" keyword. Note: this feature expects a version of haproxy greater
than or equal to 1.1.32 or 1.2.6.
| VIP=172.16.1.1 |
+----+----+ +----+----+
| Alteon1 | | Alteon2 |
+----+----+ +----+----+
192.168.1.252 | GW=192.168.1.254 | 192.168.1.253
| |
------+---+------------+--+-----------------> TSE farm : 192.168.1.10
192.168.1.1 | | 192.168.1.2
+--+--+ +--+--+
| LB1 | | LB2 |
+-----+ +-----+
haproxy haproxy
Config on both proxies (LB1 and LB2) :
--------------------------------------
listen tse-proxy
bind :3389,:1494,:5900 # TSE, ICA and VNC at once.
mode tcp
balance roundrobin
server tse-farm 192.168.1.10
monitor-net 192.168.1.252/31
The "monitor-net" option instructs the proxies that any connection coming from
192.168.1.252 or 192.168.1.253 will not be logged nor forwarded and will be
closed immediately. The Alteon load-balancers will then see the proxies alive
without perturbating the service.
Config on the Alteon :
----------------------
/c/l3/if 1
ena
addr 192.168.1.252
mask 255.255.255.0
/c/slb/real 11
ena
name "LB1"
rip 192.168.1.1
/c/slb/real 12
ena
name "LB2"
rip 192.168.1.2
/c/slb/group 10
name "LB1-2"
metric roundrobin
health tcp
add 11
add 12
/c/slb/virt 10
ena
vip 172.16.1.1
/c/slb/virt 10/service 1494
group 10
/c/slb/virt 10/service 3389
group 10
/c/slb/virt 10/service 5900
group 10
Special handling of SSL :
-------------------------
Sometimes, you want to send health-checks to remote systems, even in TCP mode,
in order to be able to failover to a backup server in case the first one is
dead. Of course, you can simply enable TCP health-checks, but it sometimes
happens that intermediate firewalls between the proxies and the remote servers
acknowledge the TCP connection themselves, showing an always-up server. Since
this is generally encountered on long-distance communications, which often
involve SSL, an SSL health-check has been implemented to work around this issue.
It sends SSL Hello messages to the remote server, which in turns replies with
SSL Hello messages. Setting it up is very easy :
listen tcp-syslog-proxy
bind :1514 # listen to TCP syslog traffic on this port (SSL)
mode tcp
balance roundrobin
option ssl-hello-chk
server syslog-prod-site 192.168.1.10 check
server syslog-back-site 192.168.2.10 check backup
=========================================================
3. Simple HTTP/HTTPS load-balancing with cookie insertion
=========================================================
This is the same context as in example 1 above, but the web
server uses HTTPS.
+-------+
|clients| clients
+---+---+
|
-+-----+--------+----
| _|_db
+--+--+ (___)
| SSL | (___)
| web | (___)
+-----+
192.168.1.1 192.168.1.2
Since haproxy does not handle SSL, this part will have to be extracted from the
servers (freeing even more resources) and installed on the load-balancer
itself. Install haproxy and apache+mod_ssl on the old box which will spread the
load between the new boxes. Apache will work in SSL reverse-proxy-cache. If the
application is correctly developed, it might even lower its load. However,
since there now is a cache between the clients and haproxy, some security
measures must be taken to ensure that inserted cookies will not be cached.
192.168.1.1 192.168.1.11-192.168.1.14 192.168.1.2
-------+-----------+-----+-----+-----+--------+----
| | | | | _|_db
+--+--+ +-+-+ +-+-+ +-+-+ +-+-+ (___)
| LB1 | | A | | B | | C | | D | (___)
+-----+ +---+ +---+ +---+ +---+ (___)
apache 4 cheap web servers
mod_ssl
haproxy
Config on haproxy (LB1) :
-------------------------
listen 127.0.0.1:8000
mode http
balance roundrobin
cookie SERVERID insert indirect nocache
option httpchk HEAD /index.html HTTP/1.0
server webA 192.168.1.11:80 cookie A check
server webB 192.168.1.12:80 cookie B check
server webC 192.168.1.13:80 cookie C check
server webD 192.168.1.14:80 cookie D check
Description :
-------------
- apache on LB1 will receive clients requests on port 443
- it forwards it to haproxy bound to 127.0.0.1:8000
- if a request does not contain a cookie, it will be forwarded to a valid
server
- in return, a cookie "SERVERID" will be inserted in the response holding the
server name (eg: "A"), and a "Cache-control: private" header will be added
so that the apache does not cache any page containing such cookie.
- when the client comes again with the cookie "SERVERID=A", LB1 will know that
it must be forwarded to server A. The cookie will be removed so that the
server does not see it.
- if server "webA" dies, the requests will be sent to another valid server
and a cookie will be reassigned.
Notes :
-------
- if the cookie works in "prefix" mode, there is no need to add the "nocache"
option because it is an application cookie which will be modified, and the
application flags will be preserved.
- if apache 1.3 is used as a front-end before haproxy, it always disables
HTTP keep-alive on the back-end, so there is no need for the "httpclose"
option on haproxy.
- configure apache to set the X-Forwarded-For header itself, and do not do
it on haproxy if you need the application to know about the client's IP.
Flows :
-------
(apache) (haproxy) (server A)
>-- GET /URI1 HTTP/1.0 ------------> |
( no cookie, haproxy forwards in load-balancing mode. )
| >-- GET /URI1 HTTP/1.0 ---------->
| <-- HTTP/1.0 200 OK -------------<
( the proxy now adds the server cookie in return )
<-- HTTP/1.0 200 OK ---------------< |
Set-Cookie: SERVERID=A |
Cache-Control: private |
>-- GET /URI2 HTTP/1.0 ------------> |
Cookie: SERVERID=A |
( the proxy sees the cookie. it forwards to server A and deletes it )
| >-- GET /URI2 HTTP/1.0 ---------->
| <-- HTTP/1.0 200 OK -------------<
( the proxy does not add the cookie in return because the client knows it )
<-- HTTP/1.0 200 OK ---------------< |
>-- GET /URI3 HTTP/1.0 ------------> |
Cookie: SERVERID=A |
( ... )
========================================
3.1. Alternate solution using Stunnel
========================================
When only SSL is required and cache is not needed, stunnel is a cheaper
solution than Apache+mod_ssl. By default, stunnel does not process HTTP and
does not add any X-Forwarded-For header, but there is a patch on the official
haproxy site to provide this feature to recent stunnel versions.
This time, stunnel will only process HTTPS and not HTTP. This means that
haproxy will get all HTTP traffic, so haproxy will have to add the
X-Forwarded-For header for HTTP traffic, but not for HTTPS traffic since
stunnel will already have done it. We will use the "except" keyword to tell
haproxy that connections from local host already have a valid header.
192.168.1.1 192.168.1.11-192.168.1.14 192.168.1.2
-------+-----------+-----+-----+-----+--------+----
| | | | | _|_db
+--+--+ +-+-+ +-+-+ +-+-+ +-+-+ (___)
| LB1 | | A | | B | | C | | D | (___)
+-----+ +---+ +---+ +---+ +---+ (___)
stunnel 4 cheap web servers
haproxy
Config on stunnel (LB1) :
-------------------------
cert=/etc/stunnel/stunnel.pem
setuid=stunnel
setgid=proxy
socket=l:TCP_NODELAY=1
socket=r:TCP_NODELAY=1
[https]
accept=192.168.1.1:443
connect=192.168.1.1:80
xforwardedfor=yes
Config on haproxy (LB1) :
-------------------------
listen 192.168.1.1:80
mode http
balance roundrobin
option forwardfor except 192.168.1.1
cookie SERVERID insert indirect nocache
option httpchk HEAD /index.html HTTP/1.0
server webA 192.168.1.11:80 cookie A check
server webB 192.168.1.12:80 cookie B check
server webC 192.168.1.13:80 cookie C check
server webD 192.168.1.14:80 cookie D check
Description :
-------------
- stunnel on LB1 will receive clients requests on port 443
- it forwards them to haproxy bound to port 80
- haproxy will receive HTTP client requests on port 80 and decrypted SSL
requests from Stunnel on the same port.
- stunnel will add the X-Forwarded-For header
- haproxy will add the X-Forwarded-For header for everyone except the local
address (stunnel).
========================================
4. Soft-stop for application maintenance
========================================
When an application is spread across several servers, the time to update all
instances increases, so the application seems jerky for a longer period.
HAProxy offers several solutions for this. Although it cannot be reconfigured
without being stopped, nor does it offer any external command, there are other
working solutions.
=========================================
4.1 Soft-stop using a file on the servers
=========================================
This trick is quite common and very simple: put a file on the server which will
be checked by the proxy. When you want to stop the server, first remove this
file. The proxy will see the server as failed, and will not send it any new
session, only the old ones if the "persist" option is used. Wait a bit then
stop the server when it does not receive anymore connections.
listen 192.168.1.1:80
mode http
balance roundrobin
cookie SERVERID insert indirect
option httpchk HEAD /running HTTP/1.0
server webA 192.168.1.11:80 cookie A check inter 2000 rise 2 fall 2
server webB 192.168.1.12:80 cookie B check inter 2000 rise 2 fall 2
server webC 192.168.1.13:80 cookie C check inter 2000 rise 2 fall 2
server webD 192.168.1.14:80 cookie D check inter 2000 rise 2 fall 2
option persist
redispatch
contimeout 5000
Description :
-------------
- every 2 seconds, haproxy will try to access the file "/running" on the
servers, and declare the server as down after 2 attempts (4 seconds).
- only the servers which respond with a 200 or 3XX response will be used.
- if a request does not contain a cookie, it will be forwarded to a valid
server
- if a request contains a cookie for a failed server, haproxy will insist
on trying to reach the server anyway, to let the user finish what they were
doing. ("persist" option)
- if the server is totally stopped, the connection will fail and the proxy
will rebalance the client to another server ("redispatch")
Usage on the web servers :
--------------------------
- to start the server :
# /etc/init.d/httpd start
# touch /home/httpd/www/running
- to soft-stop the server
# rm -f /home/httpd/www/running
- to completely stop the server :
# /etc/init.d/httpd stop
Limits
------
If the server is totally powered down, the proxy will still try to reach it
for those clients who still have a cookie referencing it, and the connection
attempt will expire after 5 seconds ("contimeout"), and only after that, the
client will be redispatched to another server. So this mode is only useful
for software updates where the server will suddenly refuse the connection
because the process is stopped. The problem is the same if the server suddenly
crashes. All of its users will be fairly perturbated.
==================================
4.2 Soft-stop using backup servers
==================================
A better solution which covers every situation is to use backup servers.
Version 1.1.30 fixed a bug which prevented a backup server from sharing
the same cookie as a standard server.
listen 192.168.1.1:80
mode http
balance roundrobin
redispatch
cookie SERVERID insert indirect
option httpchk HEAD / HTTP/1.0
server webA 192.168.1.11:80 cookie A check port 81 inter 2000
server webB 192.168.1.12:80 cookie B check port 81 inter 2000
server webC 192.168.1.13:80 cookie C check port 81 inter 2000
server webD 192.168.1.14:80 cookie D check port 81 inter 2000
server bkpA 192.168.1.11:80 cookie A check port 80 inter 2000 backup
server bkpB 192.168.1.12:80 cookie B check port 80 inter 2000 backup
server bkpC 192.168.1.13:80 cookie C check port 80 inter 2000 backup
server bkpD 192.168.1.14:80 cookie D check port 80 inter 2000 backup
Description
-----------
Four servers webA..D are checked on their port 81 every 2 seconds. The same
servers named bkpA..D are checked on the port 80, and share the exact same
cookies. Those servers will only be used when no other server is available
for the same cookie.
When the web servers are started, only the backup servers are seen as
available. On the web servers, you need to redirect port 81 to local
port 80, either with a local proxy (eg: a simple haproxy tcp instance),
or with iptables (linux) or pf (openbsd). This is because we want the
real web server to reply on this port, and not a fake one. Eg, with
iptables :
# /etc/init.d/httpd start
# iptables -t nat -A PREROUTING -p tcp --dport 81 -j REDIRECT --to-port 80
A few seconds later, the standard server is seen up and haproxy starts to send
it new requests on its real port 80 (only new users with no cookie, of course).
If a server completely crashes (even if it does not respond at the IP level),
both the standard and backup servers will fail, so clients associated to this
server will be redispatched to other live servers and will lose their sessions.
Now if you want to enter a server into maintenance, simply stop it from
responding on port 81 so that its standard instance will be seen as failed,
but the backup will still work. Users will not notice anything since the
service is still operational :
# iptables -t nat -D PREROUTING -p tcp --dport 81 -j REDIRECT --to-port 80
The health checks on port 81 for this server will quickly fail, and the
standard server will be seen as failed. No new session will be sent to this
server, and existing clients with a valid cookie will still reach it because
the backup server will still be up.
Now wait as long as you want for the old users to stop using the service, and
once you see that the server does not receive any traffic, simply stop it :
# /etc/init.d/httpd stop
The associated backup server will in turn fail, and if any client still tries
to access this particular server, they will be redispatched to any other valid
server because of the "redispatch" option.
This method has an advantage : you never touch the proxy when doing server
maintenance. The people managing the servers can make them disappear smoothly.
4.2.1 Variations for operating systems without any firewall software
--------------------------------------------------------------------
The downside is that you need a redirection solution on the server just for
the health-checks. If the server OS does not support any firewall software,
this redirection can also be handled by a simple haproxy in tcp mode :
global
daemon
quiet
pidfile /var/run/haproxy-checks.pid
listen 0.0.0.0:81
mode tcp
dispatch 127.0.0.1:80
contimeout 1000
clitimeout 10000
srvtimeout 10000
To start the web service :
# /etc/init.d/httpd start
# haproxy -f /etc/haproxy/haproxy-checks.cfg
To soft-stop the service :
# kill $(</var/run/haproxy-checks.pid)
The port 81 will stop responding and the load-balancer will notice the failure.
4.2.2 Centralizing the server management
----------------------------------------
If one finds it preferable to manage the servers from the load-balancer itself,
the port redirector can be installed on the load-balancer itself. See the
example with iptables below.
Make the servers appear as operational :
# iptables -t nat -A OUTPUT -d 192.168.1.11 -p tcp --dport 81 -j DNAT --to-dest :80
# iptables -t nat -A OUTPUT -d 192.168.1.12 -p tcp --dport 81 -j DNAT --to-dest :80
# iptables -t nat -A OUTPUT -d 192.168.1.13 -p tcp --dport 81 -j DNAT --to-dest :80
# iptables -t nat -A OUTPUT -d 192.168.1.14 -p tcp --dport 81 -j DNAT --to-dest :80
Soft stop one server :
# iptables -t nat -D OUTPUT -d 192.168.1.12 -p tcp --dport 81 -j DNAT --to-dest :80
Another solution is to use the "COMAFILE" patch provided by Alexander Lazic,
which is available for download here :
http://w.ods.org/tools/haproxy/contrib/
4.2.3 Notes :
-------------
- Never, ever, start a fake service on port 81 for the health-checks, because
a real web service failure will not be detected as long as the fake service
runs. You must really forward the check port to the real application.
- health-checks will be sent twice as often, once for each standard server,
and once for each backup server. All this will be multiplicated by the
number of processes if you use multi-process mode. You will have to ensure
that all the checks sent to the server do not overload it.
=======================
4.3 Hot reconfiguration
=======================
There are two types of haproxy users :
- those who can never do anything in production out of maintenance periods ;
- those who can do anything at any time provided that the consequences are
limited.
The first ones have no problem stopping the server to change configuration
because they got some maintenance periods during which they can break anything.
So they will even prefer doing a clean stop/start sequence to ensure everything
will work fine upon next reload. Since those have represented the majority of
haproxy uses, there has been little effort trying to improve this.
However, the second category is a bit different. They like to be able to fix an
error in a configuration file without anyone noticing. This can sometimes also
be the case for the first category because humans are not failsafe.
For this reason, a new hot reconfiguration mechanism has been introduced in
version 1.1.34. Its usage is very simple and works even in chrooted
environments with lowered privileges. The principle is very simple : upon
reception of a SIGTTOU signal, the proxy will stop listening to all the ports.
This will release the ports so that a new instance can be started. Existing
connections will not be broken at all. If the new instance fails to start,
then sending a SIGTTIN signal back to the original processes will restore
the listening ports. This is possible without any special privileges because
the sockets will not have been closed, so the bind() is still valid. Otherwise,
if the new process starts successfully, then sending a SIGUSR1 signal to the
old one ensures that it will exit as soon as its last session ends.
A hot reconfiguration script would look like this :
# save previous state
mv /etc/haproxy/config /etc/haproxy/config.old
mv /var/run/haproxy.pid /var/run/haproxy.pid.old
mv /etc/haproxy/config.new /etc/haproxy/config
kill -TTOU $(cat /var/run/haproxy.pid.old)
if haproxy -p /var/run/haproxy.pid -f /etc/haproxy/config; then
echo "New instance successfully loaded, stopping previous one."
kill -USR1 $(cat /var/run/haproxy.pid.old)
rm -f /var/run/haproxy.pid.old
exit 1
else
echo "New instance failed to start, resuming previous one."
kill -TTIN $(cat /var/run/haproxy.pid.old)
rm -f /var/run/haproxy.pid
mv /var/run/haproxy.pid.old /var/run/haproxy.pid
mv /etc/haproxy/config /etc/haproxy/config.new
mv /etc/haproxy/config.old /etc/haproxy/config
exit 0
fi
After this, you can still force old connections to end by sending
a SIGTERM to the old process if it still exists :
kill $(cat /var/run/haproxy.pid.old)
rm -f /var/run/haproxy.pid.old
Be careful with this as in multi-process mode, some pids might already
have been reallocated to completely different processes.
==================================================
5. Multi-site load-balancing with local preference
==================================================
5.1 Description of the problem
==============================
Consider a world-wide company with sites on several continents. There are two
production sites SITE1 and SITE2 which host identical applications. There are
many offices around the world. For speed and communication cost reasons, each
office uses the nearest site by default, but can switch to the backup site in
the event of a site or application failure. There also are users on the
production sites, which use their local sites by default, but can switch to the
other site in case of a local application failure.
The main constraints are :
- application persistence : although the application is the same on both
sites, there is no session synchronisation between the sites. A failure
of one server or one site can cause a user to switch to another server
or site, but when the server or site comes back, the user must not switch
again.
- communication costs : inter-site communication should be reduced to the
minimum. Specifically, in case of a local application failure, every
office should be able to switch to the other site without continuing to
use the default site.
5.2 Solution
============
- Each production site will have two haproxy load-balancers in front of its
application servers to balance the load across them and provide local HA.
We will call them "S1L1" and "S1L2" on site 1, and "S2L1" and "S2L2" on
site 2. These proxies will extend the application's JSESSIONID cookie to
put the server name as a prefix.
- Each production site will have one front-end haproxy director to provide
the service to local users and to remote offices. It will load-balance
across the two local load-balancers, and will use the other site's
load-balancers as backup servers. It will insert the local site identifier
in a SITE cookie for the local load-balancers, and the remote site
identifier for the remote load-balancers. These front-end directors will
be called "SD1" and "SD2" for "Site Director".
- Each office will have one haproxy near the border gateway which will direct
local users to their preference site by default, or to the backup site in
the event of a previous failure. It will also analyze the SITE cookie, and
direct the users to the site referenced in the cookie. Thus, the preferred
site will be declared as a normal server, and the backup site will be
declared as a backup server only, which will only be used when the primary
site is unreachable, or when the primary site's director has forwarded
traffic to the second site. These proxies will be called "OP1".."OPXX"
for "Office Proxy #XX".
5.3 Network diagram
===================
Note : offices 1 and 2 are on the same continent as site 1, while
office 3 is on the same continent as site 3. Each production
site can reach the second one either through the WAN or through
a dedicated link.
Office1 Office2 Office3
users users users
192.168 # # # 192.168 # # # # # #
.1.0/24 | | | .2.0/24 | | | 192.168.3.0/24 | | |
--+----+-+-+- --+----+-+-+- ---+----+-+-+-
| | .1 | | .1 | | .1
| +-+-+ | +-+-+ | +-+-+
| |OP1| | |OP2| | |OP3| ...
,-:-. +---+ ,-:-. +---+ ,-:-. +---+
( X ) ( X ) ( X )
`-:-' `-:-' ,---. `-:-'
--+---------------+------+----~~~( X )~~~~-------+---------+-
| `---' |
| |
+---+ ,-:-. +---+ ,-:-.
|SD1| ( X ) |SD2| ( X )
( SITE 1 ) +-+-+ `-:-' ( SITE 2 ) +-+-+ `-:-'
|.1 | |.1 |
10.1.1.0/24 | | ,---. 10.2.1.0/24 | |
-+-+-+-+-+-+-+-----+-+--( X )------+-+-+-+-+-+-+-----+-+--
| | | | | | | `---' | | | | | | |
...# # # # # |.11 |.12 ...# # # # # |.11 |.12
Site 1 +-+--+ +-+--+ Site 2 +-+--+ +-+--+
Local |S1L1| |S1L2| Local |S2L1| |S2L2|
users +-+--+ +--+-+ users +-+--+ +--+-+
| | | |
10.1.2.0/24 -+-+-+--+--++-- 10.2.2.0/24 -+-+-+--+--++--
|.1 |.4 |.1 |.4
+-+-+ +-+-+ +-+-+ +-+-+
|W11| ~~~ |W14| |W21| ~~~ |W24|
+---+ +---+ +---+ +---+
4 application servers 4 application servers
on site 1 on site 2
5.4 Description
===============
5.4.1 Local users
-----------------
- Office 1 users connect to OP1 = 192.168.1.1
- Office 2 users connect to OP2 = 192.168.2.1
- Office 3 users connect to OP3 = 192.168.3.1
- Site 1 users connect to SD1 = 10.1.1.1
- Site 2 users connect to SD2 = 10.2.1.1
5.4.2 Office proxies
--------------------
- Office 1 connects to site 1 by default and uses site 2 as a backup.
- Office 2 connects to site 1 by default and uses site 2 as a backup.
- Office 3 connects to site 2 by default and uses site 1 as a backup.
The offices check the local site's SD proxy every 30 seconds, and the
remote one every 60 seconds.
Configuration for Office Proxy OP1
----------------------------------
listen 192.168.1.1:80
mode http
balance roundrobin
redispatch
cookie SITE
option httpchk HEAD / HTTP/1.0
server SD1 10.1.1.1:80 cookie SITE1 check inter 30000
server SD2 10.2.1.1:80 cookie SITE2 check inter 60000 backup
Configuration for Office Proxy OP2
----------------------------------
listen 192.168.2.1:80
mode http
balance roundrobin
redispatch
cookie SITE
option httpchk HEAD / HTTP/1.0
server SD1 10.1.1.1:80 cookie SITE1 check inter 30000
server SD2 10.2.1.1:80 cookie SITE2 check inter 60000 backup
Configuration for Office Proxy OP3
----------------------------------
listen 192.168.3.1:80
mode http
balance roundrobin
redispatch
cookie SITE
option httpchk HEAD / HTTP/1.0
server SD2 10.2.1.1:80 cookie SITE2 check inter 30000
server SD1 10.1.1.1:80 cookie SITE1 check inter 60000 backup
5.4.3 Site directors ( SD1 and SD2 )
------------------------------------
The site directors forward traffic to the local load-balancers, and set a
cookie to identify the site. If no local load-balancer is available, or if
the local application servers are all down, it will redirect traffic to the
remote site, and report this in the SITE cookie. In order not to uselessly
load each site's WAN link, each SD will check the other site at a lower
rate. The site directors will also insert their client's address so that
the application server knows which local user or remote site accesses it.
The SITE cookie which is set by these directors will also be understood
by the office proxies. This is important because if SD1 decides to forward
traffic to site 2, it will write "SITE2" in the "SITE" cookie, and on next
request, the office proxy will automatically and directly talk to SITE2 if
it can reach it. If it cannot, it will still send the traffic to SITE1
where SD1 will in turn try to reach SITE2.
The load-balancers checks are performed on port 81. As we'll see further,
the load-balancers provide a health monitoring port 81 which reroutes to
port 80 but which allows them to tell the SD that they are going down soon
and that the SD must not use them anymore.
Configuration for SD1
---------------------
listen 10.1.1.1:80
mode http
balance roundrobin
redispatch
cookie SITE insert indirect
option httpchk HEAD / HTTP/1.0
option forwardfor
server S1L1 10.1.1.11:80 cookie SITE1 check port 81 inter 4000
server S1L2 10.1.1.12:80 cookie SITE1 check port 81 inter 4000
server S2L1 10.2.1.11:80 cookie SITE2 check port 81 inter 8000 backup
server S2L2 10.2.1.12:80 cookie SITE2 check port 81 inter 8000 backup
Configuration for SD2
---------------------
listen 10.2.1.1:80
mode http
balance roundrobin
redispatch
cookie SITE insert indirect
option httpchk HEAD / HTTP/1.0
option forwardfor
server S2L1 10.2.1.11:80 cookie SITE2 check port 81 inter 4000
server S2L2 10.2.1.12:80 cookie SITE2 check port 81 inter 4000
server S1L1 10.1.1.11:80 cookie SITE1 check port 81 inter 8000 backup
server S1L2 10.1.1.12:80 cookie SITE1 check port 81 inter 8000 backup
5.4.4 Local load-balancers S1L1, S1L2, S2L1, S2L2
-------------------------------------------------
Please first note that because SD1 and SD2 use the same cookie for both
servers on a same site, the second load-balancer of each site will only
receive load-balanced requests, but as soon as the SITE cookie will be
set, only the first LB will receive the requests because it will be the
first one to match the cookie.
The load-balancers will spread the load across 4 local web servers, and
use the JSESSIONID provided by the application to provide server persistence
using the new 'prefix' method. Soft-stop will also be implemented as described
in section 4 above. Moreover, these proxies will provide their own maintenance
soft-stop. Port 80 will be used for application traffic, while port 81 will
only be used for health-checks and locally rerouted to port 80. A grace time
will be specified to service on port 80, but not on port 81. This way, a soft
kill (kill -USR1) on the proxy will only kill the health-check forwarder so
that the site director knows it must not use this load-balancer anymore. But
the service will still work for 20 seconds and as long as there are established
sessions.
These proxies will also be the only ones to disable HTTP keep-alive in the
chain, because it is enough to do it at one place, and it's necessary to do
it with 'prefix' cookies.
Configuration for S1L1/S1L2
---------------------------
listen 10.1.1.11:80 # 10.1.1.12:80 for S1L2
grace 20000 # don't kill us until 20 seconds have elapsed
mode http
balance roundrobin
cookie JSESSIONID prefix
option httpclose
option forwardfor
option httpchk HEAD / HTTP/1.0
server W11 10.1.2.1:80 cookie W11 check port 81 inter 2000
server W12 10.1.2.2:80 cookie W12 check port 81 inter 2000
server W13 10.1.2.3:80 cookie W13 check port 81 inter 2000
server W14 10.1.2.4:80 cookie W14 check port 81 inter 2000
server B11 10.1.2.1:80 cookie W11 check port 80 inter 4000 backup
server B12 10.1.2.2:80 cookie W12 check port 80 inter 4000 backup
server B13 10.1.2.3:80 cookie W13 check port 80 inter 4000 backup
server B14 10.1.2.4:80 cookie W14 check port 80 inter 4000 backup
listen 10.1.1.11:81 # 10.1.1.12:81 for S1L2
mode tcp
dispatch 10.1.1.11:80 # 10.1.1.12:80 for S1L2
Configuration for S2L1/S2L2
---------------------------
listen 10.2.1.11:80 # 10.2.1.12:80 for S2L2
grace 20000 # don't kill us until 20 seconds have elapsed
mode http
balance roundrobin
cookie JSESSIONID prefix
option httpclose
option forwardfor
option httpchk HEAD / HTTP/1.0
server W21 10.2.2.1:80 cookie W21 check port 81 inter 2000
server W22 10.2.2.2:80 cookie W22 check port 81 inter 2000
server W23 10.2.2.3:80 cookie W23 check port 81 inter 2000
server W24 10.2.2.4:80 cookie W24 check port 81 inter 2000
server B21 10.2.2.1:80 cookie W21 check port 80 inter 4000 backup
server B22 10.2.2.2:80 cookie W22 check port 80 inter 4000 backup
server B23 10.2.2.3:80 cookie W23 check port 80 inter 4000 backup
server B24 10.2.2.4:80 cookie W24 check port 80 inter 4000 backup
listen 10.2.1.11:81 # 10.2.1.12:81 for S2L2
mode tcp
dispatch 10.2.1.11:80 # 10.2.1.12:80 for S2L2
5.5 Comments
------------
Since each site director sets a cookie identifying the site, remote office
users will have their office proxies direct them to the right site and stick
to this site as long as the user still uses the application and the site is
available. Users on production sites will be directed to the right site by the
site directors depending on the SITE cookie.
If the WAN link dies on a production site, the remote office users will not
see their site anymore, so they will redirect the traffic to the second site.
If there are dedicated inter-site links as on the diagram above, the second
SD will see the cookie and still be able to reach the original site. For
example :
Office 1 user sends the following to OP1 :
GET / HTTP/1.0
Cookie: SITE=SITE1; JSESSIONID=W14~123;
OP1 cannot reach site 1 because its external router is dead. So the SD1 server
is seen as dead, and OP1 will then forward the request to SD2 on site 2,
regardless of the SITE cookie.
SD2 on site 2 receives a SITE cookie containing "SITE1". Fortunately, it
can reach Site 1's load balancers S1L1 and S1L2. So it forwards the request
so S1L1 (the first one with the same cookie).
S1L1 (on site 1) finds "W14" in the JSESSIONID cookie, so it can forward the
request to the right server, and the user session will continue to work. Once
the Site 1's WAN link comes back, OP1 will see SD1 again, and will not route
through SITE 2 anymore.
However, when a new user on Office 1 connects to the application during a
site 1 failure, it does not contain any cookie. Since OP1 does not see SD1
because of the network failure, it will direct the request to SD2 on site 2,
which will by default direct the traffic to the local load-balancers, S2L1 and
S2L2. So only initial users will load the inter-site link, not the new ones.
===================
6. Source balancing
===================
Sometimes it may reveal useful to access servers from a pool of IP addresses
instead of only one or two. Some equipment (NAT firewalls, load-balancers)
are sensible to source address, and often need many sources to distribute the
load evenly amongst their internal hash buckets.
To do this, you simply have to use several times the same server with a
different source. Example :
listen 0.0.0.0:80
mode tcp
balance roundrobin
server from1to1 10.1.1.1:80 source 10.1.2.1
server from2to1 10.1.1.1:80 source 10.1.2.2
server from3to1 10.1.1.1:80 source 10.1.2.3
server from4to1 10.1.1.1:80 source 10.1.2.4
server from5to1 10.1.1.1:80 source 10.1.2.5
server from6to1 10.1.1.1:80 source 10.1.2.6
server from7to1 10.1.1.1:80 source 10.1.2.7
server from8to1 10.1.1.1:80 source 10.1.2.8
=============================================
7. Managing high loads on application servers
=============================================
One of the roles often expected from a load balancer is to mitigate the load on
the servers during traffic peaks. More and more often, we see heavy frameworks
used to deliver flexible and evolutive web designs, at the cost of high loads
on the servers, or very low concurrency. Sometimes, response times are also
rather high. People developing web sites relying on such frameworks very often
look for a load balancer which is able to distribute the load in the most
evenly fashion and which will be nice with the servers.
There is a powerful feature in haproxy which achieves exactly this : request
queueing associated with concurrent connections limit.
Let's say you have an application server which supports at most 20 concurrent
requests. You have 3 servers, so you can accept up to 60 concurrent HTTP
connections, which often means 30 concurrent users in case of keep-alive (2
persistent connections per user).
Even if you disable keep-alive, if the server takes a long time to respond,
you still have a high risk of multiple users clicking at the same time and
having their requests unserved because of server saturation. To work around
the problem, you increase the concurrent connection limit on the servers,
but their performance stalls under higher loads.
The solution is to limit the number of connections between the clients and the
servers. You set haproxy to limit the number of connections on a per-server
basis, and you let all the users you want connect to it. It will then fill all
the servers up to the configured connection limit, and will put the remaining
connections in a queue, waiting for a connection to be released on a server.
This ensures five essential principles :
- all clients can be served whatever their number without crashing the
servers, the only impact it that the response time can be delayed.
- the servers can be used at full throttle without the risk of stalling,
and fine tuning can lead to optimal performance.
- response times can be reduced by making the servers work below the
congestion point, effectively leading to shorter response times even
under moderate loads.
- no domino effect when a server goes down or starts up. Requests will be
queued more or less, always respecting servers limits.
- it's easy to achieve high performance even on memory-limited hardware.
Indeed, heavy frameworks often consume huge amounts of RAM and not always
all the CPU available. In case of wrong sizing, reducing the number of
concurrent connections will protect against memory shortages while still
ensuring optimal CPU usage.
Example :
---------
HAProxy is installed in front of an application servers farm. It will limit
the concurrent connections to 4 per server (one thread per CPU), thus ensuring
very fast response times.
192.168.1.1 192.168.1.11-192.168.1.13 192.168.1.2
-------+-------------+-----+-----+------------+----
| | | | _|_db
+--+--+ +-+-+ +-+-+ +-+-+ (___)
| LB1 | | A | | B | | C | (___)
+-----+ +---+ +---+ +---+ (___)
haproxy 3 application servers
with heavy frameworks
Config on haproxy (LB1) :
-------------------------
listen appfarm 192.168.1.1:80
mode http
maxconn 10000
option httpclose
option forwardfor
balance roundrobin
cookie SERVERID insert indirect
option httpchk HEAD /index.html HTTP/1.0
server railsA 192.168.1.11:80 cookie A maxconn 4 check
server railsB 192.168.1.12:80 cookie B maxconn 4 check
server railsC 192.168.1.13:80 cookie C maxconn 4 check
contimeout 60000
Description :
-------------
The proxy listens on IP 192.168.1.1, port 80, and expects HTTP requests. It
can accept up to 10000 concurrent connections on this socket. It follows the
roundrobin algorithm to assign servers to connections as long as servers are
not saturated.
It allows up to 4 concurrent connections per server, and will queue the
requests above this value. The "contimeout" parameter is used to set the
maximum time a connection may take to establish on a server, but here it
is also used to set the maximum time a connection may stay unserved in the
queue (1 minute here).
If the servers can each process 4 requests in 10 ms on average, then at 3000
connections, response times will be delayed by at most :
3000 / 3 servers / 4 conns * 10 ms = 2.5 seconds
Which is not that dramatic considering the huge number of users for such a low
number of servers.
When connection queues fill up and application servers are starving, response
times will grow and users might abort by clicking on the "Stop" button. It is
very undesirable to send aborted requests to servers, because they will eat
CPU cycles for nothing.
An option has been added to handle this specific case : "option abortonclose".
By specifying it, you tell haproxy that if an input channel is closed on the
client side AND the request is still waiting in the queue, then it is highly
likely that the user has stopped, so we remove the request from the queue
before it will get served.
Managing unfair response times
------------------------------
Sometimes, the application server will be very slow for some requests (eg:
login page) and faster for other requests. This may cause excessive queueing
of expectedly fast requests when all threads on the server are blocked on a
request to the database. Then the only solution is to increase the number of
concurrent connections, so that the server can handle a large average number
of slow connections with threads left to handle faster connections.
But as we have seen, increasing the number of connections on the servers can
be detrimental to performance (eg: Apache processes fighting for the accept()
lock). To improve this situation, the "minconn" parameter has been introduced.
When it is set, the maximum connection concurrency on the server will be bound
by this value, and the limit will increase with the number of clients waiting
in queue, till the clients connected to haproxy reach the proxy's maxconn, in
which case the connections per server will reach the server's maxconn. It means
that during low-to-medium loads, the minconn will be applied, and during surges
the maxconn will be applied. It ensures both optimal response times under
normal loads, and availability under very high loads.
Example :
---------
listen appfarm 192.168.1.1:80
mode http
maxconn 10000
option httpclose
option abortonclose
option forwardfor
balance roundrobin
# The servers will get 4 concurrent connections under low
# loads, and 12 when there will be 10000 clients.
server railsA 192.168.1.11:80 minconn 4 maxconn 12 check
server railsB 192.168.1.12:80 minconn 4 maxconn 12 check
server railsC 192.168.1.13:80 minconn 4 maxconn 12 check
contimeout 60000
|