tips.yo (48430B)
1 The following sections elaborate on the directives as described in 2 section ref(config) to illustrate how crossroads works and to help you 3 achieve the "optimal" balancing configuration. 4 5 subsect(How back ends are selected in load balancing)label(howselected) 6 7 In order to tune your load balancing, you'll need to understand how 8 crossroads computes usage, how weighing works, and so on. In this 9 section we'll focus on the dispatching modes tt(bysize), tt(byduration) 10 and tt(byconnections) only. The other dispatching types are 11 self-explanatory. 12 13 14 subsubsect(Bysize, byduration or byconnections?) 15 16 As stated before, crossroads doesn't know 'what a service does' and 17 how to judge whether a given back end is very busy or not. You 18 must therefore give the right hints: 19 20 itemization( 21 it() In general, a service which is CPU bound, will be more 22 busy when it takes longer to process a request. The dispatch 23 mode tt(byduration) is appropriate here. 24 25 it() In contrast, a service which is filesystem bound, will be 26 more busy when more data are transferred. The dispatch mode 27 tt(bysize) is apppropriate. 28 29 it() The dispatch mode tt(byduration) can also be used when 30 network latency is an issue. E.g., if your balancer has back 31 ends that are geograpically distributed, then tt(byduration) 32 would be a good way to select best available back ends. 33 34 it() Furthermore it is noteworthy that tt(dispatchmode 35 byduration) is not usable for interactive processes such as 36 SSH logins. Idle time of a 37 login adds to the duration, while causing (almost) no 38 load. Mode tt(byduration) should only be used for automated 39 processes that don't wait for user interaction (e.g., SOAP 40 calls and other HTTP requests). 41 42 it() As a last remark, the dispatching mode tt(byconnections) can 43 be used if you don't have other clues for load 44 estimations. 45 46 E.g., consider a database connection. What's 47 heavier on the back end, time-consuming connections, or connections 48 where loads of bytes are transferred? Well, that depends. A 49 tough tt(select) query that joins multiple tables can be very 50 heavy on the back end, though the response set can be quite 51 small - and hence the number of 52 transferred bytes. That would suggest 53 dispatching by duration. However, tt(byduration) 54 balancing doesn't respresent the true world, when interactive 55 connections can occur where users have an idle TCP connection to 56 the database: 57 this consumes time, but no bytes (see the SSH login example 58 above). In this case, the dispatch mode tt(byconnections) may be 59 your best bet. 60 61 ) 62 63 64 subsubsect(Averaging size and duration) 65 66 The configuration statement tt(dispatchmode bysize) or tt(byduration) 67 allows an optional modifier tt(over) em(number), where the stated 68 number represents a connection count. When this modifier is present, then 69 crossroads will use a moving average over the last em(n) connections to 70 compute duration and size figures. 71 72 In the real world you'll always want this modifier. E.g., consider two 73 back ends that are running for years now, and one of them is suddenly 74 overloaded and very busy (it experiences a 'spike' in activity). 75 When the tt(over) modifier is absent, then 76 the sudden load will hardly show up in the usage figures -- it will 77 flatten out due to the large usage figures already stored in the years 78 of service. 79 80 In contrast, when e.g. tt(over 3) is in effect, then a sudden load 81 does show up -- because it highly contributes to the average of three 82 connections. 83 84 85 subsubsect(Specifying decays) 86 87 Decays are also only relevant when crossroads computes the 'next best 88 back end' by size (bytes) or duration (seconds). E.g., imagine two 89 back ends A and B, both averaged over say 3 connections. 90 91 Now when back end A is suddenly hit by a spike, 92 its average would go up accordingly. But the back end would never 93 again be used, unless B also received a similar spike, because A's 94 'usage data' over its last three connections would forever be larger than 95 B's data. 96 97 For that reason, you should in real situations probably always 98 specify a decay, so that the backend selection algorithm recovers from 99 spikes. Note that the usage data of the back end where a decay is 100 specified, decay when bf(other) back ends are hit. The decay parameter 101 is like specifying how fast your body regenerates when someone else 102 does the work. 103 104 The below configuration illustrates this: 105 106 verb(\ 107 /* Definition of the service */ 108 service soap { 109 /* Local TCP port */ 110 port 8080; 111 112 /* We'll select back ends by the processing 113 * duration 114 */ 115 dispatchmode byduration over 3; 116 117 /* First back end: */ 118 backend A { 119 /* Back end IP address and port */ 120 server 10.1.1.1:8080; 121 122 /* When this back end is NOT hit because 123 * the other one was less busy, then the 124 * usage parameters decay 10% per connection 125 */ 126 decay 10; 127 } 128 129 /* Second back end: */ 130 backend B { 131 server 10.1.1.2:8080; 132 decay 10; 133 } 134 }) 135 136 subsubsect(Adjusting the weights) 137 138 The back end modifier tt(weight) is useful in situations where your 139 back ends differ in respect to performance. E.g,. your back ends may 140 be geographically distributed, and you know that a given back end is 141 difficult to reach and often experiences network lag. 142 143 Or you may have 144 one primary back end, a system with a fast CPU and enough memory, and a 145 small fall-back back end, with a slow CPU and short on memory. In that 146 case you know in advance that the second back end should be used only 147 rarely. Most requests should go to the big server, up to a certain load. 148 149 In such cases you will know in advance that the best performing back ends 150 should be selected the most often. Here's where the tt(weight) 151 statement comes in: you can simply increase the weight of the back 152 ends with the least performance, so that they are selected less 153 frequently. 154 155 E.g., consider the following configuration: 156 157 verb(\ 158 service soap { 159 port 8080; 160 dispatchmode byduration over 3; 161 backend A { 162 server 10.1.1.1:8080; 163 decay 20; 164 } 165 backend B { 166 server 10.1.1.2:8080; 167 weight 2; 168 decay 10; 169 } 170 backend C { 171 server 10.1.1.3:8080; 172 weight 4; 173 decay 5; 174 } 175 }) 176 177 This will cause crossroads to select back ends by the processing time, 178 averaging over the last three connections. However, backend B will kick 179 in only when its usage is half of the usage of A (back end B is 180 probably only half as fast as A). Backend C will kick in only when its 181 usage is a quarter of the usage of A, which is half of the usage of B 182 (back end C is probably very weak, and just a fall-back system incase 183 both A and B crash). Note also that A's usage data decay much faster 184 than B's and C's: we're assuming that this big server recovers quicker 185 than its smaller siblings. 186 187 188 subsubsect(Throttling the number of concurrent connections) 189 190 If you suspect that your service may occasionally receive 'spikes' of 191 activity+footnote(which you should always assume), then it might be a 192 good idea to protect your service by specifying a maximum number of 193 concurrent connections. This protection can be specified on two levels: 194 195 description( 196 dit(On the service level) a statement like tt(maxconnections 197 100;) states that the service as a whole will never 198 service more than 100 concurrent connections. This means that 199 all your back ends and the crossroads balancer itself 200 will be protected from being overloaded. 201 dit(On the back end level) a statement like tt(maxconnections 10;) 202 states that this particular back end will never have more 203 than 10 concurrent connections; regardless of the overall 204 setting on the service level. This means that this 205 particular back end will be protected from being 206 overloaded (regardless of what other back ends may 207 experience).) 208 209 The tt(maxconnections) statement, combined with a back end selection 210 algorithm, allows very fine granularity. The tt(maxconnections) statement 211 on the back end level is like a hand brake: even when you specify a 212 back end algorithm that would protect a given back end from being used 213 too much, a situation may occur where that back end is about to be 214 hit. A tt(maxconnections) statement on the level of that back may then 215 protect it. 216 217 218 subsect(Using an external program to dispatch) 219 label(externalhandler) 220 221 As mentioned before, Crossroads supports several built-in dispatch 222 modes. However, you are always free to hook-in your own dispatch mode 223 that determines the next back end using your own specific 224 algorithm. This section explains how to do it. 225 226 subsubsect(Configuring the external handler) 227 228 First, the tt(dispatchmode) statement needs to inform Crossroads that 229 an external program will do the job. The syntax is: tt(dispatchmode 230 externalhandler) em(program arguments). The em(program) must point to 231 an executable program that will be started by Crossroads. The 232 specifier em(arguments) can be anything you want; those will be the 233 arguments to Crossroads. You can however use the following special 234 format specifiers: 235 236 INCLUDEFILE(formattable) 237 238 Note that the format specifiers such as tt(%b) don't make sense in the 239 phase in which an external handler is called, since there is no 240 current back end yet (the job of the handler is to supply one). 241 242 subsubsect(Writing the external handler) 243 244 The external handler is activated using the arguments that are 245 specified in tt(/etc/crossroads.conf). The external handler can do 246 whatever it wants, but ultimately, it must write a back end name on 247 its em(stdout). Crossroads reads this, and if the back end is 248 available, uses that back end for the connection. 249 250 subsubsect(Examples of external handlers) 251 252 This section shows some examples of Crossroads configurations 253 vs. external handlers. The sample handlers that are shown here, are 254 also included in the Crossroads distribution, under the directory 255 tt(etc/). Also note that the examples shown here are just 256 quick-and-dirty Perl scripts, meant to illustrate only. Your 257 applications may need other external handlers, but you can use the 258 shown scripts as a starting point. 259 260 subsubsubsect(Round-robin dispatching) 261 262 This example is trivial in the sense that round-robin dispatching is 263 already built into Crossroads, so 264 that using an external handler for this purpose only slows down 265 Crossroads. However, it's a good starting example. 266 267 The Crossroads configuration is shown below: 268 269 verb(\ 270 service test { 271 port 8001; 272 verbosity on; 273 revivinginterval 5; 274 275 dispatchmode externalhandler 276 /usr/local/src/crossroads/etc/dispatcher-roundrobin 277 %1b %1a %2b %2a; 278 279 backend testone { 280 server localhost:3128; 281 verbosity on; 282 } 283 backend testtwo { 284 server locallhost:3128; 285 verbosity on; 286 } 287 }) 288 289 The relevant tt(dispatchmode) statement invokes the external program 290 tt(dispatcher-roundrobin) with four arguments: the name of the first 291 back end (tt(testone)), its availability (0 or 1), the name of the 292 second back end (tt(testtwo)) and its availability (0 or 1). 293 294 The external handler, which is also included in the Crossroads 295 distribution, is shown below. It is a Perl script. 296 297 verb(\ 298 #!/usr/bin/perl 299 300 use strict; 301 302 # Example of a round-robin external dispatcher. This is totally 303 # superfluous, Crossroads has this on-board; if you use the external 304 # program for determining round-robin dispatching, then you'll only 305 # slow things down. This script is just meant as an example. 306 307 # Globals / configuration 308 # ----------------------- 309 my $log = '/tmp/exthandler.log'; # Debug log, set to /dev/null to suppress 310 my $statefile = '/tmp/rr.last'; # Where we keep the last used 311 312 # Logging 313 # ------- 314 sub msg { 315 return if ($log eq '/dev/null' or $log eq ''); 316 open (my $of, ">>$log") or return; 317 print $of (scalar(localtime()), ' ', @_); 318 } 319 320 # Read the last used back end 321 # --------------------------- 322 sub readlast() { 323 my $ret; 324 325 if (open (my $if, $statefile)) { 326 $ret = <$if>; 327 chomp ($ret); 328 close ($if); 329 msg ("Last used back end: $ret\n"); 330 return ($ret); 331 } 332 msg ("No last-used back end (yet)\n"); 333 return (undef); 334 } 335 336 # Write back the last used back end, reply to Crossroads and stop 337 # --------------------------------------------------------------- 338 sub reply ($) { 339 my $last = shift; 340 341 if (open (my $of, ">$statefile")) { 342 print $of ("$last\n"); 343 } 344 print ("$last\n"); 345 exit (0); 346 } 347 348 # Main starts here 349 # ---------------- 350 351 # Collect the cmdline arguments. We expect pairs of backend-name / 352 # backend-availablility, and we'll store only the available ones. 353 msg ("Dispatch request received\n"); 354 my @backend; 355 for (my $i = 0; $i <= $#ARGV; $i += 2) { 356 push (@backend, $ARGV[$i]) if ($ARGV[$i + 1]); 357 } 358 msg ("Available back ends: @backend\n"); 359 360 # Let's see what the last one is. If none found, then we return the 361 # first available back end. Otherwise we need to go thru the list of 362 # back ends, and return the next one in line. 363 my $last = readlast(); 364 if ($last eq '') { 365 msg ("Returning first available back end $backend[0]\n"); 366 reply ($backend[0]); 367 } 368 369 # There **was** a last back end. Try to match it in the list, 370 # then return the next-in-line. 371 for (my $i = 0; $i < $#backend; $i++) { 372 if ($last eq $backend[$i]) { 373 msg ("Returning next back end ", $backend[$i + 1], "\n"); 374 reply ($backend[$i + 1]); 375 } 376 } 377 378 # No luck.. run back to the first one. 379 msg ("Returning first back end $backend[0]\n"); 380 reply ($backend[0]);) 381 382 The working of the script is basically as follows: 383 384 itemization( 385 it() The argument list is scanned. Back ends that are 386 available are collected in an array tt(@backend). 387 388 it() The script queries a state file tt(/tmp/rr.last). If a 389 back end name occurs there, then the next back end is looked 390 up in tt(@backend) and returned to Crossroads. If no last back 391 is unknown or can't be matched, then the first available back 392 end (first element of tt(@backend)) is returned to Crossroads. 393 394 it() Informing Crossroads is done via the subroutine 395 tt(reply()). This code writes the selected back end to file 396 tt(/tmp/rr.last) (for future usage) and prints the back end 397 name to em(stdout). 398 399 it() The script logs its actions to a file 400 tt(/tmp/exthandler.log). This log file can be inspected for 401 the script's actions.) 402 403 404 subsubsubsect(Dispatching by the client IP address) 405 406 The following example shows a useful real-life situation. The 407 situation is as follows: 408 409 itemization( 410 it() Crossroads is used as a single-address point to forward 411 Remote Desktop requests to a farm of Windows systems, where 412 users can work via remote access; 413 414 it() However, users may stop their session, and when they 415 re-connect, they expect to be sent to the Windows system that 416 they had worked on previously; 417 418 it() Client PC's have their distinct IP addresses, which 419 distinguishes them. 420 421 it() Of four windows systems, two are large servers, and two 422 are small ones. We'll want to assign large servers to clients 423 when we have a choice.) 424 425 The requirements resemble session stickiness in HTTP, except that the remote 426 desktop protocol doesn't support stickiness. This situation is a 427 perfect example of how an external handler can help: 428 429 itemization( 430 it() A suitable dispatch mode isn't yet available in 431 Crossroads, but can be easily coded in an external handler; 432 433 it() The potential delay due to the calling of an external 434 handler won't even be noticed. This is a network service where 435 the connection time isn't critical; we'd expect only a few 436 (albeit lengthy) TCP connections.) 437 438 The approach to the solution of this problem uses several external 439 program hooks: 440 441 itemization( 442 it() An external dispatcher handler will be responsible for 443 suggesting a back end, given a client IP and given the current 444 timestamp. This handler will consult an internal 445 administration to see whether the stated IP address should 446 re-use a back end, or to determine which back end is free for usage. 447 it() An external hook tt(onstart) will be responsible for 448 updating the internal administration; i.e., to flag a back end 449 as 'occupied'. 450 it() The external hooks tt(onfailure) and tt(onend) will be 451 responsible for flagging a back end as 'free' again; i.e., for 452 erasing any previous information that states that the back end 453 was occupied.) 454 455 The Crossroads configuration is shown below. Only four Windows back 456 ends are shown. Each back end is configured on a 457 given IP address, port 3389, and is limited to one concurrent connection 458 (otherwise a new user might 'steal' a running desktop session). 459 460 verb(\ 461 service rdp { 462 port 3389; 463 revivinginterval 5; 464 465 /* rdp-helper dispatch IP STAMP ... will suggest a back end to use, 466 * arguments are for all back ends: name, availability, weight */ 467 dispatchmode externalhandler 468 /usr/local/src/crossroads/etc/rdp-helper dispatch %r %e 469 %1b %1a %1w 470 %2b %2a %2w 471 %3b %3a %3w 472 %4b %4a %4w; 473 474 backend win1 { 475 server 10.1.1.1:3389; 476 maxconnections 1; 477 /* rdp-helper start IP STAMP BACKEND will log the actual start 478 * of a connection; 479 * rdp-helper end IP will log the ending of a connection */ 480 onstart /usr/local/src/crossroads/etc/rdp-helper start %r %e %b; 481 onend /usr/local/src/crossroads/etc/rdp-helper end %r; 482 onfail /usr/local/src/crossroads/etc/rdp-helper end %r; 483 } 484 backend win2 { 485 server 10.1.1.2:3389; 486 maxconnections 1; 487 onstart /usr/local/src/crossroads/etc/rdp-helper start %r %e %b; 488 onend /usr/local/src/crossroads/etc/rdp-helper end %r; 489 onfail /usr/local/src/crossroads/etc/rdp-helper end %r; 490 } 491 backend win3 { 492 server 10.1.1.3:3389; 493 maxconnections 1; 494 weight 2; 495 onstart /usr/local/src/crossroads/etc/rdp-helper start %r %e %b; 496 onend /usr/local/src/crossroads/etc/rdp-helper end %r; 497 onfail /usr/local/src/crossroads/etc/rdp-helper end %r; 498 } 499 backend win4 { 500 server 10.1.1.4:3389; 501 maxconnections 1; 502 weight 3; 503 onstart /usr/local/src/crossroads/etc/rdp-helper start %r %e %b; 504 onend /usr/local/src/crossroads/etc/rdp-helper end %r; 505 onfail /usr/local/src/crossroads/etc/rdp-helper end %r; 506 } 507 }) 508 509 Depending on the dispatcher stage, the exernal handler tt(rdp-helper) 510 is invoked in different ways: 511 512 description( 513 dit(During dispatching) the helper is called to suggest a back 514 end. The arguments are an action indicator tt(dispatch), the 515 client's IP address, the timestamp, and four triplets that 516 represent back ends: per back end its name, its availability, 517 and its weight. The purpose of the helper is to tell 518 Crossroads which back end to use. 519 520 dit(During connection start) the helper will be invoked to 521 inform it of the start of a connection, given a client IP 522 address. 523 524 dit(When a connection terminates) the helper will be invoked 525 to inform it that the connection has ended.) 526 527 Here's the external handler as Perl script. It uses the module 528 tt(GDBM_File) which most likely will not be part of standard Perl 529 distributions, but can be added using CPAN. (Alternatively, any other 530 database module can be used.) 531 532 verb(\ 533 #!/usr/bin/perl 534 535 use strict; 536 use GDBM_File; 537 538 # Global variables and configuration 539 # ---------------------------------- 540 my $log = '/tmp/exthandler.log'; # Debug log, set to /dev/null to suppress 541 my $cdb = '/tmp/client.db'; # GDBM database of clients 542 my %db; # .. and memory representation of it 543 my $timeout = 24*60*60; # Timeout of a connection in secs 544 545 # Logging 546 # ------- 547 sub msg { 548 return if ($log eq '/dev/null' or $log eq ''); 549 open (my $of, ">>$log") or return; 550 print $of (scalar(localtime()), ' ', @_); 551 close ($of); 552 } 553 554 # Reply a back end to the caller and stop processing. 555 # --------------------------------------------------- 556 sub reply ($) { 557 my $b = shift; 558 msg ("Suggesting $b to Crossroads.\n"); 559 print ("$b\n"); 560 exit (0); 561 } 562 563 # Is a value in an array 564 # ---------------------- 565 sub inarray { 566 my $val = shift; 567 for my $other (@_) { 568 return (1) if ($other eq $val); 569 } 570 return (0); 571 } 572 573 # A connection is starting 574 # ------------------------ 575 sub start { 576 my ($ip, $stamp, $backend) = @_; 577 msg ("Logging START of connection for IP $ip on stamp $stamp, ", 578 "back end $backend\n"); 579 $db{$ip} = "$backend:$stamp"; 580 } 581 582 # A connection has ended 583 # ---------------------- 584 sub end { 585 my $ip = shift; 586 msg ("Logging END of connection for IP $ip\n"); 587 $db{$ip} = undef; 588 } 589 590 # Request to determine a back end 591 # ------------------------------- 592 sub dispatch { 593 my $ip = shift; 594 my $stamp = shift; 595 596 msg ("Request to dispatch IP $ip on stamp $stamp\n"); 597 598 # Read the next arguments. They are triplets of 599 # backend-name / availability / weight. Store if the back end is 600 # available. 601 my (@backends, @weights); 602 for (my $i = 0; $i < $#_; $i += 3) { 603 if ($_[$i + 1] != 0) { 604 push (@backends, $_[$i]); 605 push (@weights, $_[$i + 2]); 606 msg ("Candidate back end: $_[$i] with weight ", $_[$i + 2], "\n"); 607 } 608 } 609 610 # See if this is a reconnect by a previously seen client IP. We'll 611 # treat this as a reconnect if the timeout wasn't yet exceeded. 612 if ($db{$ip} ne '') { 613 my ($last_backend, $last_stamp) = split (/:/, $db{$ip}); 614 msg ("IP $ip had last connected on $last_stamp to $last_backend\n"); 615 if ($stamp < $last_stamp + $timeout) { 616 msg ("Timeout not yet exceeded, this may be a reconnect\n"); 617 # We'll allow a reconnect only if the stated last_backend is 618 # free (sanity check). 619 if (inarray ($last_backend, @backends)) { 620 msg ("Last back end $last_backend is available, ", 621 "letting through\n"); 622 reply ($last_backend); 623 } else { 624 msg ("Last used back end isn't free, suggesting a new one\n"); 625 } 626 } else { 627 msg ("Timeout exceeded, suggesting a new back end\n"); 628 } 629 } else { 630 msg ("Np preveious connection data, suggesting a new back end\n"); 631 } 632 633 my $bestweight = -1; 634 my $bestbackend; 635 for (my $i = 0; $i <= $#weights; $i++) { 636 if ($bestweight == -1 or $bestweight > $weights[$i]) { 637 $bestweight = $weights[$i]; 638 $bestbackend = $backends[$i]; 639 } 640 } 641 642 msg ("Best back end: $bestbackend (given weight $bestweight)\n"); 643 reply ($bestbackend); 644 } 645 646 # Main starts here 647 # ---------------- 648 msg ("Start of run, attaching GDBM database '$cdb'\n"); 649 tie (%db, 'GDBM_File', $cdb, &GDBM_WRCREAT, 0600); 650 651 # The first argument must be an action 'dispatch', 'start' or 'end'. 652 # Depending on the action, we do stuff. 653 my $action = shift (@ARGV); 654 if ($action eq 'dispatch') { 655 dispatch (@ARGV); 656 } elsif ($action eq 'start') { 657 start (@ARGV); 658 } elsif ($action eq 'end') { 659 end (@ARGV); 660 } else { 661 print STDERR ("Usage: rdp-helper {dispatch|start|end} args\n"); 662 exit (1); 663 }) 664 665 666 subsect(HTTP Session Stickiness) 667 668 This section focuses on HTTP session stickiness. This term refers to 669 the ability of a balancer to route a conversation between browser and 670 a backend farm always to the same back end. In other words: once a 671 back end is selected by the balancer, it will remain the back end of 672 choice, even for subsequent connections. 673 674 subsubsect(Don't use stickiness!) 675 676 The rule of thumb as far as the balancer is concerned, is: bf(Do not 677 use HTTP session stickiness unless you really have to.) Enabling 678 session stickiness hampers failover, balancing and performance: 679 680 itemization( 681 it() Failover is hampered because during the session, 682 the balancer has to assign new connections to the same back 683 end that was selected at the start of a session. If the back 684 end suddenly goes 'down', then the session will most likely 685 crash. (Actually, when a back end becomes unreachable in the 686 middle of a session, Crossroads will assign a new back end to 687 that session. This will most likely result in a malfunction 688 of the underlying application.) 689 it() Balancing is hampered because at the start of the session, 690 the balancer has selected the next-best back end. But during 691 the session, that back end may well become overloaded. The 692 balancer however must continue to send the requests there. 693 it() Performance is hampered because crossroads needs to 'unpack' 694 messages as they are passed to and fro. That's because 695 crossroads needs to check the HTTP headers in the messages 696 for persistence cookies.) 697 698 There is a number of measures that you can take to avoid using session 699 stickiness. E.g., session data can be 'shared' between web back 700 ends. PHP offers functionality to store session data in a database, so 701 that all PHP applications have access to these data. Application 702 servers such as Websphere can be configured to replicate session data 703 between nodes. 704 705 subsubsect(But if you must..) 706 707 However, if you bf(must) use session stickiness, then proceed as 708 follows: 709 710 itemization( 711 it() At the level of a tt(service) description, set the type to 712 tt(http). 713 it() At the level of each back end description, configure the 714 tt(stickycookie) and a tt(addclientheader) directives.) 715 716 Once crossroads sees that, it will examine each HTTP message that it 717 shuttles between client and back end: 718 719 itemization( 720 it() If there is no persistence cookie in the HTTP headers of a 721 client's request, then the message must be the first one and 722 a new session should be established. 723 Crossroads selects an appropriate back 724 end, sends the message to that back end, catches the reply, 725 and inserts a tt(Set-Cookie) directive. 726 it() If there is a persistence cookie in the HTTP headers of a 727 client's request, then the request is part of an already 728 established session. Crossroads analyzes the cookie and 729 forwards the request to the appropriate back end.) 730 731 Below is a short example of a configuration. 732 733 verb(\ 734 service www { 735 port 80; 736 type http; 737 revivinginterval 15; 738 dispatchmode byconnections; 739 740 backend one { 741 server 10.1.1.100:80; 742 stickycookie XRID=100; 743 addclientheader "Set-Cookie: XRID=100; Path=/"; 744 } 745 746 backend two { 747 server 10.1.1.101:80; 748 stickycookie XRID=101; 749 addclientheader "Set-Cookie: XRID=101; Path=/"; 750 } 751 }) 752 753 Note how the cookie names and values in the directives 754 tt(stickycookie) and tt(addclientheader) match. That is obviously a 755 prerequisite for stickiness. 756 757 758 subsect(Passing the client's IP address) 759 760 Since Crossroads just shuttles bytes to and fro, meta-information of 761 network connections is lost. As far as the back ends are concerned, 762 their connections originate at the Crossroads junction. 763 For example, standard Apache access logs will show the IP address of 764 Crossroads. 765 766 In order to compensate for this, Crossroads can insert a special 767 header in HTTP connections, to inform the back end of the original 768 client's IP address. In order to enable this, the Crossroads 769 configuration must state the following: 770 771 itemization( 772 it() The service type must be tt(http), and not tt(any); 773 it() In the back end definition, the following statement must 774 occur: nl() 775 tt(addserverheader "X-Real-IP: %r";) nl() 776 You are of course free to choose the header name; the here 777 used tt(X-Real-IP) is a common name for this purpose.) 778 779 After this, HTTP traffic that arrives at the back ends has a new 780 header: tt(X-Real-IP), holding the client's IP address. 781 bf(Note that) once the type is set to tt(http), Crossroads' 782 performance will be hampered -- all passing messages will have to be 783 unpacked and analyzed. 784 785 subsubsect(Sample Crossroads configuration) 786 787 The below sample configuration shows two HTTP back ends that receive 788 the client's IP address: 789 790 verb( 791 service www { 792 port 80; 793 type http; 794 revivinginterval 5; 795 dispatchmode roundrobin; 796 797 backend one { 798 server 10.1.1.100:80; 799 addserverheader "X-Real-IP: %r"; 800 } 801 802 backend two { 803 server 10.1.1.200:80; 804 addserverheader "X-Real-IP: %r"; 805 } 806 }) 807 808 809 subsubsect(Sample Apache configuration) 810 811 The method by which each back end analyzes the header tt(X-Real-IP) 812 will obviously be different per server implementations. However, a 813 common method with the Apache webserver is to log the client's IP 814 address into the access log. 815 816 Often this is accomplished using the log format tt(custom), defined as 817 follows: 818 819 verb(\ 820 LogFormat "%h %l %u %t %D \"%r\" %>s %b" common 821 CustomLog logs/access_log common) 822 823 The first line defines the format tt(common), with the remote host 824 specified by tt(%h). The second line sends access information to a log 825 file tt(logs/access_log), using the previously defined format 826 tt(common). 827 828 Furtunately, Apache's tt(LogFormat) allows one to log contents of 829 headers. By replacing the tt(%h) with tt(%{X-Real-IP}i), the desired 830 information is sent to the log. Therefore, normally you can simply 831 redefine the tt(common) format to 832 833 verb(\ 834 LogFormat "%{X-Real-IP}i %l %u %t %D \"%r\" %>s %b" common) 835 836 837 subsect(Debugging network traffic) 838 839 Incase the traffic between 840 client and backend 841 must be debugged, the statement tt(trafficlog) em(filename) can 842 be issued. This causes the traffic to be dumped in hexadecimal 843 format to the stated filename. 844 845 Traffic sent by the client is prefixed by a bf(C), traffic sent by 846 the back end is prefixed by a bf(B). Below is a sample traffic 847 dump of a browser trying to get a HTML page. The server replies 848 that the page was not modified. 849 850 verb(\ 851 C 0000 47 45 54 20 68 74 74 70 3a 2f 2f 77 77 77 2e 63 GET http://www.c 852 C 0010 73 2e 68 65 6c 73 69 6e 6b 69 2e 66 69 2f 6c 69 s.helsinki.fi/li 853 C 0020 6e 75 78 2f 6c 69 6e 75 78 2d 6b 65 72 6e 65 6c nux/linux-kernel 854 C 0030 2f 32 30 30 31 2d 34 37 2f 30 34 31 37 2e 68 74 /2001-47/0417.ht 855 C 0040 6d 6c 20 48 54 54 50 2f 31 2e 31 0d 0a 43 6f 6e ml HTTP/1.1..Con 856 C 0050 6e 65 63 74 69 6f 6e 3a 20 63 6c 6f 73 65 0d 0a nection: close.. 857 . 858 . etcetera 859 . 860 B 0000 48 54 54 50 2f 31 2e 30 20 33 30 34 20 4e 6f 74 HTTP/1.0 304 Not 861 B 0010 20 4d 6f 64 69 66 69 65 64 0d 0a 44 61 74 65 3a Modified..Date: 862 B 0020 20 54 75 65 2c 20 31 32 20 4a 75 6c 20 32 30 30 Tue, 12 Jul 200 863 B 0030 35 20 30 39 3a 34 39 3a 34 37 20 47 4d 54 0d 0a 5 09:49:47 GMT.. 864 B 0040 43 6f 6e 74 65 6e 74 2d 54 79 70 65 3a 20 74 65 Content-Type: te 865 B 0050 78 74 2f 68 74 6d 6c 3b 20 63 68 61 72 73 65 74 xt/html; charset 866 . 867 . etcetera 868 .) 869 870 Turning on traffic dumps will em(significantly) 871 slow down crossroads. 872 873 Besides tt(trafficlog), there is also a directive 874 tt(throughputlog). This directive also takes one argument, a 875 filename. The file is appended, and the following information is 876 logged: 877 878 itemization( 879 it() The process ID of the crossroads image that serves the 880 TCP connection; 881 it() The time of the request, in seconds and microseconds 882 since start of the run; 883 it() A bf(C) when the request originated at the client, or 884 bf(B) when the request originated at the back end; 885 it() The first 100 bytes of the request.) 886 887 As an example, consider the following (the lines are shortened for 888 brevity and prefixed by line numbers for clarity): 889 890 verb( 891 1 0000594 0.000001 C GET http://public.e-tunity.com/index.html... 892 2 0000594 0.173713 B HTTP/1.0 200 OK..Date: Fri, 18 Nov 2005 0... 893 3 0000594 0.278125 B width="100" bgcolor="#e0e0e0" valign="to... 894 4 0000595 0.000001 C GET http://public.e-tunity.com/css/style/... 895 5 0000594 0.944339 B /a></td>.. </tr>.</table>.</td><td class... 896 6 0000594 0.946356 B smallboxdownl">Download</td>.. <td class... 897 7 0000594 0.961102 B td><td class="smallboxodd" valign="top"><... 898 8 0000595 0.698215 B HTTP/1.0 304 Not Modified..Date: Fri, 18 ...) 899 900 This tells us that: 901 902 itemization( 903 it() Line 1: PID 594 served a request that originated at 904 the client. The corresponding time is (almost) 0 seconds, 905 so this is really the start of the run. 906 it() Line 2: A back end replied 0.17 seconds later, and 907 0.28 seconds later, it was still replying (this is the 908 third line, again a bf(B)-type transmission). 909 it() Line 4: PID 595 served a request that originated 910 at the client. Again, the corresponding time is (almost) 911 0 seconds, since this is the first conversation part of 912 this connection. 913 it() Lines 5 to 7: This is the continuation of line 2. Line 7 914 is the last line of the bf(B) series (not visible from 915 the example, but trust me, it is), so that we may 916 conclude that it took the back end 0.96 seconds to serve 917 the file tt(index.html) requested in line 1. 918 it() Line 8: This is the answer to the client's request of 919 line 4 (you can tell by the process ID number). 920 So the back end took 0.68 seconds to confirm that 921 the stylesheet requested in line 4 wasn't modified.) 922 923 It is also worth while remembering that the start time of a bf(C) 924 request is the time that crossroads sees the activity. Any latency 925 between the true client and crossroads is obviously not 926 included. This is illustrated by the below simple ASCII art: 927 928 verb( 929 client ---->---->---->--->*crossroads ====>====>====> 930 \ 931 back end 932 / 933 client ----<----<----<---< crossroads ====<====<====< 934 ) 935 936 This simple picture shows a typical HTTP request that originates 937 at a client, travels to crossroads, and is relayed via the back 938 end. The bf(C) entry in a throughput log is the time when 939 crossroads sees the request, indicated by an asterisk. The bf(B) 940 entries are the times that it takes the back end to answer, 941 indicated by tt(===) style lines. Therefore, the true roundtrip 942 time will be longer than the number of seconds that are logged in 943 the throughput log: the latency between client and crossroads 944 isn't included in that measurement. 945 946 Summarizing, the throughput times of a client-back end connection 947 can be analyzed using the directive tt(throughputlog). In a 948 real-world analysis, you'd probably want to write up a script to 949 analyze the output and to compute round trip times. Such scripts 950 are not (yet) included in Crossroads. 951 952 subsect(Limiting Access to Crossroads by Client IP Address) 953 954 subsubsect(General Examples) 955 956 The directives tt(allowfrom), tt(denyfrom), tt(allowfile) and 957 tt(denyfile) can be used to instruct Crossroads to specifically allow 958 access by using a "whitelist" of IP addresses, or to specifically deny 959 access by using a "blacklist". E.g., the following configuration 960 allows access to service tt(webproxy) only to em(localhost): 961 962 verb(\ 963 service webproxy { 964 port 8000; 965 allowfrom 127.0.0.1; 966 backend one { 967 . 968 . Back end definitions occur here 969 . 970 } 971 . 972 . Other back ends or other service directives 973 . may occur here 974 . 975 }) 976 977 In this example there is a "whitelist" having only one entry: IP 978 address 127.0.0.1, or em(localhost). (Incidentally, the same behaviour 979 could be accomplished by stating em(bindto 127.0.0.1), in which case 980 Crossroads would only listen to the local network device.) 981 982 In the same vein, the directive tt(allowfrom 127.0.0.1 192.168.1/24) 983 would allow access to em(localhost) and to all IP addresses that start 984 with 192.168.1. The specifier tt(192.168.1/24) states that there are 985 three network bytes (192, 168 and 1), and 24 bits (or 3 bytes) are 986 relevant; so that the fourth network byte doesn't matter. 987 988 subsubsect(Using External Files) 989 990 The directives tt(allowfile) and tt(denyfile) allow you to specify IP 991 addresses in external files. The Crossroads configuration states 992 e.g. tt(allowfile /tmp/allow.txt), and the IP addresses are then in 993 tt(/tmp/allow.txt). The format of tt(/tmp/allow.txt) is as follows: 994 995 itemization( 996 it() The specifications follow again em(p.q.r.s/mask), where 997 p, q, r and s are network bytes which can be left out on the 998 right hand side when the mask allows it; 999 1000 it() The specifications must be separated by white space 1001 (spaces, tabs or newlines).) 1002 1003 E.g., the following is a valid example of an external specification 1004 file: 1005 1006 verb(\ 1007 127.0.0.1 1008 192.168.1/24 1009 10/8) 1010 1011 When external files are in effect, then the signal tt(SIGHUP) (1) 1012 causes Crossroads to reload the external file. E.g., while Crossroads 1013 is running, you may edit tt(/tmp/allow.txt), and then issue tt(killall 1014 -1 crossroads). The new contents of tt(/tmp/allow.txt) will be 1015 reloaded. 1016 1017 subsubsect(Mixing Directives) 1018 1019 Crossroads allows to mix all directives in one service 1020 description. However, some mixes are less meaningful than others. It's 1021 up to you to take this into account. 1022 1023 The following rules apply: 1024 1025 itemization( 1026 it() Blacklisting and whitelisting can be used together. When 1027 combined, the blacklist will always be interpreted 1028 first. E.g., consider the following directives: 1029 1030 verb(\ 1031 allowfrom 192.168.1/24 1032 denyfrom 192.168.1.100) 1033 1034 Given the fact that the deny list is checked first, client 1035 192.168.1.100 won't be able to access Crossroads. Then the 1036 allow list will be checked, stating that all clients whose IP 1037 address starts with 192.168.1 may connect. The effect will be 1038 that e.g., client 192.168.1.1 may connect, 192.168.1.2 may 1039 connect too, 192.168.1.100 will be blocked, and 10.1.1.1 will 1040 be blocked as well. 1041 1042 Now consider the following directives: 1043 1044 verb(\ 1045 allowfrom 192.168.1.100 127.0.0.1 1046 denyfrom 192.168.1/24) 1047 1048 This will first of all deny access to all IP addresses that 1049 start with 192.168.1. So the rule that allows 192.168.1.100 1050 won't ever be effective. The net result will be that access 1051 will be granted to 127.0.0.1, and IP addresses that don't 1052 match 192.168.1/24. 1053 1054 it() Blacklisting or whitelisting can be left out. 1055 A list is considered empty when no appropriate directives 1056 occur in tt(/etc/crossroads.conf), or when the directive 1057 points to an empty or non-existent external file. 1058 1059 it() Using tt(*from) and tt(*file) statements is allowed, but 1060 doesn't make sense. E.g., the following configuration sample 1061 is such a case: 1062 1063 verb(\ 1064 allowfrom 127.0.0.1 192.168.1/24 1065 allowfile /tmp/allow.txt) 1066 1067 There is a technical reason for this. Once Crossroads 1068 processes the tt(allowfile) directive, then the whole 1069 whitelist is cleared (thereby removing the entries 127.0.0.1 1070 and 192.168.1/24), and new entries are reloaded from the 1071 file. The net result is that the tt(allowfrom) specification 1072 is overruled. 1073 1074 Crossroads doesn't check for such configurations, which are 1075 syntactially correct, but make no semantic sense.) 1076 1077 1078 subsect(Configuration examples) 1079 1080 As a general hint, use tt(crossroads sampleconf) to view the most 1081 up-to-date examples of configurations. The description below shows a 1082 few examples too. 1083 1084 1085 subsubsect(A load balancer for three webserver back ends) 1086 1087 The following configuration example binds crossroads to port 80 of the 1088 current server, and distributes the load over three back ends. This 1089 configuration shows most of the possible settings. 1090 1091 verb(\ 1092 service www { 1093 /* We don't need session stickyness. */ 1094 type any; 1095 1096 /* Port on which we'll listen in this service: required. */ 1097 port 8000; 1098 1099 /* What IP address should this service listen? Default is 'any'. 1100 * Alternatively you can state an explicit IP address, such as 1101 * 127.0.0.1; that would bind the service only to 'localhost'. */ 1102 bindto any; 1103 1104 /* Verbose reporting or not. Default is off. */ 1105 verbosity on; 1106 1107 /* Dispatching mode, or: How to select a back end for an incoming 1108 * request. Possible values: 1109 * roundrobin: just the next back end in line 1110 * random: like roundrobin, but at random to make things more 1111 * confusing. Probably only good for testing. 1112 * bysize: The backend that transferred the least nr of bytes 1113 * is the next in line. As a modifier you can say e.g. 1114 * bysize over 10, meaning that the 10 last connections will 1115 * be used to compute the transfer size, instead of all 1116 * transfers. 1117 * byduration: The backend that was active for the shortest time 1118 * is the next in line. As a modifier you can say e.g. 1119 * byduration of 10 to compute over the last 10 connections. 1120 * byconnections: The back end with the least active connections 1121 * is the next ine line. 1122 * byorder: The first available back end is always taken. 1123 */ 1124 dispatchmode byduration over 5; 1125 1126 /* Interval at which we'll check whether a temporarily unavailable 1127 * backend has woken up. 1128 */ 1129 revivinginterval 5; 1130 1131 /* TCP backlog of connections. Default is 0 (no backlog, one 1132 * connection may be active). 1133 */ 1134 backlog 5; 1135 1136 /* For status reporting: a shared memory key. Default is the same 1137 * as the port number, OR-ed by a magic number. 1138 */ 1139 shmkey 8000; 1140 1141 /* This controls when crossroads should consider a connection as 1142 * finished even when the TCP sockets weren't closed. This is to 1143 * avoid hanging connections that don't do anything. NOTE THAT when 1144 * crossroads cuts off a connection due to timeout exceed, this is 1145 * not marked as a failure, but as a success. Default is 0: no timeout. 1146 */ 1147 connectiontimeout 300; 1148 1149 /* The max number of allowed client connections. When present, connections 1150 * won't be accepted if the max is about to be exceeded. When 1151 * absent, all connections will be accepted, which might be misused 1152 * for a DOS attack. 1153 */ 1154 maxconnections 300; 1155 1156 /* Now let's define a couple of back ends. Number 1: */ 1157 backend www_backend_1 { 1158 /* The server and its port, the minimum configuration. */ 1159 server httpserver1; 1160 port 9010; 1161 /* The 'decay' of usage data of this back end. Only relevant 1162 * when the whole service has 'dispatchmode bysize' or 1163 * 'byduration'. The number is a percentage by which the usage 1164 * parameter is decreased upon each connection of an other back 1165 * end. 1166 */ 1167 decay 10; 1168 1169 /* To see what's happening in /var/log/messages: */ 1170 verbosity on; 1171 } 1172 1173 /* The second one: */ 1174 backend www_backend_2 { 1175 /* Server and port */ 1176 server httpserver2; 1177 port 9011; 1178 1179 /* Verbosity of reporting when this back end is active */ 1180 verbosity on; 1181 1182 /* Decay */ 1183 decay 10; 1184 1185 /* This back end is twice as weak as the first one */ 1186 weight 2; 1187 1188 /* Event triggers for system commands upon succesful activation 1189 * and upon failure. 1190 */ 1191 onsuccess echo 'success on backend 2' | mail root; 1192 onfailure echo 'failure on backend 2' | mail root; 1193 } 1194 1195 /* And yet another one.. this time we will dump the traffic 1196 * to a trace file. Furthermore we don't want more than 10 concurrent 1197 * connections here. Note that there's also a total maxconnections for the 1198 * whole service. 1199 */ 1200 backend www_backend_3 { 1201 server httpserver3; 1202 verbosity on; 1203 port 9000; 1204 verbosity on; 1205 decay 10; 1206 trafficlog /tmp/backend.3.log; 1207 maxconnections 10; 1208 } 1209 }) 1210 1211 subsubsect(An HTTP forwarder when travelling) 1212 1213 As another example, here's my tt(crossroads.conf) that I use on my 1214 Unix laptop. The problem that I face is that I need many HTTP proxy 1215 configurations (at home, at customers' sites and so on) but I'm too 1216 lazy to reconfigure browsers all the time. 1217 1218 Here's how it used to be before crossroads: 1219 1220 itemization( 1221 it() At home, I would surf through a squid proxy on my local 1222 machine. The browser proxy setting is then 1223 tt(http://localhost:3128). 1224 1225 it() Sometimes I start up an SSH tunnel to our offices. The 1226 tunnel has a local port 3129, and connects to a squid proxy on 1227 our e-tunity server. Hence, the browser proxy is then 1228 tt(http://localhost:3129). 1229 1230 it() At a customer's location I need the proxy 1231 tt(http://10.120.34.113:8080), because they have configured it 1232 so. 1233 1234 it() And in yet other instances, I use a HTTP diagnostic tool 1235 url(Charles)(http://www.xk72.com/charles) 1236 that sits between browser and website and shows me 1237 what's happening. I run charles on my own machine and it 1238 listens to port 8888, behaving like a proxy. The browser 1239 configuration for the proxy is then 1240 tt(http://localhost:8888).) 1241 1242 Here's how it works with a crossroads configuration: 1243 1244 itemization( 1245 it() I have configured my browsers to use 1246 tt(http://localhost:8080) as the proxy. For all situations. 1247 1248 it() I use the following crossroads configuration, and let 1249 crossroads figure out which proxy backend works, and which 1250 doesn't. Note two particularities: 1251 1252 itemization( 1253 it() The statement tt(dispatchmode byorder). This 1254 makes sure that once crossroads determines which 1255 backend works, it will stick to it. This usage of 1256 crossroads doesn't need to balance over more than one 1257 back end. 1258 1259 it() The statement tt(bindto 127.0.0.1) makes sure 1260 that requests from other interfaces than loopback 1261 won't get serviced.) 1262 1263 verb(\ 1264 service HttpProxy { 1265 port 8080; 1266 bindto 127.0.0.1; 1267 verbosity on; 1268 dispatchmode byorder; 1269 revivinginterval 15; 1270 1271 backend Charles { 1272 server localhost:8888; 1273 verbosity on; 1274 } 1275 1276 backend CustomerProxy { 1277 server 10.120.34.113:8080; 1278 verbosity on; 1279 } 1280 1281 backend SshTunnel { 1282 server localhost:3129; 1283 } 1284 1285 backend LocalSquid { 1286 server localhost:3128; 1287 } 1288 })) 1289 1290 As a final note, the commandline argument tt(tell) can be used to 1291 influence crossroad's own detection mechanism of back end availability 1292 detection. E.g., if in the above example the back ends tt(SshTunnel) 1293 and tt(LocalSquid) are both active, then tt(crossroads tell httpproxy 1294 sshtunnel down) will 'take down' the back end tt(SshTunnel) -- and 1295 will automatically cause crossroads to switch to tt(LocalSquid). 1296 1297 1298 subsubsect(SSH login with enforced idle logout) 1299 1300 The following example shows how crossroads 'throttles' SSH 1301 logins. Connections are accepted on port 1302 22 (the normal SSH port) and forwarded to the actual SSH daemon 1303 which is running on port 2222. 1304 1305 Note the usage of the 1306 tt(connectiontimeout) directive. This makes sure that users are logged 1307 out after 10 minutes of inactivity. Note also the tt(maxconnections) 1308 setting, this makes sure that no more than 10 concurrent logins occur. 1309 1310 verb(\ 1311 service Ssh { 1312 port 22; 1313 backlog 5; 1314 maxconnections 10; 1315 connectiontimeout 600; 1316 backend TrueSshDaemon { 1317 server localhost:2222; 1318 } 1319 })