sensu-plugins-docker-swarm 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,93 @@
1
+ #! /usr/bin/env ruby
2
+ #
3
+ # check-container
4
+ #
5
+ # DESCRIPTION:
6
+ # This is a simple check script for Sensu to check that a Docker container is
7
+ # running. You can pass in either a container id or a container name.
8
+ #
9
+ # OUTPUT:
10
+ # plain text
11
+ #
12
+ # PLATFORMS:
13
+ # Linux
14
+ #
15
+ # DEPENDENCIES:
16
+ # gem: sensu-plugin
17
+ #
18
+ # USAGE:
19
+ # check-container.rb -H /var/run/docker.sock -N c92d402a5d14
20
+ # CheckDockerContainer OK: c92d402a5d14 is running on /var/run/docker.sock.
21
+ #
22
+ # check-container.rb -H https://127.0.0.1:2376 -N circle_burglar
23
+ # CheckDockerContainer CRITICAL: circle_burglar is not running on https://127.0.0.1:2376
24
+ #
25
+ # NOTES:
26
+ # => State.running == true -> OK
27
+ # => State.running == false -> CRITICAL
28
+ # => Not Found -> CRITICAL
29
+ # => Can't connect to Docker -> WARNING
30
+ # => Other exception -> WARNING
31
+ #
32
+ # LICENSE:
33
+ # Copyright 2014 Sonian, Inc. and contributors. <support@sensuapp.org>
34
+ # Released under the same terms as Sensu (the MIT license); see LICENSE
35
+ # for details.
36
+ #
37
+
38
+ require 'sensu-plugin/check/cli'
39
+ require 'sensu-plugins-docker/client_helpers'
40
+
41
+ #
42
+ # Check Docker Container
43
+ #
44
+ class CheckDockerContainer < Sensu::Plugin::Check::CLI
45
+ option :docker_host,
46
+ short: '-H DOCKER_HOST',
47
+ long: '--docker-host DOCKER_HOST',
48
+ description: 'Docker API URI. https://host, https://host:port, http://host, http://host:port, host:port, unix:///path'
49
+
50
+ option :container,
51
+ short: '-N CONTAINER',
52
+ long: '--container-name CONTAINER',
53
+ required: true
54
+
55
+ option :tag,
56
+ short: '-t TAG',
57
+ long: '--tag TAG'
58
+
59
+ option :allowexited,
60
+ short: '-x',
61
+ long: '--allow-exited',
62
+ boolean: true,
63
+ description: 'Do not raise alert if container has exited without error'
64
+
65
+ def run
66
+ @client = DockerApi.new(config[:docker_host])
67
+ path = "/containers/#{config[:container]}/json"
68
+ response = @client.call(path, false)
69
+ if response.code.to_i == 404
70
+ critical "Container #{config[:container]} is not running on #{@client.uri}"
71
+ end
72
+ body = parse_json(response)
73
+ container_running = body['State']['Running']
74
+ if container_running
75
+ if config[:tag]
76
+ image = body['Config']['Image']
77
+ match = image.match(/^(?:([^\/]+)\/)?(?:([^\/]+)\/)?([^@:\/]+)(?:[@:](.+))?$/)
78
+ unless match && match[4] == config[:tag]
79
+ critical "#{config[:container]}'s tag is '#{match[4]}', especting '#{config[:tag]}'"
80
+ end
81
+ end
82
+ ok "#{config[:container]} is running on #{@client.uri}."
83
+ elsif config[:allowexited] && body['State']['Status'] == 'exited'
84
+ if (body['State']['ExitCode']).zero?
85
+ ok "#{config[:container]} has exited without error on #{@client.uri}."
86
+ else
87
+ critical "#{config[:container]} has exited with status code #{body['State']['ExitCode']} on #{@client.uri}."
88
+ end
89
+ else
90
+ critical "#{config[:container]} is #{body['State']['Status']} on #{@client.uri}."
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,108 @@
1
+ #! /usr/bin/env ruby
2
+ #
3
+ # check-docker-container
4
+ #
5
+ # DESCRIPTION:
6
+ # This is a simple check script for Sensu to check the number of a Docker Container
7
+ #
8
+ # OUTPUT:
9
+ # plain text
10
+ #
11
+ # PLATFORMS:
12
+ # Linux
13
+ #
14
+ # DEPENDENCIES:
15
+ # gem: sensu-plugin
16
+ # gem: net_http_unix
17
+ #
18
+ # USAGE:
19
+ # check-docker-container.rb -w 3 -c 3
20
+ # => 1 container running = OK.
21
+ # => 4 container running = CRITICAL
22
+ #
23
+ # check-docker-container.rb -H /var/run/docker.sock -w 3 -c 3
24
+ # => 1 container running = OK.
25
+ # => 4 container running = CRITICAL
26
+ #
27
+ # check-docker-container.rb -H https://127.0.0.1:2376 -w 3 -c 3
28
+ # => 1 container running = OK.
29
+ # => 4 container running = CRITICAL
30
+ #
31
+ # NOTES:
32
+ #
33
+ # LICENSE:
34
+ # Author Yohei Kawahara <inokara@gmail.com>
35
+ # Released under the same terms as Sensu (the MIT license); see LICENSE
36
+ # for details.
37
+ #
38
+
39
+ require 'sensu-plugin/check/cli'
40
+ require 'sensu-plugins-docker/client_helpers'
41
+
42
+ #
43
+ # Check Docker Containers
44
+ #
45
+ class CheckDockerContainers < Sensu::Plugin::Check::CLI
46
+ option :docker_host,
47
+ description: 'Docker API URI. https://host, https://host:port, http://host, http://host:port, host:port, unix:///path',
48
+ short: '-H DOCKER_HOST',
49
+ long: '--docker-host DOCKER_HOST'
50
+
51
+ option :warn_over,
52
+ short: '-W N',
53
+ long: '--warn-over N',
54
+ description: 'Trigger a warning if over a number',
55
+ proc: proc(&:to_i)
56
+
57
+ option :crit_over,
58
+ short: '-C N',
59
+ long: '--critical-over N',
60
+ description: 'Trigger a critical if over a number',
61
+ proc: proc(&:to_i)
62
+
63
+ option :warn_under,
64
+ short: '-w N',
65
+ long: '--warn-under N',
66
+ description: 'Trigger a warning if under a number',
67
+ proc: proc(&:to_i),
68
+ default: 1
69
+
70
+ option :crit_under,
71
+ short: '-c N',
72
+ long: '--critical-under N',
73
+ description: 'Trigger a critical if under a number',
74
+ proc: proc(&:to_i),
75
+ default: 1
76
+
77
+ def under_message(crit_under, count)
78
+ "Less than #{crit_under} containers running. #{count} running."
79
+ end
80
+
81
+ def over_message(crit_over, count)
82
+ "More than #{crit_over} containers running. #{count} running."
83
+ end
84
+
85
+ def evaluate_count(count)
86
+ # #YELLOW
87
+ if config.key?(:crit_under) && count < config[:crit_under]
88
+ critical under_message(config[:crit_under], count)
89
+ # #YELLOW
90
+ elsif config.key?(:crit_over) && count > config[:crit_over]
91
+ critical over_message(config[:crit_over], count)
92
+ # #YELLOW
93
+ elsif config.key?(:warn_under) && count < config[:warn_under]
94
+ warning under_message(config[:warn_under], count)
95
+ # #YELLOW
96
+ elsif config.key?(:warn_over) && count > config[:warn_over]
97
+ warning over_message(config[:warn_over], count)
98
+ else
99
+ ok
100
+ end
101
+ end
102
+
103
+ def run
104
+ @client = DockerApi.new(config[:docker_host])
105
+ containers = @client.parse('/containers/json')
106
+ evaluate_count containers.size
107
+ end
108
+ end
@@ -0,0 +1,199 @@
1
+ #! /usr/bin/env ruby
2
+ #
3
+ # check-service-logs
4
+ #
5
+ # DESCRIPTION:
6
+ # Checks docker logs for specified strings
7
+ # with the option to ignore lines if they contain specified substrings.
8
+ #
9
+ # OUTPUT:
10
+ # plain text
11
+ #
12
+ # PLATFORMS:
13
+ # Linux
14
+ #
15
+ # DEPENDENCIES:
16
+ # gem: sensu-plugin
17
+ # gem: net_http_unix
18
+ #
19
+ # USAGE:
20
+ # # Check only one service
21
+ # check-service-logs.rb -H /tmp/docker.sock -N logspout -r 'problem sending' -r 'i/o timeout' -i 'Remark:' -i 'The configuration is'
22
+ # => 1 service running = OK
23
+ # => 4 service running = CRITICAL
24
+ #
25
+ # # Check multiple services
26
+ # check-service-logs.rb -H /tmp/docker.sock -N logspout -N logtest -r 'problem sending' -r 'i/o timeout' -i 'Remark:' -i 'The configuration is'
27
+ # => 1 service running = OK
28
+ # => 4 service running = CRITICAL
29
+ #
30
+ # # Check all services
31
+ # check-service-logs.rb -H /tmp/docker.sock -r 'problem sending' -r 'i/o timeout' -i 'Remark:' -i 'The configuration is'
32
+ # => 1 services running = OK
33
+ # => 4 services running = CRITICAL
34
+ #
35
+ # NOTES:
36
+ # The API parameter required to use the limited lookback (-t) was introduced
37
+ # the Docker server API version 1.19. This check may still work on older API
38
+ # versions if you don't want to limit the timestamps of logs.
39
+ #
40
+ # LICENSE:
41
+ # Author: Nathan Newman <newmannh@gmail.com>, Kel Cecil <kelcecil@praisechaos.com>
42
+ # Released under the same terms as Sensu (the MIT license); see LICENSE
43
+ # for details.
44
+ #
45
+
46
+ require 'sensu-plugin/check/cli'
47
+ require 'sensu-plugins-docker/client_helpers'
48
+
49
+ class ServiceLogChecker < Sensu::Plugin::Check::CLI
50
+ option :docker_host,
51
+ description: 'Docker API URI. https://host, https://host:port, http://host, http://host:port, host:port, unix:///path',
52
+ short: '-H DOCKER_HOST',
53
+ long: '--docker-host DOCKER_HOST'
54
+
55
+ option :service,
56
+ description: 'name of service; can be used multiple times. /!\ All running services will be check if this options is not provided',
57
+ short: '-N service',
58
+ long: '--service-name service',
59
+ default: [],
60
+ proc: proc { |flag| (@options[:service][:accumulated] ||= []).push(flag) }
61
+
62
+ option :red_flags,
63
+ description: 'String whose presence (case-insensitive by default) in a log line indicates an error; can be used multiple times',
64
+ short: '-r ERR_STRING',
65
+ long: '--red-flag ERR_STRING',
66
+ default: [],
67
+ proc: proc { |flag| (@options[:red_flags][:accumulated] ||= []).push(flag) }
68
+
69
+ option :ignore_list,
70
+ description: 'String whose presence (case-insensitive by default) in a log line indicates the line should be ignored; can be used multiple times',
71
+ short: '-i IGNSTR',
72
+ long: '--ignore-lines-with IGNSTR',
73
+ default: [],
74
+ proc: proc { |flag| (@options[:ignore_list][:accumulated] ||= []).push(flag) }
75
+
76
+ option :case_sensitive,
77
+ description: 'indicates all red_flag and ignore_list substring matching should be case-sensitive instead of the default case-insensitive',
78
+ short: '-c',
79
+ long: '--case-sensitive',
80
+ boolean: true
81
+
82
+ option :hours_ago,
83
+ description: 'Amount of time in hours to look back for log strings',
84
+ short: '-t HOURS',
85
+ long: '--hours-ago HOURS',
86
+ required: false
87
+
88
+ option :seconds_ago,
89
+ description: 'Amount of time in seconds to look back for log strings',
90
+ short: '-s SECONDS',
91
+ long: '--seconds-ago SECONDS',
92
+ required: false
93
+
94
+ option :check_all,
95
+ description: 'If all services are checked (no service name provided with -n) , check offline services too',
96
+ short: '-a',
97
+ long: '--all',
98
+ default: false,
99
+ boolean: true
100
+
101
+ option :disable_stdout,
102
+ description: 'Disable the check on STDOUT logs. By default both STDERR and STDOUT are checked',
103
+ short: '-1',
104
+ long: '--no-stdout',
105
+ default: true,
106
+ boolean: true,
107
+ proc: proc { false } # used to negate the false(default)->true boolean option behaviour to true(default)->false
108
+
109
+ option :disable_stderr,
110
+ description: 'Disable the check on STDERR logs. By default both STDERR and STDOUT are checked',
111
+ short: '-2',
112
+ long: '--no-stderr',
113
+ default: true,
114
+ boolean: true,
115
+ proc: proc { false } # used to negate the false(default)->true boolean option behaviour to true(default)->false
116
+
117
+ def calculate_timestamp(seconds_ago = nil)
118
+ seconds_ago = yield if block_given?
119
+ (Time.now - seconds_ago).to_i
120
+ end
121
+
122
+ def process_docker_logs(service_name)
123
+ path = "/services/#{service_name}/logs?stdout=#{config[:disable_stdout]}&stderr=#{config[:disable_stderr]}&timestamps=true"
124
+ if config.key? :hours_ago
125
+ timestamp = calculate_timestamp { config[:hours_ago].to_i * 3600 }
126
+ elsif config.key? :seconds_ago
127
+ timestamp = calculate_timestamp config[:seconds_ago].to_i
128
+ end
129
+ path = "#{path}&since=#{timestamp}"
130
+ response = @client.call(path, false)
131
+ if response.code.to_i == 404
132
+ critical "service '#{service_name}' not found on #{@client.uri}"
133
+ end
134
+ yield remove_headers response.read_body
135
+ end
136
+
137
+ def remove_headers(raw_logs)
138
+ lines = raw_logs.split("\n")
139
+ lines.map! do |line|
140
+ # Check only logs generated with the 8 bits control
141
+ if !line.nil? && line.bytesize > 8 && /^(0|1|2)000$/ =~ line.byteslice(0, 4).unpack('C*').join('')
142
+ # Remove the first 8 bits and ansii colors too
143
+ line.byteslice(8, line.bytesize).gsub(/\x1b\[[\d;]*?m/, '')
144
+ end
145
+ end
146
+ # We want the most recent logs lines first
147
+ lines.compact.reverse.join("\n")
148
+ end
149
+
150
+ def includes_any?(str, array_of_substrings)
151
+ array_of_substrings.each do |substring|
152
+ return true if str.include? substring
153
+ end
154
+ false
155
+ end
156
+
157
+ def detect_problem(logs)
158
+ whiteflags = config[:ignore_list]
159
+ redflags = config[:red_flags]
160
+ unless config[:case_sensitive]
161
+ logs = logs.downcase
162
+ whiteflags.map!(&:downcase)
163
+ redflags.map!(&:downcase)
164
+ end
165
+
166
+ logs.split("\n").each do |line|
167
+ return line if !includes_any?(line, whiteflags) && includes_any?(line, redflags)
168
+ end
169
+ nil
170
+ end
171
+
172
+ def run
173
+ @client = DockerApi.new(config[:docker_host])
174
+ problem = []
175
+ problem_string = nil
176
+ path = "/services/json?all=#{config[:check_all]}"
177
+ services = config[:service]
178
+ if config[:service].none?
179
+ warn_msg = %(
180
+ Collecting logs from all services is dangerous and could lead to sensu client hanging depending on volume of logs.
181
+ This not recommended for production environments.
182
+ ).gsub(/\s+/, ' ').strip
183
+ message warn_msg
184
+ end
185
+ services = @client.parse(path).map { |p| p['Names'][0].delete('/') } if services.none?
186
+ critical 'Check all services was asked but no services was found' if services.none?
187
+ services.each do |service|
188
+ process_docker_logs service do |log_chunk|
189
+ problem_string = detect_problem(log_chunk)
190
+ break unless problem_string.nil?
191
+ end
192
+ problem << "\tError found inside service : '#{service}'\n\t\t#{problem_string}" unless problem_string.nil?
193
+ end
194
+ problem_string = problem.join("\n")
195
+ critical "service(s) logs indicate problems :\n#{problem_string}" unless problem.none?
196
+ services_string = services.join(', ')
197
+ ok "No errors detected from logs inside service(s) : \n#{services_string}"
198
+ end
199
+ end
@@ -0,0 +1,102 @@
1
+ #! /usr/bin/env ruby
2
+ #
3
+ # check-service
4
+ #
5
+ # DESCRIPTION:
6
+ # This is a simple check script for Sensu to check that a Docker service is
7
+ # running all of it's intended tasks. You can pass in either a service id or a service name.
8
+ #
9
+ # OUTPUT:
10
+ # plain text
11
+ #
12
+ # PLATFORMS:
13
+ # Linux
14
+ #
15
+ # DEPENDENCIES:
16
+ # gem: sensu-plugin
17
+ #
18
+ # USAGE:
19
+ # check-service.rb -H /var/run/docker.sock -N c92d402a5d14
20
+ # CheckDockerService OK: c92d402a5d14 is running on /var/run/docker.sock.
21
+ #
22
+ # check-service.rb -H https://127.0.0.1:2376 -N circle_burglar
23
+ # CheckDockerService CRITICAL: circle_burglar is not running on https://127.0.0.1:2376
24
+ #
25
+ # NOTES:
26
+ # => .Replicas == number of service's tasks -> OK
27
+ # => .Replicas != number of service's tasks -> CRITICAL
28
+ # => Not Found -> CRITICAL
29
+ # => Can't connect to Docker -> WARNING
30
+ # => Other exception -> WARNING
31
+ #
32
+ # LICENSE:
33
+ # Copyright 2014 Sonian, Inc. and contributors. <support@sensuapp.org>
34
+ # Released under the same terms as Sensu (the MIT license); see LICENSE
35
+ # for details.
36
+ #
37
+
38
+ require 'sensu-plugin/check/cli'
39
+ require 'sensu-plugins-docker/client_helpers'
40
+
41
+ #
42
+ # Check Docker Service
43
+ #
44
+ class CheckDockerService < Sensu::Plugin::Check::CLI
45
+ option :docker_host,
46
+ short: '-H DOCKER_HOST',
47
+ long: '--docker-host DOCKER_HOST',
48
+ description: 'Docker API URI. https://host, https://host:port, http://host, http://host:port, host:port, unix:///path'
49
+
50
+ option :service,
51
+ short: '-N SERVICE',
52
+ long: '--service-name service',
53
+ required: true
54
+
55
+ option :tag,
56
+ short: '-t TAG',
57
+ long: '--tag TAG'
58
+
59
+ option :allowexited,
60
+ short: '-x',
61
+ long: '--allow-exited',
62
+ boolean: true,
63
+ description: 'Do not raise alert if service has exited without error'
64
+
65
+ def run
66
+ # Connect a client to the remote/local docker socket
67
+ @client = DockerApi.new(config[:docker_host])
68
+
69
+ # Call /services and get the service we want to check
70
+ path = "/services?filters=%7B%22name%22%3A%7B%22#{config[:service]}%22%3Atrue%7D%7D"
71
+ response = @client.call(path, false)
72
+ if response.code.to_i == 404
73
+ critical "service #{config[:service]} is not running on #{@client.uri} bb"
74
+ end
75
+
76
+ # Pass the number of replicas the service should be running
77
+ body = parse_json(response)
78
+ intended_replicas = body[0]['Spec']['Mode']['Replicated']['Replicas']
79
+
80
+ # Call /tasks to get the number of running replicas (this is how `docker service ls` works)
81
+ running_replicas = 0
82
+ path = "/tasks?filters=%7B%22name%22%3A%7B%22#{config[:service]}%22%3Atrue%7D%7D"
83
+ response = @client.call(path, false)
84
+ if response.code.to_i == 404
85
+ critical "service #{config[:service]} is not running on #{@client.uri}, tasks not found"
86
+ end
87
+
88
+ # Traverse the tasks and check if the state is running
89
+ tasks = parse_json(response)
90
+ tasks.each do |task|
91
+ if task['Status']['State'] == 'running'
92
+ running_replicas += 1
93
+ end
94
+ end
95
+
96
+ # If the number of running replicas is not what the service expects, return critical
97
+ if intended_replicas != running_replicas
98
+ critical "service #{config[:service]} is not running the intended number of replicas"
99
+ end
100
+ ok "#{config[:service]} is running correctly on #{@client.uri}."
101
+ end
102
+ end