haproxy-cluster 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](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:
|