pazuzu 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ *~
2
+ *.bak
3
+ /_trash
4
+ *.sublime-workspace
5
+ *.sublime-project
6
+ /.rspec
7
+ /Gemfile.lock
8
+ /.rvmrc
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ $:.push File.expand_path("../lib", __FILE__)
4
+ require "pazuzu/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "pazuzu"
8
+ s.version = Pazuzu::VERSION
9
+ s.authors = ["Alexander Staubo"]
10
+ s.email = ["alex@origo.no"]
11
+ s.homepage = "http://github.com/origo/pazuzu"
12
+ s.summary = s.description = %{Pazuzu is a supervisor daemon that manages pools of application processes as daemons.}
13
+
14
+ s.rubyforge_project = "pazuzu"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_runtime_dependency 'activesupport', '~> 3.1.0'
22
+ s.add_runtime_dependency 'statemachine', '~> 2.0.1'
23
+ s.add_runtime_dependency 'yajl-ruby', '~> 1.1'
24
+ s.add_runtime_dependency 'i18n'
25
+ s.add_runtime_dependency 'scashin133-syslog_logger', '~> 1.7.3'
26
+
27
+ s.add_development_dependency "rspec"
28
+ s.add_development_dependency "rake"
29
+ end
@@ -0,0 +1,171 @@
1
+ Pazuzu
2
+ ======
3
+
4
+ Pazuzu is a supervisor daemon that manages pools of application processes as daemons. Pazuzu uses Heroku's `Procfile` format and relies on Linux cgroups to encapsulate groups of process.
5
+
6
+ ....
7
+ ......
8
+ ..=?$.. .
9
+ ... ...N~,.... .....
10
+ .. ..... . ...M8O.... ......
11
+ ......NZ$,.. . ...DD8.... . $O$+....
12
+ .$8~Z7$. . ....:N88ZO:Z.... ..8ZOD=..
13
+ . ...8OO7Z$+.... . ..88Z:I=8$?.. ....ZO7I$M.....
14
+ ....DZ+$ZZZ8... . .....?DN878=,~..... . ...N7ODOIDO...
15
+ ....D=ZZ7Z$$?D..... ..M$D8D$D8=I~.... ...N$88IDO8O:....
16
+ ...OOZ$ZO$$?OO.... ...8D8ND8?MM,,... ....8+DZZ7$ZDOO.. .
17
+ ....OZN7$~8Z$7$$.......88O88$~?DO.......=OZZOO8MDOZODN... ..
18
+ .,D$7I7ZZ$?+$Z8,......ZDDDND8M,......M$OZNDO$NODM8+I..
19
+ ..MDO$Z$DMD+~:Z8:......DDNMM?I,....8Z8Z8Z8N?Z$DDO8D...
20
+ . ~+D7IN?Z=IOD$ONN.....N8N8~$D..IIOZ78Z$8Z$DO?ZMOOM...
21
+ ...?7DNO=?IDOO8OONDNNNNNNN8OII$I=$Z8Z78D8NZZZZDDD. ..
22
+ .....MZ$DNDDN?ZZ8DNN8DDDNNNDO7$I$+++ZOOOOO8$NMDI8...
23
+ ....:DOO8NOOZOO8DNNO88DO78O87~~77D=7OO$8OO?NZ?M...
24
+ ...O8ODN$87O8DD8NNND8Z++II:77?7~$$OZOZ$7D+8. .
25
+ .,8ONNDO~=Z8ZONNNNO=~==:8IN87I=OZZ7ID$7.,..
26
+ ...,8OND88+DZ8DNNNNNDO87+D7D8$$:7Z7Z8Z?.....
27
+ ... ...DNN88NNDONNNNNNNNMZO7$DO8ZZ$7$O7, ...
28
+ . ...MNDDDDO88DNNNNNDOOMD$DNN=77777,......
29
+ ..,D8ZDO8NDNNNNDD88D78NND8O,$O$.....
30
+ ...OONN8ODDNNNNDD8ZOI$DNDZ8Z7OO?..
31
+ ... .O78O8OZ8DNNNNNNZOI=ODNDOZI~78+...
32
+ ..8O$OZ88NNNNNNNDD8O+$ONN8DD$IZD...
33
+ .ZDD8OZZDODNDDNNDD$8I+DDNND87O$$$...
34
+ ....N7IOZ8O$O$DNNNNND7O$~,DNDOOZI+78...
35
+ ..=8D8DDNDODZNNNDDNDZOZ:?8DNNZ$~O$8.. .
36
+ ..8DN88DZDDDDNNNNDND8OOO$D8NN88$?=7?...
37
+ . ...8OM8N8D8DDNNNNNNNDZO7Z+DDNN?I7IM8O...
38
+ . ..MDDMODOND88NNN8NDMNO$$Z7ZINNMN$NNIZ,
39
+ ..8O88888DDODDNNDNNNON78Z+Z8DMN=N:O8Z?..
40
+ . .8888OD88M8DNNDONNNM+DOD,+Z$88OZIMD=I..
41
+ . M8OOO8NDO+8NNNOOZZMDNN8DO?$ZZOO8IDDOM... .
42
+ ....8878+8ZONNNN.DDOZ$..DDNDOIMI7888=8OOO+....
43
+ ..MO88O8O=8D88..8DOOO..DNN8$.Z88Z=O8Z=DIZ.. .
44
+ ..DO8ZO8Z$D=D8..ZD8Z. .NDNDM.?O8OZ$?8$8Z8....
45
+ . ..DIDDD8OO78D. .Z8DD...,DND$..8OOD87D$$IM=...
46
+ ...8DDO7OI$D$MO. .OND8,...NDD.. .M88I~N7DIDI..
47
+ ...DZDMOZ$O88.....DDODM...DDZ.....NZ77O?Z?88...
48
+ ...8D~D7D8ZO: ....DDNDDM..DDD7......=O8D$DDOD...
49
+ ..DOD8888.... ..8NNDD,.ZDD87... ...DOZN+87... .
50
+ ...... ......... ,8DND8Z.D$MOO...... ...,O.$$...
51
+ . ......... ....DNDDN+NDN8+8,... . ........
52
+ . . . ...DDDDD.MDN,7Z$. . ...
53
+ ....DDND:.MDMM.~Z....... ..
54
+ ......D8DDN..8DOZNIZ.........
55
+ ... ... .7Z8DDDDDI,.NOD=O7.... ..
56
+ .. ..........:$$$NDNN87?MNI?IM..........
57
+ . ...........~D=?D$ODNNZD8=Z7=~++++++... .
58
+ ..?????????8ZO7$Z8NNDN$O$?=++++++++....
59
+ ...???????II$ZOZ7O+7OD$=OOI$=+++++++,...
60
+ ...I+++++??I+D7O$8$?IM7:88=OO?++++++?..
61
+ ...7++++++???$ZZZZZZ$Z$Z77~~+I+++++++:
62
+ .....?+++++??I++8DO8DON87+,+7O$?++++++++...
63
+ .....?+++++++??++?++?$ODZ$8$O$$Z=+++++++...
64
+
65
+ Introduction
66
+ ------------
67
+
68
+ Pazuzu controls *applications*. An application consists of *workers*. Each worker has a name and a command line. Each worker may have multiple *instances* of itself running.
69
+
70
+ For example, a typical application may consist of:
71
+
72
+ * A Rails app, running under some web server like Thin or Unicorn.
73
+ * A WebSockets app listening on a socket.
74
+ * Some queue-processing daemons.
75
+ * Some maintenance jobs that run now and then.
76
+
77
+ Applications are configured through a simple text format:
78
+
79
+ <key>:<command line>
80
+
81
+ For example:
82
+
83
+ main: bundle exec unicorn -c config/unicorn.rb config.ru
84
+ queue_processor: bundle exec ruby app/queue_processor.rb
85
+ web_socket_server: node lib/server.js
86
+
87
+ Pazuzu will control the lifecycle of each worker. Main points:
88
+
89
+ * Workers can be started and stopped by issuing appropriate commands.
90
+ * Workers will be monitored and respawned if they crash.
91
+ * If the desired number of workers is changed, new worker instances will be spawned or terminated as needed.
92
+ * Recent output from workers is kept so that it can be viewed.
93
+ * If Pazuzu crashes, workers will continue to run, and will be reattached when Pazuzu is restarted.
94
+
95
+ Pazuzu leaves the following to external tools:
96
+
97
+ * Resource limiting. Use the Linux cgroups tools to limit CPU and memory usage.
98
+ * Scaling. Use external tools to monitor load, and invoke the `pazuzu` command to change the number of workers.
99
+
100
+ Installation
101
+ ------------
102
+
103
+ gem install pazuzu
104
+
105
+ Requirements
106
+ ------------
107
+
108
+ * Ruby 1.9.1 or later.
109
+ * Linux 2.6.24 or later with [cgroups](http://www.kernel.org/doc/Documentation/cgroups/) enabled in the kernel configuration.
110
+ * The [libcg](http://libcg.git.sourceforge.net/) binaries, which include `cgcreate` and `cgexec`.
111
+
112
+ Getting started
113
+ ---------------
114
+
115
+ Create a minimal configuration `/etc/pazuzu/pazuzu.conf`:
116
+
117
+ applications:
118
+ myapp:
119
+ procfile: /srv/myapp
120
+
121
+ Create a minimal /srv/myapp/Procfile:
122
+
123
+ myworker: sleep 1h
124
+
125
+ Start Pazuzu (non-daemonized):
126
+
127
+ sudo pazuzud
128
+
129
+ Configuration file
130
+ ------------------
131
+
132
+ `log_path`: Set the log file. Specify `syslog` to use syslog. Defaults to stderr.
133
+
134
+ `socket_path`: Where to create a socket for accepting commands. Defaults to `/var/run/pazuzud.socket`.
135
+
136
+ `include`: Include another configuration file or a number of files. May be either a single file name/glob pattern or an array of file names/glob patterns (eg., `/etc/pazuzu/conf.d/*.conf`). The included files's keys are merged into the current configuration in the order of inclusion.
137
+
138
+ `cgroups`: Has the following sub-keys:
139
+
140
+ * `hiearchy_root`: Root of hierarchy for all cgroups created. Defaults to `pazuzu`.
141
+
142
+ * `subsystems`: An array of cgroups subsystem to attach each cgroup to. Defaults to `memory`, `cpu`, `cpuacct` and `blkio` (essentially all subsystems at the time of writing).
143
+
144
+ * `fs_root`: Where the cgroups file system is mounted. Defaults to `/sys/fs/cgroup`, which may or may not be the default for your Linux distribution.
145
+
146
+ `applications`: This is where applications are listed. Each application has its own key. For example:
147
+
148
+ applications:
149
+ foo:
150
+ procfile: /srv/foo
151
+
152
+ Each application has the following sub-keys:
153
+
154
+ * `procfile` (*required*): The path to the procfile. If a directory, it's assumed that it contains a file named `Procfile`.
155
+ * `user`: User name or UID to run the process as. Defaults to the user that Pazuzu runs as.
156
+ * `group`: Group name or GID to run the process as. Defaults to the group that Pazuzu runs as.
157
+ * `workers`: This key allows per-worker overrides. Each worker has its own key, which is the name specified in the `Procfile`.
158
+
159
+ For example:
160
+
161
+ applications:
162
+ foo:
163
+ procfile: /srv/foo
164
+ workers:
165
+ bar:
166
+ num_instances: 2
167
+
168
+ Each worker has the following sub-keys:
169
+
170
+ * `command_line`: Override the worker's command line.
171
+ * `num_instances`: The number of instances to run. Defaults to 1.
@@ -0,0 +1,21 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ desc 'Bump version'
4
+ task :bump do
5
+ if `git status -uno -s --porcelain | wc -l`.to_i > 0
6
+ abort "You have uncommitted changed."
7
+ end
8
+ text = File.read('lib/pazuzu/version.rb')
9
+ if text =~ /VERSION = '(.*)'/
10
+ old_version = $1
11
+ version_parts = old_version.split('.')
12
+ version_parts[-1] = version_parts[-1].to_i + 1
13
+ new_version = version_parts.join('.')
14
+ text.gsub!(/VERSION = '(.*)'/, "VERSION = '#{new_version}'")
15
+ File.open('lib/pazuzu/version.rb', 'w') { |f| f << text }
16
+ (system("git add lib/pazuzu/version.rb") and
17
+ system("git commit -m 'Bump to #{new_version}.'")) or abort "Failed to commit."
18
+ else
19
+ abort "Could not find version number"
20
+ end
21
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pazuzu'
4
+
5
+ controller = Pazuzu::CommandLine::Controller.new
6
+ controller.run!(ARGV)
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pazuzu'
4
+
5
+ runner = Pazuzu::SupervisorRunner.new
6
+ runner.run!(ARGV)
@@ -0,0 +1,37 @@
1
+ require 'set'
2
+ require 'optparse'
3
+ require 'timeout'
4
+ require 'fileutils'
5
+ require 'yaml'
6
+ require 'singleton'
7
+ require 'logger'
8
+ require 'delegate'
9
+ require 'etc'
10
+ require 'thread'
11
+ require 'socket'
12
+
13
+ require 'statemachine'
14
+ require 'active_support/core_ext/hash'
15
+ require 'yajl/json_gem'
16
+
17
+ require 'pazuzu/utility/annotated_logger'
18
+ require 'pazuzu/utility/rate_limiter'
19
+ require 'pazuzu/utility/process_spawning'
20
+ require 'pazuzu/utility/runnable'
21
+ require 'pazuzu/utility/runnable_pool'
22
+ require 'pazuzu/utility/output_tailer'
23
+
24
+ require 'pazuzu/control/socket_server'
25
+ require 'pazuzu/control/socket_client'
26
+ require 'pazuzu/control/protocol'
27
+
28
+ require 'pazuzu/command_line/controller'
29
+
30
+ require 'pazuzu/supervisor'
31
+ require 'pazuzu/procfiles'
32
+ require 'pazuzu/application'
33
+ require 'pazuzu/instance'
34
+ require 'pazuzu/worker'
35
+ require 'pazuzu/supervisor_runner'
36
+ require 'pazuzu/version'
37
+ require 'pazuzu/cgroup'
@@ -0,0 +1,114 @@
1
+ module Pazuzu
2
+
3
+ class Application
4
+
5
+ include Utility::Runnable
6
+
7
+ def initialize(supervisor, name)
8
+ super()
9
+ @supervisor = supervisor
10
+ @name = name
11
+ @logger = Utility::AnnotatedLogger.new(supervisor.logger, qname)
12
+ @workers = Utility::RunnablePool.new
13
+ @mutex = Mutex.new
14
+ end
15
+
16
+ def configure!(procfile_path, configuration)
17
+ procfile_path = Procfiles.normalize_procfile_path(procfile_path)
18
+
19
+ root_path = File.dirname(procfile_path)
20
+
21
+ @user = configuration['user']
22
+ @group = configuration['group']
23
+
24
+ workers_config = configuration['workers'] || {}
25
+
26
+ leftover_workers = @workers.children.dup
27
+
28
+ procfile = YAML.load(File.open(procfile_path))
29
+ procfile = {} unless procfile.is_a?(Hash)
30
+ procfile.each do |name, command_line|
31
+ name = name.to_s
32
+
33
+ worker_config = workers_config[name] || {}
34
+
35
+ worker = @workers.children.select { |w| w.name == name }.first
36
+ if worker
37
+ leftover_workers.delete(worker)
38
+ else
39
+ @logger.info "Adding worker #{name}"
40
+ worker = Worker.new(self, name)
41
+ end
42
+ worker.configure!(root_path, command_line, worker_config)
43
+ @workers.register(worker)
44
+ end
45
+
46
+ leftover_workers.each do |worker|
47
+ @logger.info "Removing worker #{worker.name}"
48
+ @workers.unregister(worker)
49
+ end
50
+ end
51
+
52
+ def qname
53
+ @name
54
+ end
55
+
56
+ # Call to set up a forked process according to the application's
57
+ # configuration.
58
+ def setup_spawned_process!
59
+ Utility::ProcessSpawning.set_user_and_group!(@user, @group)
60
+ Utility::ProcessSpawning.prepare_child_process!
61
+ end
62
+
63
+ def workers
64
+ @workers.children
65
+ end
66
+
67
+ def log_entries
68
+ entries = @workers.children.map(&:log_entries)
69
+ entries.flatten!(1)
70
+ entries.sort_by! { |(source, time, message)| time }
71
+ entries
72
+ end
73
+
74
+ attr_reader :name
75
+ attr_reader :supervisor
76
+ attr_reader :user
77
+ attr_reader :group
78
+
79
+ private
80
+
81
+ def on_starting
82
+ @logger.info "Starting"
83
+ @monitor_thread ||= Thread.start { monitor_workers }
84
+ @workers.start!
85
+ runnable_state.started!
86
+ end
87
+
88
+ def on_started
89
+ @logger.info "Started"
90
+ end
91
+
92
+ def on_stopping
93
+ @logger.info "Stopping"
94
+ @workers.stop!
95
+ end
96
+
97
+ def monitor_workers
98
+ while [:starting, :running, :stopping].include?(run_state)
99
+ if runnable_state.state == :stopping
100
+ unless @workers.children.any? { |w| w.run_state == :running }
101
+ runnable_state.stopped!
102
+ end
103
+ end
104
+ sleep(1)
105
+ end
106
+ rescue Exception => e
107
+ @logger.error(e.message)
108
+ ensure
109
+ @monitor_thread = nil
110
+ end
111
+
112
+ end
113
+
114
+ end
@@ -0,0 +1,73 @@
1
+ module Pazuzu
2
+
3
+ class Cgroup
4
+
5
+ class CgroupCreationFailed < Exception; end
6
+
7
+ class << self
8
+ # Are cgroups available?
9
+ def available?
10
+ File.exist?('/proc/cgroups')
11
+ end
12
+ end
13
+
14
+ def initialize(fs_root_path, path, subsystems)
15
+ @fs_root_path = fs_root_path
16
+ @path = path
17
+ @subsystems = subsystems
18
+ end
19
+
20
+ def mounted?
21
+ return @subsystems.any? { |subsystem|
22
+ Dir.glob(File.join(@fs_root_path, subsystem, @path, "cgroup.procs")).any? { |file_name|
23
+ File.exists?(file_name)
24
+ }
25
+ }
26
+ end
27
+
28
+ def exec(command)
29
+ unless system("cgcreate -g #{@subsystems.join(',')}:#{@path}")
30
+ raise CgroupCreationFailed, "Could not create cgroup hiearchy #{@path}"
31
+ end
32
+ command = "cgexec -g #{@subsystems.join(',')}:#{@path} #{command}"
33
+ return Process.exec(command)
34
+ end
35
+
36
+ def pids
37
+ pids = pids_including_children
38
+ pids.reject { |pid| pids.include?(parent_pid_for(pid)) }
39
+ end
40
+
41
+ attr_reader :path
42
+
43
+ private
44
+
45
+ def pids_including_children
46
+ return @subsystems.flat_map { |subsystem|
47
+ Dir.glob(File.join(@fs_root_path, subsystem, @path, "cgroup.procs")).flat_map { |file_name|
48
+ begin
49
+ File.read(file_name).split("\n").map { |s| Integer(s) }
50
+ rescue Errno::ENOENT
51
+ nil
52
+ end
53
+ }
54
+ }.compact.tap(&:uniq!)
55
+ end
56
+
57
+ def parent_pid_for(pid)
58
+ begin
59
+ File.readlines("/proc/#{pid}/status").each do |line|
60
+ return $1.to_i if line =~ /^PPid:\s*(\d+)/i
61
+ end
62
+ rescue Errno::ENOENT
63
+ end
64
+ nil
65
+ end
66
+
67
+ def subsystem_path(subsystem)
68
+ File.join(@fs_root_path, subsystem, @path)
69
+ end
70
+
71
+ end
72
+
73
+ end