haproxy-cluster 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +12 -6
- data/lib/haproxy_cluster.rb +47 -29
- data/lib/haproxy_cluster/cli.rb +1 -1
- data/lib/haproxy_cluster/member.rb +3 -3
- data/lib/haproxy_cluster/version.rb +1 -1
- metadata +28 -6
data/README.md
CHANGED
@@ -15,7 +15,7 @@ While there are already a handfull of [HA Proxy](http://haproxy.1wt.edu) abstrac
|
|
15
15
|
|
16
16
|
`haproxy_cluster` provides a shell scripting interface for `HAProxyCluster`. Exit codes are meaningful and intended to be useful from Nagios.
|
17
17
|
|
18
|
-
Do you deploy new code using a sequential restart of application servers? Using this common pattern carelessly can result in too many servers being down at the same time, and cutomers seeing errors. `haproxy_cluster` can prevent this by ensuring that every load balancer agrees that
|
18
|
+
Do you deploy new code using a sequential restart of application servers? Using this common pattern carelessly can result in too many servers being down at the same time, and cutomers seeing errors. `haproxy_cluster` can prevent this by ensuring that every load balancer agrees that all (or enough) servers are up at each stage in the deployment. In the example below, we will deploy a new WAR to three Tomcat instances which are fronted by two HA Proxy instances. HA Proxy has been configured with `option httpchk /check`, a path which only returns an affirmative status code when the application is ready to serve requests.
|
19
19
|
|
20
20
|
```bash
|
21
21
|
#!bin/bash
|
@@ -24,29 +24,31 @@ servers="server1.example.com server2.example.com server3.example.com"
|
|
24
24
|
load_balancers="https://lb1.example.com:8888 http://lb2.example.com:8888"
|
25
25
|
|
26
26
|
for server in $servers ; do
|
27
|
-
haproxy_cluster --
|
27
|
+
haproxy_cluster --eval "wait_until(:condition => true){ myapp.rolling_restartable? }" $load_balancers
|
28
28
|
scp myapp.war $server:/opt/tomcat/webapps
|
29
29
|
done
|
30
30
|
```
|
31
31
|
|
32
|
-
The code block passed to `--eval` will not return until every load balancer reports that at least 80% of the backend servers defined for "myapp" are ready to serve requests. If this takes more than 5 minutes (300 seconds), the whole deployment is halted.
|
32
|
+
The code block passed to `--eval` will not return until every load balancer reports that at least 80% of the backend servers defined for "myapp" are ready to serve requests (or all of them are down). This condition must pass 3 times in a row for it to be considered valid. If this takes more than 5 minutes (300 seconds), the whole deployment is halted.
|
33
33
|
|
34
34
|
Maybe you'd like to know how many transactions per second your whole cluster is processing.
|
35
35
|
|
36
|
-
|
36
|
+
```bash
|
37
|
+
haproxy_cluster --eval 'poll{ puts members.map{|m|m.myapp.rate}.inject(:+) }' $load_balancers
|
38
|
+
```
|
37
39
|
|
38
40
|
Installation
|
39
41
|
------------
|
40
42
|
|
41
43
|
`gem install haproxy-cluster`
|
42
44
|
|
43
|
-
Requires Ruby 1.9.
|
45
|
+
Requires Ruby 1.9.3 and depends on RestClient.
|
44
46
|
|
45
47
|
Non-Features
|
46
48
|
------------
|
47
49
|
|
48
50
|
* Doesn't try to modify configuration files. Use [haproxy-tools](https://github.com/subakva/haproxy-tools), [rhaproxy](https://github.com/jjuliano/rhaproxy), [haproxy_join](https://github.com/joewilliams/haproxy_join), or better yet, [Chef](http://www.opscode.com/chef) for that.
|
49
|
-
* Doesn't talk to sockets, yet. Use [haproxy-ruby](https://github.com/inkel/haproxy-ruby) for now if you need this. I intend to add support for this using `Net::SSH` and `socat(1)` but for now HTTP is enough for my needs.
|
51
|
+
* Doesn't talk to sockets, yet. Use [haproxy-ruby](https://github.com/inkel/haproxy-ruby) for now if you need this. I intend to add support for this using `Net::SSH` and `socat(1)` but for now HTTP is enough for my needs. Please comment on Issue \#6 if you care about this feature.
|
50
52
|
|
51
53
|
ProTip
|
52
54
|
------
|
@@ -56,3 +58,7 @@ HA Proxy's awesome creator Willy Tarrreau loves [big text files](http://haproxy.
|
|
56
58
|
* http://code.google.com/p/haproxy-docs/
|
57
59
|
* http://cbonte.github.com/haproxy-dconv/configuration-1.5.html
|
58
60
|
|
61
|
+
|
62
|
+
<hr/>
|
63
|
+
[![Build Status](https://secure.travis-ci.org/jelder/haproxy_cluster.png)](http://travis-ci.org/jelder/haproxy_cluster)
|
64
|
+
|
data/lib/haproxy_cluster.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
require 'haproxy_cluster/member'
|
2
|
+
require 'timeout'
|
2
3
|
require 'thread'
|
3
4
|
|
4
5
|
class HAProxyCluster
|
6
|
+
|
7
|
+
Inf = +1.0/0.0
|
8
|
+
NegInf = -1.0/0.0
|
9
|
+
|
5
10
|
def initialize(members = [])
|
6
11
|
@members = []
|
7
12
|
threads = []
|
@@ -21,51 +26,59 @@ class HAProxyCluster
|
|
21
26
|
first = true
|
22
27
|
loop do
|
23
28
|
start = Time.now
|
24
|
-
|
29
|
+
each_member { poll! } unless first
|
25
30
|
first = false
|
26
31
|
yield
|
27
32
|
sleep interval - (Time.now - start)
|
28
33
|
end
|
29
34
|
end
|
30
35
|
|
31
|
-
# Poll the entire cluster
|
32
|
-
#
|
33
|
-
# range).
|
36
|
+
# Poll the entire cluster until the the given block's return value always
|
37
|
+
# matches the condition (expressed as boolean or range).
|
34
38
|
#
|
35
|
-
#
|
39
|
+
# This block would not return until every member of the cluster is available
|
40
|
+
# to serve requests.
|
36
41
|
#
|
37
|
-
# wait_for(true) do
|
42
|
+
# wait_for(:condition => true) do
|
38
43
|
# api.servers.map{|s|s.ok?}
|
39
44
|
# end
|
40
45
|
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
46
|
+
# The constants `Inf` and `NegInf` (representing infinity and negative
|
47
|
+
# infinity respectively) are available, which enables less than/greater than
|
48
|
+
# expressions in the form of `Range`s.
|
49
|
+
#
|
50
|
+
# Parameters:
|
51
|
+
#
|
52
|
+
# * :condition, anything accepted by `check_condition`
|
53
|
+
# * :interval, check interval (default 2 seconds, same as HA Proxy)
|
54
|
+
# * :timeout, give up after this number of seconds
|
55
|
+
# * :min_checks, require :condtion to pass this many times in a row
|
56
|
+
#
|
57
|
+
def wait_until (options = {}, &code)
|
58
|
+
opts = {
|
59
|
+
:condition => true,
|
60
|
+
:interval => 2.0,
|
61
|
+
:timeout => 300,
|
62
|
+
:min_checks => 3
|
63
|
+
}.merge options
|
64
|
+
Timeout::timeout(opts[:timeout]) do
|
65
|
+
history = []
|
66
|
+
loop do
|
67
|
+
results = each_member(&code)
|
68
|
+
|
69
|
+
# Break out as soon as we reach :min_checks in a row
|
70
|
+
history << check_condition(opts[:condition], results.values.flatten)
|
71
|
+
return true if history.last(opts[:min_checks]) == [true] * opts[:min_checks]
|
72
|
+
|
73
|
+
sleep opts[:interval]
|
74
|
+
each_member { poll! }
|
58
75
|
end
|
59
|
-
delay *= 2
|
60
|
-
sleep delay
|
61
|
-
map { poll! }
|
62
|
-
results = map(&code)
|
63
76
|
end
|
64
77
|
end
|
65
78
|
|
66
79
|
# Run the specified code against every memeber of the cluster. Results are
|
67
80
|
# returned as a Hash, with member.to_s being the key.
|
68
|
-
def
|
81
|
+
def each_member (&code)
|
69
82
|
threads = []
|
70
83
|
results = {}
|
71
84
|
@members.each do |member|
|
@@ -80,7 +93,12 @@ class HAProxyCluster
|
|
80
93
|
# Return true or false depending on the relationship between `condition` and `values`.
|
81
94
|
# `condition` may be specified as true, false, or a Range object.
|
82
95
|
# `values` is an Array of whatever type is appropriate for the condition.
|
83
|
-
|
96
|
+
#
|
97
|
+
# check_condition(0!=1, [true]) #=> true
|
98
|
+
# check_condition(1==1, [true]) #=> true
|
99
|
+
# check_condition(3..Inf, [2,2]) #=> false
|
100
|
+
# check_condition(true, [true,false]) #=> raise Timeout::timeout
|
101
|
+
def check_condition (condition, values)
|
84
102
|
case condition.class.to_s
|
85
103
|
when "Range"
|
86
104
|
values.each{ |v| return false unless condition.cover? v }
|
data/lib/haproxy_cluster/cli.rb
CHANGED
@@ -20,13 +20,13 @@ class HAProxyCluster
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def poll!
|
23
|
-
case @type
|
23
|
+
csv = case @type
|
24
24
|
when :url
|
25
|
-
|
25
|
+
RestClient.get(@source + ';csv')
|
26
26
|
when :file
|
27
27
|
File.read(@source)
|
28
28
|
end
|
29
|
-
CSV.parse(csv, { :headers => :first_row, :converters => :all, :header_converters => [:downcase,:symbol] } ) do |row|
|
29
|
+
CSV.parse(csv.gsub(/^# /,'').gsub(/,$/,''), { :headers => :first_row, :converters => :all, :header_converters => [:downcase,:symbol] } ) do |row|
|
30
30
|
case row[:type]
|
31
31
|
when BACKEND
|
32
32
|
@backends[ row[:pxname].to_sym ].stats.merge! row.to_hash
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: haproxy-cluster
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-07-
|
12
|
+
date: 2012-07-31 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rest-client
|
16
|
-
requirement: &
|
16
|
+
requirement: &70230919355820 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,8 +21,30 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
25
|
-
|
24
|
+
version_requirements: *70230919355820
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: test-unit
|
27
|
+
requirement: &70230919354840 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70230919354840
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: webmock
|
38
|
+
requirement: &70230919353540 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70230919353540
|
47
|
+
description: ! " Gem and command line tool for quickly answering questions like,
|
26
48
|
\"Can we\n survive a rolling restart?\", \"How many transactions per second am
|
27
49
|
I seeing?\",\n \"What's my session backlog?\". Intended for use within continuous
|
28
50
|
deployment\n and monitoring solutions.\n"
|
@@ -66,6 +88,6 @@ rubyforge_project:
|
|
66
88
|
rubygems_version: 1.8.11
|
67
89
|
signing_key:
|
68
90
|
specification_version: 3
|
69
|
-
summary:
|
91
|
+
summary: A DSL for inspecting and manipulating groups of HA Proxy instances.
|
70
92
|
test_files: []
|
71
93
|
has_rdoc:
|