├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .kitchen.yml ├── .rubocop.yml ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── bin ├── check-container-logs.rb ├── check-container.rb ├── check-docker-container.rb ├── metrics-docker-container.rb ├── metrics-docker-info.rb └── metrics-docker-stats.rb ├── lib ├── sensu-plugins-docker.rb └── sensu-plugins-docker │ ├── client_helpers.rb │ └── version.rb ├── sensu-plugins-docker.gemspec └── test ├── fixtures └── bootstrap.sh ├── integration ├── helpers │ └── serverspec │ │ ├── check-container_spec.rb │ │ ├── shared_spec.rb │ │ └── spec_helper.rb ├── ruby-230 │ └── serverspec │ │ └── default_spec.rb └── ruby-241 │ └── serverspec │ └── default_spec.rb └── spec_helper.rb /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Pull Request Checklist 2 | 3 | **Is this in reference to an existing issue?** 4 | 5 | #### General 6 | 7 | - [ ] Update Changelog following the conventions laid out on [Our CHANGELOG Guidelines ](https://github.com/sensu-plugins/community/blob/master/HOW_WE_CHANGELOG.md) 8 | 9 | - [ ] Update README with any necessary configuration snippets 10 | 11 | - [ ] Binstubs are created if needed 12 | 13 | - [ ] RuboCop passes 14 | 15 | - [ ] Existing tests pass 16 | 17 | #### New Plugins 18 | 19 | - [ ] Tests 20 | 21 | - [ ] Add the plugin to the README 22 | 23 | - [ ] Does it have a complete header as outlined [here](http://sensu-plugins.io/docs/developer_guidelines.html#coding-style) 24 | 25 | #### Purpose 26 | 27 | #### Known Compatibility Issues 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | .vagrant/* 16 | .DS_Store 17 | .idea/* 18 | *.gem 19 | -------------------------------------------------------------------------------- /.kitchen.yml: -------------------------------------------------------------------------------- 1 | --- 2 | driver: 3 | name: docker 4 | use_sudo: false 5 | http_proxy: <%= ENV['http_proxy'] %> 6 | https_proxy: <%= ENV['https_proxy'] %> 7 | privileged: true 8 | provision_command: 9 | - curl -sSL https://get.docker.com/ | sh 10 | 11 | provisioner: 12 | name: shell 13 | data_path: . 14 | script: test/fixtures/bootstrap.sh 15 | http_proxy: <%= ENV['http_proxy'] %> 16 | https_proxy: <%= ENV['https_proxy'] %> 17 | 18 | # verifier: 19 | # ruby_bindir: <%= ENV['MY_RUBY_HOME'] || '/opt/sensu/embedded' %>/bin 20 | verifier: 21 | ruby_bindir: /usr/local/bin 22 | 23 | platforms: 24 | - name: debian-8 25 | 26 | suites: 27 | - name: ruby-230 28 | driver: 29 | image: ruby:2.3.0-slim 30 | - name: ruby-241 31 | driver: 32 | image: ruby:2.4.1-slim 33 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | 2 | MethodLength: 3 | Max: 200 4 | 5 | LineLength: 6 | Max: 160 7 | 8 | AbcSize: 9 | Max: 100 10 | 11 | FileName: 12 | Enabled: false 13 | 14 | PerceivedComplexity: 15 | Enabled: false 16 | 17 | CyclomaticComplexity: 18 | Enabled: false 19 | 20 | ClassLength: 21 | Enabled: false 22 | 23 | IfUnlessModifier: 24 | Enabled: false 25 | 26 | RegexpLiteral: 27 | Enabled: false 28 | 29 | Style/Documentation: 30 | Enabled: false 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | cache: 3 | - bundler 4 | before_install: 5 | - gem install bundler -v 2.1 6 | install: 7 | - bundle install 8 | rvm: 9 | - 2.3.0 10 | - 2.4.1 11 | notifications: 12 | email: 13 | recipients: 14 | - sensu-plugin@sensu-plugins.io 15 | on_success: change 16 | on_failure: always 17 | script: 18 | - bundle exec rake quick 19 | #- bundle exec rake kitchen:ruby-`echo $TRAVIS_RUBY_VERSION | sed -e "s/\.//g"`-debian-8 20 | - gem build sensu-plugins-docker.gemspec 21 | - gem install sensu-plugins-docker-*.gem 22 | deploy: 23 | provider: rubygems 24 | api_key: 25 | secure: Xx+9z5uHC/rDjklTV7nSE8+7SxDh+/EzE3dt1QjutCtMYM7DK+gtleffv+irF02nUc5wbOCn61xnaLhqljyYdSbtOSXqURRQGkQF9eg/IVMaHMVZHQ6SHktrKkUyX3ZW39WKgBiKaSGs/Z9ulCWp13q1GToCEABLZTX6SDqCiyU= 26 | gem: sensu-plugins-docker 27 | on: 28 | tags: true 29 | all_branches: true 30 | rvm: 2.4.1 31 | repo: sensu-plugins/sensu-plugins-docker 32 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | This project adheres to [Semantic Versioning](http://semver.org/). 3 | 4 | This CHANGELOG follows the format listed at [Our CHANGELOG Guidelines ](https://github.com/sensu-plugins/community/blob/master/HOW_WE_CHANGELOG.md). 5 | Which is based on [Keep A Changelog](http://keepachangelog.com/) 6 | 7 | ## [Unreleased] 8 | 9 | ## [4.0.0] - 2020-04-09 10 | ### Breaking Changes 11 | - bumped `sensu-plugin` dependency from `~> 2.0` to `~> 4.0` please consult the changelog for additional details. The notable breaking change [is](https://github.com/sensu-plugins/sensu-plugin/blob/master/CHANGELOG.md#v145---2017-03-07) (@majormoses) 12 | - dopped support for ruby `< 2.3` (@majormoses) 13 | 14 | ## [3.2.0] - 2018-11-22 15 | ### Fixed 16 | - metrics-docker-stats.rb: fix #16 -n option causes metrics-docker-stats.rb to fail, in case containers are linked together 17 | 18 | ## [3.1.1] - 2018-06-29 19 | ### Fixed 20 | - check-container-logs.rb: fix nil.gsub condition on empty log lines 21 | 22 | ## [3.1.0] - 2018-05-07 23 | ### Added 24 | - intergration test for container check (@antonidabek) 25 | - check-container.rb: -x (--allow-exited) to avoid raising alerts when container exited without error, useful for task oriented containers monitoring. (@antonidabek) 26 | 27 | ## [3.0.0] - 2018-02-17 28 | ### Breaking Changes 29 | - Default docker host defined by DockerApi Class ( ENV[DOCKER_URL] => ENV[DOCKER_HOST] => /var/run/docker.sock ) 30 | - check-container-logs.rb: -N (--container-name) instead of -n for container name. Now a 'CRITICAL' is trigger if a container doesn't exist (previously, a 'OK' was trigger) 31 | - check-docker-container.rb: -H (--docker-host) instead of -h (--host) for docker Host 32 | - check-container.rb: -H (--docker-host) for docker Host instead of -h (--host) for docker host, -N (--container-name) instead of -c (--container) for container name 33 | - metrics-docker-stats.rb: -N (--container-name) instead of -c (--container) for Container name 34 | 35 | ### Added 36 | - client_helpers.rb: Add a simple DockerApi class. Add parse_json method. 37 | - metrics-docker-container.rb: Friendly names option added 38 | - check-container-logs.rb: Add an option to check logs from stopped containers if all containers are checked. Add an option to don't check stderr logs. Add an option to don't check stdout logs. Add timestamp in logs output. Add the possibility to use -n multiple times to check multiple containers at once. 39 | 40 | ### Changed 41 | - metrics-docker-stats.rb: Make use of DockerApi class. Default docker_host defined by DockerApi class. Remove docker_api method. 42 | - metrics-docker-info.rb: Make use of DockerApi class. Default docker_host defined by DockerApi class. Remove docker_api method. 43 | - metrics-docker-container.rb: Make use of DockerApi class. Re-enable rubocop for container_metrics method. 44 | - check-container.rb: Make use of DockerApi class. 45 | - check-docker-container.rb: Make use of DockerApi class. 46 | - check-container-logs.rb: Make use of DockerApi class. Check only logs generated with the 8 bits control to prevent to check logs generated in interactive mode. Check the newest logs rows first instead the oldest. Option -n is not required anymore, if -n option is not provided, the check will be applied to all running containers. Changed the messages displayed with ok and critical 47 | 48 | ### Fixed 49 | - metrics-docker-stats.rb: Remove trailing / in name value. 50 | 51 | ### Removed 52 | - Remove unnecessary `docker_api` dependency 53 | - check-container-logs.rb: Logs generated in interactive mode are not checked anymore 54 | - metrics-docker-stats.rb: option -p (--protocol) have been removed because new DockerApi don't use it 55 | - metrics-docker-info.rb: option -p (--protocol) have been removed because new DockerApi don't use it 56 | 57 | ## [2.0.0] - 2017-11-06 58 | ### Fixed 59 | - metrics-docker-stats.rb:: Fix gsub on nil docker environment variable (@epierotto) 60 | 61 | ### Breaking Change 62 | - bumped dependency of `sensu-plugin` to 2.x you can read about it [here](https://github.com/sensu-plugins/sensu-plugin/blob/master/CHANGELOG.md#v145---2017-03-07) (@majormoses) 63 | 64 | ## [1.5.0] - 2017-09-09 65 | ### Added 66 | - metrics-docker-stats.rb: Include metric with cpu usage percentage calculated based on docker stats (@alcasim) 67 | 68 | ### Changed 69 | - updated CHANGELOG guidelines location (@majormoses) 70 | 71 | ## [1.4.0] - 2017-08-08 72 | ### Added 73 | - metrics-docker-info.rb: general information metrics from docker (@alcasim) 74 | - metrics-docker-stats.rb: Added to include environment variables values as part of the metric. Added I/O stats option (@alcasim) 75 | 76 | ### Added 77 | - Ruby 2.4.1 testing 78 | 79 | ## [1.3.1] - 2017-06-12 80 | ### Fixed 81 | - check-container.rb: fixes to work with docker >= 1.17 (@israelriibeiro) 82 | 83 | ## [1.3.0] - 2017-06-04 84 | ### Added 85 | - metrics-docker-stats.rb: Add an option to ouput a portion of the docker 86 | container name using by splitting on a delimiter of the users choice. 87 | 88 | ### Fixed 89 | - check-container.rb: Fix support for docker versions >= 1.18. The key-pair [State][Status] was replaced with [State][Running] and also logic was updated (#45) 90 | 91 | ## [1.2.0] - 2017-02-08 92 | ### Added 93 | - check-container.rb: add an option to test image's tag (@obazoud) 94 | 95 | ## [1.1.5] - 2016-11-26 96 | ### Changed 97 | - Loosen `sensu-plugin` dependency to `~> 1.2` (#44) 98 | 99 | ## [1.1.4] - 2016-11-26 100 | ### Changed 101 | - metrics-docker-stats.rb: Fix JSON parse error if there is more than one chunk 102 | of response data needed to result in valid JSON because of large datasets. 103 | (#18) 104 | 105 | ## [1.1.3] - 2016-08-11 106 | ### Changed 107 | - dependencies: use net\_http\_unix = 0.2.2 108 | 109 | ## [1.1.2] - 2016-06-20 110 | ### Changed 111 | - dependencies: use sensu-plugin ~> 1.2.0, docker-api = 1.21.0 112 | 113 | ## [1.1.1] - 2016-06-10 114 | ### Fixed 115 | - metrics-docker-stats.rb: Fix error from trying to collect stats with multiple values. Stats that return array values are now excluded. (#29) 116 | 117 | ### Changed 118 | - improved help messages 119 | - check-container.rb: issue a critical event if container state != running 120 | 121 | ## [1.1.0] - 2016-06-03 122 | ### Added 123 | - check-container-logs.rb: added `-s|--seconds-ago` option to be able to set time interval more precisely 124 | 125 | ## [1.0.0] - 2016-05-24 126 | Note: this release changes how connections are made to the Docker API and also 127 | changes some options. Review your check commands before deploying this version. 128 | 129 | ### Added 130 | - Added check-container-logs.rb to check docker logs for matching strings 131 | - Support for Ruby 2.3.0 132 | - metrics-docker-container.rb: add option to override the default path to cgroup.proc 133 | 134 | ### Removed 135 | - Support for Ruby 1.9.3 136 | 137 | ### Changed 138 | - check-docker-container.rb: output the number of running containers 139 | - Refactor to connect to the Docker API socket directly instead of using the `docker` or `docker-api` gems 140 | - Update to rubocop 0.40 and cleanup 141 | 142 | ## [0.0.4] - 2015-08-10 143 | ### Changed 144 | - updated dependencies (added missing dependency `sys-proctable`) 145 | - added docker metrics using docker api 146 | 147 | ## [0.0.3] - 2015-07-14 148 | ### Changed 149 | - updated sensu-plugin gem to 1.2.0 150 | 151 | ## [0.0.2] - 2015-06-02 152 | ### Fixed 153 | - added binstubs 154 | 155 | ### Changed 156 | - removed cruft from /lib 157 | 158 | ## 0.0.1 - 2015-04-30 159 | ### Added 160 | - initial release 161 | 162 | [Unreleased]: https://github.com/sensu-plugins/sensu-plugins-docker/compare/3.1.1...HEAD 163 | [3.1.1]: https://github.com/sensu-plugins/sensu-plugins-docker/compare/3.1.0...3.1.1 164 | [3.1.0]: https://github.com/sensu-plugins/sensu-plugins-docker/compare/3.0.0...3.1.0 165 | [3.0.0]: https://github.com/sensu-plugins/sensu-plugins-docker/compare/2.0.0...3.0.0 166 | [2.0.0]: https://github.com/sensu-plugins/sensu-plugins-docker/compare/1.5.0...2.0.0 167 | [1.5.0]: https://github.com/sensu-plugins/sensu-plugins-docker/compare/1.4.0..1.5.0 168 | [1.4.0]: https://github.com/sensu-plugins/sensu-plugins-docker/compare/1.3.1...1.4.0 169 | [1.3.1]: https://github.com/sensu-plugins/sensu-plugins-docker/compare/1.3.0...1.3.1 170 | [1.3.0]: https://github.com/sensu-plugins/sensu-plugins-docker/compare/1.2.0...1.3.0 171 | [1.2.0]: https://github.com/sensu-plugins/sensu-plugins-docker/compare/1.1.5...1.2.0 172 | [1.1.5]: https://github.com/sensu-plugins/sensu-plugins-docker/compare/1.1.4...1.1.5 173 | [1.1.4]: https://github.com/sensu-plugins/sensu-plugins-docker/compare/1.1.3...1.1.4 174 | [1.1.3]: https://github.com/sensu-plugins/sensu-plugins-docker/compare/1.1.2...1.1.3 175 | [1.1.2]: https://github.com/sensu-plugins/sensu-plugins-docker/compare/1.1.1...1.1.2 176 | [1.1.1]: https://github.com/sensu-plugins/sensu-plugins-docker/compare/1.1.0...1.1.1 177 | [1.1.0]: https://github.com/sensu-plugins/sensu-plugins-docker/compare/1.0.0...1.1.0 178 | [1.0.0]: https://github.com/sensu-plugins/sensu-plugins-docker/compare/0.0.4...1.0.0 179 | [0.0.4]: https://github.com/sensu-plugins/sensu-plugins-docker/compare/0.0.3...0.0.4 180 | [0.0.3]: https://github.com/sensu-plugins/sensu-plugins-docker/compare/0.0.2...0.0.3 181 | [0.0.2]: https://github.com/sensu-plugins/sensu-plugins-docker/compare/0.0.1...0.0.2 182 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | [Development Documentation](http://sensu-plugins.io/docs/developer_guidelines.html) 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in sensu-plugins-docker.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Sensu-Plugins 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. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Sensu-Plugins-docker 2 | 3 | [![Build Status](https://travis-ci.org/sensu-plugins/sensu-plugins-docker.svg?branch=master)](https://travis-ci.org/sensu-plugins/sensu-plugins-docker) 4 | [![Gem Version](https://badge.fury.io/rb/sensu-plugins-docker.svg)](http://badge.fury.io/rb/sensu-plugins-docker) 5 | [![Code Climate](https://codeclimate.com/github/sensu-plugins/sensu-plugins-docker/badges/gpa.svg)](https://codeclimate.com/github/sensu-plugins/sensu-plugins-docker) 6 | [![Test Coverage](https://codeclimate.com/github/sensu-plugins/sensu-plugins-docker/badges/coverage.svg)](https://codeclimate.com/github/sensu-plugins/sensu-plugins-docker) 7 | [![Dependency Status](https://gemnasium.com/sensu-plugins/sensu-plugins-docker.svg)](https://gemnasium.com/sensu-plugins/sensu-plugins-docker) 8 | 9 | ## Functionality 10 | This check supports docker versions >= 1.18. Check docker-engine API for more information 11 | 12 | ## Files 13 | * check-container.rb 14 | * check-container-logs.rb 15 | * check-docker-container.rb 16 | * metrics-docker-container.rb 17 | * metrics-docker-stats.rb 18 | * metrics-docker-info.rb 19 | 20 | ## Usage 21 | 22 | ### Default docker host 23 | By default, all the checks will try to use a default docker host if a specific docker host is not provided to the check on the command line (-H / --docker-host ). 24 | 25 | Those paramaters will be tried in this order as default docker host : 26 | 27 | DOCKER_URL environnement variable 28 | DOCKER_HOST environnement variable 29 | /var/run/docker.sock file 30 | 31 | ## Installation 32 | 33 | [Installation and Setup](http://sensu-plugins.io/docs/installation_instructions.html) 34 | 35 | ## Notes 36 | [docker-engine API](https://docs.docker.com/engine/api/v1.29/#section/Versioning) 37 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'github/markup' 3 | require 'redcarpet' 4 | require 'rspec/core/rake_task' 5 | require 'rubocop/rake_task' 6 | require 'yard' 7 | require 'yard/rake/yardoc_task' 8 | require 'kitchen/rake_tasks' 9 | 10 | YARD::Rake::YardocTask.new do |t| 11 | OTHER_PATHS = %w().freeze 12 | t.files = ['lib/**/*.rb', 'bin/**/*.rb', OTHER_PATHS] 13 | t.options = %w(--markup-provider=redcarpet --markup=markdown --main=README.md --files CHANGELOG.md) 14 | end 15 | 16 | RuboCop::RakeTask.new 17 | 18 | RSpec::Core::RakeTask.new(:spec) do |r| 19 | r.pattern = FileList['**/**/*_spec.rb'] 20 | end 21 | 22 | desc 'Make all plugins executable' 23 | task :make_bin_executable do 24 | `chmod -R +x bin/*` 25 | end 26 | 27 | desc 'Test for binstubs' 28 | task :check_binstubs do 29 | bin_list = Gem::Specification.load('sensu-plugins-docker.gemspec').executables 30 | bin_list.each do |b| 31 | `which #{ b }` 32 | unless $CHILD_STATUS.success? 33 | puts "#{b} was not a binstub" 34 | exit 35 | end 36 | end 37 | end 38 | 39 | Kitchen::RakeTasks.new 40 | 41 | task integration: 'kitchen:all' 42 | 43 | task default: %i(make_bin_executable yard rubocop check_binstubs integration) 44 | 45 | task quick: %i(make_bin_executable yard rubocop check_binstubs) 46 | -------------------------------------------------------------------------------- /bin/check-container-logs.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # 3 | # check-container-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 container 21 | # check-container-logs.rb -H /tmp/docker.sock -N logspout -r 'problem sending' -r 'i/o timeout' -i 'Remark:' -i 'The configuration is' 22 | # => 1 container running = OK 23 | # => 4 container running = CRITICAL 24 | # 25 | # # Check multiple containers 26 | # check-container-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 container running = OK 28 | # => 4 container running = CRITICAL 29 | # 30 | # # Check all containers 31 | # check-container-logs.rb -H /tmp/docker.sock -r 'problem sending' -r 'i/o timeout' -i 'Remark:' -i 'The configuration is' 32 | # => 1 containers running = OK 33 | # => 4 containers 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 , Kel Cecil 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 ContainerLogChecker < 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 :container, 56 | description: 'name of container; can be used multiple times. /!\ All running containers will be check if this options is not provided', 57 | short: '-N CONTAINER', 58 | long: '--container-name CONTAINER', 59 | default: [], 60 | proc: proc { |flag| (@options[:container][: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 containers are checked (no container name provided with -n) , check offline containers 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(container_name) 123 | path = "/containers/#{container_name}/logs?stdout=#{config[:disable_stdout]}&stderr=#{config[:disable_stderr]}×tamps=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 "Container '#{container_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 = "/containers/json?all=#{config[:check_all]}" 177 | containers = config[:container] 178 | if config[:container].none? 179 | warn_msg = %( 180 | Collecting logs from all containers 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 | containers = @client.parse(path).map { |p| p['Names'][0].delete('/') } if containers.none? 186 | critical 'Check all containers was asked but no containers was found' if containers.none? 187 | containers.each do |container| 188 | process_docker_logs container do |log_chunk| 189 | problem_string = detect_problem(log_chunk) 190 | break unless problem_string.nil? 191 | end 192 | problem << "\tError found inside container : '#{container}'\n\t\t#{problem_string}" unless problem_string.nil? 193 | end 194 | problem_string = problem.join("\n") 195 | critical "Container(s) logs indicate problems :\n#{problem_string}" unless problem.none? 196 | containers_string = containers.join(', ') 197 | ok "No errors detected from logs inside container(s) : \n#{containers_string}" 198 | end 199 | end 200 | -------------------------------------------------------------------------------- /bin/check-container.rb: -------------------------------------------------------------------------------- 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. 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 94 | -------------------------------------------------------------------------------- /bin/check-docker-container.rb: -------------------------------------------------------------------------------- 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 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 109 | -------------------------------------------------------------------------------- /bin/metrics-docker-container.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # 3 | # docker-container-metrics 4 | # 5 | # DESCRIPTION: 6 | # 7 | # OUTPUT: 8 | # metric-data 9 | # 10 | # PLATFORMS: 11 | # Linux 12 | # 13 | # DEPENDENCIES: 14 | # gem: sensu-plugin 15 | # 16 | # USAGE: 17 | # 18 | # metrics-docker-container.rb -H /var/run/docker.sock -n 19 | # docker.host.sensu.sh.rss 1 1508825823 20 | # docker.host.sensu.sh.vsize 1572864 1508825823 21 | # docker.host.sensu.sh.nswap 0 1508825823 22 | # docker.host.sensu.sh.pctmem 0.0 1508825823 23 | # docker.host.sensu.sh.fd 0 1508825823 24 | # docker.host.sensu.sh.cpu 0 1508825823 25 | # 26 | # metrics-docker-container.rb -H https://127.0.0.1:2376 27 | # docker.host.ff5240e079488d248c021f8da5d13074a6a9db72ffaf1129eded445f4e16cf50.sh.rss 183 1508825793 28 | # docker.host.ff5240e079488d248c021f8da5d13074a6a9db72ffaf1129eded445f4e16cf50.sh.vsize 4616192 1508825793 29 | # docker.host.ff5240e079488d248c021f8da5d13074a6a9db72ffaf1129eded445f4e16cf50.sh.nswap 0 1508825793 30 | # docker.host.ff5240e079488d248c021f8da5d13074a6a9db72ffaf1129eded445f4e16cf50.sh.pctmem 0.01 1508825793 31 | # docker.host.ff5240e079488d248c021f8da5d13074a6a9db72ffaf1129eded445f4e16cf50.sh.fd 0 1508825793 32 | # docker.host.ff5240e079488d248c021f8da5d13074a6a9db72ffaf1129eded445f4e16cf50.sh.cpu 0 1508825793 33 | # 34 | # NOTES: 35 | # 36 | # LICENSE: 37 | # Copyright 2014 Michal Cichra. Github @mikz 38 | # Released under the same terms as Sensu (the MIT license); see LICENSE 39 | # for details. 40 | # 41 | 42 | require 'sensu-plugin/metric/cli' 43 | require 'sensu-plugins-docker/client_helpers' 44 | require 'pathname' 45 | require 'sys/proctable' 46 | 47 | # 48 | # Docker Container Metrics 49 | # 50 | class DockerContainerMetrics < Sensu::Plugin::Metric::CLI::Graphite 51 | option :scheme, 52 | description: 'Metric naming scheme, text to prepend to metric', 53 | short: '-s SCHEME', 54 | long: '--scheme SCHEME', 55 | default: "docker.#{Socket.gethostname}" 56 | 57 | option :cgroup_path, 58 | description: 'path to cgroup mountpoint', 59 | short: '-c PATH', 60 | long: '--cgroup PATH', 61 | default: '/sys/fs/cgroup' 62 | 63 | option :docker_host, 64 | description: 'Docker API URI. https://host, https://host:port, http://host, http://host:port, host:port, unix:///path', 65 | short: '-H DOCKER_HOST', 66 | long: '--docker-host DOCKER_HOST' 67 | 68 | option :cgroup_template, 69 | description: 'cgroup_template', 70 | short: '-T TPL_STRING', 71 | long: '--cgroup-template TPL_STRING', 72 | default: 'cpu/docker/%{container}/cgroup.procs' 73 | 74 | option :friendly_names, 75 | description: 'use friendly name if available', 76 | short: '-n', 77 | long: '--names', 78 | boolean: true, 79 | default: false 80 | 81 | def run 82 | @client = DockerApi.new(config[:docker_host]) 83 | container_metrics 84 | ok 85 | end 86 | 87 | def container_metrics 88 | cgroup = "#{config[:cgroup_path]}/#{config[:cgroup_template]}" 89 | 90 | timestamp = Time.now.to_i 91 | ps = Sys::ProcTable.ps.group_by(&:pid) 92 | sleep(1) 93 | ps2 = Sys::ProcTable.ps.group_by(&:pid) 94 | 95 | fields = [:rss, :vsize, :nswap, :pctmem] 96 | 97 | path = '/containers/json' 98 | containers = @client.parse(path) 99 | 100 | containers.each do |container| 101 | path = Pathname(format(cgroup, container: container['Id'])) 102 | pids = path.readlines.map(&:to_i) 103 | 104 | container_name = if config[:friendly_names] 105 | container['Names'][0].delete('/') 106 | else 107 | container['Id'] 108 | end 109 | 110 | processes = ps.values_at(*pids).flatten.compact.group_by(&:comm) 111 | processes2 = ps2.values_at(*pids).flatten.compact.group_by(&:comm) 112 | 113 | processes.each do |comm, process| 114 | prefix = "#{config[:scheme]}.#{container_name}.#{comm}" 115 | fields.each do |field| 116 | output "#{prefix}.#{field}", process.map(&field).reduce(:+), timestamp 117 | end 118 | # this check requires a lot of permissions, even root maybe? 119 | output "#{prefix}.fd", process.map { |p| p.fd.keys.count }.reduce(:+), timestamp 120 | 121 | second = processes2[comm] 122 | cpu = second.map { |p| p.utime + p.stime }.reduce(:+) - process.map { |p| p.utime + p.stime }.reduce(:+) 123 | output "#{prefix}.cpu", cpu, timestamp 124 | end 125 | end 126 | end 127 | end 128 | -------------------------------------------------------------------------------- /bin/metrics-docker-info.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # 3 | # metrics-docker-info 4 | # 5 | # DESCRIPTION: 6 | # 7 | # This check gather certain general stats from Docker (number of CPUs, number of containers, images...) 8 | # Supports the stats feature of the docker remote api ( docker server 1.5 and newer ) 9 | # Supports connecting to docker remote API over Unix socket or TCP 10 | # Based on metrics-docker-stats by @paulczar 11 | # 12 | # 13 | # OUTPUT: 14 | # metric-data 15 | # 16 | # PLATFORMS: 17 | # Linux 18 | # 19 | # DEPENDENCIES: 20 | # gem: sensu-plugin 21 | # 22 | # USAGE: 23 | # Gather stats using unix socket: 24 | # metrics-docker-info.rb -H /var/run/docker.sock 25 | # 26 | # Gather stats from localhost using HTTP: 27 | # metrics-docker-info.rb -H localhost:2375 28 | # 29 | # See metrics-docker-info.rb --help for full usage flags 30 | # 31 | # NOTES: 32 | # 33 | # LICENSE: 34 | # Copyright 2017 Alfonso Casimiro. Github @alcasim 35 | # Released under the same terms as Sensu (the MIT license); see LICENSE 36 | # for details. 37 | # 38 | 39 | require 'sensu-plugin/metric/cli' 40 | require 'sensu-plugins-docker/client_helpers' 41 | 42 | class DockerStatsMetrics < Sensu::Plugin::Metric::CLI::Graphite 43 | option :scheme, 44 | description: 'Metric naming scheme, text to prepend to metric', 45 | short: '-s SCHEME', 46 | long: '--scheme SCHEME', 47 | default: "#{Socket.gethostname}.docker" 48 | 49 | option :docker_host, 50 | description: 'Docker API URI. https://host, https://host:port, http://host, http://host:port, host:port, unix:///path', 51 | short: '-H DOCKER_HOST', 52 | long: '--docker-host DOCKER_HOST' 53 | 54 | def run 55 | @timestamp = Time.now.to_i 56 | @client = DockerApi.new(config[:docker_host]) 57 | path = '/info' 58 | infolist = @client.parse(path) 59 | filtered_list = infolist.select { |key, _value| key.match(/NCPU|NFd|Containers|Images|NGoroutines|NEventsListener|MemTotal/) } 60 | filtered_list.each do |key, value| 61 | output "#{config[:scheme]}.#{key}", value, @timestamp 62 | end 63 | ok 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /bin/metrics-docker-stats.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # 3 | # metrics-docker-stats 4 | # 5 | # DESCRIPTION: 6 | # 7 | # Supports the stats feature of the docker remote api ( docker server 1.5 and newer ) 8 | # Supports connecting to docker remote API over Unix socket or TCP 9 | # 10 | # 11 | # OUTPUT: 12 | # metric-data 13 | # 14 | # PLATFORMS: 15 | # Linux 16 | # 17 | # DEPENDENCIES: 18 | # gem: sensu-plugin 19 | # 20 | # USAGE: 21 | # Gather stats from all containers on a host using socket: 22 | # metrics-docker-stats.rb -H /var/run/docker.sock 23 | # 24 | # Gather stats from all containers on a host using HTTP: 25 | # metrics-docker-stats.rb -H localhost:2375 26 | # 27 | # Gather stats from a specific container using socket: 28 | # metrics-docker-stats.rb -H /var/run/docker.sock -N 5bf1b82382eb 29 | # 30 | # See metrics-docker-stats.rb --help for full usage flags 31 | # 32 | # NOTES: 33 | # 34 | # LICENSE: 35 | # Copyright 2015 Paul Czarkowski. Github @paulczar 36 | # Released under the same terms as Sensu (the MIT license); see LICENSE 37 | # for details. 38 | # 39 | 40 | require 'sensu-plugin/metric/cli' 41 | require 'sensu-plugins-docker/client_helpers' 42 | 43 | class Hash 44 | def self.to_dotted_hash(hash, recursive_key = '') 45 | hash.each_with_object({}) do |(k, v), ret| 46 | key = recursive_key + k.to_s 47 | if v.is_a? Hash 48 | ret.merge! to_dotted_hash(v, key + '.') 49 | else 50 | ret[key] = v 51 | end 52 | end 53 | end 54 | end 55 | 56 | class DockerStatsMetrics < Sensu::Plugin::Metric::CLI::Graphite 57 | option :scheme, 58 | description: 'Metric naming scheme, text to prepend to metric', 59 | short: '-s SCHEME', 60 | long: '--scheme SCHEME', 61 | default: "#{Socket.gethostname}.docker" 62 | 63 | option :container, 64 | description: 'Name of container to collect metrics for', 65 | short: '-N CONTAINER', 66 | long: '--container-name CONTAINER', 67 | default: '' 68 | 69 | option :docker_host, 70 | description: 'Docker API URI. https://host, https://host:port, http://host, http://host:port, host:port, unix:///path', 71 | short: '-H DOCKER_HOST', 72 | long: '--docker-host DOCKER_HOST' 73 | 74 | option :friendly_names, 75 | description: 'use friendly name if available', 76 | short: '-n', 77 | long: '--names', 78 | boolean: true, 79 | default: false 80 | 81 | option :name_parts, 82 | description: 'Partial names by spliting and returning at index(es). 83 | eg. -m 3,4 my-docker-container-process_name-b2ffdab8f1aceae85300 for process_name.b2ffdab8f1aceae85300', 84 | short: '-m index', 85 | long: '--match index' 86 | 87 | option :delim, 88 | description: 'the deliminator to use with -m', 89 | short: '-d', 90 | long: '--deliminator', 91 | default: '-' 92 | 93 | option :environment_tags, 94 | description: 'Name of environment variables on each container to be appended to metric name, separated by commas', 95 | short: '-e ENV_VARS', 96 | long: '--environment-tags ENV_VARS' 97 | 98 | option :ioinfo, 99 | description: 'enable IO Docker metrics', 100 | short: '-i', 101 | long: '--ioinfo', 102 | boolean: true, 103 | default: false 104 | 105 | option :cpupercent, 106 | description: 'add cpu usage percentage metric', 107 | short: '-P', 108 | long: '--percentage', 109 | boolean: true, 110 | default: false 111 | 112 | def run 113 | @timestamp = Time.now.to_i 114 | @client = DockerApi.new(config[:docker_host]) 115 | 116 | list = if config[:container] != '' 117 | [config[:container]] 118 | else 119 | list_containers 120 | end 121 | list.each do |container| 122 | stats = container_stats(container) 123 | scheme = '' 124 | unless config[:environment_tags].nil? 125 | scheme << container_tags(container) 126 | end 127 | if config[:name_parts] 128 | config[:name_parts].split(',').each do |key| 129 | scheme << '.' unless scheme == '' 130 | scheme << container.split(config[:delim])[key.to_i] 131 | end 132 | else 133 | scheme << container 134 | end 135 | output_stats(scheme, stats) 136 | end 137 | ok 138 | end 139 | 140 | def output_stats(container, stats) 141 | dotted_stats = Hash.to_dotted_hash stats 142 | dotted_stats.each do |key, value| 143 | next if key == 'read' # unecessary timestamp 144 | next if value.is_a?(Array) 145 | value.delete!('/') if key == 'name' 146 | output "#{config[:scheme]}.#{container}.#{key}", value, @timestamp 147 | end 148 | if config[:ioinfo] 149 | blkio_stats(stats['blkio_stats']).each do |key, value| 150 | output "#{config[:scheme]}.#{container}.#{key}", value, @timestamp 151 | end 152 | end 153 | output "#{config[:scheme]}.#{container}.cpu_stats.usage_percent", calculate_cpu_percent(stats), @timestamp if config[:cpupercent] 154 | end 155 | 156 | def list_containers 157 | list = [] 158 | path = '/containers/json' 159 | containers = @client.parse(path) 160 | 161 | containers.each do |container| 162 | list << if config[:friendly_names] 163 | container['Names'][-1].delete('/') 164 | elsif config[:name_parts] 165 | container['Names'][-1].delete('/') 166 | else 167 | container['Id'] 168 | end 169 | end 170 | list 171 | end 172 | 173 | def container_stats(container) 174 | path = "/containers/#{container}/stats?stream=0" 175 | response = @client.call(path) 176 | if response.code.to_i == 404 177 | critical "#{config[:container]} is not running on #{@client.uri}" 178 | end 179 | parse_json(response) 180 | end 181 | 182 | def container_tags(container) 183 | tags = '' 184 | path = "/containers/#{container}/json" 185 | response = @client.call(path) 186 | if response.code.to_i == 404 187 | critical "#{config[:container]} is not running on #{@client.uri}" 188 | end 189 | inspect = parse_json(response) 190 | tag_list = config[:environment_tags].split(',') 191 | tag_list.each do |value| 192 | tags << inspect['Config']['Env'].select { |tag| tag.to_s.match(/#{value}=/) }.first.to_s.gsub(/#{value}=/, '') + '.' 193 | end 194 | tags 195 | end 196 | 197 | def blkio_stats(io_stats) 198 | stats_out = {} 199 | io_stats.each do |stats_type, stats_vals| 200 | stats_vals.each do |value| 201 | stats_out["#{stats_type}.#{value['op']}.#{value['major']}.#{value['minor']}"] = value['value'] 202 | end 203 | end 204 | stats_out 205 | end 206 | 207 | def calculate_cpu_percent(stats) 208 | cpu_percent = 0.0 209 | previous_cpu = stats['precpu_stats']['cpu_usage']['total_usage'] 210 | previous_system = stats['precpu_stats']['system_cpu_usage'] 211 | cpu_delta = stats['cpu_stats']['cpu_usage']['total_usage'] - previous_cpu 212 | system_delta = stats['cpu_stats']['system_cpu_usage'] - previous_system 213 | if system_delta > 0 && cpu_delta > 0 214 | number_of_cpu = stats['cpu_stats']['cpu_usage']['percpu_usage'].length 215 | cpu_percent = (cpu_delta.to_f / system_delta.to_f) * number_of_cpu * 100 216 | end 217 | format('%.2f', cpu_percent) 218 | end 219 | end 220 | -------------------------------------------------------------------------------- /lib/sensu-plugins-docker.rb: -------------------------------------------------------------------------------- 1 | require 'sensu-plugins-docker/version' 2 | -------------------------------------------------------------------------------- /lib/sensu-plugins-docker/client_helpers.rb: -------------------------------------------------------------------------------- 1 | require 'net_http_unix' 2 | require 'json' 3 | 4 | class DockerApi 5 | def initialize(uri = nil) 6 | @client = nil 7 | @docker_uri = uri || ENV['DOCKER_URL'] || ENV['DOCKER_HOST'] || '/var/run/docker.sock' 8 | if @docker_uri.sub!(%r{^(unix://)?/}, '') 9 | @docker_uri = 'unix:///' + @docker_uri 10 | @client = NetX::HTTPUnix.new(@docker_uri) 11 | else 12 | protocol = %r{^(https?|tcp)://}.match(@docker_uri) || 'http://' 13 | @docker_uri.sub!(protocol.to_s, '') 14 | split_host = @docker_uri.split ':' 15 | @client = if split_host.length == 2 16 | NetX::HTTPUnix.new("#{protocol}#{split_host[0]}", split_host[1]) 17 | else 18 | NetX::HTTPUnix.new("#{protocol}#{@docker_uri}", 2375) 19 | end 20 | end 21 | @client.start 22 | end 23 | 24 | def uri 25 | @docker_uri 26 | end 27 | 28 | def call(path, halt = true, limit = 10) 29 | raise ArgumentError, "HTTP redirect too deep. Last url called : #{path}" if limit.zero? 30 | if %r{^unix:///} =~ @docker_uri 31 | request = Net::HTTP::Get.new path.to_s 32 | else 33 | uri = URI("#{@docker_uri}#{path}") 34 | request = Net::HTTP::Get.new uri.request_uri 35 | end 36 | response = @client.request(request) 37 | case response 38 | when Net::HTTPSuccess then response 39 | when Net::HTTPRedirection then call(response['location'], true, limit - 1) 40 | else 41 | return response.error! unless halt == false 42 | return response 43 | end 44 | end 45 | 46 | def parse(path, halt = true, limit = 10) 47 | parsed = parse_json(call(path, halt, limit)) 48 | parsed 49 | end 50 | end 51 | 52 | def parse_json(response) 53 | parsed = nil 54 | begin 55 | parsed = JSON.parse(response.read_body) 56 | rescue JSON::ParserError => e 57 | raise "JSON Error: #{e.inspect}" 58 | end 59 | parsed 60 | end 61 | -------------------------------------------------------------------------------- /lib/sensu-plugins-docker/version.rb: -------------------------------------------------------------------------------- 1 | module SensuPluginsDocker 2 | module Version 3 | MAJOR = 4 4 | MINOR = 0 5 | PATCH = 0 6 | 7 | VER_STRING = [MAJOR, MINOR, PATCH].compact.join('.') 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /sensu-plugins-docker.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('../lib', __FILE__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | 4 | require 'date' 5 | require_relative 'lib/sensu-plugins-docker' 6 | 7 | Gem::Specification.new do |s| 8 | s.authors = ['Sensu-Plugins and contributors'] 9 | s.date = Date.today.to_s 10 | s.description = 'This plugin provides native Docker instrumentation 11 | for monitoring and metrics collection, including: 12 | container status, container number, and container 13 | metrics via `docker ps`' 14 | s.email = '' 15 | s.executables = Dir.glob('bin/**/*.rb').map { |file| File.basename(file) } 16 | s.files = Dir.glob('{bin,lib}/**/*') + %w(LICENSE README.md CHANGELOG.md) 17 | s.homepage = 'https://github.com/sensu-plugins/sensu-plugins-docker' 18 | s.license = 'MIT' 19 | s.metadata = { 'maintainer' => '@mattyjones', 20 | 'development_status' => 'active', 21 | 'production_status' => 'unstable - testing recommended', 22 | 'release_draft' => 'false', 23 | 'release_prerelease' => 'false' } 24 | s.name = 'sensu-plugins-docker' 25 | s.platform = Gem::Platform::RUBY 26 | s.post_install_message = 'You can use the embedded Ruby by setting EMBEDDED_RUBY=true in /etc/default/sensu' 27 | s.require_paths = ['lib'] 28 | s.required_ruby_version = '>= 2.3.0' 29 | s.summary = 'Sensu plugins for docker' 30 | s.test_files = s.files.grep(%r{^(test|spec|features)/}) 31 | s.version = SensuPluginsDocker::Version::VER_STRING 32 | 33 | s.add_runtime_dependency 'sensu-plugin', '~> 4.0' 34 | s.add_runtime_dependency 'sys-proctable', '1.2.6' 35 | s.add_runtime_dependency 'net_http_unix', '0.2.2' 36 | 37 | s.add_development_dependency 'bundler', '~> 2.1' 38 | s.add_development_dependency 'codeclimate-test-reporter', '~> 0.4' 39 | s.add_development_dependency 'github-markup', '~> 4.0' 40 | s.add_development_dependency 'kitchen-docker', '~> 2.6' 41 | s.add_development_dependency 'pry', '~> 0.10' 42 | s.add_development_dependency 'rake', '~> 13.0' 43 | s.add_development_dependency 'redcarpet', '~> 3.2' 44 | s.add_development_dependency 'rspec', '~> 3.1' 45 | s.add_development_dependency 'rubocop', '~> 0.40.0' 46 | s.add_development_dependency 'test-kitchen', '~> 1.16.0' 47 | s.add_development_dependency 'yard', '~> 0.8' 48 | end 49 | -------------------------------------------------------------------------------- /test/fixtures/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # start docker daemon 5 | nohup dockerd --host=unix:///var/run/docker.sock & 6 | 7 | source /etc/profile 8 | 9 | DATA_DIR=/tmp/kitchen/data 10 | RUBY_HOME=${MY_RUBY_HOME} 11 | 12 | cd $DATA_DIR 13 | SIGN_GEM=false gem build sensu-plugins-docker.gemspec 14 | gem install sensu-plugins-docker-*.gem 15 | 16 | # start container for testing 17 | 18 | docker run --name test_running -d --rm alpine sh -c 'while true; do sleep 1; done' 19 | docker run --name test_exited_ok -d alpine sh -c 'exit 0' 20 | docker run --name test_exited_fail -d alpine sh -c 'exit 1' 21 | 22 | # for debugging 23 | docker ps -a 24 | -------------------------------------------------------------------------------- /test/integration/helpers/serverspec/check-container_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require 'shared_spec' 5 | 6 | gem_path = '/usr/local/bin' 7 | check_name = 'check-container.rb' 8 | check = "#{gem_path}/#{check_name}" 9 | 10 | describe 'ruby environment' do 11 | it_behaves_like 'ruby checks', check 12 | end 13 | 14 | describe command("#{check} -N test_running") do 15 | its(:exit_status) { should eq 0 } 16 | its(:stdout) { should match(/CheckDockerContainer OK: test_running is running on unix:\/\/\/var\/run\/docker.sock./) } 17 | end 18 | 19 | describe command("#{check} -x -N test_running") do 20 | its(:exit_status) { should eq 0 } 21 | its(:stdout) { should match(/CheckDockerContainer OK: test_running is running on unix:\/\/\/var\/run\/docker.sock./) } 22 | end 23 | 24 | describe command("#{check} -N test_exited_ok") do 25 | its(:exit_status) { should eq 2 } 26 | its(:stdout) { should match(/CheckDockerContainer CRITICAL: test_exited_ok is exited on unix:\/\/\/var\/run\/docker.sock./) } 27 | end 28 | 29 | describe command("#{check} -x -N test_exited_ok") do 30 | its(:exit_status) { should eq 0 } 31 | its(:stdout) { should match(/CheckDockerContainer OK: test_exited_ok has exited without error on unix:\/\/\/var\/run\/docker.sock./) } 32 | end 33 | 34 | describe command("#{check} -x -N test_exited_fail") do 35 | its(:exit_status) { should eq 2 } 36 | its(:stdout) { should match(/CheckDockerContainer CRITICAL: test_exited_fail has exited with status code 1 on unix:\/\/\/var\/run\/docker.sock./) } 37 | end 38 | -------------------------------------------------------------------------------- /test/integration/helpers/serverspec/shared_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | shared_examples_for 'ruby checks' do |check| 6 | describe command('which ruby') do 7 | its(:exit_status) { should eq 0 } 8 | its(:stdout) { should match(/\/usr\/local\/bin\/ruby/) } 9 | end 10 | 11 | describe command('which gem') do 12 | its(:exit_status) { should eq 0 } 13 | its(:stdout) { should match(/\/usr\/local\/bin\/gem/) } 14 | end 15 | 16 | describe command("which #{check}") do 17 | its(:exit_status) { should eq 0 } 18 | its(:stdout) { should match(Regexp.new(Regexp.escape(check))) } 19 | end 20 | 21 | describe file(check) do 22 | it { should be_file } 23 | it { should be_executable } 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/integration/helpers/serverspec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'serverspec' 4 | 5 | set :backend, :exec 6 | -------------------------------------------------------------------------------- /test/integration/ruby-230/serverspec/default_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | -------------------------------------------------------------------------------- /test/integration/ruby-241/serverspec/default_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | -------------------------------------------------------------------------------- /test/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'codeclimate-test-reporter' 2 | CodeClimate::TestReporter.start 3 | --------------------------------------------------------------------------------