skyeye 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ pkg
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "aws-sdk"
4
+ gem "open4"
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Greg Fodor
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,5 @@
1
+ SkyEye
2
+ ======
3
+
4
+ SkyEye is a gem that allows the creation and management of nagios-like alerts
5
+ using CloudWatch.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/skyeye ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $: << File.dirname(__FILE__) + "/../lib"
4
+
5
+ require "aws-sdk"
6
+ require "skyeye"
7
+
8
+ @exec = SkyEye::Exec.new
9
+
10
+ trap("INT") do
11
+ @exec.shutdown!
12
+ end
13
+
14
+ trap("TERM") do
15
+ @exec.shutdown!
16
+ end
17
+
18
+ @exec.go(*ARGV)
19
+
@@ -0,0 +1,48 @@
1
+ :namespace: posto-dev
2
+
3
+ :topics:
4
+ - posto-sauron-alerts
5
+
6
+ :alarm_periods:
7
+ :warning: 5
8
+ :critical: 1
9
+
10
+ :watches:
11
+ :instance:
12
+ - :name: posto-mysql
13
+ :command: /usr/local/sbin/check_tcp -H localhost -p 3306
14
+ :interval: 5
15
+ - :name: CPUUtilization
16
+ :comparison_operator: GreaterThanThreshold
17
+ :warning: 50
18
+ :critical: 80
19
+ :elb:
20
+ - :name: HealthyHostCount
21
+ :comparison_operator: LessThanThreshold
22
+ :warning: 2
23
+ :critical: 1
24
+ :statistic: Minimum
25
+ :period: 1
26
+ :rds:
27
+ - :name: CPUUtilization
28
+ :comparison_operator: GreaterThanThreshold
29
+ :warning: 50
30
+ :critical: 80
31
+ :statistic: Average
32
+ :period: 2
33
+ - :name: DatabaseConnections
34
+ :comparison_operator: GreaterThanThreshold
35
+ :critical: 128
36
+ :statistic: Maximum
37
+ :period: 1
38
+ :elasticache:
39
+ - :name: CmdGet
40
+ :comparison_operator: GreaterThanThreshold
41
+ :critical: 6000
42
+ :period: 1
43
+ - :name: SwapUsage
44
+ :comparison_operator: GreaterThanThreshold
45
+ :critical: 0
46
+ :statistic: Minimum
47
+ :period: 5
48
+
@@ -0,0 +1,300 @@
1
+ require "logger"
2
+ require "pp"
3
+ require "json"
4
+ require "open4"
5
+
6
+ module SkyEye
7
+ class Exec
8
+ attr_accessor :config
9
+
10
+ def instance_id
11
+ "i-d3b842a1"
12
+ end
13
+
14
+ def go(*args)
15
+ config_file = ENV["SKYEYE_CONFIG_FILE"] || "/etc/skyeye.yml"
16
+
17
+ unless File.exists?(config_file)
18
+ puts "Missing #{config_file}. You can set the path to this file by exporting SKYEYE_CONFIG_FILE."
19
+ return
20
+ end
21
+
22
+ @config = YAML.load(config_file)
23
+ @logger = Logger.new(STDOUT)
24
+ @mutex = Mutex.new
25
+
26
+ if args.size == 0
27
+ start!
28
+ elsif args[0] == "register:instance"
29
+ load_or_register_topics!
30
+ register_instance_alarms!
31
+ elsif args[0] == "deregister:instance"
32
+ deregister_instance_alarms!
33
+ elsif args[0] == "deregister:aws"
34
+ deregister_aws_alarms!
35
+ elsif args[0] == "register:aws"
36
+ load_or_register_topics!
37
+ register_aws_alarms!
38
+ else
39
+ puts "unknown command #{args[0]}"
40
+ end
41
+ end
42
+
43
+ def deregister_instance_alarms!
44
+ deregister_alarms_matching!(/^skyeye::instance::#{instance_id}::/)
45
+ end
46
+
47
+ def deregister_aws_alarms!
48
+ @config[:watches].keys.each do |resource_type|
49
+ unless resource_type == :instance
50
+ deregister_alarms_matching!(/^skyeye::#{resource_type}::/)
51
+ end
52
+ end
53
+ end
54
+
55
+ def deregister_alarms_matching!(pattern)
56
+ cw = AWS::CloudWatch.new
57
+ alarms = cw.client.describe_alarms
58
+
59
+ to_delete = []
60
+
61
+ alarms.data[:metric_alarms].each do |alarm|
62
+ if alarm[:alarm_name] =~ pattern
63
+ to_delete << alarm[:alarm_name]
64
+ end
65
+ end
66
+
67
+ if to_delete.size > 0
68
+ @logger.info "De-Register Alarms #{to_delete.inspect}"
69
+ cw.alarms.delete(*to_delete)
70
+ end
71
+ end
72
+
73
+ def register_aws_alarms!
74
+ @config[:watches].each do |resource_type, watches|
75
+ dimension_values = dimension_values_for_resource_type(resource_type)
76
+ register_alarms_for_watches!(resource_type, dimension_values) if dimension_values
77
+ end
78
+ end
79
+
80
+ def dimension_values_for_resource_type(resource_type)
81
+ values = []
82
+
83
+ case resource_type
84
+ when :elb
85
+ AWS::ELB.new.load_balancers.each do |load_balancer|
86
+ values << [{ name: "LoadBalancerName", value: load_balancer.name }]
87
+ end
88
+ when :rds
89
+ AWS::RDS.new.db_instances.each do |db_instance|
90
+ values << [{ name: "DBInstanceIdentifier", value: db_instance.db_instance_identifier }]
91
+ end
92
+ when :elasticache
93
+ AWS::ElastiCache.new.client.describe_cache_clusters(show_cache_node_info: true).data[:cache_clusters].each do |cluster|
94
+ cluster[:cache_nodes].each do |node|
95
+ values << [{ name: "CacheClusterId", value: cluster[:cache_cluster_id] },
96
+ { name: "CacheNodeId", value: node[:cache_node_id] }]
97
+ end
98
+ end
99
+ when :sqs
100
+ AWS::SQS.new.queues.each do |queue|
101
+ values << [{ name: "QueueName", value: queue.arn.split(/:/)[-1] }]
102
+ end
103
+ end
104
+
105
+ values
106
+ end
107
+
108
+ def namespace_for_resource_type(resource_type)
109
+ case resource_type
110
+ when :instance
111
+ return "AWS/EC2"
112
+ when :elb
113
+ return "AWS/ELB"
114
+ when :rds
115
+ return "AWS/RDS"
116
+ when :elasticache
117
+ return "AWS/ElastiCache"
118
+ when :sqs
119
+ return "AWS/SQS"
120
+ end
121
+ end
122
+
123
+ def register_instance_alarms!
124
+ cw = AWS::CloudWatch.new
125
+
126
+ watches = @config[:watches][:instance]
127
+ dimension_values = [[{ name: "InstanceId", value: instance_id }]]
128
+
129
+ register_alarms_for_watches!(:instance, dimension_values)
130
+ end
131
+
132
+ def register_alarms_for_watches!(resource_type, dimension_values)
133
+ cw = AWS::CloudWatch.new
134
+
135
+ dimension_values.each do |dimension_value|
136
+ @config[:watches][resource_type].each do |watch|
137
+ [:warning, :critical].each do |threshold|
138
+ target = dimension_value.map { |v| v[:value] }.join("_")
139
+ alarm_name = "skyeye::#{resource_type}::#{target}::#{watch[:name]}-#{threshold}"
140
+
141
+ alarm = cw.alarms[alarm_name]
142
+
143
+ unless alarm.exists?
144
+ is_command = !!watch[:command]
145
+ raise "Cannot specify command for non-instance watch" if is_command && resource_type != :instance
146
+
147
+ if is_command || watch[threshold]
148
+ @logger.info "Register Alarm #{alarm_name}"
149
+
150
+ cw.alarms.create(alarm_name, {
151
+ namespace: is_command ? @config[:namespace] : namespace_for_resource_type(resource_type),
152
+ metric_name: watch[:name],
153
+ dimensions: dimension_value,
154
+ comparison_operator: watch[:comparison_operator] || "GreaterThanThreshold",
155
+ evaluation_periods: 1,
156
+ period: (watch[:period] || @config[:alarm_periods][threshold] || 5) * 60,
157
+ statistic: watch[:statistic] || "Maximum",
158
+ threshold: is_command ? (threshold == :warning ? 0 : 1) : watch[threshold],
159
+ insufficient_data_actions: (threshold == :critical && is_command ? @config[:arns] : []),
160
+ actions_enabled: true,
161
+ alarm_actions: @config[:arns],
162
+ alarm_description: "skyeye: #{watch.to_json}",
163
+ })
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+
171
+ def start!
172
+ @running = true
173
+ @logger.info "SkyEye starting."
174
+
175
+ namespace = @config[:namespace] || "skyeye"
176
+
177
+ @threads = @config[:watches][:instance].map do |w|
178
+ thread_for_watch(namespace, w)
179
+ end.compact
180
+
181
+ @threads.each(&:join)
182
+
183
+ if @killed_by
184
+ @logger.error(@killed_by.to_s + " " + @killed_by.backtrace.join("\n"))
185
+ raise @killed_by
186
+ end
187
+ end
188
+
189
+ def kill!(e)
190
+ @mutex.synchronize do
191
+ @killed_by = e
192
+ end
193
+ end
194
+
195
+ def thread_for_watch(namespace, watch)
196
+ return nil if watch[:type] == "aws"
197
+ raise "missing name" unless watch[:name]
198
+
199
+ Thread.new do
200
+ last_check = nil
201
+
202
+ interval = watch[:interval] || 5
203
+ cw = AWS::CloudWatch.new.client
204
+
205
+ do_check = lambda do |&block|
206
+ if !last_check || (Time.now - last_check > interval)
207
+ last_check = Time.now
208
+ block.call
209
+ end
210
+ end
211
+
212
+ begin
213
+ loop do
214
+ running = true
215
+
216
+ @mutex.synchronize do
217
+ running = @running && !@killed_by
218
+ end
219
+
220
+ break unless running
221
+
222
+ do_check.call do
223
+ @logger.info "[CHECK] [#{instance_id}] #{watch[:name]}::#{watch[:type]} #{watch[:command] || ""}"
224
+
225
+ if watch[:command]
226
+ command = watch[:command]
227
+ message = nil
228
+
229
+ status = Open4::popen4(command) do |pid, stdin, stdout, stderr|
230
+ message = "#{watch[:name]} :: #{stdout.readlines.join(" ").gsub(/\n/, " ").chomp}"
231
+ end
232
+
233
+ case status
234
+ when 0
235
+ @logger.info message
236
+ when 1
237
+ @logger.warn message
238
+ when 2
239
+ @logger.error message
240
+ else
241
+ @logger.warn message
242
+ end
243
+
244
+ cw.put_metric_data({
245
+ namespace: namespace,
246
+ metric_data: [{
247
+ metric_name: watch[:name],
248
+ dimensions: [{
249
+ :name => "InstanceId",
250
+ :value => instance_id
251
+ }],
252
+ value: status.exitstatus,
253
+ unit: "None"
254
+ }]
255
+ })
256
+ else
257
+ raise "Missing command or aws id for watch #{watch[:name]}"
258
+ end
259
+ end
260
+
261
+ sleep 1
262
+ end
263
+ rescue Exception => e
264
+ kill!(e)
265
+ end
266
+ end
267
+ end
268
+
269
+ def shutdown!
270
+ @mutex.synchronize do
271
+ @running = false
272
+ end
273
+ end
274
+
275
+ def load_or_register_topics!
276
+ sns = AWS::SNS.new
277
+
278
+ current_topics = sns.client.list_topics
279
+
280
+ arns = []
281
+
282
+ (@config[:topics] || ["skyeye-alerts"]).each do |topic_name|
283
+ current_topic = current_topics[:topics].find do |topic|
284
+ topic[:topic_arn].split(/:/)[-1] == topic_name
285
+ end
286
+
287
+ if current_topic
288
+ arns << current_topic[:topic_arn]
289
+ else
290
+ arn = sns.topics.create(topic_name).arn
291
+ arns << arn
292
+
293
+ @logger.info "Created Topic #{arn}"
294
+ end
295
+ end
296
+
297
+ @config[:arns] = arns
298
+ end
299
+ end
300
+ end
@@ -0,0 +1,3 @@
1
+ module Skyeye
2
+ VERSION = "0.0.1"
3
+ end
data/lib/skyeye.rb ADDED
@@ -0,0 +1,4 @@
1
+ module SkyEye
2
+ end
3
+
4
+ require 'skyeye/exec'
data/skyeye.gemspec ADDED
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'skyeye/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "skyeye"
8
+ gem.version = Skyeye::VERSION
9
+ gem.authors = ["Greg Fodor"]
10
+ gem.email = ["gfodor@gmail.com"]
11
+ gem.description = %q{SkyEye is a daemon that lets you have nagios-like alerts on EC2 using CloudWatch.}
12
+ gem.summary = %q{SkyEye is a daemon that lets you have nagios-like alerts on EC2 using CloudWatch.}
13
+ gem.homepage = "http://github.com/gfodor/skyeye"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: skyeye
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Greg Fodor
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-14 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: SkyEye is a daemon that lets you have nagios-like alerts on EC2 using
15
+ CloudWatch.
16
+ email:
17
+ - gfodor@gmail.com
18
+ executables:
19
+ - skyeye
20
+ extensions: []
21
+ extra_rdoc_files: []
22
+ files:
23
+ - .gitignore
24
+ - Gemfile
25
+ - LICENSE.txt
26
+ - README.md
27
+ - Rakefile
28
+ - bin/skyeye
29
+ - config/skyeye.yml.example
30
+ - lib/skyeye.rb
31
+ - lib/skyeye/exec.rb
32
+ - lib/skyeye/version.rb
33
+ - skyeye.gemspec
34
+ homepage: http://github.com/gfodor/skyeye
35
+ licenses: []
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubyforge_project:
54
+ rubygems_version: 1.8.24
55
+ signing_key:
56
+ specification_version: 3
57
+ summary: SkyEye is a daemon that lets you have nagios-like alerts on EC2 using CloudWatch.
58
+ test_files: []