Rubix is a Ruby client for Zabbix. It has a few goals:
-
Provide a
Connection
andResponse
class that abstract away the complexity of authenticating with and sending API requests to Zabbix. -
Provide an ORM for Zabbix resources like Hosts, HostGroups, Templates, &c. using the Zabbix API.
-
Provide a command-line script that wraps the {
zabbix_sender
} utility, allowing it to consume JSON data and to ‘auto-vivify’ hosts, hostgroups, applications, and items. -
Provide some
Monitor
classes that make it easy to write scripts that periodically measure something and push it to Zabbix. -
Have as few dependencies as possible: the core classes only depend on Ruby 1.8 standard library &
JSON
and scripts additionally depend onConfigliere
[http://github.com/mrflip/configliere]. -
Play nicely with Chef
There are a lot of other projects out there that connect Ruby to Zabbix. Here’s a quick list:
- zabbix
-
zabbix aws templates, scripts, chef automations
- zabbixapi
-
Ruby module for work with zabbix api
- zabbix-rb
-
send data to zabbix from ruby
- zabbix_pusher
-
zabbix_pusher is a gem to parse zabbix templates and push the data to the corresponding zabbix server
- zabbix-trappers
-
Collection of ruby scripts for zabbix trappers
- rzabbix
-
Zabbix API client for Ruby
- zabboard
-
zabbix analytics
- zabbix-web
-
Zabbix frontend
- zabcon
-
Zabcon is a command line interface for Zabbix written in Ruby
None of these projects was satisfactory for our purposes so I decided to write Rubix. The name is terrible but the code is better. Enjoy!
Getting connected is easy
require 'rubix' # Provide API URL & credentials. These are the defaults. Rubix.connect('http://localhost/api_jsonrpc.php', 'admin', 'zabbix')
As per the Zabbixi API documentation, each request to the Zabbix API needs four values:
id
-
an integer identifying the request ID.
auth
-
a string confirming that the API request is authenticated.
method
-
the name of the API method you’re calling, e.g. -
host.get
,template.delete
, &c. params
-
parameters for the invocation of the
method
.
When you send a request, Rubix only requires you to specify the method
and the params
, handling the id
and authentication quietly for you:
response = Rubix.connection.request('host.get', 'filter' => { 'host' => 'My Zabbix Host' }) case when response.has_data? # Response is a success and "has data" -- it's not empty. This # means we found our host. puts response.result #=> [{"hostid"=>"10017"}] when response.success? # Response was succssful but doesn't "have data" -- it's empty, no # such host! puts "No such host" else # Response was an error. Uh oh! puts response.error_message end
Rubix comes with a command line utility zabbix_api
which lets you issue these sorts of requests directly on the command line.
$ zabbix_api host.get '{"filter": {"host": "My Zabbix Host"}}' [{"hostid"=>"10017"}]
zabbix_api
lets you specify the credentials and will pretty-print responses for you. Try zabbix_api --help
for more details.
If you don’t want to deal with the particulars of the Zabbix API itself, Rubix provides a set of classes that you can use instead.
The following example goes through setting up an item on a host complete with host groups, templates, applications, and so on.
require 'rubix' Rubix.connect('http://localhost/api_jsonrpc.php', 'admin', 'zabbix') # Ensure the host group we want exists. host_group = Rubix::HostGroup.find_or_create(:name => "My Zabbix Hosts") # Now the template -- created templates are empty by default! template = Rubix::Template.new(:name => "Template_Some_Service") template.save # Now the host. host = Rubix::Host.new(:name => "My Host", :ip => '123.123.123.123', :templates => [template], :host_groups => [host_group]) host.save # Now for the application app = Rubix::Application.new(:name => 'Some App', :host => host) app.save # Now the item item = Rubix::Item.new(:host => host, :key => 'foo.bar.baz', :description => "Some Item", :value_type => :unsigned_int, :applications => [app]) item.save
You can also update
and destroy
resources.
Only host groups, templates, hosts, applications, user macros, and items are available at present. Other Zabbix resources (actions, alerts, events, maps, users, &c.) can be similarly wrapped, they just haven’t been yet because we don’t use them as much as we use these core resources.
Rubix comes with a zabbix_pipe
script which, coupled with the {zabbix_sender
} utility, allows for writing data to Zabbix.
By the design of Zabbix, all data written via zabbix_sender
(and therefore zabbix_pipe
) must be written to items with type “Zabbix trapper”. This type instructs Zabbix to accept values written by zabbix_sender
.
zabbix_pipe
can consume data from STDIN
, a file on disk, or a named pipe. It consumes data one line at a time from any of these sources and uses the zabbix_sender
utility to send this data to a Zabbix server.
Here’s an example of starting zabbix_pipe
and sending some data onto Zabbix. (Credentials and addresses for the Zabbix server and the Zabbix API can all be customized; see the --help
option.)
# Send one data point -- this is tab separated! $ echo "foo.bar.baz 123" | zabbix_pipe --host='My Zabbix Host' --host_groups='My Zabbix Servers,Some Other Group' # Send a bunch of data points in a file $ cat my_data.tsv | zabbix_pipe --host='My Zabbix Host' --host_groups='My Zabbix Servers,Some Other Group'
You can also pass the file directly:
# Send a bunch of data points in a file $ zabbix_pipe --host='My Zabbix Host' --host_groups='My Zabbix Servers,Some Other Group' my_data.tsv
You can also listen from a named pipe. This is useful on a “production” system in which many processes may want to simply and easily write somewhere without worrying about what happens.
# In the first terminal $ mkfifo /dev/zabbix $ zabbix_pipe --pipe=/dev/zabbix --host='My Zabbix Host' --host_groups='My Zabbix Servers,Some Other Group' # In another terminal $ echo "foo.bar.baz 123" > /dev/zabbix $ cat my_data > /dev/zabbix
In any of these modes, you can also send JSON data directly.
$ echo '{"data": [{"key": "foo.bar.baz", "value": 123}, {"key": "foo.bar.baz", "value": 101}]}' > /dev/zabbix
This simple block of JSON doesn’t really add any power to zabbix_pipe
. What becomes more interesting is that we can wedge in data for diffferent hosts, items, &c. when using JSON input:
$ echo '{"host": "My first host", "host_groups": "My Zabbix Servers,Some Other Group", "data": [{"key": "foo.bar.baz", "value": 123}, {"key": "foo.bar.baz", "value": 101}]}' > /dev/zabbix $ echo '{"host": "My second host", "host_groups": "My Zabbix Servers,Some Other Group", "data": [{"key": "foo.bar.baz", "value": 123}, {"key": "foo.bar.baz", "value": 101}]}' > /dev/zabbix $ echo '{"host": "My third host", "host_groups": "My Zabbix Servers,Some Other Group", "data": [{"key": "foo.bar.baz", "value": 123}, {"key": "foo.bar.baz", "value": 101}]}' > /dev/zabbix
Rubix will switch hosts on the fly.
By default, for every item written into zabbix_pipe
, Rubix will first check, using the Zabbix API, whether an item with the given key exists for the given host. If not, Rubix will create the host and the item. You can pass a few details along with your data (when writing in JSON format) or at startup of the zabbix_pipe
to tune some of the properites of newly created hosts and items:
$ echo '{"host": "My host", "hostgroups": "Host Group 1,Host Group 2", "templates": "Template 1,Template 2", "applications": "App 1, App2", "data": [{"key": "foo.bar.baz", "value": 123}, {"key": "foo.bar.baz", "value": 101}]}' > /dev/zabbix
If the host ‘My host’ does not exist, Rubix will create it, putting in each of the host groups ‘Host Group 1’ and ‘Host Group 2’ (which will also be auto-vivified), and attach it to templates ‘Template 1’ and ‘Template 2’ (auto-vivified). If the item does not exist for the host ‘My host’, then it will be created and put inside applications ‘App1’ and ‘App2’ (auto-vivified). The value type of the item (unsigned int, float, character, text, &c.) will be chosen dynamically based on the value being written. The created items will all be of the type “Zabbix trapper” so that they can be written to.
Auto-vivification is intended to make it easy to register a lot of dynamic hosts and items in Zabbix. This is perfect for cloud deployments where resources to be monitored are often dynamic.
By the design of Zabbix, a newly created item of type ‘Zabbix trapper’ will not accept data for some interval, typically a minute or so, after being created. This means that when writing a series of values for some non-existent item ‘foo.bar.baz’, the first write will cause the item to be created (auto-vivified), the next several writes will fail as the Zabbix server is not accepting writes for this item yet, and then all writes will begin to succeed as the Zabbix server catches up. If it is absolutely essential for all writes to succeed, including the first, then zabbix_pipe
needs to go to sleep for a while after creating a new item in order to give the Zabbix server time to catch up. This can be configured with the --create_item_sleep
option. By default this is set to 0.
Attempting to auto-vivify keys on every single write is expensive and does not scale. It’s recommended, therefore, to run zabbix_pipe
with the --fast
flag in production settings. In this mode, zabbix_pipe
will not attempt to auto-vivify anything – if items do not exist, writes will just fail, as they do with zabbix_sender
itself.
A good pattern is to set up a production pipe (in --fast
) mode at /dev/zabbix
and to do all development/deployment with a separate instance of zabbix_pipe
. Once development/deployment is complete and all hosts, groups, templates, applications, and items have been created, switch to writing all data in “production mode” using the --fast
pipe at /dev/zabbix
.
Rubix also comes with some classes that make it easy to write simple monitors. The output of these monitors should match the expected input format of zabbix_pipe
. This way they can be chained together. Here’s an example of a simple monitor that calculates the currently used memory in bytes.
# in memory_monitor.rb require 'rubix' class MemoryMonitor < Rubix::Monitor def measure write do |data| mem_used = `free | tail -n+2 | head -n1`.chomp.split[2].to_i data << [['mem.used', mem_used]] end end end MemoryMonitor.run if $0 == __FILE__
The file memory_monitor.rb
can now be run on the command line in various ways:
# Write the output to STDOUT $ ruby memory_monitor.rb 'mem.used' 11595908 # Loop every 30 seconds $ ruby memory_monitor.rb --loop=30 'mem.used' 11595760 'mem.used' 11595800 'mem.used' 11596016 'mem.used' 11596008
It can be piped into a running zabbix_pipe
:
$ ruby memory_monitor.rb --loop=30 > /dev/zabbix &
It’s useful to be able to monitor resources defined in Chef and so Rubix comes with a ChefMonitor
class.
# in webserver_monitor.rb require 'rubix' require 'net/http' require 'uri' class WebserverMonitor < Rubix::ChefMonitor def webserver @webserver ||= chef_node_from_node_name('my_webserver') end def uri URI.parse("http://#{webserver['ec2']['public_hostname']}") end def measure begin if Net::HTTP.get_response(uri).code.to_i == 200 availability = 1 else availability = 0
end
rescue => e availability = 0 end write do |data| data << ([['webserver.available', availability]]) end end end WebserverMonitor.run if $0 == __FILE__
To run this monitor you’ll have to pass in credentials used to authenticate with Chef:
$ sudo ruby webserver_monitor.rb --chef_server_url=http://api.opscode.com/organizations/my_company --chef_node_name=this_node --chef_client_key=/etc/chef/client.pem --loop=30 > /dev/zabbix
Make sure you can read the file at /etc/chef/client.pem
!