haproxyctl 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +23 -0
- data/README.md +314 -0
- data/Rakefile +2 -0
- data/bin/haproxyctl +164 -0
- data/haproxyctl +164 -0
- data/haproxyctl.gemspec +18 -0
- data/install-haproxy/haproxy_src_install.sh +82 -0
- data/lib/haproxyctl.rb +122 -0
- data/lib/haproxyctl/environment.rb +71 -0
- data/lib/haproxyctl/version.rb +3 -0
- data/rhapr/.gitignore +4 -0
- data/rhapr/.rspec +1 -0
- data/rhapr/Gemfile +4 -0
- data/rhapr/Rakefile +14 -0
- data/rhapr/lib/rhapr.rb +6 -0
- data/rhapr/lib/rhapr/environment.rb +99 -0
- data/rhapr/lib/rhapr/interface.rb +111 -0
- data/rhapr/lib/rhapr/version.rb +3 -0
- data/rhapr/rhapr.gemspec +22 -0
- data/rhapr/spec/config_fixtures/basic_haproxy.cfg +34 -0
- data/rhapr/spec/config_fixtures/crappy_haproxy.cfg +4 -0
- data/rhapr/spec/config_fixtures/pid_test_haproxy.cfg +6 -0
- data/rhapr/spec/quality_spec.rb +11 -0
- data/rhapr/spec/rhapr/environment_spec.rb +132 -0
- data/rhapr/spec/rhapr/interface_spec.rb +82 -0
- data/rhapr/spec/spec_helper.rb +11 -0
- data/rhapr/spec/support/config_fixtures.rb +53 -0
- data/rhapr/spec/support/custom_matchers.rb +44 -0
- metadata +97 -0
data/haproxyctl
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# HAProxy control script to start, stop, restart, configcheck, etc, as
|
4
|
+
# well as communicate to the stats socket.
|
5
|
+
#
|
6
|
+
# See https://github.com/flores/haproxyctl/README
|
7
|
+
#
|
8
|
+
# This line here is just for Redhat users who like "service haproxyctl blah"
|
9
|
+
# chkconfig: 2345 80 30
|
10
|
+
# description: HAProxy is a fast and reliable load balancer for UNIX systems
|
11
|
+
# HAProxyctl is an easy way to do init shit and talk to its stats socket
|
12
|
+
#
|
13
|
+
|
14
|
+
require 'pathname'
|
15
|
+
lib = File.join(File.dirname(Pathname.new(__FILE__).realpath), '../lib')
|
16
|
+
$:.unshift lib unless $:.include?(lib)
|
17
|
+
|
18
|
+
require 'haproxyctl'
|
19
|
+
include HAProxyCTL
|
20
|
+
|
21
|
+
argument = ARGV.join(' ')
|
22
|
+
|
23
|
+
unless has_exec?
|
24
|
+
puts usage if argument =~ /help/ || ARGV.length < 1
|
25
|
+
|
26
|
+
raise "Cannot find haproxy executable. Please ensure it is in your $PATH, or set $HAPROXY_BIN environment variable."
|
27
|
+
end
|
28
|
+
|
29
|
+
display_usage! if argument =~ /help/ || ARGV.length < 1
|
30
|
+
|
31
|
+
begin
|
32
|
+
case argument
|
33
|
+
when "start"
|
34
|
+
if pidof
|
35
|
+
raise("haproxy is already running on pid #{pidof}!")
|
36
|
+
else
|
37
|
+
start
|
38
|
+
end
|
39
|
+
when "stop"
|
40
|
+
stop(check_running)
|
41
|
+
when "restart"
|
42
|
+
if pidof
|
43
|
+
stop(pidof)
|
44
|
+
stillpidof = check_running()
|
45
|
+
while stillpidof == pidof
|
46
|
+
puts "still haven't killed old pid. waiting 3s for existing connections to die... (ctrl+c to stop)"
|
47
|
+
sleep 3
|
48
|
+
stillpidof = check_running() || 0
|
49
|
+
end
|
50
|
+
start()
|
51
|
+
else
|
52
|
+
puts "haproxy was not running. starting..."
|
53
|
+
start()
|
54
|
+
end
|
55
|
+
when "reload"
|
56
|
+
if ( pidof )
|
57
|
+
reload(pidof)
|
58
|
+
else
|
59
|
+
puts "haproxy not running. starting..."
|
60
|
+
start()
|
61
|
+
end
|
62
|
+
when "status"
|
63
|
+
if ( pidof )
|
64
|
+
puts "haproxy is running on pid #{pidof}.\nthese ports are used and guys are connected:"
|
65
|
+
system("lsof -ln -i |awk \'$2 ~ /#{pidof}/ {print $8\" \"$9}\'")
|
66
|
+
else
|
67
|
+
puts "haproxy is not running"
|
68
|
+
end
|
69
|
+
when "configcheck"
|
70
|
+
puts `#{exec} -c -f #{config_path}`
|
71
|
+
when "nagios"
|
72
|
+
if ( pidof )
|
73
|
+
puts "OK"
|
74
|
+
exit
|
75
|
+
else
|
76
|
+
puts "CRITICAL: HAProxy is not running!"
|
77
|
+
exit(2)
|
78
|
+
end
|
79
|
+
when "cloudkick"
|
80
|
+
if ( pidof )
|
81
|
+
puts "status ok haproxy is running"
|
82
|
+
conn = `lsof -ln -i |grep -c #{pidof}`.chomp.to_i
|
83
|
+
# removes the listener
|
84
|
+
conn = conn - 1
|
85
|
+
puts "metric connections int #{conn}"
|
86
|
+
status=unixsock("show stat")
|
87
|
+
status.each do |line|
|
88
|
+
line = line.split(',')
|
89
|
+
if (line[0] !~ /^#/)
|
90
|
+
host = "#{line[0]}_#{line[1]}"
|
91
|
+
puts "metric #{host}_request_rate int #{line[47]}" if line[47].to_i > 0
|
92
|
+
puts "metric #{host}_total_requests gauge #{line[49]}" if line[49].to_i > 0
|
93
|
+
puts "metric #{host}_health_check_duration int #{line[35]}" if line[35].to_i > 0
|
94
|
+
puts "metric ${host}_current_queue int #{line[3]}" if line[3].to_i > 0
|
95
|
+
end
|
96
|
+
end
|
97
|
+
else
|
98
|
+
puts "status err haproxy is not running!"
|
99
|
+
end
|
100
|
+
when "show health"
|
101
|
+
status=unixsock("show stat")
|
102
|
+
status.each do |line|
|
103
|
+
data = line.split(',')
|
104
|
+
printf "%-30s %-30s %-7s %3s\n", data[0], data[1], data[17], data[18]
|
105
|
+
end
|
106
|
+
when /show backend(s?)/
|
107
|
+
status=unixsock("show stat").grep(/BACKEND/)
|
108
|
+
status.each do |line|
|
109
|
+
data = line.split(',')
|
110
|
+
printf "%-30s %-30s %-7s %3s\n", data[0], data[1], data[17], data[18]
|
111
|
+
end
|
112
|
+
when /disable all EXCEPT (.+)/
|
113
|
+
servername=$1
|
114
|
+
status=unixsock("show stat")
|
115
|
+
backend = status.grep(/#{servername}/)
|
116
|
+
backend.each do |line|
|
117
|
+
backend_group = line.split(',')
|
118
|
+
status.each do |pool|
|
119
|
+
data = pool.split(',')
|
120
|
+
if ( (data[0] == backend_group[0]) && ( data[1] !~ /#{servername}|BACKEND|FRONTEND/ ) && ( data[17] = 'UP' ) )
|
121
|
+
unixsock("disable server #{data[0]}/#{data[1]}")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
when /disable all (.+)/
|
126
|
+
servername=$1
|
127
|
+
status=unixsock("show stat")
|
128
|
+
status.each do |line|
|
129
|
+
data = line.split(',')
|
130
|
+
if ( ( data[1] = servername ) && ( data[17] = 'UP' ) )
|
131
|
+
unixsock("disable server #{data[0]}/#{servername}")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
when /enable all EXCEPT (.+)/
|
135
|
+
servername=$1
|
136
|
+
status=unixsock("show stat")
|
137
|
+
backend = status.grep(/#{servername}/)
|
138
|
+
backend.each do |line|
|
139
|
+
backend_group = line.split(',')
|
140
|
+
status.each do |pool|
|
141
|
+
data = pool.split(',')
|
142
|
+
if ( (data[0] == backend_group[0]) && ( data[1] !~ /#{servername}|BACKEND|FRONTEND/ ) && ( data[17] =~ /Down|MAINT/i ) )
|
143
|
+
unixsock("enable server #{data[0]}/#{data[1]}")
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
when /enable all (.+)/
|
148
|
+
servername=$1
|
149
|
+
status=unixsock("show stat")
|
150
|
+
status.each do |line|
|
151
|
+
data = line.split(',')
|
152
|
+
if ( ( data[1] = servername ) && ( data[17] =~ /Down|MAINT/i ) )
|
153
|
+
unixsock("enable server #{data[0]}/#{servername}")
|
154
|
+
end
|
155
|
+
end
|
156
|
+
when 'version'
|
157
|
+
version
|
158
|
+
else
|
159
|
+
puts unixsock(argument)
|
160
|
+
end
|
161
|
+
rescue Errno::ENOENT => e
|
162
|
+
STDERR.puts e
|
163
|
+
exit 1
|
164
|
+
end
|
data/haproxyctl.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/haproxyctl/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Carlo Flores"]
|
6
|
+
gem.email = ["github@petalphile.com"]
|
7
|
+
gem.description = %q{This is a simple wrapper to make life with HAProxy a little more convenient. Acts as an init script for start, stop, reload, restart, etc. Leverages 'socket' to enable and disable servers on the fly. Formats server weight and backends in a readable way. Provides Nagios and Cloudkick health checks. Compatible with RHEL chkconfig/service.}
|
8
|
+
gem.summary = %q{Wrapper to talk to the HAProxy socket, as well as regular init (start|stop|status|etc)}
|
9
|
+
gem.homepage = "https://github.com/flores/haproxyctl"
|
10
|
+
gem.rubyforge_project = "haproxyctl"
|
11
|
+
gem.license = "MIT"
|
12
|
+
gem.files = `git ls-files`.split($\)
|
13
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
14
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
15
|
+
gem.name = "haproxyctl"
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
gem.version = HAProxyCTL::VERSION
|
18
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
#!/bin/bash -ex
|
2
|
+
#
|
3
|
+
# This installs latest HAProxy from source along with HAProxyCTL
|
4
|
+
#
|
5
|
+
# It will clobber files and stuff and is only meant as a very
|
6
|
+
# quick and dirty (but sometimes handy) installer.
|
7
|
+
#
|
8
|
+
|
9
|
+
STARTINGDIR=$PWD
|
10
|
+
|
11
|
+
# make sure we have make, pcre and junk
|
12
|
+
if [ -e /etc/redhat-release ]; then
|
13
|
+
OS=redhat;
|
14
|
+
elif [ -e /etc/debian_version ]; then
|
15
|
+
OS=debian;
|
16
|
+
fi
|
17
|
+
|
18
|
+
if [ $OS ]; then
|
19
|
+
if [ $OS = 'redhat' ]; then
|
20
|
+
yum install -y pcre-devel make gcc libgcc git;
|
21
|
+
elif [ $OS = 'debian' ]; then
|
22
|
+
apt-get update;
|
23
|
+
apt-get install -y libpcre3 libpcre3-dev build-essential libgcc1 git;
|
24
|
+
fi
|
25
|
+
else
|
26
|
+
echo -e "I only understand Debian/RedHat/CentOS and this box does not appear to be any.\nExiting.\n- love, $0.";
|
27
|
+
exit 2;
|
28
|
+
fi
|
29
|
+
|
30
|
+
# grab last stable. HAProxy's site versions nicely - these will still be here after the next update
|
31
|
+
mkdir /usr/local/src || echo "Oops, /usr/local/src exists!"
|
32
|
+
cd /usr/local/src || exit 2
|
33
|
+
wget http://haproxy.1wt.eu/download/1.4/src/haproxy-1.4.20.tar.gz
|
34
|
+
|
35
|
+
# get rid of an existing haproxy
|
36
|
+
if [ -e /usr/local/haproxy ]; then
|
37
|
+
rm -fr /usr/local/haproxy
|
38
|
+
fi
|
39
|
+
|
40
|
+
# check the checksum
|
41
|
+
MD5CHECK=`md5sum /usr/local/src/haproxy-1.4.20.tar.gz |awk '{print $1}'`
|
42
|
+
if [ "$MD5CHECK" != "0cd3b91812ff31ae09ec4ace6355e29e" ] ; then
|
43
|
+
echo -e "MD5s do not match!\nBailing.";
|
44
|
+
exit 2;
|
45
|
+
fi
|
46
|
+
|
47
|
+
tar xvfz haproxy-1.4.20.tar.gz
|
48
|
+
cd haproxy-1.4.20
|
49
|
+
|
50
|
+
if uname -a | grep x86_64 ; then
|
51
|
+
make TARGET=linux26 CPU=x86_64 USE_PCRE=1
|
52
|
+
else
|
53
|
+
make TARGET=linux26 CPU=686 USE_PCRE=1
|
54
|
+
fi
|
55
|
+
|
56
|
+
make install
|
57
|
+
|
58
|
+
if [ -e /usr/sbin/haproxy ]; then
|
59
|
+
rm -f /usr/sbin/haproxy
|
60
|
+
fi
|
61
|
+
|
62
|
+
ln -s /usr/local/sbin/haproxy /usr/sbin/haproxy
|
63
|
+
|
64
|
+
# grab carlo's haproxyctl script/init
|
65
|
+
cd /usr/local
|
66
|
+
if [ -e /usr/local/haproxy ]; then
|
67
|
+
cd haproxy;
|
68
|
+
git pull;
|
69
|
+
else
|
70
|
+
git clone https://github.com/flores/haproxyctl.git
|
71
|
+
ln -s /usr/local/haproxyctl/haproxyctl /etc/init.d/haproxyctl
|
72
|
+
fi
|
73
|
+
|
74
|
+
# remove make and gcc
|
75
|
+
if [ $OS = 'redhat' ]; then
|
76
|
+
chkconfig --add haproxyctl;
|
77
|
+
yum remove -y gcc make
|
78
|
+
elif [ $OS = 'debian' ]; then
|
79
|
+
apt-get purge -y build-essential
|
80
|
+
fi
|
81
|
+
|
82
|
+
cd $STARTINGDIR
|
data/lib/haproxyctl.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'haproxyctl/version'
|
2
|
+
require 'haproxyctl/environment'
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
module HAProxyCTL
|
6
|
+
include Environment
|
7
|
+
|
8
|
+
def start
|
9
|
+
puts "starting haproxy..."
|
10
|
+
system("#{exec} -f #{config_path} -D -p #{pidfile}")
|
11
|
+
newpid = check_running()
|
12
|
+
if ( newpid =~ /^\d+$/ )
|
13
|
+
puts "haproxy is running on pid #{newpid}"
|
14
|
+
return true
|
15
|
+
else
|
16
|
+
puts "error. haproxy did not start!"
|
17
|
+
return nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def stop(pid)
|
22
|
+
if pid
|
23
|
+
puts "stopping haproxy on pid #{pid}..."
|
24
|
+
system("kill #{pid}") || system("kill -9 #{pid}")
|
25
|
+
puts "... stopped"
|
26
|
+
else
|
27
|
+
puts "haproxy is not running!"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def reload(pid)
|
32
|
+
if ( pid )
|
33
|
+
puts "gracefully stopping connections on pid #{pid}..."
|
34
|
+
system("#{exec} -f #{config_path} -sf #{pid}")
|
35
|
+
puts "checking if connections still alive on #{pid}..."
|
36
|
+
nowpid = check_running()
|
37
|
+
while ( pid == nowpid )
|
38
|
+
puts "still haven't killed old pid.
|
39
|
+
waiting 2s for existing connections to die...
|
40
|
+
(ctrl+c to stop this check)"
|
41
|
+
sleep 2
|
42
|
+
nowpid = check_running() || 0
|
43
|
+
end
|
44
|
+
puts "reloaded haproxy on pid #{nowpid}"
|
45
|
+
else
|
46
|
+
puts "haproxy is not running!"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def unixsock(command)
|
51
|
+
|
52
|
+
output=[]
|
53
|
+
runs = 0
|
54
|
+
|
55
|
+
begin
|
56
|
+
ctl=UNIXSocket.open(socket)
|
57
|
+
if (ctl)
|
58
|
+
ctl.write "#{command}\r\n"
|
59
|
+
else
|
60
|
+
puts "cannot talk to #{socket}"
|
61
|
+
end
|
62
|
+
rescue Errno::EPIPE
|
63
|
+
ctl.close
|
64
|
+
sleep 0.5
|
65
|
+
runs += 1
|
66
|
+
if ( runs < 4 )
|
67
|
+
retry
|
68
|
+
else
|
69
|
+
puts "the unix socket at #{socket} closed before we could complete this request"
|
70
|
+
exit
|
71
|
+
end
|
72
|
+
end
|
73
|
+
while (line = ctl.gets) do
|
74
|
+
unless ( line =~ /Unknown command/ )
|
75
|
+
output << line
|
76
|
+
end
|
77
|
+
end
|
78
|
+
ctl.close
|
79
|
+
|
80
|
+
return output
|
81
|
+
end
|
82
|
+
|
83
|
+
def display_usage!
|
84
|
+
puts usage
|
85
|
+
exit
|
86
|
+
end
|
87
|
+
|
88
|
+
def usage
|
89
|
+
<<-USAGE
|
90
|
+
usage: #{$0} <argument>
|
91
|
+
where <argument> can be:
|
92
|
+
start : start haproxy unless it is already running
|
93
|
+
stop : stop an existing haproxy
|
94
|
+
restart : immediately shutdown and restart
|
95
|
+
reload : gracefully terminate existing connections, reload #{config_path}
|
96
|
+
status : is haproxy running? on what ports per lsof?
|
97
|
+
configcheck : check #{config_path}
|
98
|
+
nagios : nagios-friendly status for running process and listener
|
99
|
+
cloudkick : cloudkick.com-friendly status and metric for connected users
|
100
|
+
show health : show status of all frontends and backend servers
|
101
|
+
show backends : show status of backend pools of servers
|
102
|
+
enable all <server> : re-enable a server previously in maint mode on multiple backends
|
103
|
+
disable all <server> : disable a server from every backend it exists
|
104
|
+
enable all EXCEPT <server> : like 'enable all', but re-enables every backend except for <server>
|
105
|
+
disable all EXCEPT <server> : like 'disable all', but disables every backend except for <server>
|
106
|
+
clear counters : clear max statistics counters (add 'all' for all counters)
|
107
|
+
help : this message
|
108
|
+
prompt : toggle interactive mode with prompt
|
109
|
+
quit : disconnect
|
110
|
+
show info : report information about the running process
|
111
|
+
show stat : report counters for each proxy and server
|
112
|
+
show errors : report last request and response errors for each proxy
|
113
|
+
show sess [id] : report the list of current sessions or dump this session
|
114
|
+
get weight : report a server's current weight
|
115
|
+
set weight : change a server's weight
|
116
|
+
set timeout : change a timeout setting
|
117
|
+
disable server : set a server in maintenance mode
|
118
|
+
enable server : re-enable a server that was previously in maintenance mode
|
119
|
+
version : version of this script
|
120
|
+
USAGE
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module HAProxyCTL
|
2
|
+
module Environment
|
3
|
+
attr_accessor :pidof, :config_path, :config, :exec
|
4
|
+
|
5
|
+
def version
|
6
|
+
puts "HAProxyCTL #{HAProxyCTL::VERSION}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def config_path
|
10
|
+
@config_path ||= ENV['HAPROXY_CONFIG'] || '/etc/haproxy/haproxy.cfg'
|
11
|
+
end
|
12
|
+
|
13
|
+
def config
|
14
|
+
@config ||= File.read(config_path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def has_exec?
|
18
|
+
!exec.nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
def exec
|
22
|
+
return(@exec) if @exec
|
23
|
+
|
24
|
+
@exec = ENV['HAPROXY_BIN']
|
25
|
+
@exec ||= `which haproxy`.chomp
|
26
|
+
|
27
|
+
if @exec.empty?
|
28
|
+
begin
|
29
|
+
`haproxy -v 2>/dev/null`
|
30
|
+
@exec = 'haproxy'
|
31
|
+
rescue Errno::ENOENT => e
|
32
|
+
@exec = nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
return(@exec)
|
37
|
+
end
|
38
|
+
|
39
|
+
def socket
|
40
|
+
@socket ||= begin
|
41
|
+
config.match /stats socket \s*([^\s]*)/
|
42
|
+
$1 || raise("Expecting 'stats socket <UNIX_socket_path>' in #{config_path}")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def pidfile
|
47
|
+
if config.match(/pidfile \s*([^\s]*)/)
|
48
|
+
@pidfile = $1
|
49
|
+
else
|
50
|
+
std_pid = "/var/run/haproxy.pid"
|
51
|
+
if File.exists?(std_pid)
|
52
|
+
@pidfile = std_pid
|
53
|
+
else
|
54
|
+
raise("Expecting 'pidfile <pid_file_path>' in #{config_path} or a pid file in #{std_pid}")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [String, nil] Returns the PID of HAProxy as a string, if running. Nil otherwise.
|
60
|
+
def check_running
|
61
|
+
pid = File.read(pidfile)
|
62
|
+
pid.strip!
|
63
|
+
|
64
|
+
# verify this pid exists and is haproxy
|
65
|
+
if pid =~ /^\d+$/ and `ps -p #{pid} -o cmd=` =~ /#{exec}/
|
66
|
+
return pid
|
67
|
+
end
|
68
|
+
end
|
69
|
+
alias :pidof :check_running
|
70
|
+
end
|
71
|
+
end
|